Envmap BC6 compression (#686)

This commit is contained in:
Turánszki János
2023-05-29 21:56:48 +02:00
committed by GitHub
parent 6fd24883c5
commit d88122e28e
19 changed files with 1042 additions and 167 deletions
+2
View File
@@ -33,6 +33,8 @@ wi::vector<ShaderEntry> shaders = {
{"blockcompressCS_BC3", wi::graphics::ShaderStage::CS},
{"blockcompressCS_BC4", wi::graphics::ShaderStage::CS},
{"blockcompressCS_BC5", wi::graphics::ShaderStage::CS},
{"blockcompressCS_BC6H", wi::graphics::ShaderStage::CS},
{"blockcompressCS_BC6H_cubemap", wi::graphics::ShaderStage::CS},
{"blur_gaussian_float4CS", wi::graphics::ShaderStage::CS},
{"bloomseparateCS", wi::graphics::ShaderStage::CS},
{"depthoffield_mainCS", wi::graphics::ShaderStage::CS},
@@ -1195,14 +1195,16 @@ struct FilterEnvmapPushConstants
{
uint2 filterResolution;
float2 filterResolution_rcp;
uint filterArrayIndex;
float filterRoughness;
uint filterRayCount;
uint padding_filterCB;
int texture_input;
int texture_output;
int padding0;
int padding1;
int padding2;
};
// CopyTexture2D params:
@@ -1277,10 +1279,10 @@ struct AerialPerspectiveCapturePushConstants
uint2 resolution;
float2 resolution_rcp;
uint arrayIndex;
int texture_input;
int texture_output;
float padding;
float padding0;
float padding1;
};
struct VolumetricCloudCapturePushConstants
@@ -1288,15 +1290,15 @@ struct VolumetricCloudCapturePushConstants
uint2 resolution;
float2 resolution_rcp;
uint arrayIndex;
int texture_input;
int texture_output;
int maxStepCount;
float LODMin;
float shadowSampleCount;
float groundContributionSampleCount;
float padding;
float padding0;
float padding1;
};
@@ -70,6 +70,14 @@
<ShaderType Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">Compute</ShaderType>
<ShaderModel Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">4.0</ShaderModel>
</FxCompile>
<FxCompile Include="$(MSBuildThisFileDirectory)blockcompressCS_BC6H.hlsl">
<ShaderType Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">Compute</ShaderType>
<ShaderModel Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">4.0</ShaderModel>
</FxCompile>
<FxCompile Include="$(MSBuildThisFileDirectory)blockcompressCS_BC6H_cubemap.hlsl">
<ShaderType Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">Compute</ShaderType>
<ShaderModel Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">4.0</ShaderModel>
</FxCompile>
<FxCompile Include="$(MSBuildThisFileDirectory)bloomseparateCS.hlsl">
<ShaderType Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">Compute</ShaderType>
<ShaderType Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">Compute</ShaderType>
@@ -1100,6 +1100,12 @@
<FxCompile Include="$(MSBuildThisFileDirectory)blockcompressCS_BC4.hlsl">
<Filter>CS</Filter>
</FxCompile>
<FxCompile Include="$(MSBuildThisFileDirectory)blockcompressCS_BC6H.hlsl">
<Filter>CS</Filter>
</FxCompile>
<FxCompile Include="$(MSBuildThisFileDirectory)blockcompressCS_BC6H_cubemap.hlsl">
<Filter>CS</Filter>
</FxCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="$(MSBuildThisFileDirectory)ShaderInterop.h">
@@ -95,7 +95,7 @@ void RenderAerialPerspective(uint3 DTid, float2 uv, float depth, float3 depthWor
[numthreads(POSTPROCESS_BLOCKSIZE, POSTPROCESS_BLOCKSIZE, 1)]
void main(uint3 DTid : SV_DispatchThreadID)
{
TextureCubeArray input = bindless_cubearrays[capture.texture_input];
TextureCube input = bindless_cubemaps[capture.texture_input];
RWTexture2DArray<float4> output = bindless_rwtextures2DArray[capture.texture_output];
const float2 uv = (DTid.xy + 0.5) * capture.resolution_rcp;
@@ -113,12 +113,12 @@ void main(uint3 DTid : SV_DispatchThreadID)
const float depth = texture_input_depth.SampleLevel(sampler_point_clamp, N, 0).r;
#endif // MSAA
float4 composite = input.SampleLevel(sampler_linear_clamp, float4(N, capture.arrayIndex), 0);
float4 composite = input.SampleLevel(sampler_linear_clamp, N, 0);
// Ignore skybox
if (depth == 0.0)
{
output[uint3(DTid.xy, DTid.z + capture.arrayIndex * 6)] = composite;
output[uint3(DTid.xy, DTid.z)] = composite;
return;
}
@@ -134,7 +134,7 @@ void main(uint3 DTid : SV_DispatchThreadID)
float4 result = float4(luminance, transmittance);
// Output
output[uint3(DTid.xy, DTid.z + capture.arrayIndex * 6)] = float4(composite.rgb * (1.0 - result.a) + result.rgb, composite.a * (1.0 - result.a));
output[uint3(DTid.xy, DTid.z)] = float4(composite.rgb * (1.0 - result.a) + result.rgb, composite.a * (1.0 - result.a));
}
#else
[numthreads(POSTPROCESS_BLOCKSIZE, POSTPROCESS_BLOCKSIZE, 1)]
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,2 @@
#define COMPRESS_CUBEMAP
#include "blockcompressCS_BC6H.hlsl"
+3 -3
View File
@@ -22,7 +22,7 @@ void main(uint3 DTid : SV_DispatchThreadID)
{
if (DTid.x < push.filterResolution.x && DTid.y < push.filterResolution.y)
{
TextureCubeArray input = bindless_cubearrays[push.texture_input];
TextureCube input = bindless_cubemaps[push.texture_input];
RWTexture2DArray<float4> output = bindless_rwtextures2DArray[push.texture_output];
float2 uv = (DTid.xy + 0.5f) * push.filterResolution_rcp.xy;
@@ -38,10 +38,10 @@ void main(uint3 DTid : SV_DispatchThreadID)
float3 hemisphere = ImportanceSampleGGX(hamm, push.filterRoughness, N);
float3 cone = mul(hemisphere, tangentSpace);
col += input.SampleLevel(sampler_linear_clamp, float4(cone, push.filterArrayIndex), 0);
col += input.SampleLevel(sampler_linear_clamp, cone, 0);
}
col /= (float)push.filterRayCount;
output[uint3(DTid.xy, DTid.z + push.filterArrayIndex * 6)] = col;
output[uint3(DTid.xy, DTid.z)] = col;
}
}
@@ -724,7 +724,7 @@ void RenderClouds(uint3 DTid, float2 uv, float depth, float3 depthWorldPosition,
[numthreads(POSTPROCESS_BLOCKSIZE, POSTPROCESS_BLOCKSIZE, 1)]
void main(uint3 DTid : SV_DispatchThreadID)
{
TextureCubeArray input = bindless_cubearrays[capture.texture_input];
TextureCube input = bindless_cubemaps[capture.texture_input];
RWTexture2DArray<float4> output = bindless_rwtextures2DArray[capture.texture_output];
const float2 uv = (DTid.xy + 0.5) * capture.resolution_rcp;
@@ -751,10 +751,10 @@ void main(uint3 DTid : SV_DispatchThreadID)
float2 cloudDepth = 0;
RenderClouds(DTid, uv, depth, depthWorldPosition, rayOrigin, rayDirection, cloudColor, cloudDepth);
float4 composite = input.SampleLevel(sampler_linear_clamp, float4(N, capture.arrayIndex), 0);
float4 composite = input.SampleLevel(sampler_linear_clamp, N, 0);
// Output
output[uint3(DTid.xy, DTid.z + capture.arrayIndex * 6)] = float4(composite.rgb * (1.0 - cloudColor.a) + cloudColor.rgb, composite.a * (1.0 - cloudColor.a));
output[uint3(DTid.xy, DTid.z)] = float4(composite.rgb * (1.0 - cloudColor.a) + cloudColor.rgb, composite.a * (1.0 - cloudColor.a));
}
#else
[numthreads(POSTPROCESS_BLOCKSIZE, POSTPROCESS_BLOCKSIZE, 1)]
+2
View File
@@ -250,6 +250,8 @@ namespace wi::enums
CSTYPE_BLOCKCOMPRESS_BC3,
CSTYPE_BLOCKCOMPRESS_BC4,
CSTYPE_BLOCKCOMPRESS_BC5,
CSTYPE_BLOCKCOMPRESS_BC6H,
CSTYPE_BLOCKCOMPRESS_BC6H_CUBEMAP,
CSTYPE_FILTERENVMAP,
CSTYPE_COPYTEXTURE2D_UNORM4,
CSTYPE_COPYTEXTURE2D_FLOAT4,
+1 -1
View File
@@ -4638,7 +4638,7 @@ using namespace dx12_internal;
{
if (has_flag(texture->desc.misc_flags, ResourceMiscFlag::TEXTURECUBE))
{
if (texture->desc.array_size > 6 && sliceCount > 6)
if (texture->desc.array_size > 6)
{
srv_desc.ViewDimension = D3D12_SRV_DIMENSION_TEXTURECUBEARRAY;
srv_desc.TextureCubeArray.First2DArrayFace = firstSlice;
+4
View File
@@ -8401,6 +8401,10 @@ using namespace vulkan_internal;
copy.extent.height = std::min(dst->desc.height, src->desc.height);
}
copy.extent.depth = std::min(dst->desc.depth, src->desc.depth);
copy.extent.width = std::max(1u, copy.extent.width >> srcMip);
copy.extent.height = std::max(1u, copy.extent.height >> srcMip);
copy.extent.depth = std::max(1u, copy.extent.depth >> srcMip);
}
else
{
+84 -101
View File
@@ -896,6 +896,8 @@ void LoadShaders()
wi::jobsystem::Execute(ctx, [](wi::jobsystem::JobArgs args) { LoadShader(ShaderStage::CS, shaders[CSTYPE_BLOCKCOMPRESS_BC3], "blockcompressCS_BC3.cso"); });
wi::jobsystem::Execute(ctx, [](wi::jobsystem::JobArgs args) { LoadShader(ShaderStage::CS, shaders[CSTYPE_BLOCKCOMPRESS_BC4], "blockcompressCS_BC4.cso"); });
wi::jobsystem::Execute(ctx, [](wi::jobsystem::JobArgs args) { LoadShader(ShaderStage::CS, shaders[CSTYPE_BLOCKCOMPRESS_BC5], "blockcompressCS_BC5.cso"); });
wi::jobsystem::Execute(ctx, [](wi::jobsystem::JobArgs args) { LoadShader(ShaderStage::CS, shaders[CSTYPE_BLOCKCOMPRESS_BC6H], "blockcompressCS_BC6H.cso"); });
wi::jobsystem::Execute(ctx, [](wi::jobsystem::JobArgs args) { LoadShader(ShaderStage::CS, shaders[CSTYPE_BLOCKCOMPRESS_BC6H_CUBEMAP], "blockcompressCS_BC6H_cubemap.cso"); });
wi::jobsystem::Execute(ctx, [](wi::jobsystem::JobArgs args) { LoadShader(ShaderStage::CS, shaders[CSTYPE_FILTERENVMAP], "filterEnvMapCS.cso"); });
wi::jobsystem::Execute(ctx, [](wi::jobsystem::JobArgs args) { LoadShader(ShaderStage::CS, shaders[CSTYPE_COPYTEXTURE2D_UNORM4], "copytexture2D_unorm4CS.cso"); });
wi::jobsystem::Execute(ctx, [](wi::jobsystem::JobArgs args) { LoadShader(ShaderStage::CS, shaders[CSTYPE_COPYTEXTURE2D_FLOAT4], "copytexture2D_float4CS.cso"); });
@@ -6493,7 +6495,7 @@ void DrawDebugWorld(
}
else
{
device->BindResource(&scene.envmapArray, 0, cmd, scene.envmapArray.GetDesc().mip_levels + probe.textureIndex);
device->BindResource(&scene.envmapArray, 0, cmd, probe.textureIndex);
}
device->Draw(vertexCount_uvsphere, 0, cmd);
@@ -7099,6 +7101,14 @@ void ComputeSkyAtmosphereTextures(CommandList cmd)
}
void ComputeSkyAtmosphereSkyViewLut(CommandList cmd)
{
const int threadSize = 8;
const int skyViewLutWidth = textures[TEXTYPE_2D_SKYATMOSPHERE_SKYVIEWLUT].GetDesc().width;
const int skyViewLutHeight = textures[TEXTYPE_2D_SKYATMOSPHERE_SKYVIEWLUT].GetDesc().height;
const int skyViewLutThreadX = static_cast<uint32_t>(std::ceil(skyViewLutWidth / threadSize));
const int skyViewLutThreadY = static_cast<uint32_t>(std::ceil(skyViewLutHeight / threadSize));
if (skyViewLutThreadX * skyViewLutThreadY < 1)
return;
device->EventBegin("ComputeSkyAtmosphereSkyViewLut", cmd);
BindCommonResources(cmd);
@@ -7123,17 +7133,10 @@ void ComputeSkyAtmosphereSkyViewLut(CommandList cmd)
device->Barrier(barriers, arraysize(barriers), cmd);
}
const int threadSize = 8;
const int skyViewLutWidth = textures[TEXTYPE_2D_SKYATMOSPHERE_SKYVIEWLUT].GetDesc().width;
const int skyViewLutHeight = textures[TEXTYPE_2D_SKYATMOSPHERE_SKYVIEWLUT].GetDesc().height;
const int skyViewLutThreadX = static_cast<uint32_t>(std::ceil(skyViewLutWidth / threadSize));
const int skyViewLutThreadY = static_cast<uint32_t>(std::ceil(skyViewLutHeight / threadSize));
device->Dispatch(skyViewLutThreadX, skyViewLutThreadY, 1, cmd);
{
GPUBarrier barriers[] = {
GPUBarrier::Memory(),
GPUBarrier::Image(&textures[TEXTYPE_2D_SKYATMOSPHERE_SKYVIEWLUT], ResourceState::UNORDERED_ACCESS, textures[TEXTYPE_2D_SKYATMOSPHERE_SKYVIEWLUT].desc.layout)
};
device->Barrier(barriers, arraysize(barriers), cmd);
@@ -7285,10 +7288,10 @@ void RefreshEnvProbes(const Visibility& vis, CommandList cmd)
ResourceState::RENDERTARGET
),
RenderPassImage::Resolve(
&vis.scene->envmapArray,
&vis.scene->envrenderingColorBuffer,
ResourceState::SHADER_RESOURCE,
ResourceState::SHADER_RESOURCE,
vis.scene->envmapArray.desc.mip_levels + vis.scene->envmapCount + probe.textureIndex // subresource: individual cubes only mip0
0
)
};
device->RenderPassBegin(rp, arraysize(rp), cmd);
@@ -7305,12 +7308,11 @@ void RefreshEnvProbes(const Visibility& vis, CommandList cmd)
ResourceState::SHADER_RESOURCE
),
RenderPassImage::RenderTarget(
&vis.scene->envmapArray,
&vis.scene->envrenderingColorBuffer,
RenderPassImage::LoadOp::DONTCARE,
RenderPassImage::StoreOp::STORE,
ResourceState::SHADER_RESOURCE,
ResourceState::SHADER_RESOURCE,
probe.textureIndex
ResourceState::SHADER_RESOURCE
)
};
device->RenderPassBegin(rp, arraysize(rp), cmd);
@@ -7385,28 +7387,21 @@ void RefreshEnvProbes(const Visibility& vis, CommandList cmd)
device->BindResource(&vis.scene->envrenderingDepthBuffer, 0, cmd);
}
TextureDesc desc = vis.scene->envmapArray.GetDesc();
int arrayIndex = probe.textureIndex;
TextureDesc desc = vis.scene->envrenderingColorBuffer.GetDesc();
AerialPerspectiveCapturePushConstants push;
push.resolution.x = desc.width;
push.resolution.y = desc.height;
push.resolution_rcp.x = 1.0f / push.resolution.x;
push.resolution_rcp.y = 1.0f / push.resolution.y;
push.arrayIndex = arrayIndex;
push.texture_input = device->GetDescriptorIndex(&vis.scene->envmapArray, SubresourceType::SRV);
push.texture_output = device->GetDescriptorIndex(&vis.scene->envmapArray, SubresourceType::UAV);
push.texture_input = device->GetDescriptorIndex(&vis.scene->envrenderingColorBuffer, SubresourceType::SRV);
push.texture_output = device->GetDescriptorIndex(&vis.scene->envrenderingColorBuffer, SubresourceType::UAV);
device->PushConstants(&push, sizeof(push), cmd);
{
GPUBarrier barriers[] = {
GPUBarrier::Image(&vis.scene->envmapArray, ResourceState::SHADER_RESOURCE, ResourceState::UNORDERED_ACCESS, 0, arrayIndex * 6 + 0),
GPUBarrier::Image(&vis.scene->envmapArray, ResourceState::SHADER_RESOURCE, ResourceState::UNORDERED_ACCESS, 0, arrayIndex * 6 + 1),
GPUBarrier::Image(&vis.scene->envmapArray, ResourceState::SHADER_RESOURCE, ResourceState::UNORDERED_ACCESS, 0, arrayIndex * 6 + 2),
GPUBarrier::Image(&vis.scene->envmapArray, ResourceState::SHADER_RESOURCE, ResourceState::UNORDERED_ACCESS, 0, arrayIndex * 6 + 3),
GPUBarrier::Image(&vis.scene->envmapArray, ResourceState::SHADER_RESOURCE, ResourceState::UNORDERED_ACCESS, 0, arrayIndex * 6 + 4),
GPUBarrier::Image(&vis.scene->envmapArray, ResourceState::SHADER_RESOURCE, ResourceState::UNORDERED_ACCESS, 0, arrayIndex * 6 + 5),
GPUBarrier::Image(&vis.scene->envrenderingColorBuffer, ResourceState::SHADER_RESOURCE, ResourceState::UNORDERED_ACCESS),
};
device->Barrier(barriers, arraysize(barriers), cmd);
}
@@ -7419,13 +7414,7 @@ void RefreshEnvProbes(const Visibility& vis, CommandList cmd)
{
GPUBarrier barriers[] = {
GPUBarrier::Memory(),
GPUBarrier::Image(&vis.scene->envmapArray, ResourceState::UNORDERED_ACCESS, ResourceState::SHADER_RESOURCE, 0, arrayIndex * 6 + 0),
GPUBarrier::Image(&vis.scene->envmapArray, ResourceState::UNORDERED_ACCESS, ResourceState::SHADER_RESOURCE, 0, arrayIndex * 6 + 1),
GPUBarrier::Image(&vis.scene->envmapArray, ResourceState::UNORDERED_ACCESS, ResourceState::SHADER_RESOURCE, 0, arrayIndex * 6 + 2),
GPUBarrier::Image(&vis.scene->envmapArray, ResourceState::UNORDERED_ACCESS, ResourceState::SHADER_RESOURCE, 0, arrayIndex * 6 + 3),
GPUBarrier::Image(&vis.scene->envmapArray, ResourceState::UNORDERED_ACCESS, ResourceState::SHADER_RESOURCE, 0, arrayIndex * 6 + 4),
GPUBarrier::Image(&vis.scene->envmapArray, ResourceState::UNORDERED_ACCESS, ResourceState::SHADER_RESOURCE, 0, arrayIndex * 6 + 5),
GPUBarrier::Image(&vis.scene->envrenderingColorBuffer, ResourceState::UNORDERED_ACCESS, ResourceState::SHADER_RESOURCE),
};
device->Barrier(barriers, arraysize(barriers), cmd);
}
@@ -7471,17 +7460,15 @@ void RefreshEnvProbes(const Visibility& vis, CommandList cmd)
device->BindResource(&texture_weatherMap, 4, cmd);
}
TextureDesc desc = vis.scene->envmapArray.GetDesc();
int arrayIndex = probe.textureIndex;
TextureDesc desc = vis.scene->envrenderingColorBuffer.GetDesc();
VolumetricCloudCapturePushConstants push;
push.resolution.x = desc.width;
push.resolution.y = desc.height;
push.resolution_rcp.x = 1.0f / push.resolution.x;
push.resolution_rcp.y = 1.0f / push.resolution.y;
push.arrayIndex = arrayIndex;
push.texture_input = device->GetDescriptorIndex(&vis.scene->envmapArray, SubresourceType::SRV);
push.texture_output = device->GetDescriptorIndex(&vis.scene->envmapArray, SubresourceType::UAV);
push.texture_input = device->GetDescriptorIndex(&vis.scene->envrenderingColorBuffer, SubresourceType::SRV);
push.texture_output = device->GetDescriptorIndex(&vis.scene->envrenderingColorBuffer, SubresourceType::UAV);
if (probe.IsRealTime())
{
@@ -7503,12 +7490,7 @@ void RefreshEnvProbes(const Visibility& vis, CommandList cmd)
{
GPUBarrier barriers[] = {
GPUBarrier::Image(&vis.scene->envmapArray, ResourceState::SHADER_RESOURCE, ResourceState::UNORDERED_ACCESS, 0, arrayIndex * 6 + 0),
GPUBarrier::Image(&vis.scene->envmapArray, ResourceState::SHADER_RESOURCE, ResourceState::UNORDERED_ACCESS, 0, arrayIndex * 6 + 1),
GPUBarrier::Image(&vis.scene->envmapArray, ResourceState::SHADER_RESOURCE, ResourceState::UNORDERED_ACCESS, 0, arrayIndex * 6 + 2),
GPUBarrier::Image(&vis.scene->envmapArray, ResourceState::SHADER_RESOURCE, ResourceState::UNORDERED_ACCESS, 0, arrayIndex * 6 + 3),
GPUBarrier::Image(&vis.scene->envmapArray, ResourceState::SHADER_RESOURCE, ResourceState::UNORDERED_ACCESS, 0, arrayIndex * 6 + 4),
GPUBarrier::Image(&vis.scene->envmapArray, ResourceState::SHADER_RESOURCE, ResourceState::UNORDERED_ACCESS, 0, arrayIndex * 6 + 5),
GPUBarrier::Image(&vis.scene->envrenderingColorBuffer, ResourceState::SHADER_RESOURCE, ResourceState::UNORDERED_ACCESS),
};
device->Barrier(barriers, arraysize(barriers), cmd);
}
@@ -7521,13 +7503,7 @@ void RefreshEnvProbes(const Visibility& vis, CommandList cmd)
{
GPUBarrier barriers[] = {
GPUBarrier::Memory(),
GPUBarrier::Image(&vis.scene->envmapArray, ResourceState::UNORDERED_ACCESS, ResourceState::SHADER_RESOURCE, 0, arrayIndex * 6 + 0),
GPUBarrier::Image(&vis.scene->envmapArray, ResourceState::UNORDERED_ACCESS, ResourceState::SHADER_RESOURCE, 0, arrayIndex * 6 + 1),
GPUBarrier::Image(&vis.scene->envmapArray, ResourceState::UNORDERED_ACCESS, ResourceState::SHADER_RESOURCE, 0, arrayIndex * 6 + 2),
GPUBarrier::Image(&vis.scene->envmapArray, ResourceState::UNORDERED_ACCESS, ResourceState::SHADER_RESOURCE, 0, arrayIndex * 6 + 3),
GPUBarrier::Image(&vis.scene->envmapArray, ResourceState::UNORDERED_ACCESS, ResourceState::SHADER_RESOURCE, 0, arrayIndex * 6 + 4),
GPUBarrier::Image(&vis.scene->envmapArray, ResourceState::UNORDERED_ACCESS, ResourceState::SHADER_RESOURCE, 0, arrayIndex * 6 + 5),
GPUBarrier::Image(&vis.scene->envrenderingColorBuffer, ResourceState::UNORDERED_ACCESS, ResourceState::SHADER_RESOURCE),
};
device->Barrier(barriers, arraysize(barriers), cmd);
}
@@ -7535,49 +7511,37 @@ void RefreshEnvProbes(const Visibility& vis, CommandList cmd)
device->EventEnd(cmd);
}
MIPGEN_OPTIONS mipopt;
mipopt.arrayIndex = probe.textureIndex;
GenerateMipChain(vis.scene->envmapArray, MIPGENFILTER_LINEAR, cmd, mipopt);
GenerateMipChain(vis.scene->envrenderingColorBuffer, MIPGENFILTER_LINEAR, cmd);
// Filter the enviroment map mip chain according to BRDF:
// A bit similar to MIP chain generation, but its input is the MIP-mapped texture,
// and we generatethe filtered MIPs from bottom to top.
device->EventBegin("FilterEnvMap", cmd);
{
TextureDesc desc = vis.scene->envmapArray.GetDesc();
int arrayIndex = probe.textureIndex;
TextureDesc desc = vis.scene->envrenderingColorBuffer.GetDesc();
device->BindComputeShader(&shaders[CSTYPE_FILTERENVMAP], cmd);
desc.width = 1;
desc.height = 1;
desc.width = std::max(1u, desc.width >> (desc.mip_levels - 1));
desc.height = std::max(1u, desc.height >> (desc.mip_levels - 1));
for (uint32_t i = desc.mip_levels - 1; i > 0; --i)
{
{
GPUBarrier barriers[] = {
GPUBarrier::Image(&vis.scene->envmapArray, ResourceState::SHADER_RESOURCE, ResourceState::UNORDERED_ACCESS, i, arrayIndex * 6 + 0),
GPUBarrier::Image(&vis.scene->envmapArray, ResourceState::SHADER_RESOURCE, ResourceState::UNORDERED_ACCESS, i, arrayIndex * 6 + 1),
GPUBarrier::Image(&vis.scene->envmapArray, ResourceState::SHADER_RESOURCE, ResourceState::UNORDERED_ACCESS, i, arrayIndex * 6 + 2),
GPUBarrier::Image(&vis.scene->envmapArray, ResourceState::SHADER_RESOURCE, ResourceState::UNORDERED_ACCESS, i, arrayIndex * 6 + 3),
GPUBarrier::Image(&vis.scene->envmapArray, ResourceState::SHADER_RESOURCE, ResourceState::UNORDERED_ACCESS, i, arrayIndex * 6 + 4),
GPUBarrier::Image(&vis.scene->envmapArray, ResourceState::SHADER_RESOURCE, ResourceState::UNORDERED_ACCESS, i, arrayIndex * 6 + 5),
GPUBarrier::Image(&vis.scene->envrenderingColorBuffer, ResourceState::SHADER_RESOURCE, ResourceState::UNORDERED_ACCESS, i),
};
device->Barrier(barriers, arraysize(barriers), cmd);
}
device->BindUAV(&vis.scene->envmapArray, 0, cmd, i);
device->BindResource(&vis.scene->envmapArray, 0, cmd, std::max(0, (int)i - 2));
FilterEnvmapPushConstants push;
push.filterResolution.x = desc.width;
push.filterResolution.y = desc.height;
push.filterResolution_rcp.x = 1.0f / push.filterResolution.x;
push.filterResolution_rcp.y = 1.0f / push.filterResolution.y;
push.filterArrayIndex = arrayIndex;
push.filterRoughness = (float)i / (float)desc.mip_levels;
push.filterRayCount = 128;
push.texture_input = device->GetDescriptorIndex(&vis.scene->envmapArray, SubresourceType::SRV, std::max(0, (int)i - 2));
push.texture_output = device->GetDescriptorIndex(&vis.scene->envmapArray, SubresourceType::UAV, i);
push.texture_input = device->GetDescriptorIndex(&vis.scene->envrenderingColorBuffer, SubresourceType::SRV, std::max(0, (int)i - 2));
push.texture_output = device->GetDescriptorIndex(&vis.scene->envrenderingColorBuffer, SubresourceType::UAV, i);
device->PushConstants(&push, sizeof(push), cmd);
device->Dispatch(
@@ -7588,13 +7552,7 @@ void RefreshEnvProbes(const Visibility& vis, CommandList cmd)
{
GPUBarrier barriers[] = {
GPUBarrier::Memory(),
GPUBarrier::Image(&vis.scene->envmapArray, ResourceState::UNORDERED_ACCESS, ResourceState::SHADER_RESOURCE, i, arrayIndex * 6 + 0),
GPUBarrier::Image(&vis.scene->envmapArray, ResourceState::UNORDERED_ACCESS, ResourceState::SHADER_RESOURCE, i, arrayIndex * 6 + 1),
GPUBarrier::Image(&vis.scene->envmapArray, ResourceState::UNORDERED_ACCESS, ResourceState::SHADER_RESOURCE, i, arrayIndex * 6 + 2),
GPUBarrier::Image(&vis.scene->envmapArray, ResourceState::UNORDERED_ACCESS, ResourceState::SHADER_RESOURCE, i, arrayIndex * 6 + 3),
GPUBarrier::Image(&vis.scene->envmapArray, ResourceState::UNORDERED_ACCESS, ResourceState::SHADER_RESOURCE, i, arrayIndex * 6 + 4),
GPUBarrier::Image(&vis.scene->envmapArray, ResourceState::UNORDERED_ACCESS, ResourceState::SHADER_RESOURCE, i, arrayIndex * 6 + 5),
GPUBarrier::Image(&vis.scene->envrenderingColorBuffer, ResourceState::UNORDERED_ACCESS, ResourceState::SHADER_RESOURCE, i),
};
device->Barrier(barriers, arraysize(barriers), cmd);
}
@@ -7604,6 +7562,9 @@ void RefreshEnvProbes(const Visibility& vis, CommandList cmd)
}
}
device->EventEnd(cmd);
// Finally, the complete envmap is block compressed into the envmapArray:
BlockCompress(vis.scene->envrenderingColorBuffer, vis.scene->envmapArray, cmd, probe.textureIndex * 6);
};
if (vis.scene->probes.GetCount() == 0)
@@ -8594,7 +8555,7 @@ void GenerateMipChain(const Texture& texture, MIPGENFILTER filter, CommandList c
}
}
void BlockCompress(const wi::graphics::Texture& texture_src, const wi::graphics::Texture& texture_bc, wi::graphics::CommandList cmd)
void BlockCompress(const wi::graphics::Texture& texture_src, const wi::graphics::Texture& texture_bc, wi::graphics::CommandList cmd, uint32_t dst_slice_offset)
{
const uint32_t block_size = GetFormatBlockSize(texture_bc.desc.format);
TextureDesc desc;
@@ -8606,58 +8567,77 @@ void BlockCompress(const wi::graphics::Texture& texture_src, const wi::graphics:
static Texture bc_raw_uint2;
static Texture bc_raw_uint4;
static Texture bc_raw_uint4_cubemap;
Texture* bc_raw = nullptr;
switch (texture_bc.desc.format)
{
case Format::BC1_UNORM:
case Format::BC1_UNORM_SRGB:
bc_raw = &bc_raw_uint2;
desc.format = Format::R32G32_UINT;
bc_raw = &bc_raw_uint2;
device->BindComputeShader(&shaders[CSTYPE_BLOCKCOMPRESS_BC1], cmd);
device->EventBegin("BlockCompress - BC1", cmd);
break;
case Format::BC3_UNORM:
case Format::BC3_UNORM_SRGB:
bc_raw = &bc_raw_uint4;
desc.format = Format::R32G32B32A32_UINT;
bc_raw = &bc_raw_uint4;
device->BindComputeShader(&shaders[CSTYPE_BLOCKCOMPRESS_BC3], cmd);
device->EventBegin("BlockCompress - BC3", cmd);
break;
case Format::BC4_UNORM:
bc_raw = &bc_raw_uint2;
desc.format = Format::R32G32_UINT;
bc_raw = &bc_raw_uint2;
device->BindComputeShader(&shaders[CSTYPE_BLOCKCOMPRESS_BC4], cmd);
device->EventBegin("BlockCompress - BC4", cmd);
break;
case Format::BC5_UNORM:
bc_raw = &bc_raw_uint4;
desc.format = Format::R32G32B32A32_UINT;
bc_raw = &bc_raw_uint4;
device->BindComputeShader(&shaders[CSTYPE_BLOCKCOMPRESS_BC5], cmd);
device->EventBegin("BlockCompress - BC5", cmd);
break;
case Format::BC6H_UF16:
desc.format = Format::R32G32B32A32_UINT;
if (has_flag(texture_src.desc.misc_flags, ResourceMiscFlag::TEXTURECUBE))
{
bc_raw = &bc_raw_uint4_cubemap;
device->BindComputeShader(&shaders[CSTYPE_BLOCKCOMPRESS_BC6H_CUBEMAP], cmd);
device->EventBegin("BlockCompress - BC6H - Cubemap", cmd);
desc.array_size = texture_src.desc.array_size; // src array size not dst!!
}
else
{
bc_raw = &bc_raw_uint4;
device->BindComputeShader(&shaders[CSTYPE_BLOCKCOMPRESS_BC6H], cmd);
device->EventBegin("BlockCompress - BC6H", cmd);
}
break;
default:
assert(0); // not supported
return;
}
if (!bc_raw->IsValid() || bc_raw->desc.width < desc.width || bc_raw->desc.height < desc.height)
if (!bc_raw->IsValid() || bc_raw->desc.width < desc.width || bc_raw->desc.height < desc.height || bc_raw->desc.array_size < desc.array_size)
{
device->CreateTexture(&desc, nullptr, bc_raw);
device->SetName(bc_raw, "bc_raw");
for (uint32_t i = 0; i < bc_raw->desc.mip_levels; ++i)
{
int subresource_index = device->CreateSubresource(bc_raw, SubresourceType::UAV, 0, 1, i, 1);
int subresource_index = device->CreateSubresource(bc_raw, SubresourceType::UAV, 0, desc.array_size, i, 1);
assert(subresource_index == i);
}
}
device->EventBegin("BlockCompress", cmd);
for (uint32_t mip = 0; mip < desc.mip_levels; ++mip)
{
const uint32_t width = std::max(1u, desc.width >> mip);
const uint32_t height = std::max(1u, desc.height >> mip);
device->BindResource(&texture_src, 0, cmd, mip);
device->BindUAV(bc_raw, 0, cmd, mip);
device->Dispatch((width + 7u) / 8u, (height + 7u) / 8u, 1, cmd);
device->Dispatch((width + 7u) / 8u, (height + 7u) / 8u, desc.array_size, cmd);
}
GPUBarrier barriers[] = {
@@ -8666,24 +8646,27 @@ void BlockCompress(const wi::graphics::Texture& texture_src, const wi::graphics:
};
device->Barrier(barriers, arraysize(barriers), cmd);
for (uint32_t mip = 0; mip < texture_bc.desc.mip_levels; ++mip)
for (uint32_t slice = 0; slice < desc.array_size; ++slice)
{
const uint32_t width = std::max(1u, desc.width >> mip);
const uint32_t height = std::max(1u, desc.height >> mip);
Box box;
box.left = 0;
box.right = width;
box.top = 0;
box.bottom = height;
box.front = 0;
box.back = 1;
for (uint32_t mip = 0; mip < texture_bc.desc.mip_levels; ++mip)
{
const uint32_t width = std::max(1u, desc.width >> mip);
const uint32_t height = std::max(1u, desc.height >> mip);
Box box;
box.left = 0;
box.right = width;
box.top = 0;
box.bottom = height;
box.front = 0;
box.back = 1;
device->CopyTexture(
&texture_bc, 0, 0, 0, mip, 0,
bc_raw, std::min(mip, bc_raw->desc.mip_levels - 1), 0,
cmd,
&box
);
device->CopyTexture(
&texture_bc, 0, 0, 0, mip, dst_slice_offset + slice,
bc_raw, std::min(mip, bc_raw->desc.mip_levels - 1), slice,
cmd,
&box
);
}
}
for (int i = 0; i < arraysize(barriers); ++i)
+3 -2
View File
@@ -910,8 +910,9 @@ namespace wi::renderer
// Compress a texture into Block Compressed format
// texture_src : source uncompressed texture
// texture_bc : destination comporessed texture, must be a supported BC format (BC1/BC3/BC4/BC5)
void BlockCompress(const wi::graphics::Texture& texture_src, const wi::graphics::Texture& texture_bc, wi::graphics::CommandList cmd);
// texture_bc : destination comporessed texture, must be a supported BC format (BC1/BC3/BC4/BC5/BC6H_UFLOAT)
// Currently this will handle simple Texture2D with mip levels, and additionally BC6H cubemap
void BlockCompress(const wi::graphics::Texture& texture_src, const wi::graphics::Texture& texture_bc, wi::graphics::CommandList cmd, uint32_t dst_slice_offset = 0);
enum BORDEREXPANDSTYLE
{
+33 -23
View File
@@ -316,12 +316,13 @@ namespace wi
if (has_flag(flags, Flags::IMPORT_BLOCK_COMPRESSED))
{
if (has_flag(flags, Flags::IMPORT_NORMALMAP))
{
fmt = basist::transcoder_texture_format::cTFBC5_RG;
desc.format = Format::BC5_UNORM;
}
else
// BC5 is disabled because it's missing green channel!
//if (has_flag(flags, Flags::IMPORT_NORMALMAP))
//{
// fmt = basist::transcoder_texture_format::cTFBC5_RG;
// desc.format = Format::BC5_UNORM;
//}
//else
{
if (transcoder.get_has_alpha())
{
@@ -443,23 +444,29 @@ namespace wi
desc.mip_levels = info.m_total_levels;
desc.misc_flags = ResourceMiscFlag::TYPED_FORMAT_CASTING;
basist::transcoder_texture_format fmt;
if (has_flag(flags, Flags::IMPORT_NORMALMAP))
basist::transcoder_texture_format fmt = basist::transcoder_texture_format::cTFRGBA32;
desc.format = Format::R8G8B8A8_UNORM;
if (has_flag(flags, Flags::IMPORT_BLOCK_COMPRESSED))
{
fmt = basist::transcoder_texture_format::cTFBC5_RG;
desc.format = Format::BC5_UNORM;
}
else
{
if (info.m_alpha_flag)
// BC5 is disabled because it's missing green channel!
//if (has_flag(flags, Flags::IMPORT_NORMALMAP))
//{
// fmt = basist::transcoder_texture_format::cTFBC5_RG;
// desc.format = Format::BC5_UNORM;
//}
//else
{
fmt = basist::transcoder_texture_format::cTFBC3_RGBA;
desc.format = Format::BC3_UNORM;
}
else
{
fmt = basist::transcoder_texture_format::cTFBC1_RGB;
desc.format = Format::BC1_UNORM;
if (info.m_alpha_flag)
{
fmt = basist::transcoder_texture_format::cTFBC3_RGBA;
desc.format = Format::BC3_UNORM;
}
else
{
fmt = basist::transcoder_texture_format::cTFBC1_RGB;
desc.format = Format::BC1_UNORM;
}
}
}
uint32_t bytes_per_block = basis_get_bytes_per_block_or_pixel(fmt);
@@ -842,7 +849,7 @@ namespace wi
if (has_flag(flags, Flags::IMPORT_BLOCK_COMPRESSED))
{
// Schedul additional task to compress into BC format and replace resource texture:
// Schedule additional task to compress into BC format and replace resource texture:
Texture uncompressed_src = std::move(resource->texture);
resource->srgb_subresource = -1;
@@ -856,8 +863,9 @@ namespace wi
}
else
{
// scan for transparency and check if fully grayscale:
// scan for transparency and also check if fully grayscale:
// By default we should use BC1 that doesn't have transparency, but half the size of BC3 that supports it
// We only care about grayscale if it's not transparent
bool has_transparency = false;
bool is_grayscale = true;
for (int y = 0; (y < height) && !has_transparency; ++y)
@@ -881,7 +889,9 @@ namespace wi
}
else if (is_grayscale)
{
// If not transparent and grayscale, than BC4 is better quality than BC1 with same memory footprint
desc.format = Format::BC4_UNORM;
// In this case, reswizzle the texture to be grayscale, not red. Red is ok for some maps, but not all, it's better to use all channels, for example grayscale specular map
desc.swizzle.r = ComponentSwizzle::R;
desc.swizzle.g = ComponentSwizzle::R;
desc.swizzle.b = ComponentSwizzle::R;
+56 -22
View File
@@ -3414,6 +3414,9 @@ namespace wi::scene
}
void Scene::RunImpostorUpdateSystem(wi::jobsystem::context& ctx)
{
if (dt == 0)
return;
if (impostors.GetCount() > 0 && !impostorArray.IsValid())
{
GraphicsDevice* device = wi::graphics::GetDevice();
@@ -3444,6 +3447,19 @@ namespace wi::scene
subresource_index = device->CreateSubresource(&impostorArray, SubresourceType::RTV, i, 1, 0, 1);
assert(subresource_index == i);
}
std::string info;
info += "Created impostor array with " + std::to_string(maxImpostorCount) + " max impostors";
info += "\n\tResolution (width * height * angles * properties) = " + std::to_string(impostorTextureDim) + " * " + std::to_string(impostorTextureDim) + " * " + std::to_string(impostorCaptureAngles) + " * 3";
info += "\n\tRender Format = ";
info += GetFormatString(impostorArray.desc.format);
info += "\n\tDepth Format = ";
info += GetFormatString(impostorDepthStencil.desc.format);
size_t total_size = 0;
total_size += ComputeTextureMemorySizeInBytes(impostorArray.desc);
total_size += ComputeTextureMemorySizeInBytes(impostorDepthStencil.desc);
info += "\n\tMemory = " + std::to_string(total_size / 1024.0f / 1024.0f) + " MB\n";
wi::backlog::post(info);
}
// reconstruct impostor array status:
@@ -3885,10 +3901,17 @@ namespace wi::scene
{
aabb_probes.resize(probes.GetCount());
if (dt == 0)
return;
if (!envmapArray.IsValid()) // even when zero probes, this will be created, since sometimes only the sky will be rendered into it
{
GraphicsDevice* device = wi::graphics::GetDevice();
constexpr Format format = Format::BC6H_UF16;
constexpr uint32_t blocks = envmapRes / GetFormatBlockSize(format);
constexpr uint32_t mip_count = GetMipCount(blocks, blocks);
TextureDesc desc;
desc.array_size = 6;
desc.height = envmapRes;
@@ -3912,11 +3935,11 @@ namespace wi::scene
desc.sample_count = 1;
desc.array_size = envmapCount * 6;
desc.bind_flags = BindFlag::SHADER_RESOURCE | BindFlag::UNORDERED_ACCESS | BindFlag::RENDER_TARGET;
desc.format = wi::renderer::format_rendertarget_envprobe;
desc.bind_flags = BindFlag::SHADER_RESOURCE;
desc.format = format;
desc.height = envmapRes;
desc.width = envmapRes;
desc.mip_levels = 0; // all mips
desc.mip_levels = mip_count;
desc.misc_flags = ResourceMiscFlag::TEXTURECUBE;
desc.usage = Usage::DEFAULT;
desc.layout = ResourceState::SHADER_RESOURCE;
@@ -3924,6 +3947,13 @@ namespace wi::scene
device->SetName(&envmapArray, "envmapArray");
desc.array_size = 6;
desc.mip_levels = mip_count;
desc.bind_flags = BindFlag::RENDER_TARGET | BindFlag::SHADER_RESOURCE | BindFlag::UNORDERED_ACCESS;
desc.format = wi::renderer::format_rendertarget_envprobe;
desc.layout = ResourceState::SHADER_RESOURCE;
device->CreateTexture(&desc, nullptr, &envrenderingColorBuffer);
device->SetName(&envrenderingColorBuffer, "envrenderingColorBuffer");
desc.mip_levels = 1;
desc.format = wi::renderer::format_depthbuffer_envprobe;
desc.bind_flags = BindFlag::DEPTH_STENCIL | BindFlag::SHADER_RESOURCE;
@@ -3931,13 +3961,13 @@ namespace wi::scene
device->CreateTexture(&desc, nullptr, &envrenderingDepthBuffer);
device->SetName(&envrenderingDepthBuffer, "envrenderingDepthBuffer");
// Cube arrays per mip level:
for (uint32_t i = 0; i < envmapArray.desc.mip_levels; ++i)
// Cubes per mip level:
for (uint32_t i = 0; i < envrenderingColorBuffer.desc.mip_levels; ++i)
{
int subresource_index;
subresource_index = device->CreateSubresource(&envmapArray, SubresourceType::SRV, 0, envmapArray.desc.array_size, i, 1);
subresource_index = device->CreateSubresource(&envrenderingColorBuffer, SubresourceType::SRV, 0, envrenderingColorBuffer.desc.array_size, i, 1);
assert(subresource_index == i);
subresource_index = device->CreateSubresource(&envmapArray, SubresourceType::UAV, 0, envmapArray.desc.array_size, i, 1);
subresource_index = device->CreateSubresource(&envrenderingColorBuffer, SubresourceType::UAV, 0, envrenderingColorBuffer.desc.array_size, i, 1);
assert(subresource_index == i);
}
@@ -3946,23 +3976,27 @@ namespace wi::scene
{
int subresource_index;
subresource_index = device->CreateSubresource(&envmapArray, SubresourceType::SRV, i * 6, 6, 0, -1);
assert(subresource_index == envmapArray.desc.mip_levels + i);
}
// individual cubes only mip0:
for (uint32_t i = 0; i < envmapCount; ++i)
{
int subresource_index;
subresource_index = device->CreateSubresource(&envmapArray, SubresourceType::SRV, i * 6, 6, 0, 1);
assert(subresource_index == envmapArray.desc.mip_levels + envmapCount + i);
}
for (uint32_t i = 0; i < envmapCount; ++i)
{
int subresource_index;
subresource_index = device->CreateSubresource(&envmapArray, SubresourceType::RTV, i * 6, 6, 0, 1);
assert(subresource_index == i);
}
std::string info;
info += "Created envprobe array with " + std::to_string(envmapCount) + " probes";
info += "\n\tResolution = " + std::to_string(envmapRes) + " * " + std::to_string(envmapRes) + " * 6";
info += "\n\tMip Levels = " + std::to_string(envmapArray.desc.mip_levels);
info += "\n\tRender Format = ";
info += GetFormatString(envrenderingColorBuffer.desc.format);
info += "\n\tDepth Format = ";
info += GetFormatString(envrenderingDepthBuffer.desc.format);
info += "\n\tCompressed Format = ";
info += GetFormatString(envmapArray.desc.format);
size_t total_size = 0;
total_size += ComputeTextureMemorySizeInBytes(envrenderingDepthBuffer.desc);
total_size += ComputeTextureMemorySizeInBytes(envrenderingColorBuffer.desc);
total_size += ComputeTextureMemorySizeInBytes(envrenderingDepthBuffer_MSAA.desc);
total_size += ComputeTextureMemorySizeInBytes(envrenderingColorBuffer_MSAA.desc);
total_size += ComputeTextureMemorySizeInBytes(envmapArray.desc);
info += "\n\tMemory = " + std::to_string(total_size / 1024.0f / 1024.0f) + " MB\n";
wi::backlog::post(info);
}
// reconstruct envmap array status:
+2 -1
View File
@@ -210,9 +210,10 @@ namespace wi::scene
// Environment probe cubemap array state:
static constexpr uint32_t envmapCount = 16;
static constexpr uint32_t envmapRes = 128;
static constexpr uint32_t envmapRes = 256;
static constexpr uint32_t envmapMSAASampleCount = 8;
wi::graphics::Texture envrenderingDepthBuffer;
wi::graphics::Texture envrenderingColorBuffer;
wi::graphics::Texture envrenderingDepthBuffer_MSAA;
wi::graphics::Texture envrenderingColorBuffer_MSAA;
wi::graphics::Texture envmapArray;
+1 -1
View File
@@ -9,7 +9,7 @@ namespace wi::version
// minor features, major updates, breaking compatibility changes
const int minor = 71;
// minor bug fixes, alterations, refactors, updates
const int revision = 215;
const int revision = 216;
const std::string version_string = std::to_string(major) + "." + std::to_string(minor) + "." + std::to_string(revision);
+26
View File
@@ -577,6 +577,32 @@ This software is distributed without any warranty.
See <http://creativecommons.org/publicdomain/zero/1.0/>.
###############################################################################################################################
GPURealTimeBC6H: https://github.com/knarkowicz/GPURealTimeBC6H
MIT License
Copyright (c) 2015 Krzysztof Narkowicz
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
###############################################################################################################################
###############################################################################################################################