Terrain BC compression (#587)

This commit is contained in:
Turánszki János
2022-11-02 10:21:04 +01:00
committed by GitHub
parent 635f96ea4f
commit 2328d4dca9
14 changed files with 6988 additions and 96 deletions
@@ -1096,9 +1096,9 @@ struct VolumetricCloudCapturePushConstants
struct TerrainVirtualTexturePush
{
uint2 offset;
float2 resolution_rcp;
uint map_type;
int region_weights_textureRO;
int output_textureRW;
};
struct VirtualTextureResidencyUpdatePush
{
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -1,5 +1,8 @@
#include "globals.hlsli"
#define ASPM_HLSL
#include "compressonator/bcn_common_kernel.h"
static const uint region_count = 4;
PUSHCONSTANT(push, TerrainVirtualTexturePush);
@@ -10,94 +13,131 @@ struct Terrain
};
ConstantBuffer<Terrain> terrain : register(b0);
RWTexture2D<uint2> output_bc1_unorm : register(u0);
RWTexture2D<uint4> output_bc3_unorm : register(u1);
RWTexture2D<uint4> output_bc5_unorm : register(u2);
[numthreads(8, 8, 1)]
void main(uint3 DTid : SV_DispatchThreadID, uint2 GTid : SV_GroupThreadID)
{
Texture2D<float4> region_weights_texture = bindless_textures[push.region_weights_textureRO];
RWTexture2D<unorm float4> output = bindless_rwtextures[push.output_textureRW];
const uint2 pixel = DTid.xy + push.offset;
float3 block_rgb[BLOCK_SIZE_4X4];
float block_x[BLOCK_SIZE_4X4];
float block_y[BLOCK_SIZE_4X4];
float2 output_dim;
output.GetDimensions(output_dim.x, output_dim.y);
const float2 uv = (pixel.xy + 0.5f) / output_dim;
float4 region_weights = region_weights_texture.SampleLevel(sampler_linear_clamp, uv, 0);
float weight_sum = 0;
float4 total_color = 0;
for (uint i = 0; i < region_count; ++i)
for (uint y = 0; y < 4; ++y)
{
float weight = region_weights[i];
ShaderMaterial material = terrain.materials[i];
switch (push.map_type)
for (uint x = 0; x < 4; ++x)
{
const uint2 pixel = push.offset + DTid.xy * 4 + uint2(x, y);
const float2 uv = (pixel.xy + 0.5f) * push.resolution_rcp;
default:
case 0:
{
float4 baseColor = material.baseColor;
[branch]
if (material.textures[BASECOLORMAP].IsValid())
float4 region_weights = region_weights_texture.SampleLevel(sampler_linear_clamp, uv, 0);
float weight_sum = 0;
float4 total_color = 0;
for (uint i = 0; i < region_count; ++i)
{
Texture2D tex = bindless_textures[material.textures[BASECOLORMAP].texture_descriptor];
float2 dim = 0;
tex.GetDimensions(dim.x, dim.y);
float2 diff = dim / output_dim;
float lod = log2(max(diff.x, diff.y));
float2 overscale = lod < 0 ? diff : 1;
float4 baseColorMap = tex.SampleLevel(sampler_linear_wrap, uv / overscale, lod);
baseColor *= baseColorMap;
}
total_color += baseColor * weight;
}
break;
float weight = region_weights[i];
case 1:
{
float2 normal = float2(0.5, 0.5);
[branch]
if (material.textures[NORMALMAP].IsValid())
ShaderMaterial material = terrain.materials[i];
switch (push.map_type)
{
default:
case 0:
{
float4 baseColor = material.baseColor;
[branch]
if (material.textures[BASECOLORMAP].IsValid())
{
Texture2D tex = bindless_textures[material.textures[BASECOLORMAP].texture_descriptor];
float2 dim = 0;
tex.GetDimensions(dim.x, dim.y);
float2 diff = dim * push.resolution_rcp;
float lod = log2(max(diff.x, diff.y));
float2 overscale = lod < 0 ? diff : 1;
float4 baseColorMap = tex.SampleLevel(sampler_linear_wrap, uv / overscale, lod);
baseColor *= baseColorMap;
}
total_color += baseColor * weight;
}
break;
case 1:
{
float2 normal = float2(0.5, 0.5);
[branch]
if (material.textures[NORMALMAP].IsValid())
{
Texture2D tex = bindless_textures[material.textures[NORMALMAP].texture_descriptor];
float2 dim = 0;
tex.GetDimensions(dim.x, dim.y);
float2 diff = dim * push.resolution_rcp;
float lod = log2(max(diff.x, diff.y));
float2 overscale = lod < 0 ? diff : 1;
float2 normalMap = tex.SampleLevel(sampler_linear_wrap, uv / overscale, lod).rg;
normal = normalMap;
}
total_color += float4(normal.rg, 1, 1) * weight;
}
break;
case 2:
{
float4 surface = float4(1, material.roughness, material.metalness, material.reflectance);
[branch]
if (material.textures[SURFACEMAP].IsValid())
{
Texture2D tex = bindless_textures[material.textures[SURFACEMAP].texture_descriptor];
float2 dim = 0;
tex.GetDimensions(dim.x, dim.y);
float2 diff = dim * push.resolution_rcp;
float lod = log2(max(diff.x, diff.y));
float2 overscale = lod < 0 ? diff : 1;
float4 surfaceMap = tex.SampleLevel(sampler_linear_wrap, uv / overscale, lod);
surface *= surfaceMap;
}
total_color += surface * weight;
}
break;
}
weight_sum += weight;
}
total_color /= weight_sum;
const uint idx = x + y * 4;
if (push.map_type == 1)
{
Texture2D tex = bindless_textures[material.textures[NORMALMAP].texture_descriptor];
float2 dim = 0;
tex.GetDimensions(dim.x, dim.y);
float2 diff = dim / output_dim;
float lod = log2(max(diff.x, diff.y));
float2 overscale = lod < 0 ? diff : 1;
float2 normalMap = tex.SampleLevel(sampler_linear_wrap, uv / overscale, lod).rg;
normal = normalMap;
block_x[idx] = total_color.r;
block_y[idx] = total_color.g;
}
total_color += float4(normal.rg, 1, 1) * weight;
}
break;
case 2:
{
float4 surface = float4(1, material.roughness, material.metalness, material.reflectance);
[branch]
if (material.textures[SURFACEMAP].IsValid())
if (push.map_type == 2)
{
Texture2D tex = bindless_textures[material.textures[SURFACEMAP].texture_descriptor];
float2 dim = 0;
tex.GetDimensions(dim.x, dim.y);
float2 diff = dim / output_dim;
float lod = log2(max(diff.x, diff.y));
float2 overscale = lod < 0 ? diff : 1;
float4 surfaceMap = tex.SampleLevel(sampler_linear_wrap, uv / overscale, lod);
surface *= surfaceMap;
block_rgb[idx] = total_color.rgb;
block_x[idx] = total_color.a;
}
else
{
block_rgb[idx] = total_color.rgb;
}
total_color += surface * weight;
}
break;
}
weight_sum += weight;
}
total_color /= weight_sum;
output[pixel] = total_color;
if (push.map_type == 1)
{
output_bc5_unorm[DTid.xy] = CompressBlockBC5_UNORM(block_x, block_y, CMP_QUALITY0);
}
if (push.map_type == 2)
{
output_bc3_unorm[DTid.xy] = CompressBlockBC3_UNORM(block_rgb, block_x, CMP_QUALITY2, /*isSRGB =*/ false);
}
else
{
output_bc1_unorm[DTid.xy] = CompressBlockBC1_UNORM(block_rgb, CMP_QUALITY0, /*isSRGB =*/ false);
}
}
+10
View File
@@ -697,6 +697,16 @@ namespace wi::graphics
int32_t bottom = 0;
};
struct Box
{
uint32_t left = 0;
uint32_t top = 0;
uint32_t front = 0;
uint32_t right = 0;
uint32_t bottom = 0;
uint32_t back = 0;
};
struct SparseTextureProperties
{
uint32_t tile_width = 0; // width of 1 tile in texels
+1
View File
@@ -203,6 +203,7 @@ namespace wi::graphics
virtual void DispatchMeshIndirect(const GPUBuffer* args, uint64_t args_offset, CommandList cmd) {}
virtual void CopyResource(const GPUResource* pDst, const GPUResource* pSrc, CommandList cmd) = 0;
virtual void CopyBuffer(const GPUBuffer* pDst, uint64_t dst_offset, const GPUBuffer* pSrc, uint64_t src_offset, uint64_t size, CommandList cmd) = 0;
virtual void CopyTexture(const Texture* dst, uint32_t dstX, uint32_t dstY, uint32_t dstZ, uint32_t dstMip, uint32_t dstSlice, const Texture* src, uint32_t srcMip, uint32_t srcSlice, CommandList cmd, const Box* srcbox = nullptr) = 0;
virtual void QueryBegin(const GPUQueryHeap *heap, uint32_t index, CommandList cmd) = 0;
virtual void QueryEnd(const GPUQueryHeap *heap, uint32_t index, CommandList cmd) = 0;
virtual void QueryResolve(const GPUQueryHeap* heap, uint32_t index, uint32_t count, const GPUBuffer* dest, uint64_t dest_offset, CommandList cmd) = 0;
+38
View File
@@ -6135,6 +6135,44 @@ using namespace dx12_internal;
commandlist.GetGraphicsCommandList()->CopyBufferRegion(dst_internal->resource.Get(), dst_offset, src_internal->resource.Get(), src_offset, size);
}
void GraphicsDevice_DX12::CopyTexture(const Texture* dst, uint32_t dstX, uint32_t dstY, uint32_t dstZ, uint32_t dstMip, uint32_t dstSlice, const Texture* src, uint32_t srcMip, uint32_t srcSlice, CommandList cmd, const Box* srcbox)
{
CommandList_DX12& commandlist = GetCommandList(cmd);
auto src_internal = to_internal((const GPUBuffer*)src);
auto dst_internal = to_internal((const GPUBuffer*)dst);
CD3DX12_TEXTURE_COPY_LOCATION src_location(src_internal->resource.Get(), D3D12CalcSubresource(srcMip, srcSlice, 0, src->desc.mip_levels, src->desc.array_size));
CD3DX12_TEXTURE_COPY_LOCATION dst_location(dst_internal->resource.Get(), D3D12CalcSubresource(dstMip, dstSlice, 0, dst->desc.mip_levels, dst->desc.array_size));
if (srcbox == nullptr)
{
commandlist.GetGraphicsCommandList()->CopyTextureRegion(
&dst_location,
dstX,
dstY,
dstZ,
&src_location,
nullptr
);
}
else
{
D3D12_BOX d3dbox = {};
d3dbox.left = srcbox->left;
d3dbox.top = srcbox->top;
d3dbox.front = srcbox->front;
d3dbox.right = srcbox->right;
d3dbox.bottom = srcbox->bottom;
d3dbox.back = srcbox->back;
commandlist.GetGraphicsCommandList()->CopyTextureRegion(
&dst_location,
dstX,
dstY,
dstZ,
&src_location,
&d3dbox
);
}
}
void GraphicsDevice_DX12::QueryBegin(const GPUQueryHeap* heap, uint32_t index, CommandList cmd)
{
CommandList_DX12& commandlist = GetCommandList(cmd);
+1
View File
@@ -307,6 +307,7 @@ namespace wi::graphics
void DispatchMeshIndirect(const GPUBuffer* args, uint64_t args_offset, CommandList cmd) override;
void CopyResource(const GPUResource* pDst, const GPUResource* pSrc, CommandList cmd) override;
void CopyBuffer(const GPUBuffer* pDst, uint64_t dst_offset, const GPUBuffer* pSrc, uint64_t src_offset, uint64_t size, CommandList cmd) override;
void CopyTexture(const Texture* dst, uint32_t dstX, uint32_t dstY, uint32_t dstZ, uint32_t dstMip, uint32_t dstSlice, const Texture* src, uint32_t srcMip, uint32_t srcSlice, CommandList cmd, const Box* srcbox) override;
void QueryBegin(const GPUQueryHeap* heap, uint32_t index, CommandList cmd) override;
void QueryEnd(const GPUQueryHeap* heap, uint32_t index, CommandList cmd) override;
void QueryResolve(const GPUQueryHeap* heap, uint32_t index, uint32_t count, const GPUBuffer* dest, uint64_t dest_offset, CommandList cmd) override;
+51 -2
View File
@@ -7706,8 +7706,8 @@ using namespace vulkan_internal;
void GraphicsDevice_Vulkan::CopyBuffer(const GPUBuffer* pDst, uint64_t dst_offset, const GPUBuffer* pSrc, uint64_t src_offset, uint64_t size, CommandList cmd)
{
CommandList_Vulkan& commandlist = GetCommandList(cmd);
auto internal_state_src = to_internal((const GPUBuffer*)pSrc);
auto internal_state_dst = to_internal((const GPUBuffer*)pDst);
auto internal_state_src = to_internal(pSrc);
auto internal_state_dst = to_internal(pDst);
VkBufferCopy copy = {};
copy.srcOffset = src_offset;
@@ -7720,6 +7720,55 @@ using namespace vulkan_internal;
1, &copy
);
}
void GraphicsDevice_Vulkan::CopyTexture(const Texture* dst, uint32_t dstX, uint32_t dstY, uint32_t dstZ, uint32_t dstMip, uint32_t dstSlice, const Texture* src, uint32_t srcMip, uint32_t srcSlice, CommandList cmd, const Box* srcbox)
{
CommandList_Vulkan& commandlist = GetCommandList(cmd);
auto src_internal = to_internal(src);
auto dst_internal = to_internal(dst);
VkImageCopy copy = {};
copy.dstSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
copy.dstSubresource.baseArrayLayer = dstSlice;
copy.dstSubresource.layerCount = 1;
copy.dstSubresource.mipLevel = dstMip;
copy.dstOffset.x = dstX;
copy.dstOffset.y = dstY;
copy.dstOffset.z = dstZ;
copy.srcSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
copy.srcSubresource.baseArrayLayer = srcSlice;
copy.srcSubresource.layerCount = 1;
copy.srcSubresource.mipLevel = srcMip;
if (srcbox == nullptr)
{
copy.srcOffset.x = 0;
copy.srcOffset.y = 0;
copy.srcOffset.z = 0;
copy.extent.width = std::min(dst->desc.width, src->desc.width);
copy.extent.height = std::min(dst->desc.height, src->desc.height);
copy.extent.depth = std::min(dst->desc.depth, src->desc.depth);
}
else
{
copy.srcOffset.x = srcbox->left;
copy.srcOffset.y = srcbox->top;
copy.srcOffset.z = srcbox->front;
copy.extent.width = srcbox->right - srcbox->left;
copy.extent.height = srcbox->bottom - srcbox->top;
copy.extent.depth = srcbox->back - srcbox->front;
}
vkCmdCopyImage(
commandlist.GetCommandBuffer(),
src_internal->resource,
VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
dst_internal->resource,
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
1,
&copy
);
}
void GraphicsDevice_Vulkan::QueryBegin(const GPUQueryHeap* heap, uint32_t index, CommandList cmd)
{
CommandList_Vulkan& commandlist = GetCommandList(cmd);
+1
View File
@@ -402,6 +402,7 @@ namespace wi::graphics
void DispatchMeshIndirect(const GPUBuffer* args, uint64_t args_offset, CommandList cmd) override;
void CopyResource(const GPUResource* pDst, const GPUResource* pSrc, CommandList cmd) override;
void CopyBuffer(const GPUBuffer* pDst, uint64_t dst_offset, const GPUBuffer* pSrc, uint64_t src_offset, uint64_t size, CommandList cmd) override;
void CopyTexture(const Texture* dst, uint32_t dstX, uint32_t dstY, uint32_t dstZ, uint32_t dstMip, uint32_t dstSlice, const Texture* src, uint32_t srcMip, uint32_t srcSlice, CommandList cmd, const Box* srcbox) override;
void QueryBegin(const GPUQueryHeap* heap, uint32_t index, CommandList cmd) override;
void QueryEnd(const GPUQueryHeap* heap, uint32_t index, CommandList cmd) override;
void QueryResolve(const GPUQueryHeap* heap, uint32_t index, uint32_t count, const GPUBuffer* dest, uint64_t dest_offset, CommandList cmd) override;
+96 -20
View File
@@ -233,13 +233,6 @@ namespace wi::terrain
bool success = device->CreateTexture(&desc, nullptr, &texture);
assert(success);
for (uint32_t i = 0; i < texture.desc.mip_levels; ++i)
{
int subresource = 0;
subresource = device->CreateSubresource(&texture, SubresourceType::UAV, 0, 1, i, 1);
assert(subresource == i);
}
residencyMap = {};
feedbackMap = {};
requestBuffer = {};
@@ -1459,13 +1452,17 @@ namespace wi::terrain
// This should have been created on generation thread, but if not (serialized), create it last minute:
CreateChunkRegionTexture(chunk_data);
const uint32_t min_resolution = 128u;
const uint32_t min_resolution = 256u;
const uint32_t min_mips = 7u;
#ifdef TERRAIN_VIRTUAL_TEXTURE_DEBUG
const uint32_t max_resolution = 2048;
const uint32_t max_mips = 10u;
#else
const uint32_t max_resolution = 16384u;
const uint32_t max_mips = 13u;
#endif // TERRAIN_VIRTUAL_TEXTURE_DEBUG
const uint32_t required_resolution = dist < 2 ? max_resolution : min_resolution;
const uint32_t required_mips = dist < 2 ? max_mips : min_mips;
chunk_data.vt.resize(3); // base, normal, surface
for (uint32_t map_type = 0; map_type < chunk_data.vt.size(); ++map_type)
@@ -1480,17 +1477,21 @@ namespace wi::terrain
desc.width = required_resolution;
desc.height = required_resolution;
desc.misc_flags = ResourceMiscFlag::SPARSE;
desc.bind_flags = BindFlag::SHADER_RESOURCE | BindFlag::UNORDERED_ACCESS;
desc.mip_levels = 0;
desc.bind_flags = BindFlag::SHADER_RESOURCE;
desc.mip_levels = required_mips;
desc.layout = ResourceState::SHADER_RESOURCE_COMPUTE;
if (map_type == MaterialComponent::NORMALMAP)
switch (map_type)
{
desc.format = Format::R8G8_UNORM;
}
else
{
desc.format = Format::R8G8B8A8_UNORM;
default:
desc.format = Format::BC1_UNORM;
break;
case MaterialComponent::NORMALMAP:
desc.format = Format::BC5_UNORM;
break;
case MaterialComponent::SURFACEMAP:
desc.format = Format::BC3_UNORM;
break;
}
vt.init(page_allocator, desc);
@@ -1504,8 +1505,8 @@ namespace wi::terrain
material->textures[map_type].lod_clamp = (float)vt.lod_count - 1;
virtual_textures_in_use.push_back(&vt);
virtual_texture_barriers_before_update.push_back(GPUBarrier::Image(&vt.texture, vt.texture.desc.layout, ResourceState::UNORDERED_ACCESS));
virtual_texture_barriers_after_update.push_back(GPUBarrier::Image(&vt.texture, ResourceState::UNORDERED_ACCESS, vt.texture.desc.layout));
virtual_texture_barriers_before_update.push_back(GPUBarrier::Image(&vt.texture, vt.texture.desc.layout, ResourceState::COPY_DST));
virtual_texture_barriers_after_update.push_back(GPUBarrier::Image(&vt.texture, ResourceState::COPY_DST, vt.texture.desc.layout));
if (!vt.residencyMap.IsValid())
continue;
@@ -1668,6 +1669,46 @@ namespace wi::terrain
material_HighAltitude.WriteShaderMaterial(&materials[3]);
device->BindDynamicConstantBuffer(materials, 0, cmd);
static Texture bc1_raw;
if (!bc1_raw.IsValid())
{
TextureDesc td;
td.width = 512 / 4;
td.height = 256 / 4;
td.format = Format::R32G32_UINT;
td.bind_flags = BindFlag::UNORDERED_ACCESS;
td.layout = ResourceState::UNORDERED_ACCESS;
bool success = device->CreateTexture(&td, nullptr, &bc1_raw);
assert(success);
}
static Texture bc3_raw;
if (!bc3_raw.IsValid())
{
TextureDesc td;
td.width = 256 / 4;
td.height = 256 / 4;
td.format = Format::R32G32B32A32_UINT;
td.bind_flags = BindFlag::UNORDERED_ACCESS;
td.layout = ResourceState::UNORDERED_ACCESS;
bool success = device->CreateTexture(&td, nullptr, &bc3_raw);
assert(success);
}
static Texture bc5_raw;
if (!bc5_raw.IsValid())
{
TextureDesc td;
td.width = 256 / 4;
td.height = 256 / 4;
td.format = Format::R32G32B32A32_UINT;
td.bind_flags = BindFlag::UNORDERED_ACCESS;
td.layout = ResourceState::UNORDERED_ACCESS;
bool success = device->CreateTexture(&td, nullptr, &bc5_raw);
assert(success);
}
device->BindUAV(&bc1_raw, 0, cmd);
device->BindUAV(&bc3_raw, 1, cmd);
device->BindUAV(&bc5_raw, 2, cmd);
for (const VirtualTexture* vt : virtual_textures_in_use)
{
for (auto& request : vt->update_requests)
@@ -1692,18 +1733,53 @@ namespace wi::terrain
std::min(request_lod_resolution.x, vt->texture.sparse_properties->tile_width),
std::min(request_lod_resolution.y, vt->texture.sparse_properties->tile_height)
);
const uint2 bc_size = uint2(
(size.x + 3u) / 4u,
(size.y + 3u) / 4u
);
TerrainVirtualTexturePush push;
push.offset = uint2(
request.tile_x * size.x,
request.tile_y * size.y
);
push.resolution_rcp.x = 1.0f / request_lod_resolution.x;
push.resolution_rcp.y = 1.0f / request_lod_resolution.y;
push.map_type = vt->map_type;
push.region_weights_textureRO = device->GetDescriptorIndex(&vt->region_weights_texture, SubresourceType::SRV);
push.output_textureRW = device->GetDescriptorIndex(&vt->texture, SubresourceType::UAV, mip);
device->PushConstants(&push, sizeof(push), cmd);
device->Dispatch((size.x + 7u) / 8u, (size.y + 7u) / 8u, 1, cmd);
device->Dispatch((bc_size.x + 7u) / 8u, (bc_size.y + 7u) / 8u, 1, cmd);
// Sadly we can't write directly into block compressed texture, so we must copy from raw block format to real BC format:
const Texture* bc_raw = nullptr;
switch (vt->map_type)
{
default:
bc_raw = &bc1_raw;
break;
case MaterialComponent::NORMALMAP:
bc_raw = &bc5_raw;
break;
case MaterialComponent::SURFACEMAP:
bc_raw = &bc3_raw;
break;
}
GPUBarrier barrier = GPUBarrier::Image(bc_raw, bc_raw->desc.layout, ResourceState::COPY_SRC);
device->Barrier(&barrier, 1, cmd);
Box srcbox;
srcbox.left = 0;
srcbox.right = bc_size.x;
srcbox.top = 0;
srcbox.bottom = bc_size.y;
srcbox.front = 0;
srcbox.back = 1;
device->CopyTexture(
&vt->texture, request.tile_x * size.x, request.tile_y * size.y, 0, mip, 0,
bc_raw, 0, 0, cmd, &srcbox
);
std::swap(barrier.image.layout_before, barrier.image.layout_after);
device->Barrier(&barrier, 1, cmd);
}
}
vt->update_requests.clear();
+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 = 77;
const int revision = 78;
const std::string version_string = std::to_string(major) + "." + std::to_string(minor) + "." + std::to_string(revision);
+30
View File
@@ -508,6 +508,36 @@ ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, WHETHER IN
OTHERWISE, ARISING FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM OTHER DEALINGS IN THE FONT
SOFTWARE.
###############################################################################################################################
Compressonator: https://github.com/GPUOpen-Tools/compressonator
//===============================================================================
// Copyright (c) 2021 Advanced Micro Devices, Inc. All rights reserved.
//
// 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.
//
//===============================================================================
###############################################################################################################################
###############################################################################################################################