diff --git a/util/test/demos/CMakeLists.txt b/util/test/demos/CMakeLists.txt index 3d22ab5a9..0f0741c0c 100644 --- a/util/test/demos/CMakeLists.txt +++ b/util/test/demos/CMakeLists.txt @@ -29,6 +29,7 @@ set(VULKAN_SRC vk/vk_spec_constants.cpp vk/vk_spirv_13_shaders.cpp vk/vk_structured_buffer_nested.cpp + vk/vk_texture_zoo.cpp vk/vk_triangle_fan.cpp vk/vk_vertex_attr_zoo.cpp vk/vk_video_textures.cpp @@ -58,12 +59,14 @@ set(OPENGL_SRC gl/gl_shader_editing.cpp gl/gl_simple_triangle.cpp gl/gl_spirv_shader.cpp + gl/gl_texture_zoo.cpp gl/gl_unsized_ms_fbo_attachment.cpp gl/gl_vao_0.cpp gl/gl_vertex_attr_zoo.cpp) set(SRC main.cpp test_common.cpp + texture_zoo.cpp linux/linux_platform.cpp linux/linux_window.cpp) diff --git a/util/test/demos/d3d11/d3d11_helpers.h b/util/test/demos/d3d11/d3d11_helpers.h index e480f1b67..696856e24 100644 --- a/util/test/demos/d3d11/d3d11_helpers.h +++ b/util/test/demos/d3d11/d3d11_helpers.h @@ -325,15 +325,16 @@ private: } template -inline void SetDebugName(T pObj, const char *name) +inline void SetDebugName(T pObj, const std::string &name) { if(pObj) - pObj->SetPrivateData(WKPDID_D3DDebugObjectName, (UINT)strlen(name), name); + pObj->SetPrivateData(WKPDID_D3DDebugObjectName, (UINT)name.size(), name.c_str()); } template -inline void SetDebugName(T pObj, const wchar_t *name) +inline void SetDebugName(T pObj, const std::wstring &name) { if(pObj) - pObj->SetPrivateData(WKPDID_D3DDebugObjectNameW, UINT(wcslen(name) * sizeof(wchar_t)), name); + pObj->SetPrivateData(WKPDID_D3DDebugObjectNameW, UINT(name.size() * sizeof(wchar_t)), + name.c_str()); } diff --git a/util/test/demos/d3d11/d3d11_test.cpp b/util/test/demos/d3d11/d3d11_test.cpp index 933972ce3..fa94f45b9 100644 --- a/util/test/demos/d3d11/d3d11_test.cpp +++ b/util/test/demos/d3d11/d3d11_test.cpp @@ -334,6 +334,24 @@ void D3D11GraphicsTest::Present() swap->Present(0, 0); } +void D3D11GraphicsTest::pushMarker(const std::string &name) +{ + if(annot) + annot->BeginEvent(UTF82Wide(name).c_str()); +} + +void D3D11GraphicsTest::setMarker(const std::string &name) +{ + if(annot) + annot->SetMarker(UTF82Wide(name).c_str()); +} + +void D3D11GraphicsTest::popMarker() +{ + if(annot) + annot->EndEvent(); +} + std::vector D3D11GraphicsTest::GetBufferData(ID3D11Buffer *buffer, uint32_t offset, uint32_t len) { D3D11_MAPPED_SUBRESOURCE mapped; diff --git a/util/test/demos/d3d11/d3d11_test.h b/util/test/demos/d3d11/d3d11_test.h index 2e9fa28d3..807af1a77 100644 --- a/util/test/demos/d3d11/d3d11_test.h +++ b/util/test/demos/d3d11/d3d11_test.h @@ -152,6 +152,10 @@ struct D3D11GraphicsTest : public GraphicsTest bool Running(); void Present(); + void pushMarker(const std::string &name); + void setMarker(const std::string &name); + void popMarker(); + DXGI_FORMAT backbufferFmt = DXGI_FORMAT_R8G8B8A8_UNORM_SRGB; int backbufferCount = 2; int backbufferMSAA = 1; diff --git a/util/test/demos/d3d11/d3d11_texture_zoo.cpp b/util/test/demos/d3d11/d3d11_texture_zoo.cpp new file mode 100644 index 000000000..6a12f2a66 --- /dev/null +++ b/util/test/demos/d3d11/d3d11_texture_zoo.cpp @@ -0,0 +1,1106 @@ +/****************************************************************************** + * The MIT License (MIT) + * + * Copyright (c) 2019 Baldur Karlsson + * + * 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. + ******************************************************************************/ + +#include "d3d11_test.h" + +using namespace TextureZoo; + +RD_TEST(D3D11_Texture_Zoo, D3D11GraphicsTest) +{ + static constexpr const char *Description = + "Tests all possible combinations of texture type and format that are supported."; + + std::string pixelTemplate = R"EOSHADER( intex : register(t0); + +float4 main() : SV_Target0 +{ + return intex.Load(¶ms).&swizzle; +} +)EOSHADER"; + + std::string pixelBlit = R"EOSHADER( + +Texture2D intex : register(t0); + +float4 main(float4 pos : SV_Position) : SV_Target0 +{ + return intex.Load(float3(pos.xy, 0)); +} + +)EOSHADER"; + + std::string pixelMSFloat = R"EOSHADER( + +cbuffer consts : register(b0) +{ + uint slice; + uint mip; + uint flags; + uint z; +}; + +float srgb2linear(float f) +{ + if (f <= 0.04045f) + return f / 12.92f; + else + return pow((f + 0.055f) / 1.055f, 2.4f); +} + +float4 main(float4 pos : SV_Position, uint samp : SV_SampleIndex) : SV_Target0 +{ + uint x = uint(pos.x); + uint y = uint(pos.y); + + float4 ret = float4(0.1f, 0.35f, 0.6f, 0.85f); + + // each 3D slice cycles the x. This only affects the primary diagonal + uint offs_x = (x + z) % max(1u, TEX_WIDTH >> mip); + + // pixels off the diagonal invert the colors + if(offs_x != y) + ret = ret.wzyx; + + // second slice adds a coarse checkerboard pattern of inversion + if(slice > 0 && (((x / 2) % 2) != ((y / 2) % 2))) + ret = ret.wzyx; + + // second sample/mip is shifted up a bit. MSAA textures have no mips, + // textures with mips have no samples. + ret += 0.075f.xxxx * (samp + mip); + + // Signed normals are negative + if(flags & 1) + ret = -ret; + + // undo SRGB curve applied in output merger, to match the textures we just blat values into + // without conversion (which are then interpreted as srgb implicitly) + if(flags & 2) + { + ret.r = srgb2linear(ret.r); + ret.g = srgb2linear(ret.g); + ret.b = srgb2linear(ret.b); + } + + // BGR flip - same as above, for BGRA textures + if(flags & 4) + ret.rgb = ret.bgr; + + // put red into alpha, because that's what we did in manual upload + if(flags & 8) + ret.a = ret.r; + + return ret; +} + +)EOSHADER"; + + std::string pixelMSDepth = R"EOSHADER( + +cbuffer consts : register(b0) +{ + uint slice; + uint mip; + uint flags; + uint z; +}; + +float main(float4 pos : SV_Position, uint samp : SV_SampleIndex) : SV_Depth +{ + uint x = uint(pos.x); + uint y = uint(pos.y); + + float ret = 0.1f; + + // each 3D slice cycles the x. This only affects the primary diagonal + uint offs_x = (x + z) % max(1u, TEX_WIDTH >> mip); + + // pixels off the diagonal invert the colors + // second slice adds a coarse checkerboard pattern of inversion + if((offs_x != y) != (slice > 0 && (((x / 2) % 2) != ((y / 2) % 2)))) + { + ret = 0.85f; + + // so we can fill stencil data, clip off the inverted values + if(flags == 1) + clip(-1); + } + + // second sample/mip is shifted up a bit. MSAA textures have no mips, + // textures with mips have no samples. + ret += 0.075f * (samp + mip); + + return ret; +} + +)EOSHADER"; + + std::string pixelMSUInt = R"EOSHADER( + +cbuffer consts : register(b0) +{ + uint slice; + uint mip; + uint flags; + uint z; +}; + +uint4 main(float4 pos : SV_Position, uint samp : SV_SampleIndex) : SV_Target0 +{ + uint x = uint(pos.x); + uint y = uint(pos.y); + + uint4 ret = uint4(10, 40, 70, 100); + + // each 3D slice cycles the x. This only affects the primary diagonal + uint offs_x = (x + z) % max(1u, TEX_WIDTH >> mip); + + // pixels off the diagonal invert the colors + if(offs_x != y) + ret = ret.wzyx; + + // second slice adds a coarse checkerboard pattern of inversion + if(slice > 0 && (((x / 2) % 2) != ((y / 2) % 2))) + ret = ret.wzyx; + + // second sample/mip is shifted up a bit. MSAA textures have no mips, + // textures with mips have no samples. + ret += uint4(10, 10, 10, 10) * (samp + mip); + + return ret; +} + +)EOSHADER"; + + std::string pixelMSSInt = R"EOSHADER( + +cbuffer consts : register(b0) +{ + uint slice; + uint mip; + uint flags; + uint z; +}; + +int4 main(float4 pos : SV_Position, uint samp : SV_SampleIndex) : SV_Target0 +{ + uint x = uint(pos.x); + uint y = uint(pos.y); + + int4 ret = int4(10, 40, 70, 100); + + // each 3D slice cycles the x. This only affects the primary diagonal + uint offs_x = (x + z) % max(1u, TEX_WIDTH >> mip); + + // pixels off the diagonal invert the colors + if(offs_x != y) + ret = ret.wzyx; + + // second slice adds a coarse checkerboard pattern of inversion + if(slice > 0 && (((x / 2) % 2) != ((y / 2) % 2))) + ret = ret.wzyx; + + // second sample/mip is shifted up a bit. MSAA textures have no mips, + // textures with mips have no samples. + ret += int4(10, 10, 10, 10) * (samp + mip); + + return -ret; +} + +)EOSHADER"; + + struct D3D11Format + { + const std::string name; + DXGI_FORMAT texFmt; + DXGI_FORMAT viewFmt; + TexConfig cfg; + }; + + struct TestCase + { + D3D11Format fmt; + uint32_t dim; + bool isArray; + bool canRender; + bool isDepth; + bool isMSAA; + bool hasData; + ID3D11ResourcePtr res; + ID3D11ShaderResourceViewPtr srv; + }; + + std::string MakeName(const TestCase &test) + { + std::string name = "Texture " + std::to_string(test.dim) + "D"; + + if(test.isMSAA) + name += " MSAA"; + if(test.isArray) + name += " Array"; + + return name; + } + + ID3D11PixelShaderPtr GetShader(const TestCase &test) + { + static std::map shaders; + + bool isStencilOut = (test.fmt.viewFmt == DXGI_FORMAT_X32_TYPELESS_G8X24_UINT || + test.fmt.viewFmt == DXGI_FORMAT_X24_TYPELESS_G8_UINT); + + uint32_t key = uint32_t(test.fmt.cfg.data); + key |= test.dim << 6; + key |= test.isMSAA ? 0x80000 : 0; + key |= isStencilOut ? 0x100000 : 0; + + ID3D11PixelShaderPtr ret = shaders[key]; + if(!ret) + { + std::string texType = "Texture" + std::to_string(test.dim) + "D"; + if(test.isMSAA) + texType += "MS"; + if(test.dim < 3) + texType += "Array"; + + const std::string innerType[] = { + "float", "unorm float", "float", "uint", "int", + }; + static_assert(ARRAY_COUNT(innerType) == (size_t)DataType::Count, + "DataType has changed, update array"); + + texType += "<" + innerType[(int)test.fmt.cfg.data] + "4>"; + + std::string src = texType + pixelTemplate; + + if(test.isMSAA) + src.replace(src.find("¶ms"), 7, "0, 0"); + else + src.replace(src.find("¶ms"), 7, "0"); + + if(isStencilOut) + src.replace(src.find("&swizzle"), 8, "zyzz"); + else + src.replace(src.find("&swizzle"), 8, "xyzw"); + + ret = shaders[key] = CreatePS(Compile(src, "main", "ps_5_0")); + } + + return ret; + } + + bool SetData(ID3D11ResourcePtr res, const D3D11Format &fmt) + { + Vec4i dim; + UINT mips, slices; + GetDimensions(res, dim, mips, slices); + + TexData data; + + for(UINT s = 0; s < slices; s++) + { + for(UINT m = 0; m < mips; m++) + { + MakeData(data, fmt.cfg, dim, m, s); + + if(data.byteData.empty()) + return false; + + ctx->UpdateSubresource(res, s * mips + m, NULL, data.byteData.data(), data.rowPitch, + data.slicePitch); + } + } + + return true; + } + + void GetDimensions(ID3D11ResourcePtr res, Vec4i & dim, UINT & mips, UINT & slices) + { + dim = Vec4i(); + mips = 1; + slices = 1; + + D3D11_RESOURCE_DIMENSION t; + res->GetType(&t); + + if(t == D3D11_RESOURCE_DIMENSION_TEXTURE1D) + { + ID3D11Texture1DPtr tex = res; + D3D11_TEXTURE1D_DESC desc; + tex->GetDesc(&desc); + + dim.x = desc.Width; + mips = std::max(mips, desc.MipLevels); + slices = std::max(slices, desc.ArraySize); + } + else if(t == D3D11_RESOURCE_DIMENSION_TEXTURE2D) + { + ID3D11Texture2DPtr tex = res; + D3D11_TEXTURE2D_DESC desc; + tex->GetDesc(&desc); + + dim.x = desc.Width; + dim.y = desc.Height; + mips = std::max(mips, desc.MipLevels); + slices = std::max(slices, desc.ArraySize); + } + else if(t == D3D11_RESOURCE_DIMENSION_TEXTURE3D) + { + ID3D11Texture3DPtr tex = res; + D3D11_TEXTURE3D_DESC desc; + tex->GetDesc(&desc); + + dim.x = desc.Width; + dim.y = desc.Height; + dim.z = desc.Depth; + mips = std::max(mips, desc.MipLevels); + } + else + { + TEST_ERROR("Unexpected resource type!"); + } + } + + TestCase FinaliseTest(TestCase test) + { + if(test.dim == 1) + { + D3D11TextureCreator creator = + MakeTexture(test.fmt.texFmt, texWidth).Mips(texMips).Array(test.isArray ? texSlices : 1).SRV(); + + if(test.isDepth) + creator.DSV(); + else if(test.canRender) + creator.RTV(); + + ID3D11Texture1DPtr tex = creator; + + test.srv = MakeSRV(tex).Format(test.fmt.viewFmt); + test.res = tex; + } + else if(test.dim == 2 && !test.isMSAA) + { + D3D11TextureCreator creator = MakeTexture(test.fmt.texFmt, texWidth, texHeight) + .Mips(texMips) + .Array(test.isArray ? texSlices : 1) + .SRV(); + + if(test.isDepth) + creator.DSV(); + else if(test.canRender) + creator.RTV(); + + ID3D11Texture2DPtr tex = creator; + + test.srv = MakeSRV(tex).Format(test.fmt.viewFmt); + test.res = tex; + } + else if(test.dim == 2 && test.isMSAA) + { + D3D11TextureCreator creator = MakeTexture(test.fmt.texFmt, texWidth, texHeight) + .Multisampled(texSamples) + .Array(test.isArray ? texSlices : 1) + .SRV(); + + if(test.isDepth) + creator.DSV(); + else + creator.RTV(); + + ID3D11Texture2DPtr tex = creator; + + test.srv = MakeSRV(tex).Format(test.fmt.viewFmt); + test.res = tex; + test.canRender = true; + } + else if(test.dim == 3) + { + D3D11TextureCreator creator = + MakeTexture(test.fmt.texFmt, texWidth, texHeight, texDepth).Mips(texMips).SRV(); + + if(test.canRender) + creator.RTV(); + + ID3D11Texture3DPtr tex = creator; + + test.srv = MakeSRV(tex).Format(test.fmt.viewFmt); + test.res = tex; + } + + SetDebugName(test.res, MakeName(test) + " " + test.fmt.name); + + // discard the resource when possible, this makes renderdoc treat it as dirty + if(ctx1) + ctx1->DiscardResource(test.res); + + if(!test.isMSAA) + { + pushMarker("Set data for " + test.fmt.name + " " + MakeName(test)); + + test.hasData = SetData(test.res, test.fmt); + + popMarker(); + } + + return test; + } + + DXGI_FORMAT GetDepthFormat(const D3D11Format &f) + { + DXGI_FORMAT queryFormat = DXGI_FORMAT_UNKNOWN; + + switch(f.texFmt) + { + case DXGI_FORMAT_R32G8X24_TYPELESS: queryFormat = DXGI_FORMAT_D32_FLOAT_S8X24_UINT; break; + case DXGI_FORMAT_R24G8_TYPELESS: queryFormat = DXGI_FORMAT_D24_UNORM_S8_UINT; break; + case DXGI_FORMAT_R32_TYPELESS: queryFormat = DXGI_FORMAT_D32_FLOAT; break; + case DXGI_FORMAT_R16_TYPELESS: queryFormat = DXGI_FORMAT_D16_UNORM; break; + default: TEST_ERROR("Unexpected base texture format"); + } + + return queryFormat; + } + + void AddSupportedTests(const D3D11Format &f, std::vector &test_textures, bool depthMode) + { + DXGI_FORMAT queryFormat = f.viewFmt; + + if(depthMode) + queryFormat = GetDepthFormat(f); + + UINT supp = 0; + dev->CheckFormatSupport(queryFormat, &supp); + + bool renderable = (supp & D3D11_FORMAT_SUPPORT_RENDER_TARGET) != 0; + bool depth = (supp & D3D11_FORMAT_SUPPORT_DEPTH_STENCIL) != 0; + + if((supp & D3D11_FORMAT_SUPPORT_SHADER_LOAD) || depth) + { + // TODO: disable 1D depth textures for now, we don't support displaying them + if(!depthMode) + { + if(supp & D3D11_FORMAT_SUPPORT_TEXTURE1D) + { + test_textures.push_back(FinaliseTest({f, 1, false, renderable, depth})); + test_textures.push_back(FinaliseTest({f, 1, true, renderable, depth})); + } + else + { + test_textures.push_back({f, 1, false}); + test_textures.push_back({f, 1, true}); + } + } + + if(supp & D3D11_FORMAT_SUPPORT_TEXTURE2D) + { + test_textures.push_back(FinaliseTest({f, 2, false, renderable, depth})); + test_textures.push_back(FinaliseTest({f, 2, true, renderable, depth})); + } + else + { + test_textures.push_back({f, 2, false}); + test_textures.push_back({f, 2, true}); + } + if(supp & D3D11_FORMAT_SUPPORT_TEXTURE3D) + { + test_textures.push_back(FinaliseTest({f, 3, false, renderable, depth})); + } + else + { + test_textures.push_back({f, 3, false}); + } + if(((supp & D3D11_FORMAT_SUPPORT_MULTISAMPLE_LOAD) || depth) && + (supp & D3D11_FORMAT_SUPPORT_MULTISAMPLE_RENDERTARGET)) + { + test_textures.push_back(FinaliseTest({f, 2, false, true, depth, true})); + test_textures.push_back(FinaliseTest({f, 2, true, true, depth, true})); + } + else + { + test_textures.push_back({f, 2, false, true, depth, true}); + test_textures.push_back({f, 2, true, true, depth, true}); + } + } + else + { + test_textures.push_back({f, 2, false}); + + if(supp & (D3D11_FORMAT_SUPPORT_TEXTURE1D | D3D11_FORMAT_SUPPORT_TEXTURE2D | + D3D11_FORMAT_SUPPORT_TEXTURE3D)) + { + TEST_ERROR("Format %d can't be loaded in shader but can be a texture!", f.texFmt); + } + } + } + + int main() + { + // initialise, create window, create device, etc + if(!Init()) + return 3; + + ID3D11VertexShaderPtr vs = CreateVS(Compile(D3DFullscreenQuadVertex, "main", "vs_4_0")); + ID3D11PixelShaderPtr blitps = CreatePS(Compile(pixelBlit, "main", "ps_4_0")); + + pushMarker("Add tests"); + +#define TEST_CASE_NAME(texFmt, viewFmt) \ + (texFmt == viewFmt) ? std::string(#texFmt + 12) \ + : (std::string(#texFmt + 12) + "->" + (strchr(#viewFmt + 12, '_') + 1)) + +#define TEST_CASE(texType, texFmt, viewFmt, compCount, byteWidth, dataType) \ + { \ + TEST_CASE_NAME(texFmt, viewFmt), texFmt, viewFmt, {texType, compCount, byteWidth, dataType}, \ + } + + std::vector test_textures; + + const D3D11Format color_tests[] = { + TEST_CASE(TextureType::Regular, DXGI_FORMAT_R32G32B32A32_TYPELESS, + DXGI_FORMAT_R32G32B32A32_FLOAT, 4, 4, DataType::Float), + TEST_CASE(TextureType::Regular, DXGI_FORMAT_R32G32B32A32_TYPELESS, + DXGI_FORMAT_R32G32B32A32_UINT, 4, 4, DataType::UInt), + TEST_CASE(TextureType::Regular, DXGI_FORMAT_R32G32B32A32_FLOAT, + DXGI_FORMAT_R32G32B32A32_FLOAT, 4, 4, DataType::Float), + TEST_CASE(TextureType::Regular, DXGI_FORMAT_R32G32B32A32_UINT, + DXGI_FORMAT_R32G32B32A32_UINT, 4, 4, DataType::UInt), + TEST_CASE(TextureType::Regular, DXGI_FORMAT_R32G32B32A32_SINT, + DXGI_FORMAT_R32G32B32A32_SINT, 4, 4, DataType::SInt), + + TEST_CASE(TextureType::Regular, DXGI_FORMAT_R32G32B32_TYPELESS, DXGI_FORMAT_R32G32B32_FLOAT, + 3, 4, DataType::Float), + TEST_CASE(TextureType::Regular, DXGI_FORMAT_R32G32B32_TYPELESS, DXGI_FORMAT_R32G32B32_UINT, + 3, 4, DataType::UInt), + TEST_CASE(TextureType::Regular, DXGI_FORMAT_R32G32B32_FLOAT, DXGI_FORMAT_R32G32B32_FLOAT, 3, + 4, DataType::Float), + TEST_CASE(TextureType::Regular, DXGI_FORMAT_R32G32B32_UINT, DXGI_FORMAT_R32G32B32_UINT, 3, + 4, DataType::UInt), + TEST_CASE(TextureType::Regular, DXGI_FORMAT_R32G32B32_SINT, DXGI_FORMAT_R32G32B32_SINT, 3, + 4, DataType::SInt), + + TEST_CASE(TextureType::Regular, DXGI_FORMAT_R16G16B16A16_TYPELESS, + DXGI_FORMAT_R16G16B16A16_FLOAT, 4, 2, DataType::Float), + TEST_CASE(TextureType::Regular, DXGI_FORMAT_R16G16B16A16_TYPELESS, + DXGI_FORMAT_R16G16B16A16_UINT, 4, 2, DataType::UInt), + TEST_CASE(TextureType::Regular, DXGI_FORMAT_R16G16B16A16_TYPELESS, + DXGI_FORMAT_R16G16B16A16_UNORM, 4, 2, DataType::UNorm), + TEST_CASE(TextureType::Regular, DXGI_FORMAT_R16G16B16A16_FLOAT, + DXGI_FORMAT_R16G16B16A16_FLOAT, 4, 2, DataType::Float), + TEST_CASE(TextureType::Regular, DXGI_FORMAT_R16G16B16A16_UNORM, + DXGI_FORMAT_R16G16B16A16_UNORM, 4, 2, DataType::UNorm), + TEST_CASE(TextureType::Regular, DXGI_FORMAT_R16G16B16A16_UINT, + DXGI_FORMAT_R16G16B16A16_UINT, 4, 2, DataType::UInt), + TEST_CASE(TextureType::Regular, DXGI_FORMAT_R16G16B16A16_SNORM, + DXGI_FORMAT_R16G16B16A16_SNORM, 4, 2, DataType::SNorm), + TEST_CASE(TextureType::Regular, DXGI_FORMAT_R16G16B16A16_SINT, + DXGI_FORMAT_R16G16B16A16_SINT, 4, 2, DataType::SInt), + + TEST_CASE(TextureType::Regular, DXGI_FORMAT_R32G32_TYPELESS, DXGI_FORMAT_R32G32_FLOAT, 2, 4, + DataType::Float), + TEST_CASE(TextureType::Regular, DXGI_FORMAT_R32G32_TYPELESS, DXGI_FORMAT_R32G32_UINT, 2, 4, + DataType::UInt), + TEST_CASE(TextureType::Regular, DXGI_FORMAT_R32G32_FLOAT, DXGI_FORMAT_R32G32_FLOAT, 2, 4, + DataType::Float), + TEST_CASE(TextureType::Regular, DXGI_FORMAT_R32G32_UINT, DXGI_FORMAT_R32G32_UINT, 2, 4, + DataType::UInt), + TEST_CASE(TextureType::Regular, DXGI_FORMAT_R32G32_SINT, DXGI_FORMAT_R32G32_SINT, 2, 4, + DataType::SInt), + + TEST_CASE(TextureType::Regular, DXGI_FORMAT_R8G8B8A8_TYPELESS, DXGI_FORMAT_R8G8B8A8_UINT, 4, + 1, DataType::UInt), + TEST_CASE(TextureType::Regular, DXGI_FORMAT_R8G8B8A8_TYPELESS, DXGI_FORMAT_R8G8B8A8_UNORM, + 4, 1, DataType::UNorm), + TEST_CASE(TextureType::Regular, DXGI_FORMAT_R8G8B8A8_UNORM, DXGI_FORMAT_R8G8B8A8_UNORM, 4, + 1, DataType::UNorm), + TEST_CASE(TextureType::Regular, DXGI_FORMAT_R8G8B8A8_UNORM_SRGB, + DXGI_FORMAT_R8G8B8A8_UNORM_SRGB, 4, 1, DataType::UNorm), + TEST_CASE(TextureType::Regular, DXGI_FORMAT_R8G8B8A8_UINT, DXGI_FORMAT_R8G8B8A8_UINT, 4, 1, + DataType::UInt), + TEST_CASE(TextureType::Regular, DXGI_FORMAT_R8G8B8A8_SNORM, DXGI_FORMAT_R8G8B8A8_SNORM, 4, + 1, DataType::SNorm), + TEST_CASE(TextureType::Regular, DXGI_FORMAT_R8G8B8A8_SINT, DXGI_FORMAT_R8G8B8A8_SINT, 4, 1, + DataType::SInt), + + TEST_CASE(TextureType::Regular, DXGI_FORMAT_R16G16_TYPELESS, DXGI_FORMAT_R16G16_FLOAT, 2, 2, + DataType::Float), + TEST_CASE(TextureType::Regular, DXGI_FORMAT_R16G16_TYPELESS, DXGI_FORMAT_R16G16_UINT, 2, 2, + DataType::UInt), + TEST_CASE(TextureType::Regular, DXGI_FORMAT_R16G16_TYPELESS, DXGI_FORMAT_R16G16_UNORM, 2, 2, + DataType::UNorm), + TEST_CASE(TextureType::Regular, DXGI_FORMAT_R16G16_FLOAT, DXGI_FORMAT_R16G16_FLOAT, 2, 2, + DataType::Float), + TEST_CASE(TextureType::Regular, DXGI_FORMAT_R16G16_UNORM, DXGI_FORMAT_R16G16_UNORM, 2, 2, + DataType::UNorm), + TEST_CASE(TextureType::Regular, DXGI_FORMAT_R16G16_UINT, DXGI_FORMAT_R16G16_UINT, 2, 2, + DataType::UInt), + TEST_CASE(TextureType::Regular, DXGI_FORMAT_R16G16_SNORM, DXGI_FORMAT_R16G16_SNORM, 2, 2, + DataType::SNorm), + TEST_CASE(TextureType::Regular, DXGI_FORMAT_R16G16_SINT, DXGI_FORMAT_R16G16_SINT, 2, 2, + DataType::SInt), + + TEST_CASE(TextureType::Regular, DXGI_FORMAT_R32_TYPELESS, DXGI_FORMAT_R32_FLOAT, 1, 4, + DataType::Float), + TEST_CASE(TextureType::Regular, DXGI_FORMAT_R32_TYPELESS, DXGI_FORMAT_R32_UINT, 1, 4, + DataType::UInt), + TEST_CASE(TextureType::Regular, DXGI_FORMAT_R32_FLOAT, DXGI_FORMAT_R32_FLOAT, 1, 4, + DataType::Float), + TEST_CASE(TextureType::Regular, DXGI_FORMAT_R32_UINT, DXGI_FORMAT_R32_UINT, 1, 4, + DataType::UInt), + TEST_CASE(TextureType::Regular, DXGI_FORMAT_R32_SINT, DXGI_FORMAT_R32_SINT, 1, 4, + DataType::SInt), + + TEST_CASE(TextureType::Regular, DXGI_FORMAT_R8G8_TYPELESS, DXGI_FORMAT_R8G8_UINT, 2, 1, + DataType::UInt), + TEST_CASE(TextureType::Regular, DXGI_FORMAT_R8G8_TYPELESS, DXGI_FORMAT_R8G8_UNORM, 2, 1, + DataType::UNorm), + TEST_CASE(TextureType::Regular, DXGI_FORMAT_R8G8_UNORM, DXGI_FORMAT_R8G8_UNORM, 2, 1, + DataType::UNorm), + TEST_CASE(TextureType::Regular, DXGI_FORMAT_R8G8_UINT, DXGI_FORMAT_R8G8_UINT, 2, 1, + DataType::UInt), + TEST_CASE(TextureType::Regular, DXGI_FORMAT_R8G8_SNORM, DXGI_FORMAT_R8G8_SNORM, 2, 1, + DataType::SNorm), + TEST_CASE(TextureType::Regular, DXGI_FORMAT_R8G8_SINT, DXGI_FORMAT_R8G8_SINT, 2, 1, + DataType::SInt), + + TEST_CASE(TextureType::Regular, DXGI_FORMAT_R16_TYPELESS, DXGI_FORMAT_R16_FLOAT, 1, 2, + DataType::Float), + TEST_CASE(TextureType::Regular, DXGI_FORMAT_R16_TYPELESS, DXGI_FORMAT_R16_UINT, 1, 2, + DataType::UInt), + TEST_CASE(TextureType::Regular, DXGI_FORMAT_R16_TYPELESS, DXGI_FORMAT_R16_UNORM, 1, 2, + DataType::UNorm), + TEST_CASE(TextureType::Regular, DXGI_FORMAT_R16_FLOAT, DXGI_FORMAT_R16_FLOAT, 1, 2, + DataType::Float), + TEST_CASE(TextureType::Regular, DXGI_FORMAT_R16_UNORM, DXGI_FORMAT_R16_UNORM, 1, 2, + DataType::UNorm), + TEST_CASE(TextureType::Regular, DXGI_FORMAT_R16_UINT, DXGI_FORMAT_R16_UINT, 1, 2, + DataType::UInt), + TEST_CASE(TextureType::Regular, DXGI_FORMAT_R16_SNORM, DXGI_FORMAT_R16_SNORM, 1, 2, + DataType::SNorm), + TEST_CASE(TextureType::Regular, DXGI_FORMAT_R16_SINT, DXGI_FORMAT_R16_SINT, 1, 2, + DataType::SInt), + + TEST_CASE(TextureType::Regular, DXGI_FORMAT_R8_TYPELESS, DXGI_FORMAT_R8_UINT, 1, 1, + DataType::UInt), + TEST_CASE(TextureType::Regular, DXGI_FORMAT_R8_TYPELESS, DXGI_FORMAT_R8_UNORM, 1, 1, + DataType::UNorm), + TEST_CASE(TextureType::Regular, DXGI_FORMAT_R8_UNORM, DXGI_FORMAT_R8_UNORM, 1, 1, + DataType::UNorm), + TEST_CASE(TextureType::Regular, DXGI_FORMAT_R8_UINT, DXGI_FORMAT_R8_UINT, 1, 1, + DataType::UInt), + TEST_CASE(TextureType::Regular, DXGI_FORMAT_R8_SNORM, DXGI_FORMAT_R8_SNORM, 1, 1, + DataType::SNorm), + TEST_CASE(TextureType::Regular, DXGI_FORMAT_R8_SINT, DXGI_FORMAT_R8_SINT, 1, 1, + DataType::SInt), + + TEST_CASE(TextureType::Regular, DXGI_FORMAT_B8G8R8A8_TYPELESS, DXGI_FORMAT_B8G8R8A8_UNORM, + 4, 1, DataType::UNorm), + TEST_CASE(TextureType::Regular, DXGI_FORMAT_B8G8R8A8_TYPELESS, + DXGI_FORMAT_B8G8R8A8_UNORM_SRGB, 4, 1, DataType::UNorm), + TEST_CASE(TextureType::Regular, DXGI_FORMAT_B8G8R8A8_UNORM, DXGI_FORMAT_B8G8R8A8_UNORM, 4, + 1, DataType::UNorm), + TEST_CASE(TextureType::Regular, DXGI_FORMAT_B8G8R8A8_UNORM_SRGB, + DXGI_FORMAT_B8G8R8A8_UNORM_SRGB, 4, 1, DataType::UNorm), + + TEST_CASE(TextureType::Regular, DXGI_FORMAT_B8G8R8X8_TYPELESS, DXGI_FORMAT_B8G8R8X8_UNORM, + 4, 1, DataType::UNorm), + TEST_CASE(TextureType::Regular, DXGI_FORMAT_B8G8R8X8_TYPELESS, + DXGI_FORMAT_B8G8R8X8_UNORM_SRGB, 4, 1, DataType::UNorm), + TEST_CASE(TextureType::Regular, DXGI_FORMAT_B8G8R8X8_UNORM, DXGI_FORMAT_B8G8R8X8_UNORM, 4, + 1, DataType::UNorm), + TEST_CASE(TextureType::Regular, DXGI_FORMAT_B8G8R8X8_UNORM_SRGB, + DXGI_FORMAT_B8G8R8X8_UNORM_SRGB, 4, 1, DataType::UNorm), + TEST_CASE(TextureType::Regular, DXGI_FORMAT_A8_UNORM, DXGI_FORMAT_A8_UNORM, 1, 1, + DataType::UNorm), + + TEST_CASE(TextureType::BC1, DXGI_FORMAT_BC1_TYPELESS, DXGI_FORMAT_BC1_UNORM, 0, 0, + DataType::UNorm), + TEST_CASE(TextureType::BC1, DXGI_FORMAT_BC1_TYPELESS, DXGI_FORMAT_BC1_UNORM_SRGB, 0, 0, + DataType::UNorm), + TEST_CASE(TextureType::BC1, DXGI_FORMAT_BC1_UNORM, DXGI_FORMAT_BC1_UNORM, 0, 0, + DataType::UNorm), + TEST_CASE(TextureType::BC1, DXGI_FORMAT_BC1_UNORM_SRGB, DXGI_FORMAT_BC1_UNORM_SRGB, 0, 0, + DataType::UNorm), + + TEST_CASE(TextureType::BC2, DXGI_FORMAT_BC2_TYPELESS, DXGI_FORMAT_BC2_UNORM, 0, 0, + DataType::UNorm), + TEST_CASE(TextureType::BC2, DXGI_FORMAT_BC2_TYPELESS, DXGI_FORMAT_BC2_UNORM_SRGB, 0, 0, + DataType::UNorm), + TEST_CASE(TextureType::BC2, DXGI_FORMAT_BC2_UNORM, DXGI_FORMAT_BC2_UNORM, 0, 0, + DataType::UNorm), + TEST_CASE(TextureType::BC2, DXGI_FORMAT_BC2_UNORM_SRGB, DXGI_FORMAT_BC2_UNORM_SRGB, 0, 0, + DataType::UNorm), + + TEST_CASE(TextureType::BC3, DXGI_FORMAT_BC3_TYPELESS, DXGI_FORMAT_BC3_UNORM, 0, 0, + DataType::UNorm), + TEST_CASE(TextureType::BC3, DXGI_FORMAT_BC3_TYPELESS, DXGI_FORMAT_BC3_UNORM_SRGB, 0, 0, + DataType::UNorm), + TEST_CASE(TextureType::BC3, DXGI_FORMAT_BC3_UNORM, DXGI_FORMAT_BC3_UNORM, 0, 0, + DataType::UNorm), + TEST_CASE(TextureType::BC3, DXGI_FORMAT_BC3_UNORM_SRGB, DXGI_FORMAT_BC3_UNORM_SRGB, 0, 0, + DataType::UNorm), + + TEST_CASE(TextureType::BC4, DXGI_FORMAT_BC4_TYPELESS, DXGI_FORMAT_BC4_UNORM, 0, 0, + DataType::UNorm), + TEST_CASE(TextureType::BC4, DXGI_FORMAT_BC4_TYPELESS, DXGI_FORMAT_BC4_SNORM, 0, 0, + DataType::SNorm), + TEST_CASE(TextureType::BC4, DXGI_FORMAT_BC4_UNORM, DXGI_FORMAT_BC4_UNORM, 0, 0, + DataType::UNorm), + TEST_CASE(TextureType::BC4, DXGI_FORMAT_BC4_SNORM, DXGI_FORMAT_BC4_SNORM, 0, 0, + DataType::SNorm), + + TEST_CASE(TextureType::BC5, DXGI_FORMAT_BC5_TYPELESS, DXGI_FORMAT_BC5_UNORM, 0, 0, + DataType::UNorm), + TEST_CASE(TextureType::BC5, DXGI_FORMAT_BC5_TYPELESS, DXGI_FORMAT_BC5_SNORM, 0, 0, + DataType::SNorm), + TEST_CASE(TextureType::BC5, DXGI_FORMAT_BC5_UNORM, DXGI_FORMAT_BC5_UNORM, 0, 0, + DataType::UNorm), + TEST_CASE(TextureType::BC5, DXGI_FORMAT_BC5_SNORM, DXGI_FORMAT_BC5_SNORM, 0, 0, + DataType::SNorm), + + TEST_CASE(TextureType::BC6, DXGI_FORMAT_BC6H_TYPELESS, DXGI_FORMAT_BC6H_UF16, 0, 0, + DataType::Float), + TEST_CASE(TextureType::BC6, DXGI_FORMAT_BC6H_TYPELESS, DXGI_FORMAT_BC6H_SF16, 0, 0, + DataType::SNorm), + TEST_CASE(TextureType::BC6, DXGI_FORMAT_BC6H_UF16, DXGI_FORMAT_BC6H_UF16, 0, 0, + DataType::Float), + TEST_CASE(TextureType::BC6, DXGI_FORMAT_BC6H_SF16, DXGI_FORMAT_BC6H_SF16, 0, 0, + DataType::SNorm), + + TEST_CASE(TextureType::BC7, DXGI_FORMAT_BC7_TYPELESS, DXGI_FORMAT_BC7_UNORM, 0, 0, + DataType::UNorm), + TEST_CASE(TextureType::BC7, DXGI_FORMAT_BC7_TYPELESS, DXGI_FORMAT_BC7_UNORM_SRGB, 0, 0, + DataType::UNorm), + TEST_CASE(TextureType::BC7, DXGI_FORMAT_BC7_UNORM, DXGI_FORMAT_BC7_UNORM, 0, 0, + DataType::UNorm), + TEST_CASE(TextureType::BC7, DXGI_FORMAT_BC7_UNORM_SRGB, DXGI_FORMAT_BC7_UNORM_SRGB, 0, 0, + DataType::UNorm), + + TEST_CASE(TextureType::R9G9B9E5, DXGI_FORMAT_R9G9B9E5_SHAREDEXP, + DXGI_FORMAT_R9G9B9E5_SHAREDEXP, 0, 0, DataType::Float), + TEST_CASE(TextureType::Unknown, DXGI_FORMAT_B5G6R5_UNORM, DXGI_FORMAT_B5G6R5_UNORM, 0, 0, + DataType::UNorm), + TEST_CASE(TextureType::Unknown, DXGI_FORMAT_B5G5R5A1_UNORM, DXGI_FORMAT_B5G5R5A1_UNORM, 0, + 0, DataType::UNorm), + TEST_CASE(TextureType::R4G4B4A4, DXGI_FORMAT_B4G4R4A4_UNORM, DXGI_FORMAT_B4G4R4A4_UNORM, 0, + 0, DataType::UNorm), + TEST_CASE(TextureType::Unknown, DXGI_FORMAT_R1_UNORM, DXGI_FORMAT_R1_UNORM, 0, 0, + DataType::UNorm), + + TEST_CASE(TextureType::Unknown, DXGI_FORMAT_R10G10B10A2_TYPELESS, + DXGI_FORMAT_R10G10B10A2_UNORM, 1, 4, DataType::UNorm), + TEST_CASE(TextureType::Unknown, DXGI_FORMAT_R10G10B10A2_TYPELESS, + DXGI_FORMAT_R10G10B10A2_UINT, 1, 4, DataType::UInt), + TEST_CASE(TextureType::Unknown, DXGI_FORMAT_R10G10B10A2_UNORM, + DXGI_FORMAT_R10G10B10A2_UNORM, 1, 4, DataType::UNorm), + TEST_CASE(TextureType::Unknown, DXGI_FORMAT_R10G10B10A2_UINT, DXGI_FORMAT_R10G10B10A2_UINT, + 1, 4, DataType::UInt), + + TEST_CASE(TextureType::Unknown, DXGI_FORMAT_R11G11B10_FLOAT, DXGI_FORMAT_R11G11B10_FLOAT, 0, + 0, DataType::Float), + }; + + for(D3D11Format f : color_tests) + AddSupportedTests(f, test_textures, false); + + // finally add the depth tests + const D3D11Format depth_tests[] = { + TEST_CASE(TextureType::Unknown, DXGI_FORMAT_R32G8X24_TYPELESS, + DXGI_FORMAT_R32_FLOAT_X8X24_TYPELESS, 0, 0, DataType::Float), + TEST_CASE(TextureType::Unknown, DXGI_FORMAT_R32G8X24_TYPELESS, + DXGI_FORMAT_X32_TYPELESS_G8X24_UINT, 0, 0, DataType::UInt), + + TEST_CASE(TextureType::Unknown, DXGI_FORMAT_R24G8_TYPELESS, + DXGI_FORMAT_R24_UNORM_X8_TYPELESS, 0, 0, DataType::UNorm), + TEST_CASE(TextureType::Unknown, DXGI_FORMAT_R24G8_TYPELESS, + DXGI_FORMAT_X24_TYPELESS_G8_UINT, 0, 0, DataType::UInt), + + TEST_CASE(TextureType::Unknown, DXGI_FORMAT_R32_TYPELESS, DXGI_FORMAT_R32_FLOAT, 0, 0, + DataType::Float), + + TEST_CASE(TextureType::Unknown, DXGI_FORMAT_R16_TYPELESS, DXGI_FORMAT_R16_UNORM, 0, 0, + DataType::UNorm), + }; + + for(D3D11Format f : depth_tests) + AddSupportedTests(f, test_textures, true); + + popMarker(); + + ID3D11Texture2DPtr fltTex = + MakeTexture(DXGI_FORMAT_R32G32B32A32_FLOAT, screenWidth, screenHeight).RTV().SRV(); + ID3D11RenderTargetViewPtr fltRT = MakeRTV(fltTex); + ID3D11ShaderResourceViewPtr fltSRV = MakeSRV(fltTex); + + ID3D11BufferPtr mscb = MakeBuffer().Constant().Size(sizeof(Vec4i)); + ID3D11VertexShaderPtr msvs = CreateVS(Compile(D3DFullscreenQuadVertex, "main", "vs_4_0")); + ID3D11PixelShaderPtr msps[(size_t)DataType::Count]; + + std::string def = "#define TEX_WIDTH " + std::to_string(texWidth) + "\n\n"; + + msps[(size_t)DataType::Float] = msps[(size_t)DataType::UNorm] = msps[(size_t)DataType::SNorm] = + CreatePS(Compile(def + pixelMSFloat, "main", "ps_5_0")); + msps[(size_t)DataType::UInt] = CreatePS(Compile(def + pixelMSUInt, "main", "ps_5_0")); + msps[(size_t)DataType::SInt] = CreatePS(Compile(def + pixelMSSInt, "main", "ps_5_0")); + + ID3D11PixelShaderPtr msdepthps = CreatePS(Compile(def + pixelMSDepth, "main", "ps_5_0")); + + D3D11_DEPTH_STENCIL_DESC ds = GetDepthState(); + ds.DepthFunc = D3D11_COMPARISON_ALWAYS; + ds.DepthWriteMask = D3D11_DEPTH_WRITE_MASK_ALL; + ds.FrontFace.StencilFunc = D3D11_COMPARISON_ALWAYS; + ds.FrontFace.StencilPassOp = D3D11_STENCIL_OP_REPLACE; + + for(TestCase &t : test_textures) + { + if(!t.res || t.hasData) + continue; + + if(!t.canRender && !t.isDepth) + { + TEST_ERROR("Need data for test %s, but it's not a renderable/depthable format", + t.fmt.name.c_str()); + continue; + } + + ds.DepthEnable = t.isDepth; + ds.StencilEnable = t.isDepth; + SetDepthState(ds); + + ID3D11ResourcePtr res; + t.srv->GetResource(&res); + + ID3D11Texture1DPtr tex1 = res; + ID3D11Texture2DPtr tex2 = res; + ID3D11Texture3DPtr tex3 = res; + + UINT ArraySize = 1, MipLevels = 1, SampleCount = 1; + + if(tex1) + { + D3D11_TEXTURE1D_DESC desc; + tex1->GetDesc(&desc); + ArraySize = desc.ArraySize; + MipLevels = desc.MipLevels; + } + if(tex2) + { + D3D11_TEXTURE2D_DESC desc; + tex2->GetDesc(&desc); + ArraySize = desc.ArraySize; + MipLevels = desc.MipLevels; + SampleCount = desc.SampleDesc.Count; + } + if(tex3) + { + D3D11_TEXTURE3D_DESC desc; + tex3->GetDesc(&desc); + MipLevels = desc.MipLevels; + ArraySize = desc.Depth; + } + + pushMarker("Render data for " + t.fmt.name + " " + MakeName(t)); + + t.hasData = true; + + bool srgb = false, bgra = false; + switch(t.fmt.viewFmt) + { + // only need to handle renderable BGRA/SRGB formats here + case DXGI_FORMAT_B5G6R5_UNORM: + case DXGI_FORMAT_B5G5R5A1_UNORM: + case DXGI_FORMAT_B8G8R8A8_UNORM: + case DXGI_FORMAT_B8G8R8X8_UNORM: + case DXGI_FORMAT_B4G4R4A4_UNORM: bgra = true; break; + + case DXGI_FORMAT_B8G8R8A8_UNORM_SRGB: + case DXGI_FORMAT_B8G8R8X8_UNORM_SRGB: + bgra = true; + srgb = true; + break; + case DXGI_FORMAT_R8G8B8A8_UNORM_SRGB: srgb = true; break; + default: break; + } + + int flags = 0; + + if(t.fmt.cfg.data == DataType::SNorm) + flags |= 1; + if(srgb) + flags |= 2; + if(bgra) + flags |= 4; + if(t.fmt.viewFmt == DXGI_FORMAT_A8_UNORM) + flags |= 8; + + ctx->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP); + RSSetViewport({0.0f, 0.0f, (float)texWidth, (float)texHeight, 0.0f, 1.0f}); + + for(UINT mp = 0; mp < MipLevels; mp++) + { + UINT SlicesOrDepth = ArraySize; + if(tex3) + SlicesOrDepth >>= mp; + for(UINT sl = 0; sl < SlicesOrDepth; sl++) + { + if(t.isDepth) + { + ID3D11DepthStencilViewPtr dsv = + tex1 ? MakeDSV(tex1).Format(GetDepthFormat(t.fmt)).FirstSlice(sl).FirstMip(mp) + : MakeDSV(tex2).Format(GetDepthFormat(t.fmt)).FirstSlice(sl).FirstMip(mp); + + ctx->ClearDepthStencilView(dsv, D3D11_CLEAR_DEPTH | D3D11_CLEAR_STENCIL, 0.0, 0); + + ctx->VSSetShader(msvs, NULL, 0); + ctx->PSSetShader(msdepthps, NULL, 0); + ctx->PSSetConstantBuffers(0, 1, &mscb.GetInterfacePtr()); + + ctx->OMSetRenderTargets(0, NULL, dsv); + + // need to do each sample separately to let us vary the stencil value + for(UINT sm = 0; sm < SampleCount; sm++) + { + Vec4i params(sl, mp); + ctx->UpdateSubresource(mscb, 0, NULL, ¶ms, sizeof(params), sizeof(params)); + + ctx->OMSetBlendState(NULL, NULL, 1 << sm); + + SetStencilRef(100 + (mp + sm) * 10); + + ctx->Draw(4, 0); + + // clip off the diagonal + params.z = 1; + ctx->UpdateSubresource(mscb, 0, NULL, ¶ms, sizeof(params), sizeof(params)); + + SetStencilRef(10 + (mp + sm) * 10); + + ctx->Draw(4, 0); + } + } + else + { + ID3D11RenderTargetViewPtr rtv = + tex1 ? MakeRTV(tex1).Format(t.fmt.viewFmt).FirstSlice(sl).FirstMip(mp) + : tex2 ? MakeRTV(tex2).Format(t.fmt.viewFmt).FirstSlice(sl).FirstMip(mp) + : MakeRTV(tex3).Format(t.fmt.viewFmt).FirstSlice(sl).FirstMip(mp); + + Vec4i params(tex3 ? 0 : sl, mp, flags, tex3 ? sl : 0); + ctx->UpdateSubresource(mscb, 0, NULL, ¶ms, sizeof(params), sizeof(params)); + + ctx->VSSetShader(msvs, NULL, 0); + ctx->PSSetShader(msps[(size_t)t.fmt.cfg.data], NULL, 0); + ctx->PSSetConstantBuffers(0, 1, &mscb.GetInterfacePtr()); + + ctx->OMSetRenderTargets(1, &rtv.GetInterfacePtr(), NULL); + + ctx->Draw(4, 0); + } + } + } + + popMarker(); + } + + ds.DepthEnable = FALSE; + ds.StencilEnable = FALSE; + SetDepthState(ds); + + while(Running()) + { + ctx->ClearState(); + ClearRenderTargetView(fltRT, {0.4f, 0.5f, 0.6f, 1.0f}); + + ctx->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP); + + ctx->VSSetShader(vs, NULL, 0); + + ctx->OMSetRenderTargets(1, &fltRT.GetInterfacePtr(), NULL); + + D3D11_VIEWPORT view = {0.0f, 0.0f, 10.0f, 10.0f, 0.0f, 1.0f}; + + D3D11_RASTERIZER_DESC rs = GetRasterState(); + rs.ScissorEnable = TRUE; + SetRasterState(rs); + + for(size_t i = 0; i < test_textures.size(); i++) + { + if(i == 0 || test_textures[i].fmt.texFmt != test_textures[i - 1].fmt.texFmt || + test_textures[i].fmt.viewFmt != test_textures[i - 1].fmt.viewFmt) + { + if(i != 0) + popMarker(); + + pushMarker(test_textures[i].fmt.name); + } + + setMarker(MakeName(test_textures[i])); + + RSSetViewport(view); + D3D11_RECT rect = { + (LONG)view.TopLeftX, (LONG)view.TopLeftY, LONG(view.TopLeftX + view.Width), + LONG(view.TopLeftY + view.Height), + }; + rect.left++; + rect.top++; + rect.right--; + rect.bottom--; + ctx->RSSetScissorRects(1, &rect); + ctx->PSSetShader(GetShader(test_textures[i]), NULL, 0); + ctx->PSSetShaderResources(0, 1, &test_textures[i].srv.GetInterfacePtr()); + + if(test_textures[i].srv) + ctx->Draw(4, 0); + else + setMarker("UNSUPPORTED"); + + // advance to next viewport + view.TopLeftX += view.Width; + if(view.TopLeftX + view.Width > (float)screenWidth) + { + view.TopLeftX = 0; + view.TopLeftY += view.Height; + } + } + + // pop the last format region + popMarker(); + + rs.ScissorEnable = FALSE; + SetRasterState(rs); + + // blit to the screen for a nicer preview + ctx->OMSetRenderTargets(1, &bbRTV.GetInterfacePtr(), NULL); + RSSetViewport({0.0f, 0.0f, (float)screenWidth, (float)screenHeight, 0.0f, 1.0f}); + ctx->PSSetShader(blitps, NULL, 0); + ctx->PSSetShaderResources(0, 1, &fltSRV.GetInterfacePtr()); + ctx->Draw(4, 0); + + Present(); + } + + return 0; + } +}; + +REGISTER_TEST(); diff --git a/util/test/demos/d3d12/d3d12_helpers.cpp b/util/test/demos/d3d12/d3d12_helpers.cpp index 595254d92..07a14d7e4 100644 --- a/util/test/demos/d3d12/d3d12_helpers.cpp +++ b/util/test/demos/d3d12/d3d12_helpers.cpp @@ -700,8 +700,6 @@ D3D12ViewCreator &D3D12ViewCreator::FirstMip(UINT mip) { if(firstMip) *firstMip = mip; - else - TEST_ERROR("This view & resource doesn't support FirstMip"); return *this; } @@ -709,8 +707,6 @@ D3D12ViewCreator &D3D12ViewCreator::NumMips(UINT num) { if(numMips) *numMips = num; - else - TEST_ERROR("This view & resource doesn't support NumMips"); return *this; } @@ -718,8 +714,6 @@ D3D12ViewCreator &D3D12ViewCreator::FirstSlice(UINT mip) { if(firstSlice) *firstSlice = mip; - else - TEST_ERROR("This view & resource doesn't support FirstSlice"); return *this; } @@ -727,8 +721,6 @@ D3D12ViewCreator &D3D12ViewCreator::NumSlices(UINT num) { if(numSlices) *numSlices = num; - else - TEST_ERROR("This view & resource doesn't support NumSlices"); return *this; } @@ -742,8 +734,6 @@ D3D12ViewCreator &D3D12ViewCreator::PlaneSlice(UINT plane) { if(planeSlice) *planeSlice = plane; - else - TEST_ERROR("This view & resource doesn't support NumSlices"); return *this; } @@ -874,6 +864,16 @@ D3D12PSOCreator::D3D12PSOCreator(D3D12GraphicsTest *test) : m_Test(test) GraphicsDesc.BlendState.RenderTarget[0].DestBlendAlpha = D3D12_BLEND_INV_SRC_ALPHA; GraphicsDesc.BlendState.RenderTarget[0].BlendOpAlpha = D3D12_BLEND_OP_ADD; GraphicsDesc.BlendState.RenderTarget[0].RenderTargetWriteMask = D3D12_COLOR_WRITE_ENABLE_ALL; + GraphicsDesc.DepthStencilState.DepthEnable = FALSE; + GraphicsDesc.DepthStencilState.DepthWriteMask = D3D12_DEPTH_WRITE_MASK_ALL; + GraphicsDesc.DepthStencilState.DepthFunc = D3D12_COMPARISON_FUNC_LESS_EQUAL; + GraphicsDesc.DepthStencilState.StencilReadMask = 0xff; + GraphicsDesc.DepthStencilState.StencilWriteMask = 0xff; + GraphicsDesc.DepthStencilState.FrontFace.StencilFunc = D3D12_COMPARISON_FUNC_EQUAL; + GraphicsDesc.DepthStencilState.FrontFace.StencilFailOp = D3D12_STENCIL_OP_KEEP; + GraphicsDesc.DepthStencilState.FrontFace.StencilDepthFailOp = D3D12_STENCIL_OP_KEEP; + GraphicsDesc.DepthStencilState.FrontFace.StencilPassOp = D3D12_STENCIL_OP_REPLACE; + GraphicsDesc.DepthStencilState.BackFace = GraphicsDesc.DepthStencilState.FrontFace; } D3D12PSOCreator &D3D12PSOCreator::VS(ID3DBlobPtr blob) diff --git a/util/test/demos/d3d12/d3d12_texture_zoo.cpp b/util/test/demos/d3d12/d3d12_texture_zoo.cpp new file mode 100644 index 000000000..87c57e69b --- /dev/null +++ b/util/test/demos/d3d12/d3d12_texture_zoo.cpp @@ -0,0 +1,1211 @@ +/****************************************************************************** + * The MIT License (MIT) + * + * Copyright (c) 2018-2019 Baldur Karlsson + * + * 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. + ******************************************************************************/ + +#include "d3d12_test.h" + +using namespace TextureZoo; + +RD_TEST(D3D12_Texture_Zoo, D3D12GraphicsTest) +{ + static constexpr const char *Description = + "Tests all possible combinations of texture type and format that are supported."; + + std::string pixelTemplate = R"EOSHADER( intex : register(t0); + +float4 main() : SV_Target0 +{ + return intex.Load(¶ms); +} +)EOSHADER"; + + std::string pixelBlit = R"EOSHADER( + +Texture2D intex : register(t0); + +float4 main(float4 pos : SV_Position) : SV_Target0 +{ + return intex.Load(float3(pos.xy, 0)); +} + +)EOSHADER"; + + std::string pixelMSFloat = R"EOSHADER( + +cbuffer consts : register(b0) +{ + uint slice; + uint mip; + uint flags; + uint z; +}; + +float srgb2linear(float f) +{ + if (f <= 0.04045f) + return f / 12.92f; + else + return pow((f + 0.055f) / 1.055f, 2.4f); +} + +float4 main(float4 pos : SV_Position, uint samp : SV_SampleIndex) : SV_Target0 +{ + uint x = uint(pos.x); + uint y = uint(pos.y); + + float4 ret = float4(0.1f, 0.35f, 0.6f, 0.85f); + + // each 3D slice cycles the x. This only affects the primary diagonal + uint offs_x = (x + z) % max(1u, TEX_WIDTH >> mip); + + // pixels off the diagonal invert the colors + if(offs_x != y) + ret = ret.wzyx; + + // second slice adds a coarse checkerboard pattern of inversion + if(slice > 0 && (((x / 2) % 2) != ((y / 2) % 2))) + ret = ret.wzyx; + + // second sample/mip is shifted up a bit. MSAA textures have no mips, + // textures with mips have no samples. + ret += 0.075f.xxxx * (samp + mip); + + // Signed normals are negative + if(flags & 1) + ret = -ret; + + // undo SRGB curve applied in output merger, to match the textures we just blat values into + // without conversion (which are then interpreted as srgb implicitly) + if(flags & 2) + { + ret.r = srgb2linear(ret.r); + ret.g = srgb2linear(ret.g); + ret.b = srgb2linear(ret.b); + } + + // BGR flip - same as above, for BGRA textures + if(flags & 4) + ret.rgb = ret.bgr; + + // put red into alpha, because that's what we did in manual upload + if(flags & 8) + ret.a = ret.r; + + return ret; +} + +)EOSHADER"; + + std::string pixelMSDepth = R"EOSHADER( + +cbuffer consts : register(b0) +{ + uint slice; + uint mip; + uint flags; + uint z; +}; + +float main(float4 pos : SV_Position, uint samp : SV_SampleIndex) : SV_Depth +{ + uint x = uint(pos.x); + uint y = uint(pos.y); + + float ret = 0.1f; + + // each 3D slice cycles the x. This only affects the primary diagonal + uint offs_x = (x + z) % max(1u, TEX_WIDTH >> mip); + + // pixels off the diagonal invert the colors + // second slice adds a coarse checkerboard pattern of inversion + if((offs_x != y) != (slice > 0 && (((x / 2) % 2) != ((y / 2) % 2)))) + { + ret = 0.85f; + + // so we can fill stencil data, clip off the inverted values + if(flags == 1) + clip(-1); + } + + // second sample/mip is shifted up a bit. MSAA textures have no mips, + // textures with mips have no samples. + ret += 0.075f * (samp + mip); + + return ret; +} + +)EOSHADER"; + + std::string pixelMSUInt = R"EOSHADER( + +cbuffer consts : register(b0) +{ + uint slice; + uint mip; + uint flags; + uint z; +}; + +uint4 main(float4 pos : SV_Position, uint samp : SV_SampleIndex) : SV_Target0 +{ + uint x = uint(pos.x); + uint y = uint(pos.y); + + uint4 ret = uint4(10, 40, 70, 100); + + // each 3D slice cycles the x. This only affects the primary diagonal + uint offs_x = (x + z) % max(1u, TEX_WIDTH >> mip); + + // pixels off the diagonal invert the colors + if(offs_x != y) + ret = ret.wzyx; + + // second slice adds a coarse checkerboard pattern of inversion + if(slice > 0 && (((x / 2) % 2) != ((y / 2) % 2))) + ret = ret.wzyx; + + // second sample/mip is shifted up a bit. MSAA textures have no mips, + // textures with mips have no samples. + ret += uint4(10, 10, 10, 10) * (samp + mip); + + return ret; +} + +)EOSHADER"; + + std::string pixelMSSInt = R"EOSHADER( + +cbuffer consts : register(b0) +{ + uint slice; + uint mip; + uint flags; + uint z; +}; + +int4 main(float4 pos : SV_Position, uint samp : SV_SampleIndex) : SV_Target0 +{ + uint x = uint(pos.x); + uint y = uint(pos.y); + + int4 ret = int4(10, 40, 70, 100); + + // each 3D slice cycles the x. This only affects the primary diagonal + uint offs_x = (x + z) % max(1u, TEX_WIDTH >> mip); + + // pixels off the diagonal invert the colors + if(offs_x != y) + ret = ret.wzyx; + + // second slice adds a coarse checkerboard pattern of inversion + if(slice > 0 && (((x / 2) % 2) != ((y / 2) % 2))) + ret = ret.wzyx; + + // second sample/mip is shifted up a bit. MSAA textures have no mips, + // textures with mips have no samples. + ret += int4(10, 10, 10, 10) * (samp + mip); + + return -ret; +} + +)EOSHADER"; + + struct D3D12Format + { + const std::string name; + DXGI_FORMAT texFmt; + DXGI_FORMAT viewFmt; + TexConfig cfg; + }; + + struct TestCase + { + D3D12Format fmt; + uint32_t dim; + bool isArray; + bool canRender; + bool isDepth; + bool isMSAA; + bool hasData; + ID3D12ResourcePtr res; + D3D12_GPU_DESCRIPTOR_HANDLE srv; + }; + + std::string MakeName(const TestCase &test) + { + std::string name = "Texture " + std::to_string(test.dim) + "D"; + + if(test.isMSAA) + name += " MSAA"; + if(test.isArray) + name += " Array"; + + return name; + } + + ID3DBlobPtr vsblob; + ID3D12RootSignaturePtr sig; + + ID3D12PipelineStatePtr GetPSO(const TestCase &test) + { + static std::map PSOs; + + uint32_t key = uint32_t(test.fmt.cfg.data); + key |= test.dim << 6; + key |= test.isMSAA ? 0x80000 : 0; + + ID3D12PipelineStatePtr ret = PSOs[key]; + if(!ret) + { + std::string texType = "Texture" + std::to_string(test.dim) + "D"; + if(test.isMSAA) + texType += "MS"; + if(test.dim < 3) + texType += "Array"; + + const std::string innerType[] = { + "float", "unorm float", "float", "uint", "int", + }; + static_assert(ARRAY_COUNT(innerType) == (size_t)DataType::Count, + "DataType has changed, update array"); + + texType += "<" + innerType[(int)test.fmt.cfg.data] + "4>"; + + std::string src = texType + pixelTemplate; + + if(test.isMSAA) + src.replace(src.find("¶ms"), 7, "0, 0"); + else + src.replace(src.find("¶ms"), 7, "0"); + + ID3DBlobPtr psblob = Compile(src, "main", "ps_5_0"); + ret = PSOs[key] = + MakePSO().RootSig(sig).VS(vsblob).PS(psblob).RTVs({DXGI_FORMAT_R32G32B32A32_FLOAT}); + } + + return ret; + } + + uint32_t srvIndex = 0; + UINT64 CurOffset = 0; + ID3D12ResourcePtr uploadBuf; + byte *CurBuf = NULL; + + bool SetData(ID3D12GraphicsCommandListPtr cmd, ID3D12ResourcePtr res, const D3D12Format &fmt) + { + Vec4i dim; + UINT mips, slices; + GetDimensions(res, dim, mips, slices); + + TexData data; + + for(UINT s = 0; s < slices; s++) + { + for(UINT m = 0; m < mips; m++) + { + MakeData(data, fmt.cfg, dim, m, s); + + if(data.byteData.empty()) + return false; + + if(s == 0 && m == 0) + ResourceBarrier(cmd, res, D3D12_RESOURCE_STATE_COMMON, D3D12_RESOURCE_STATE_COPY_DEST); + + D3D12_TEXTURE_COPY_LOCATION dst = {}; + D3D12_TEXTURE_COPY_LOCATION src = {}; + + dst.Type = D3D12_TEXTURE_COPY_TYPE_SUBRESOURCE_INDEX; + dst.pResource = res; + dst.SubresourceIndex = s * mips + m; + + TEST_ASSERT(D3D12_TEXTURE_DATA_PITCH_ALIGNMENT >= data.rowPitch, + "Row pitch higher than alignment!"); + + src.Type = D3D12_TEXTURE_COPY_TYPE_PLACED_FOOTPRINT; + src.pResource = uploadBuf; + src.PlacedFootprint.Offset = CurOffset; + src.PlacedFootprint.Footprint.Format = res->GetDesc().Format; + src.PlacedFootprint.Footprint.RowPitch = D3D12_TEXTURE_DATA_PITCH_ALIGNMENT; + src.PlacedFootprint.Footprint.Width = std::max(1, dim.x >> m); + src.PlacedFootprint.Footprint.Height = std::max(1, dim.y >> m); + src.PlacedFootprint.Footprint.Depth = std::max(1, dim.z >> m); + + switch(src.PlacedFootprint.Footprint.Format) + { + case DXGI_FORMAT_BC1_TYPELESS: + case DXGI_FORMAT_BC1_UNORM: + case DXGI_FORMAT_BC1_UNORM_SRGB: + case DXGI_FORMAT_BC4_TYPELESS: + case DXGI_FORMAT_BC4_UNORM: + case DXGI_FORMAT_BC4_SNORM: + case DXGI_FORMAT_BC2_TYPELESS: + case DXGI_FORMAT_BC2_UNORM: + case DXGI_FORMAT_BC2_UNORM_SRGB: + case DXGI_FORMAT_BC3_TYPELESS: + case DXGI_FORMAT_BC3_UNORM: + case DXGI_FORMAT_BC3_UNORM_SRGB: + case DXGI_FORMAT_BC5_TYPELESS: + case DXGI_FORMAT_BC5_UNORM: + case DXGI_FORMAT_BC5_SNORM: + case DXGI_FORMAT_BC6H_TYPELESS: + case DXGI_FORMAT_BC6H_UF16: + case DXGI_FORMAT_BC6H_SF16: + case DXGI_FORMAT_BC7_TYPELESS: + case DXGI_FORMAT_BC7_UNORM: + case DXGI_FORMAT_BC7_UNORM_SRGB: + src.PlacedFootprint.Footprint.Width = AlignUp(src.PlacedFootprint.Footprint.Width, 4U); + src.PlacedFootprint.Footprint.Height = AlignUp(src.PlacedFootprint.Footprint.Height, 4U); + break; + } + + UINT numRows = src.PlacedFootprint.Footprint.Height * src.PlacedFootprint.Footprint.Depth; + + for(UINT r = 0; r < numRows; r++) + { + memcpy(CurBuf + CurOffset + D3D12_TEXTURE_DATA_PITCH_ALIGNMENT * r, + data.byteData.data() + data.rowPitch * r, data.rowPitch); + } + + CurOffset += D3D12_TEXTURE_DATA_PITCH_ALIGNMENT * numRows; + + CurOffset = AlignUp(CurOffset, (UINT64)D3D12_TEXTURE_DATA_PLACEMENT_ALIGNMENT); + + cmd->CopyTextureRegion(&dst, 0, 0, 0, &src, NULL); + } + } + + ResourceBarrier(cmd, res, D3D12_RESOURCE_STATE_COPY_DEST, + D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE); + + return true; + } + + void GetDimensions(ID3D12ResourcePtr res, Vec4i & dim, UINT & mips, UINT & slices) + { + D3D12_RESOURCE_DESC desc = res->GetDesc(); + + dim.x = (int)desc.Width; + dim.y = desc.Height; + + mips = std::max(1, (int)desc.MipLevels); + + if(desc.Dimension == D3D12_RESOURCE_DIMENSION_TEXTURE3D) + { + dim.z = desc.DepthOrArraySize; + slices = 1; + } + else + { + dim.z = 1; + slices = desc.Dimension == D3D12_RESOURCE_DIMENSION_TEXTURE3D + ? 1 + : std::max(1, (int)desc.DepthOrArraySize); + } + } + + TestCase FinaliseTest(ID3D12GraphicsCommandListPtr cmd, TestCase test) + { + UINT planeSlice = 0; + + if(test.fmt.viewFmt == DXGI_FORMAT_X32_TYPELESS_G8X24_UINT || + test.fmt.viewFmt == DXGI_FORMAT_X24_TYPELESS_G8_UINT) + planeSlice = 1; + + if(test.dim == 1) + { + D3D12TextureCreator creator = + MakeTexture(test.fmt.texFmt, texWidth).Mips(texMips).Array(test.isArray ? texSlices : 1); + + if(test.isDepth) + creator.DSV(); + else if(test.canRender) + creator.RTV(); + + test.res = creator; + test.srv = + MakeSRV(test.res).PlaneSlice(planeSlice).Format(test.fmt.viewFmt).CreateGPU(srvIndex++); + } + else if(test.dim == 2 && !test.isMSAA) + { + D3D12TextureCreator creator = MakeTexture(test.fmt.texFmt, texWidth, texHeight) + .Mips(texMips) + .Array(test.isArray ? texSlices : 1); + + if(test.isDepth) + creator.DSV(); + else if(test.canRender) + creator.RTV(); + + test.res = creator; + test.srv = + MakeSRV(test.res).PlaneSlice(planeSlice).Format(test.fmt.viewFmt).CreateGPU(srvIndex++); + } + else if(test.dim == 2 && test.isMSAA) + { + D3D12TextureCreator creator = MakeTexture(test.fmt.texFmt, texWidth, texHeight) + .Multisampled(texSamples) + .Array(test.isArray ? texSlices : 1); + + if(test.isDepth) + creator.DSV(); + else + creator.RTV(); + + test.res = creator; + test.srv = + MakeSRV(test.res).PlaneSlice(planeSlice).Format(test.fmt.viewFmt).CreateGPU(srvIndex++); + test.canRender = true; + } + else if(test.dim == 3) + { + D3D12TextureCreator creator = + MakeTexture(test.fmt.texFmt, texWidth, texHeight, texDepth).Mips(texMips); + + if(test.canRender) + creator.RTV(); + + test.res = creator; + test.srv = + MakeSRV(test.res).PlaneSlice(planeSlice).Format(test.fmt.viewFmt).CreateGPU(srvIndex++); + } + + test.res->SetName(UTF82Wide(MakeName(test) + " " + test.fmt.name).c_str()); + + if(!test.isMSAA) + { + pushMarker(cmd, "Set data for " + test.fmt.name + " " + MakeName(test)); + + test.hasData = SetData(cmd, test.res, test.fmt); + + popMarker(cmd); + } + + return test; + } + + DXGI_FORMAT GetDepthFormat(const D3D12Format &f) + { + DXGI_FORMAT queryFormat = DXGI_FORMAT_UNKNOWN; + + switch(f.texFmt) + { + case DXGI_FORMAT_R32G8X24_TYPELESS: queryFormat = DXGI_FORMAT_D32_FLOAT_S8X24_UINT; break; + case DXGI_FORMAT_R24G8_TYPELESS: queryFormat = DXGI_FORMAT_D24_UNORM_S8_UINT; break; + case DXGI_FORMAT_R32_TYPELESS: queryFormat = DXGI_FORMAT_D32_FLOAT; break; + case DXGI_FORMAT_R16_TYPELESS: queryFormat = DXGI_FORMAT_D16_UNORM; break; + default: TEST_ERROR("Unexpected base texture format"); + } + + return queryFormat; + } + + void AddSupportedTests(const D3D12Format &f, std::vector &test_textures, bool depthMode) + { + DXGI_FORMAT queryFormat = f.viewFmt; + + ID3D12GraphicsCommandListPtr cmd = GetCommandBuffer(); + + Reset(cmd); + + CurOffset = 0; + + if(depthMode) + queryFormat = GetDepthFormat(f); + + D3D12_FEATURE_DATA_FORMAT_SUPPORT supp = {}; + supp.Format = queryFormat; + dev->CheckFeatureSupport(D3D12_FEATURE_FORMAT_SUPPORT, &supp, sizeof(supp)); + + bool renderable = (supp.Support1 & D3D12_FORMAT_SUPPORT1_RENDER_TARGET) != 0; + bool depth = (supp.Support1 & D3D12_FORMAT_SUPPORT1_DEPTH_STENCIL) != 0; + + if((supp.Support1 & D3D12_FORMAT_SUPPORT1_SHADER_LOAD) || depth) + { + // TODO: disable 1D depth textures for now, we don't support displaying them + if(!depthMode) + { + if(supp.Support1 & D3D12_FORMAT_SUPPORT1_TEXTURE1D) + { + test_textures.push_back(FinaliseTest(cmd, {f, 1, false, renderable, depth})); + test_textures.push_back(FinaliseTest(cmd, {f, 1, true, renderable, depth})); + } + else + { + test_textures.push_back({f, 1, false}); + test_textures.push_back({f, 1, true}); + } + } + + if(supp.Support1 & D3D12_FORMAT_SUPPORT1_TEXTURE2D) + { + test_textures.push_back(FinaliseTest(cmd, {f, 2, false, renderable, depth})); + test_textures.push_back(FinaliseTest(cmd, {f, 2, true, renderable, depth})); + } + else + { + test_textures.push_back({f, 2, false}); + test_textures.push_back({f, 2, true}); + } + if(supp.Support1 & D3D12_FORMAT_SUPPORT1_TEXTURE3D) + { + test_textures.push_back(FinaliseTest(cmd, {f, 3, false, renderable, depth})); + } + else + { + test_textures.push_back({f, 3, false}); + } + if(((supp.Support1 & D3D12_FORMAT_SUPPORT1_MULTISAMPLE_LOAD) || depth) && + (supp.Support1 & D3D12_FORMAT_SUPPORT1_MULTISAMPLE_RENDERTARGET)) + { + test_textures.push_back(FinaliseTest(cmd, {f, 2, false, true, depth, true})); + test_textures.push_back(FinaliseTest(cmd, {f, 2, true, true, depth, true})); + } + else + { + test_textures.push_back({f, 2, false, true, depth, true}); + test_textures.push_back({f, 2, true, true, depth, true}); + } + } + else + { + test_textures.push_back({f, 2, false}); + + if(supp.Support1 & (D3D12_FORMAT_SUPPORT1_TEXTURE1D | D3D12_FORMAT_SUPPORT1_TEXTURE2D | + D3D12_FORMAT_SUPPORT1_TEXTURE3D)) + { + TEST_ERROR("Format %d can't be loaded in shader but can be a texture!", f.texFmt); + } + } + + cmd->Close(); + + Submit({cmd}); + + GPUSync(); + } + + int main() + { + // initialise, create window, create device, etc + if(!Init()) + return 3; + + vsblob = Compile(D3DFullscreenQuadVertex, "main", "vs_4_0"); + + sig = MakeSig({ + constParam(D3D12_SHADER_VISIBILITY_PIXEL, 0, 0, 4), + tableParam(D3D12_SHADER_VISIBILITY_PIXEL, D3D12_DESCRIPTOR_RANGE_TYPE_SRV, 0, 0, 1), + }); + + ID3DBlobPtr psblob = Compile(pixelBlit, "main", "ps_5_0"); + + ID3D12PipelineStatePtr blitpso = MakePSO().RootSig(sig).VS(vsblob).PS(psblob); + + uploadBuf = MakeBuffer().Upload().Size(1024 * 1024); + + CurBuf = Map(uploadBuf, 0); + +#define TEST_CASE_NAME(texFmt, viewFmt) \ + (texFmt == viewFmt) ? std::string(#texFmt + 12) \ + : (std::string(#texFmt + 12) + "->" + (strchr(#viewFmt + 12, '_') + 1)) + +#define TEST_CASE(texType, texFmt, viewFmt, compCount, byteWidth, dataType) \ + { \ + TEST_CASE_NAME(texFmt, viewFmt), texFmt, viewFmt, {texType, compCount, byteWidth, dataType}, \ + } + + std::vector test_textures; + + const D3D12Format color_tests[] = { + TEST_CASE(TextureType::Regular, DXGI_FORMAT_R32G32B32A32_TYPELESS, + DXGI_FORMAT_R32G32B32A32_FLOAT, 4, 4, DataType::Float), + TEST_CASE(TextureType::Regular, DXGI_FORMAT_R32G32B32A32_TYPELESS, + DXGI_FORMAT_R32G32B32A32_UINT, 4, 4, DataType::UInt), + TEST_CASE(TextureType::Regular, DXGI_FORMAT_R32G32B32A32_FLOAT, + DXGI_FORMAT_R32G32B32A32_FLOAT, 4, 4, DataType::Float), + TEST_CASE(TextureType::Regular, DXGI_FORMAT_R32G32B32A32_UINT, + DXGI_FORMAT_R32G32B32A32_UINT, 4, 4, DataType::UInt), + TEST_CASE(TextureType::Regular, DXGI_FORMAT_R32G32B32A32_SINT, + DXGI_FORMAT_R32G32B32A32_SINT, 4, 4, DataType::SInt), + + TEST_CASE(TextureType::Regular, DXGI_FORMAT_R32G32B32_TYPELESS, DXGI_FORMAT_R32G32B32_FLOAT, + 3, 4, DataType::Float), + TEST_CASE(TextureType::Regular, DXGI_FORMAT_R32G32B32_TYPELESS, DXGI_FORMAT_R32G32B32_UINT, + 3, 4, DataType::UInt), + TEST_CASE(TextureType::Regular, DXGI_FORMAT_R32G32B32_FLOAT, DXGI_FORMAT_R32G32B32_FLOAT, 3, + 4, DataType::Float), + TEST_CASE(TextureType::Regular, DXGI_FORMAT_R32G32B32_UINT, DXGI_FORMAT_R32G32B32_UINT, 3, + 4, DataType::UInt), + TEST_CASE(TextureType::Regular, DXGI_FORMAT_R32G32B32_SINT, DXGI_FORMAT_R32G32B32_SINT, 3, + 4, DataType::SInt), + + TEST_CASE(TextureType::Regular, DXGI_FORMAT_R16G16B16A16_TYPELESS, + DXGI_FORMAT_R16G16B16A16_FLOAT, 4, 2, DataType::Float), + TEST_CASE(TextureType::Regular, DXGI_FORMAT_R16G16B16A16_TYPELESS, + DXGI_FORMAT_R16G16B16A16_UINT, 4, 2, DataType::UInt), + TEST_CASE(TextureType::Regular, DXGI_FORMAT_R16G16B16A16_TYPELESS, + DXGI_FORMAT_R16G16B16A16_UNORM, 4, 2, DataType::UNorm), + TEST_CASE(TextureType::Regular, DXGI_FORMAT_R16G16B16A16_FLOAT, + DXGI_FORMAT_R16G16B16A16_FLOAT, 4, 2, DataType::Float), + TEST_CASE(TextureType::Regular, DXGI_FORMAT_R16G16B16A16_UNORM, + DXGI_FORMAT_R16G16B16A16_UNORM, 4, 2, DataType::UNorm), + TEST_CASE(TextureType::Regular, DXGI_FORMAT_R16G16B16A16_UINT, + DXGI_FORMAT_R16G16B16A16_UINT, 4, 2, DataType::UInt), + TEST_CASE(TextureType::Regular, DXGI_FORMAT_R16G16B16A16_SNORM, + DXGI_FORMAT_R16G16B16A16_SNORM, 4, 2, DataType::SNorm), + TEST_CASE(TextureType::Regular, DXGI_FORMAT_R16G16B16A16_SINT, + DXGI_FORMAT_R16G16B16A16_SINT, 4, 2, DataType::SInt), + + TEST_CASE(TextureType::Regular, DXGI_FORMAT_R32G32_TYPELESS, DXGI_FORMAT_R32G32_FLOAT, 2, 4, + DataType::Float), + TEST_CASE(TextureType::Regular, DXGI_FORMAT_R32G32_TYPELESS, DXGI_FORMAT_R32G32_UINT, 2, 4, + DataType::UInt), + TEST_CASE(TextureType::Regular, DXGI_FORMAT_R32G32_FLOAT, DXGI_FORMAT_R32G32_FLOAT, 2, 4, + DataType::Float), + TEST_CASE(TextureType::Regular, DXGI_FORMAT_R32G32_UINT, DXGI_FORMAT_R32G32_UINT, 2, 4, + DataType::UInt), + TEST_CASE(TextureType::Regular, DXGI_FORMAT_R32G32_SINT, DXGI_FORMAT_R32G32_SINT, 2, 4, + DataType::SInt), + + TEST_CASE(TextureType::Regular, DXGI_FORMAT_R8G8B8A8_TYPELESS, DXGI_FORMAT_R8G8B8A8_UINT, 4, + 1, DataType::UInt), + TEST_CASE(TextureType::Regular, DXGI_FORMAT_R8G8B8A8_TYPELESS, DXGI_FORMAT_R8G8B8A8_UNORM, + 4, 1, DataType::UNorm), + TEST_CASE(TextureType::Regular, DXGI_FORMAT_R8G8B8A8_UNORM, DXGI_FORMAT_R8G8B8A8_UNORM, 4, + 1, DataType::UNorm), + TEST_CASE(TextureType::Regular, DXGI_FORMAT_R8G8B8A8_UNORM_SRGB, + DXGI_FORMAT_R8G8B8A8_UNORM_SRGB, 4, 1, DataType::UNorm), + TEST_CASE(TextureType::Regular, DXGI_FORMAT_R8G8B8A8_UINT, DXGI_FORMAT_R8G8B8A8_UINT, 4, 1, + DataType::UInt), + TEST_CASE(TextureType::Regular, DXGI_FORMAT_R8G8B8A8_SNORM, DXGI_FORMAT_R8G8B8A8_SNORM, 4, + 1, DataType::SNorm), + TEST_CASE(TextureType::Regular, DXGI_FORMAT_R8G8B8A8_SINT, DXGI_FORMAT_R8G8B8A8_SINT, 4, 1, + DataType::SInt), + + TEST_CASE(TextureType::Regular, DXGI_FORMAT_R16G16_TYPELESS, DXGI_FORMAT_R16G16_FLOAT, 2, 2, + DataType::Float), + TEST_CASE(TextureType::Regular, DXGI_FORMAT_R16G16_TYPELESS, DXGI_FORMAT_R16G16_UINT, 2, 2, + DataType::UInt), + TEST_CASE(TextureType::Regular, DXGI_FORMAT_R16G16_TYPELESS, DXGI_FORMAT_R16G16_UNORM, 2, 2, + DataType::UNorm), + TEST_CASE(TextureType::Regular, DXGI_FORMAT_R16G16_FLOAT, DXGI_FORMAT_R16G16_FLOAT, 2, 2, + DataType::Float), + TEST_CASE(TextureType::Regular, DXGI_FORMAT_R16G16_UNORM, DXGI_FORMAT_R16G16_UNORM, 2, 2, + DataType::UNorm), + TEST_CASE(TextureType::Regular, DXGI_FORMAT_R16G16_UINT, DXGI_FORMAT_R16G16_UINT, 2, 2, + DataType::UInt), + TEST_CASE(TextureType::Regular, DXGI_FORMAT_R16G16_SNORM, DXGI_FORMAT_R16G16_SNORM, 2, 2, + DataType::SNorm), + TEST_CASE(TextureType::Regular, DXGI_FORMAT_R16G16_SINT, DXGI_FORMAT_R16G16_SINT, 2, 2, + DataType::SInt), + + TEST_CASE(TextureType::Regular, DXGI_FORMAT_R32_TYPELESS, DXGI_FORMAT_R32_FLOAT, 1, 4, + DataType::Float), + TEST_CASE(TextureType::Regular, DXGI_FORMAT_R32_TYPELESS, DXGI_FORMAT_R32_UINT, 1, 4, + DataType::UInt), + TEST_CASE(TextureType::Regular, DXGI_FORMAT_R32_FLOAT, DXGI_FORMAT_R32_FLOAT, 1, 4, + DataType::Float), + TEST_CASE(TextureType::Regular, DXGI_FORMAT_R32_UINT, DXGI_FORMAT_R32_UINT, 1, 4, + DataType::UInt), + TEST_CASE(TextureType::Regular, DXGI_FORMAT_R32_SINT, DXGI_FORMAT_R32_SINT, 1, 4, + DataType::SInt), + + TEST_CASE(TextureType::Regular, DXGI_FORMAT_R8G8_TYPELESS, DXGI_FORMAT_R8G8_UINT, 2, 1, + DataType::UInt), + TEST_CASE(TextureType::Regular, DXGI_FORMAT_R8G8_TYPELESS, DXGI_FORMAT_R8G8_UNORM, 2, 1, + DataType::UNorm), + TEST_CASE(TextureType::Regular, DXGI_FORMAT_R8G8_UNORM, DXGI_FORMAT_R8G8_UNORM, 2, 1, + DataType::UNorm), + TEST_CASE(TextureType::Regular, DXGI_FORMAT_R8G8_UINT, DXGI_FORMAT_R8G8_UINT, 2, 1, + DataType::UInt), + TEST_CASE(TextureType::Regular, DXGI_FORMAT_R8G8_SNORM, DXGI_FORMAT_R8G8_SNORM, 2, 1, + DataType::SNorm), + TEST_CASE(TextureType::Regular, DXGI_FORMAT_R8G8_SINT, DXGI_FORMAT_R8G8_SINT, 2, 1, + DataType::SInt), + + TEST_CASE(TextureType::Regular, DXGI_FORMAT_R16_TYPELESS, DXGI_FORMAT_R16_FLOAT, 1, 2, + DataType::Float), + TEST_CASE(TextureType::Regular, DXGI_FORMAT_R16_TYPELESS, DXGI_FORMAT_R16_UINT, 1, 2, + DataType::UInt), + TEST_CASE(TextureType::Regular, DXGI_FORMAT_R16_TYPELESS, DXGI_FORMAT_R16_UNORM, 1, 2, + DataType::UNorm), + TEST_CASE(TextureType::Regular, DXGI_FORMAT_R16_FLOAT, DXGI_FORMAT_R16_FLOAT, 1, 2, + DataType::Float), + TEST_CASE(TextureType::Regular, DXGI_FORMAT_R16_UNORM, DXGI_FORMAT_R16_UNORM, 1, 2, + DataType::UNorm), + TEST_CASE(TextureType::Regular, DXGI_FORMAT_R16_UINT, DXGI_FORMAT_R16_UINT, 1, 2, + DataType::UInt), + TEST_CASE(TextureType::Regular, DXGI_FORMAT_R16_SNORM, DXGI_FORMAT_R16_SNORM, 1, 2, + DataType::SNorm), + TEST_CASE(TextureType::Regular, DXGI_FORMAT_R16_SINT, DXGI_FORMAT_R16_SINT, 1, 2, + DataType::SInt), + + TEST_CASE(TextureType::Regular, DXGI_FORMAT_R8_TYPELESS, DXGI_FORMAT_R8_UINT, 1, 1, + DataType::UInt), + TEST_CASE(TextureType::Regular, DXGI_FORMAT_R8_TYPELESS, DXGI_FORMAT_R8_UNORM, 1, 1, + DataType::UNorm), + TEST_CASE(TextureType::Regular, DXGI_FORMAT_R8_UNORM, DXGI_FORMAT_R8_UNORM, 1, 1, + DataType::UNorm), + TEST_CASE(TextureType::Regular, DXGI_FORMAT_R8_UINT, DXGI_FORMAT_R8_UINT, 1, 1, + DataType::UInt), + TEST_CASE(TextureType::Regular, DXGI_FORMAT_R8_SNORM, DXGI_FORMAT_R8_SNORM, 1, 1, + DataType::SNorm), + TEST_CASE(TextureType::Regular, DXGI_FORMAT_R8_SINT, DXGI_FORMAT_R8_SINT, 1, 1, + DataType::SInt), + + TEST_CASE(TextureType::Regular, DXGI_FORMAT_B8G8R8A8_TYPELESS, DXGI_FORMAT_B8G8R8A8_UNORM, + 4, 1, DataType::UNorm), + TEST_CASE(TextureType::Regular, DXGI_FORMAT_B8G8R8A8_TYPELESS, + DXGI_FORMAT_B8G8R8A8_UNORM_SRGB, 4, 1, DataType::UNorm), + TEST_CASE(TextureType::Regular, DXGI_FORMAT_B8G8R8A8_UNORM, DXGI_FORMAT_B8G8R8A8_UNORM, 4, + 1, DataType::UNorm), + TEST_CASE(TextureType::Regular, DXGI_FORMAT_B8G8R8A8_UNORM_SRGB, + DXGI_FORMAT_B8G8R8A8_UNORM_SRGB, 4, 1, DataType::UNorm), + + TEST_CASE(TextureType::Regular, DXGI_FORMAT_B8G8R8X8_TYPELESS, DXGI_FORMAT_B8G8R8X8_UNORM, + 4, 1, DataType::UNorm), + TEST_CASE(TextureType::Regular, DXGI_FORMAT_B8G8R8X8_TYPELESS, + DXGI_FORMAT_B8G8R8X8_UNORM_SRGB, 4, 1, DataType::UNorm), + TEST_CASE(TextureType::Regular, DXGI_FORMAT_B8G8R8X8_UNORM, DXGI_FORMAT_B8G8R8X8_UNORM, 4, + 1, DataType::UNorm), + TEST_CASE(TextureType::Regular, DXGI_FORMAT_B8G8R8X8_UNORM_SRGB, + DXGI_FORMAT_B8G8R8X8_UNORM_SRGB, 4, 1, DataType::UNorm), + TEST_CASE(TextureType::Regular, DXGI_FORMAT_A8_UNORM, DXGI_FORMAT_A8_UNORM, 1, 1, + DataType::UNorm), + + TEST_CASE(TextureType::BC1, DXGI_FORMAT_BC1_TYPELESS, DXGI_FORMAT_BC1_UNORM, 0, 0, + DataType::UNorm), + TEST_CASE(TextureType::BC1, DXGI_FORMAT_BC1_TYPELESS, DXGI_FORMAT_BC1_UNORM_SRGB, 0, 0, + DataType::UNorm), + TEST_CASE(TextureType::BC1, DXGI_FORMAT_BC1_UNORM, DXGI_FORMAT_BC1_UNORM, 0, 0, + DataType::UNorm), + TEST_CASE(TextureType::BC1, DXGI_FORMAT_BC1_UNORM_SRGB, DXGI_FORMAT_BC1_UNORM_SRGB, 0, 0, + DataType::UNorm), + + TEST_CASE(TextureType::BC2, DXGI_FORMAT_BC2_TYPELESS, DXGI_FORMAT_BC2_UNORM, 0, 0, + DataType::UNorm), + TEST_CASE(TextureType::BC2, DXGI_FORMAT_BC2_TYPELESS, DXGI_FORMAT_BC2_UNORM_SRGB, 0, 0, + DataType::UNorm), + TEST_CASE(TextureType::BC2, DXGI_FORMAT_BC2_UNORM, DXGI_FORMAT_BC2_UNORM, 0, 0, + DataType::UNorm), + TEST_CASE(TextureType::BC2, DXGI_FORMAT_BC2_UNORM_SRGB, DXGI_FORMAT_BC2_UNORM_SRGB, 0, 0, + DataType::UNorm), + + TEST_CASE(TextureType::BC3, DXGI_FORMAT_BC3_TYPELESS, DXGI_FORMAT_BC3_UNORM, 0, 0, + DataType::UNorm), + TEST_CASE(TextureType::BC3, DXGI_FORMAT_BC3_TYPELESS, DXGI_FORMAT_BC3_UNORM_SRGB, 0, 0, + DataType::UNorm), + TEST_CASE(TextureType::BC3, DXGI_FORMAT_BC3_UNORM, DXGI_FORMAT_BC3_UNORM, 0, 0, + DataType::UNorm), + TEST_CASE(TextureType::BC3, DXGI_FORMAT_BC3_UNORM_SRGB, DXGI_FORMAT_BC3_UNORM_SRGB, 0, 0, + DataType::UNorm), + + TEST_CASE(TextureType::BC4, DXGI_FORMAT_BC4_TYPELESS, DXGI_FORMAT_BC4_UNORM, 0, 0, + DataType::UNorm), + TEST_CASE(TextureType::BC4, DXGI_FORMAT_BC4_TYPELESS, DXGI_FORMAT_BC4_SNORM, 0, 0, + DataType::SNorm), + TEST_CASE(TextureType::BC4, DXGI_FORMAT_BC4_UNORM, DXGI_FORMAT_BC4_UNORM, 0, 0, + DataType::UNorm), + TEST_CASE(TextureType::BC4, DXGI_FORMAT_BC4_SNORM, DXGI_FORMAT_BC4_SNORM, 0, 0, + DataType::SNorm), + + TEST_CASE(TextureType::BC5, DXGI_FORMAT_BC5_TYPELESS, DXGI_FORMAT_BC5_UNORM, 0, 0, + DataType::UNorm), + TEST_CASE(TextureType::BC5, DXGI_FORMAT_BC5_TYPELESS, DXGI_FORMAT_BC5_SNORM, 0, 0, + DataType::SNorm), + TEST_CASE(TextureType::BC5, DXGI_FORMAT_BC5_UNORM, DXGI_FORMAT_BC5_UNORM, 0, 0, + DataType::UNorm), + TEST_CASE(TextureType::BC5, DXGI_FORMAT_BC5_SNORM, DXGI_FORMAT_BC5_SNORM, 0, 0, + DataType::SNorm), + + TEST_CASE(TextureType::BC6, DXGI_FORMAT_BC6H_TYPELESS, DXGI_FORMAT_BC6H_UF16, 0, 0, + DataType::Float), + TEST_CASE(TextureType::BC6, DXGI_FORMAT_BC6H_TYPELESS, DXGI_FORMAT_BC6H_SF16, 0, 0, + DataType::SNorm), + TEST_CASE(TextureType::BC6, DXGI_FORMAT_BC6H_UF16, DXGI_FORMAT_BC6H_UF16, 0, 0, + DataType::Float), + TEST_CASE(TextureType::BC6, DXGI_FORMAT_BC6H_SF16, DXGI_FORMAT_BC6H_SF16, 0, 0, + DataType::SNorm), + + TEST_CASE(TextureType::BC7, DXGI_FORMAT_BC7_TYPELESS, DXGI_FORMAT_BC7_UNORM, 0, 0, + DataType::UNorm), + TEST_CASE(TextureType::BC7, DXGI_FORMAT_BC7_TYPELESS, DXGI_FORMAT_BC7_UNORM_SRGB, 0, 0, + DataType::UNorm), + TEST_CASE(TextureType::BC7, DXGI_FORMAT_BC7_UNORM, DXGI_FORMAT_BC7_UNORM, 0, 0, + DataType::UNorm), + TEST_CASE(TextureType::BC7, DXGI_FORMAT_BC7_UNORM_SRGB, DXGI_FORMAT_BC7_UNORM_SRGB, 0, 0, + DataType::UNorm), + + TEST_CASE(TextureType::R9G9B9E5, DXGI_FORMAT_R9G9B9E5_SHAREDEXP, + DXGI_FORMAT_R9G9B9E5_SHAREDEXP, 0, 0, DataType::Float), + TEST_CASE(TextureType::Unknown, DXGI_FORMAT_B5G6R5_UNORM, DXGI_FORMAT_B5G6R5_UNORM, 0, 0, + DataType::UNorm), + TEST_CASE(TextureType::Unknown, DXGI_FORMAT_B5G5R5A1_UNORM, DXGI_FORMAT_B5G5R5A1_UNORM, 0, + 0, DataType::UNorm), + TEST_CASE(TextureType::R4G4B4A4, DXGI_FORMAT_B4G4R4A4_UNORM, DXGI_FORMAT_B4G4R4A4_UNORM, 0, + 0, DataType::UNorm), + TEST_CASE(TextureType::Unknown, DXGI_FORMAT_R1_UNORM, DXGI_FORMAT_R1_UNORM, 0, 0, + DataType::UNorm), + + TEST_CASE(TextureType::Unknown, DXGI_FORMAT_R10G10B10A2_TYPELESS, + DXGI_FORMAT_R10G10B10A2_UNORM, 1, 4, DataType::UNorm), + TEST_CASE(TextureType::Unknown, DXGI_FORMAT_R10G10B10A2_TYPELESS, + DXGI_FORMAT_R10G10B10A2_UINT, 1, 4, DataType::UInt), + TEST_CASE(TextureType::Unknown, DXGI_FORMAT_R10G10B10A2_UNORM, + DXGI_FORMAT_R10G10B10A2_UNORM, 1, 4, DataType::UNorm), + TEST_CASE(TextureType::Unknown, DXGI_FORMAT_R10G10B10A2_UINT, DXGI_FORMAT_R10G10B10A2_UINT, + 1, 4, DataType::UInt), + + TEST_CASE(TextureType::Unknown, DXGI_FORMAT_R11G11B10_FLOAT, DXGI_FORMAT_R11G11B10_FLOAT, 0, + 0, DataType::Float), + }; + + for(D3D12Format f : color_tests) + AddSupportedTests(f, test_textures, false); + + // finally add the depth tests + const D3D12Format depth_tests[] = { + TEST_CASE(TextureType::Unknown, DXGI_FORMAT_R32G8X24_TYPELESS, + DXGI_FORMAT_R32_FLOAT_X8X24_TYPELESS, 0, 0, DataType::Float), + TEST_CASE(TextureType::Unknown, DXGI_FORMAT_R32G8X24_TYPELESS, + DXGI_FORMAT_X32_TYPELESS_G8X24_UINT, 0, 0, DataType::UInt), + + TEST_CASE(TextureType::Unknown, DXGI_FORMAT_R24G8_TYPELESS, + DXGI_FORMAT_R24_UNORM_X8_TYPELESS, 0, 0, DataType::UNorm), + TEST_CASE(TextureType::Unknown, DXGI_FORMAT_R24G8_TYPELESS, + DXGI_FORMAT_X24_TYPELESS_G8_UINT, 0, 0, DataType::UInt), + + TEST_CASE(TextureType::Unknown, DXGI_FORMAT_R32_TYPELESS, DXGI_FORMAT_R32_FLOAT, 0, 0, + DataType::Float), + + TEST_CASE(TextureType::Unknown, DXGI_FORMAT_R16_TYPELESS, DXGI_FORMAT_R16_UNORM, 0, 0, + DataType::UNorm), + }; + + for(D3D12Format f : depth_tests) + AddSupportedTests(f, test_textures, true); + + uploadBuf->Unmap(0, NULL); + + ID3D12ResourcePtr fltTex = MakeTexture(DXGI_FORMAT_R32G32B32A32_FLOAT, screenWidth, screenHeight) + .RTV() + .InitialState(D3D12_RESOURCE_STATE_RENDER_TARGET); + D3D12_GPU_DESCRIPTOR_HANDLE fltSRV = MakeSRV(fltTex).CreateGPU(srvIndex++); + + ID3DBlobPtr msps[(size_t)DataType::Count]; + + std::string def = "#define TEX_WIDTH " + std::to_string(texWidth) + "\n\n"; + + msps[(size_t)DataType::Float] = msps[(size_t)DataType::UNorm] = msps[(size_t)DataType::SNorm] = + Compile(def + pixelMSFloat, "main", "ps_5_0"); + msps[(size_t)DataType::UInt] = Compile(def + pixelMSUInt, "main", "ps_5_0"); + msps[(size_t)DataType::SInt] = Compile(def + pixelMSSInt, "main", "ps_5_0"); + + ID3DBlobPtr msdepthps = Compile(def + pixelMSDepth, "main", "ps_5_0"); + + D3D12PSOCreator psoCreator = MakePSO().RootSig(sig).VS(vsblob); + + psoCreator.GraphicsDesc.DepthStencilState.DepthFunc = D3D12_COMPARISON_FUNC_ALWAYS; + psoCreator.GraphicsDesc.DepthStencilState.FrontFace.StencilFunc = D3D12_COMPARISON_FUNC_ALWAYS; + psoCreator.GraphicsDesc.DepthStencilState.FrontFace.StencilPassOp = D3D12_STENCIL_OP_REPLACE; + + for(TestCase &t : test_textures) + { + if(!t.res || t.hasData) + continue; + + if(!t.canRender && !t.isDepth) + { + TEST_ERROR("Need data for test %s, but it's not a renderable/depthable format", + t.fmt.name.c_str()); + continue; + } + + psoCreator.GraphicsDesc.DepthStencilState.DepthEnable = t.isDepth; + psoCreator.GraphicsDesc.DepthStencilState.StencilEnable = t.isDepth; + + D3D12_RESOURCE_DESC desc = t.res->GetDesc(); + + bool tex3d = desc.Dimension == D3D12_RESOURCE_DIMENSION_TEXTURE3D; + + UINT MipLevels = desc.MipLevels, SampleCount = desc.SampleDesc.Count; + + ID3D12GraphicsCommandListPtr cmd = GetCommandBuffer(); + + Reset(cmd); + + pushMarker(cmd, "Render data for " + t.fmt.name + " " + MakeName(t)); + + t.hasData = true; + + bool srgb = false, bgra = false; + switch(t.fmt.viewFmt) + { + // only need to handle renderable BGRA/SRGB formats here + case DXGI_FORMAT_B5G6R5_UNORM: + case DXGI_FORMAT_B5G5R5A1_UNORM: + case DXGI_FORMAT_B8G8R8A8_UNORM: + case DXGI_FORMAT_B8G8R8X8_UNORM: + case DXGI_FORMAT_B4G4R4A4_UNORM: bgra = true; break; + + case DXGI_FORMAT_B8G8R8A8_UNORM_SRGB: + case DXGI_FORMAT_B8G8R8X8_UNORM_SRGB: + bgra = true; + srgb = true; + break; + case DXGI_FORMAT_R8G8B8A8_UNORM_SRGB: srgb = true; break; + default: break; + } + + int flags = 0; + + if(t.fmt.cfg.data == DataType::SNorm) + flags |= 1; + if(srgb) + flags |= 2; + if(bgra) + flags |= 4; + if(t.fmt.viewFmt == DXGI_FORMAT_A8_UNORM) + flags |= 8; + + D3D12_RESOURCE_STATES state = D3D12_RESOURCE_STATE_COMMON; + + if(t.isDepth) + { + psoCreator.PS(msdepthps).DSV(GetDepthFormat(t.fmt)); + + state = D3D12_RESOURCE_STATE_DEPTH_WRITE; + } + else + { + psoCreator.PS(msps[(size_t)t.fmt.cfg.data]).RTVs({t.fmt.viewFmt}); + + state = D3D12_RESOURCE_STATE_RENDER_TARGET; + } + + ResourceBarrier(cmd, t.res, D3D12_RESOURCE_STATE_COMMON, state); + + psoCreator.SampleCount(SampleCount); + + cmd->IASetPrimitiveTopology(D3D_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP); + cmd->SetGraphicsRootSignature(sig); + cmd->SetDescriptorHeaps(1, &m_CBVUAVSRV.GetInterfacePtr()); + RSSetViewport(cmd, {0.0f, 0.0f, (float)texWidth, (float)texHeight, 0.0f, 1.0f}); + RSSetScissorRect(cmd, {0, 0, texWidth, texHeight}); + + // keep all PSOs alive until the end of the loop where we submit the command buffer + std::vector PSOs; + + for(UINT mp = 0; mp < MipLevels; mp++) + { + UINT SlicesOrDepth = desc.DepthOrArraySize; + if(tex3d) + SlicesOrDepth >>= mp; + + for(UINT sl = 0; sl < SlicesOrDepth; sl++) + { + if(t.isDepth) + { + D3D12_CPU_DESCRIPTOR_HANDLE dsv = MakeDSV(t.res) + .Format(GetDepthFormat(t.fmt)) + .FirstSlice(sl) + .NumSlices(1) + .FirstMip(mp) + .NumMips(1) + .CreateCPU(0); + + D3D12_RECT rect = {}; + rect.right = (LONG)std::max(1ULL, desc.Width >> mp); + rect.bottom = (LONG)std::max(1U, desc.Height >> mp); + + cmd->ClearDepthStencilView(dsv, D3D12_CLEAR_FLAG_DEPTH | D3D12_CLEAR_FLAG_STENCIL, 0.0, + 0, 1, &rect); + + OMSetRenderTargets(cmd, {}, dsv); + + // need to do each sample separately to let us vary the stencil value + for(UINT sm = 0; sm < SampleCount; sm++) + { + psoCreator.GraphicsDesc.SampleMask = 1 << sm; + + ID3D12PipelineStatePtr pso = psoCreator; + PSOs.push_back(pso); + + cmd->SetPipelineState(pso); + + Vec4i params(sl, mp); + cmd->SetGraphicsRoot32BitConstants(0, 4, ¶ms, 0); + + cmd->OMSetStencilRef(100 + (mp + sm) * 10); + + cmd->DrawInstanced(4, 1, 0, 0); + + // clip off the diagonal + params.z = 1; + cmd->SetGraphicsRoot32BitConstants(0, 4, ¶ms, 0); + + cmd->OMSetStencilRef(10 + (mp + sm) * 10); + + cmd->DrawInstanced(4, 1, 0, 0); + } + } + else + { + D3D12_CPU_DESCRIPTOR_HANDLE rtv = MakeRTV(t.res) + .Format(t.fmt.viewFmt) + .FirstSlice(sl) + .NumSlices(1) + .FirstMip(mp) + .NumMips(1) + .CreateCPU(0); + + ID3D12PipelineStatePtr pso = psoCreator; + PSOs.push_back(pso); + + cmd->SetPipelineState(pso); + + Vec4i params(tex3d ? 0 : sl, mp, flags, tex3d ? sl : 0); + cmd->SetGraphicsRoot32BitConstants(0, 4, ¶ms, 0); + + OMSetRenderTargets(cmd, {rtv}, {}); + + cmd->DrawInstanced(4, 1, 0, 0); + } + } + } + + ResourceBarrier(cmd, t.res, state, D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE); + + popMarker(cmd); + + cmd->Close(); + + Submit({cmd}); + + GPUSync(); + } + + while(Running()) + { + ID3D12GraphicsCommandListPtr cmd = GetCommandBuffer(); + + Reset(cmd); + + D3D12_CPU_DESCRIPTOR_HANDLE fltRTV = MakeRTV(fltTex).CreateCPU(0); + + ClearRenderTargetView(cmd, fltRTV, {0.4f, 0.5f, 0.6f, 1.0f}); + + cmd->IASetPrimitiveTopology(D3D_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP); + + OMSetRenderTargets(cmd, {fltRTV}, {}); + + cmd->SetGraphicsRootSignature(sig); + + cmd->SetDescriptorHeaps(1, &m_CBVUAVSRV.GetInterfacePtr()); + + D3D12_VIEWPORT view = {0.0f, 0.0f, 10.0f, 10.0f, 0.0f, 1.0f}; + + for(size_t i = 0; i < test_textures.size(); i++) + { + if(i == 0 || test_textures[i].fmt.texFmt != test_textures[i - 1].fmt.texFmt || + test_textures[i].fmt.viewFmt != test_textures[i - 1].fmt.viewFmt) + { + if(i != 0) + popMarker(cmd); + + pushMarker(cmd, test_textures[i].fmt.name); + } + + setMarker(cmd, MakeName(test_textures[i])); + + RSSetViewport(cmd, view); + D3D12_RECT rect = { + (LONG)view.TopLeftX, (LONG)view.TopLeftY, LONG(view.TopLeftX + view.Width), + LONG(view.TopLeftY + view.Height), + }; + rect.left++; + rect.top++; + rect.right--; + rect.bottom--; + RSSetScissorRect(cmd, rect); + + cmd->SetPipelineState(GetPSO(test_textures[i])); + + if(test_textures[i].srv.ptr != 0) + { + cmd->SetGraphicsRootDescriptorTable(1, test_textures[i].srv); + cmd->DrawInstanced(4, 1, 0, 0); + } + else + { + setMarker(cmd, "UNSUPPORTED"); + } + + // advance to next viewport + view.TopLeftX += view.Width; + if(view.TopLeftX + view.Width > (float)screenWidth) + { + view.TopLeftX = 0; + view.TopLeftY += view.Height; + } + } + + // pop the last format region + popMarker(cmd); + + ResourceBarrier(cmd, fltTex, D3D12_RESOURCE_STATE_RENDER_TARGET, + D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE); + + ID3D12ResourcePtr bb = StartUsingBackbuffer(cmd, D3D12_RESOURCE_STATE_RENDER_TARGET); + + D3D12_CPU_DESCRIPTOR_HANDLE rtv = + MakeRTV(bb).Format(DXGI_FORMAT_R8G8B8A8_UNORM_SRGB).CreateCPU(0); + + ClearRenderTargetView(cmd, rtv, {0.4f, 0.5f, 0.6f, 1.0f}); + + OMSetRenderTargets(cmd, {rtv}, {}); + + RSSetViewport(cmd, {0.0f, 0.0f, (float)screenWidth, (float)screenHeight, 0.0f, 1.0f}); + RSSetScissorRect(cmd, {0, 0, screenWidth, screenHeight}); + + cmd->SetPipelineState(blitpso); + cmd->SetGraphicsRootDescriptorTable(1, fltSRV); + cmd->DrawInstanced(4, 1, 0, 0); + + FinishUsingBackbuffer(cmd, D3D12_RESOURCE_STATE_RENDER_TARGET); + + ResourceBarrier(cmd, fltTex, D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE, + D3D12_RESOURCE_STATE_RENDER_TARGET); + + cmd->Close(); + + Submit({cmd}); + + Present(); + } + + return 0; + } +}; + +REGISTER_TEST(); diff --git a/util/test/demos/demos.vcxproj b/util/test/demos/demos.vcxproj index ce3574fc8..afbe8f5b2 100644 --- a/util/test/demos/demos.vcxproj +++ b/util/test/demos/demos.vcxproj @@ -152,6 +152,7 @@ + @@ -161,6 +162,7 @@ + @@ -200,6 +202,7 @@ true + @@ -212,6 +215,7 @@ + @@ -222,6 +226,7 @@ + diff --git a/util/test/demos/demos.vcxproj.filters b/util/test/demos/demos.vcxproj.filters index 1846cbef5..1fee3ebb7 100644 --- a/util/test/demos/demos.vcxproj.filters +++ b/util/test/demos/demos.vcxproj.filters @@ -348,6 +348,19 @@ D3D12\demos + + D3D11\demos + + + OpenGL\demos + + + + D3D12\demos + + + Vulkan\demos + diff --git a/util/test/demos/gl/gl_texture_zoo.cpp b/util/test/demos/gl/gl_texture_zoo.cpp new file mode 100644 index 000000000..7c70e3e19 --- /dev/null +++ b/util/test/demos/gl/gl_texture_zoo.cpp @@ -0,0 +1,985 @@ +/****************************************************************************** + * The MIT License (MIT) + * + * Copyright (c) 2019 Baldur Karlsson + * + * 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. + ******************************************************************************/ + +#include "gl_test.h" + +using namespace TextureZoo; + +RD_TEST(GL_Texture_Zoo, OpenGLGraphicsTest) +{ + static constexpr const char *Description = + "Tests all possible combinations of texture type and format that are supported."; + + std::string blitVertex = R"EOSHADER( +#version 420 core + +void main() +{ + const vec4 verts[4] = vec4[4](vec4(-1.0, -1.0, 0.5, 1.0), vec4(3.0, -1.0, 0.5, 1.0), + vec4(-1.0, 3.0, 0.5, 1.0), vec4(1.0, 1.0, 0.5, 1.0)); + + gl_Position = verts[gl_VertexID]; +} + +)EOSHADER"; + + std::string pixelTemplate = R"EOSHADER( +#version 420 core + +layout(binding = 0) uniform &texdecl intex; + +layout(location = 0, index = 0) out vec4 Color; + +void main() +{ + Color = vec4(texelFetch(intex, ¶ms)); +} +)EOSHADER"; + + std::string pixelMSFloat = R"EOSHADER( +#version 420 core + +uniform uint texWidth; +uniform uint slice; +uniform uint mip; +uniform uint flags; +uniform uint zlayer; + +float srgb2linear(float f) +{ + if (f <= 0.04045f) + return f / 12.92f; + else + return pow((f + 0.055f) / 1.055f, 2.4f); +} + +layout(location = 0, index = 0) out vec4 Color; + +void main() +{ + uint x = uint(gl_FragCoord.x); + uint y = uint(gl_FragCoord.y); + + vec4 ret = vec4(0.1f, 0.35f, 0.6f, 0.85f); + + // each 3D slice cycles the x. This only affects the primary diagonal + uint offs_x = (x + zlayer) % max(1u, texWidth >> mip); + + // pixels off the diagonal invert the colors + if(offs_x != y) + ret = ret.wzyx; + + // second slice adds a coarse checkerboard pattern of inversion + if(slice > 0 && (((x / 2) % 2) != ((y / 2) % 2))) + ret = ret.wzyx; + + // second sample/mip is shifted up a bit. MSAA textures have no mips, + // textures with mips have no samples. + ret += 0.075f.xxxx * (gl_SampleID + mip); + + // Signed normals are negative + if((flags & 1) != 0) + ret = -ret; + + // undo SRGB curve applied in output merger, to match the textures we just blat values into + // without conversion (which are then interpreted as srgb implicitly) + if((flags & 2) != 0) + { + ret.r = srgb2linear(ret.r); + ret.g = srgb2linear(ret.g); + ret.b = srgb2linear(ret.b); + } + + // BGR flip - same as above, for BGRA textures + if((flags & 4) != 0) + ret.rgb = ret.bgr; + + // put red into alpha, because that's what we did in manual upload + if((flags & 8) != 0) + ret.a = ret.r; + + Color = ret; +} + +)EOSHADER"; + + std::string pixelMSDepth = R"EOSHADER( +#version 420 core + +uniform uint texWidth; +uniform uint slice; +uniform uint mip; +uniform uint flags; +uniform uint zlayer; + +void main() +{ + uint x = uint(gl_FragCoord.x); + uint y = uint(gl_FragCoord.y); + + float ret = 0.1f; + + // each 3D slice cycles the x. This only affects the primary diagonal + uint offs_x = (x + zlayer) % max(1u, texWidth >> mip); + + // pixels off the diagonal invert the colors + // second slice adds a coarse checkerboard pattern of inversion + if((offs_x != y) != (slice > 0 && (((x / 2) % 2) != ((y / 2) % 2)))) + { + ret = 0.85f; + + // so we can fill stencil data, clip off the inverted values + if(flags == 1) + discard; + } + + // second sample/mip is shifted up a bit. MSAA textures have no mips, + // textures with mips have no samples. + ret += 0.075f * (gl_SampleID + mip); + + gl_FragDepth = ret; +} + +)EOSHADER"; + + std::string pixelMSUInt = R"EOSHADER( +#version 420 core + +uniform uint texWidth; +uniform uint slice; +uniform uint mip; +uniform uint flags; +uniform uint zlayer; + +layout(location = 0, index = 0) out uvec4 Color; + +void main() +{ + uint x = uint(gl_FragCoord.x); + uint y = uint(gl_FragCoord.y); + + uvec4 ret = uvec4(10, 40, 70, 100); + + // each 3D slice cycles the x. This only affects the primary diagonal + uint offs_x = (x + zlayer) % max(1u, texWidth >> mip); + + // pixels off the diagonal invert the colors + if(offs_x != y) + ret = ret.wzyx; + + // second slice adds a coarse checkerboard pattern of inversion + if(slice > 0 && (((x / 2) % 2) != ((y / 2) % 2))) + ret = ret.wzyx; + + // second sample/mip is shifted up a bit. MSAA textures have no mips, + // textures with mips have no samples. + ret += uvec4(10, 10, 10, 10) * (gl_SampleID + mip); + + Color = ret; +} + +)EOSHADER"; + + std::string pixelMSSInt = R"EOSHADER( +#version 420 core + +uniform uint texWidth; +uniform uint slice; +uniform uint mip; +uniform uint flags; +uniform uint zlayer; + +layout(location = 0, index = 0) out ivec4 Color; + +void main() +{ + uint x = uint(gl_FragCoord.x); + uint y = uint(gl_FragCoord.y); + + ivec4 ret = ivec4(10, 40, 70, 100); + + // each 3D slice cycles the x. This only affects the primary diagonal + uint offs_x = (x + zlayer) % max(1u, texWidth >> mip); + + // pixels off the diagonal invert the colors + if(offs_x != y) + ret = ret.wzyx; + + // second slice adds a coarse checkerboard pattern of inversion + if(slice > 0 && (((x / 2) % 2) != ((y / 2) % 2))) + ret = ret.wzyx; + + // second sample/mip is shifted up a bit. MSAA textures have no mips, + // textures with mips have no samples. + ret += ivec4(10 * (gl_SampleID + mip)); + + Color = -ret; +} + +)EOSHADER"; + + struct GLFormat + { + const std::string name; + GLenum internalFormat; + TexConfig cfg; + }; + + struct TestCase + { + GLFormat fmt; + GLenum target; + uint32_t dim; + bool isArray; + bool isMSAA; + bool isRect; + bool canRender; + bool canDepth; + bool canStencil; + bool hasData; + GLuint tex; + }; + + std::string MakeName(const TestCase &test) + { + std::string name = "Texture " + std::to_string(test.dim) + "D"; + + if(test.isRect) + name = "Texture Rect"; + + if(test.isMSAA) + name += " MSAA"; + if(test.isArray) + name += " Array"; + + return name; + } + + GLuint GetProgram(const TestCase &test) + { + static std::map programs; + + uint32_t key = uint32_t(test.fmt.cfg.data); + key |= test.dim << 6; + key |= test.isMSAA ? 0x80000 : 0; + key |= test.isArray ? 0x100000 : 0; + key |= test.isRect ? 0x200000 : 0; + + GLuint ret = programs[key]; + if(!ret) + { + std::string texType = "sampler" + std::to_string(test.dim) + "D"; + if(test.isMSAA) + texType += "MS"; + if(test.isRect) + texType += "Rect"; + if(test.dim < 3 && test.isArray) + texType += "Array"; + + std::string typemod = ""; + + if(test.fmt.cfg.data == DataType::UInt) + typemod = "u"; + else if(test.fmt.cfg.data == DataType::SInt) + typemod = "i"; + + std::string src = pixelTemplate; + + uint32_t dim = test.dim + (test.isArray ? 1 : 0); + + if(test.isRect) + src.replace(src.find("¶ms"), 7, "ivec2(0)"); + else if(dim == 1) + src.replace(src.find("¶ms"), 7, "int(0), 0"); + else if(dim == 2) + src.replace(src.find("¶ms"), 7, "ivec2(0), 0"); + else if(dim == 3) + src.replace(src.find("¶ms"), 7, "ivec3(0), 0"); + + src.replace(src.find("&texdecl"), 8, typemod + texType); + + ret = programs[key] = MakeProgram(blitVertex, src); + } + + return ret; + } + + bool QueryFormatBool(GLenum target, GLenum format, GLenum pname) + { + GLint param = 0; + glGetInternalformativ(target, format, pname, 4, ¶m); + return param != 0; + } + + void FinaliseTest(TestCase & test) + { + test.canRender = QueryFormatBool(test.target, test.fmt.internalFormat, GL_COLOR_RENDERABLE); + test.canDepth = QueryFormatBool(test.target, test.fmt.internalFormat, GL_DEPTH_RENDERABLE); + test.canStencil = QueryFormatBool(test.target, test.fmt.internalFormat, GL_STENCIL_RENDERABLE); + + Vec4i dimensions(texWidth, texHeight, texDepth); + + bool isCompressed = + (test.fmt.cfg.type != TextureType::R9G9B9E5 && test.fmt.cfg.type != TextureType::Regular); + + // Some GL drivers report that block compressed textures are supported for MSAA and color + // rendering. Save them from themselves. Similarly they report support for 1D and 3D but then it + // doesn't work properly + if(isCompressed && (test.dim == 1 || test.dim == 3 || test.isRect || test.isMSAA)) + return; + + // any format that supports MSAA but can't be rendered to is unsupported + if(!test.canRender && !test.canDepth && !test.canStencil && test.isMSAA) + return; + + test.tex = MakeTexture(); + glBindTexture(test.target, test.tex); + + if(test.dim == 1) + { + if(test.isArray) + glTexStorage2D(test.target, texMips, test.fmt.internalFormat, texWidth, texSlices); + else + glTexStorage1D(test.target, texMips, test.fmt.internalFormat, texWidth); + + dimensions.y = dimensions.z = 1; + } + else if(test.isRect) + { + glTexStorage2D(test.target, 1, test.fmt.internalFormat, texWidth, texHeight); + + dimensions.z = 1; + } + else if(test.dim == 2) + { + if(test.isMSAA) + { + if(test.isArray) + glTexStorage3DMultisample(test.target, texSamples, test.fmt.internalFormat, texWidth, + texHeight, texSlices, GL_TRUE); + else + glTexStorage2DMultisample(test.target, texSamples, test.fmt.internalFormat, texWidth, + texHeight, GL_TRUE); + } + else + { + if(test.isArray) + glTexStorage3D(test.target, texMips, test.fmt.internalFormat, texWidth, texHeight, + texSlices); + else + glTexStorage2D(test.target, texMips, test.fmt.internalFormat, texWidth, texHeight); + } + + dimensions.z = 1; + } + else if(test.dim == 3) + { + glTexStorage3D(test.target, texMips, test.fmt.internalFormat, texWidth, texHeight, texDepth); + } + + if(test.canRender || test.canDepth || test.canStencil) + { + glFramebufferTexture(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, 0, 0); + glFramebufferTexture(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, 0, 0); + + GLenum attach = GL_COLOR_ATTACHMENT0; + if(test.canDepth && test.canStencil) + attach = GL_DEPTH_STENCIL_ATTACHMENT; + else if(test.canDepth) + attach = GL_DEPTH_ATTACHMENT; + else if(test.canStencil) + attach = GL_STENCIL_ATTACHMENT; + + if(test.dim == 3 || test.isArray) + glFramebufferTextureLayer(GL_FRAMEBUFFER, attach, test.tex, 0, 0); + else + glFramebufferTexture(GL_FRAMEBUFFER, attach, test.tex, 0); + + GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER); + + // sometimes the GL driver lies about being able to render to a format! weeee! + if(status != GL_FRAMEBUFFER_COMPLETE) + { + test.canRender = false; + test.canDepth = false; + test.canStencil = false; + } + + if(!test.canRender && !test.canDepth && !test.canStencil && test.isMSAA) + { + test.tex = 0; + return; + } + } + + glObjectLabel(GL_TEXTURE, test.tex, -1, (MakeName(test) + " " + test.fmt.name).c_str()); + + // invalidate the texture, this makes renderdoc treat it as dirty + glInvalidateTexImage(test.tex, 0); + + if(!test.isMSAA) + { + pushMarker("Set data for " + test.fmt.name + " " + MakeName(test)); + + test.hasData = SetData(test, dimensions); + + popMarker(); + } + } + + bool SetData(const TestCase &test, Vec4i dim) + { + bool isCompressed = + (test.fmt.cfg.type != TextureType::R9G9B9E5 && test.fmt.cfg.type != TextureType::Regular); + + TexData data; + + // tightly packed data + glPixelStorei(GL_UNPACK_ALIGNMENT, 1); + glPixelStorei(GL_PACK_ALIGNMENT, 1); + + GLenum format = GL_RGBA; + GLenum type = GL_UNSIGNED_BYTE; + if(test.fmt.cfg.componentBytes == 2) + type = GL_UNSIGNED_SHORT; + else if(test.fmt.cfg.componentBytes == 2) + type = GL_UNSIGNED_INT; + + bool isInt = (test.fmt.cfg.data == DataType::SInt || test.fmt.cfg.data == DataType::UInt); + if(test.fmt.cfg.componentCount == 4) + format = isInt ? GL_RGBA_INTEGER : GL_RGBA; + else if(test.fmt.cfg.componentCount == 3) + format = isInt ? GL_RGB_INTEGER : GL_RGB; + else if(test.fmt.cfg.componentCount == 2) + format = isInt ? GL_RG_INTEGER : GL_RG; + else if(test.fmt.cfg.componentCount == 1) + format = isInt ? GL_RED_INTEGER : GL_RED; + + if(test.fmt.cfg.type == TextureType::R9G9B9E5) + format = GL_RGB; + + GLint slices = test.isArray ? texSlices : 1; + GLint mips = test.isMSAA || test.isRect ? 1 : texMips; + + for(GLint s = 0; s < slices; s++) + { + for(GLint m = 0; m < mips; m++) + { + MakeData(data, test.fmt.cfg, dim, m, s); + + if(data.byteData.empty()) + return false; + + uint32_t mipWidth = std::max(texWidth >> m, 1U); + uint32_t mipHeight = std::max(texHeight >> m, 1U); + uint32_t mipDepth = std::max(texDepth >> m, 1U); + + if(isCompressed) + { + if(test.dim == 1) + { + if(test.isArray) + glCompressedTexSubImage2D(test.target, m, 0, s, mipWidth, 1, test.fmt.internalFormat, + (GLsizei)data.byteData.size(), data.byteData.data()); + else + glCompressedTexSubImage1D(test.target, m, 0, mipWidth, format, + (GLsizei)data.byteData.size(), data.byteData.data()); + } + else if(test.isRect) + { + glCompressedTexSubImage2D(test.target, 0, 0, 0, mipWidth, mipHeight, + test.fmt.internalFormat, (GLsizei)data.byteData.size(), + data.byteData.data()); + } + else if(test.dim == 2) + { + if(test.isArray) + glCompressedTexSubImage3D(test.target, m, 0, 0, s, mipWidth, mipHeight, 1, + test.fmt.internalFormat, (GLsizei)data.byteData.size(), + data.byteData.data()); + else + glCompressedTexSubImage2D(test.target, m, 0, 0, mipWidth, mipHeight, + test.fmt.internalFormat, (GLsizei)data.byteData.size(), + data.byteData.data()); + } + else if(test.dim == 3) + { + glCompressedTexSubImage3D(test.target, m, 0, 0, 0, mipWidth, mipHeight, mipDepth, + test.fmt.internalFormat, (GLsizei)data.byteData.size(), + data.byteData.data()); + } + } + else + { + if(test.dim == 1) + { + if(test.isArray) + glTexSubImage2D(test.target, m, 0, s, mipWidth, 1, format, type, data.byteData.data()); + else + glTexSubImage1D(test.target, m, 0, mipWidth, format, type, data.byteData.data()); + } + else if(test.isRect) + { + glTexSubImage2D(test.target, 0, 0, 0, mipWidth, mipHeight, format, type, + data.byteData.data()); + } + else if(test.dim == 2) + { + if(test.isArray) + glTexSubImage3D(test.target, m, 0, 0, s, mipWidth, mipHeight, 1, format, type, + data.byteData.data()); + else + glTexSubImage2D(test.target, m, 0, 0, mipWidth, mipHeight, format, type, + data.byteData.data()); + } + else if(test.dim == 3) + { + glTexSubImage3D(test.target, m, 0, 0, 0, mipWidth, mipHeight, mipDepth, format, type, + data.byteData.data()); + } + } + } + } + + return true; + } + + void AddSupportedTests(const GLFormat &f, std::vector &test_textures, bool depthMode) + { + // TODO: disable 1D depth textures for now, we don't support displaying them + if(!depthMode) + { + test_textures.push_back({f, GL_TEXTURE_1D, 1, false}); + test_textures.push_back({f, GL_TEXTURE_1D_ARRAY, 1, true}); + } + + test_textures.push_back({f, GL_TEXTURE_2D, 2, false}); + test_textures.push_back({f, GL_TEXTURE_2D_ARRAY, 2, true}); + + test_textures.push_back({f, GL_TEXTURE_3D, 3, false}); + + // TODO: we don't support MSAA<->Array copies for these odd sized pixels, and I suspect drivers + // emulate the formats anyway. Disable for now + if(f.cfg.type != TextureType::Regular || f.cfg.componentCount != 3) + { + test_textures.push_back({f, GL_TEXTURE_2D_MULTISAMPLE, 2, false, true}); + test_textures.push_back({f, GL_TEXTURE_2D_MULTISAMPLE_ARRAY, 2, true, true}); + } + + test_textures.push_back({f, GL_TEXTURE_RECTANGLE, 2, false, false, true}); + } + + int main() + { + // initialise, create window, create context, etc + if(!Init()) + return 3; + + GLuint vao = MakeVAO(); + glBindVertexArray(vao); + + pushMarker("Add tests"); + +#define TEST_CASE_NAME(texFmt) #texFmt + +#define TEST_CASE(texType, texFmt, compCount, byteWidth, dataType) \ + { \ + #texFmt + 3, texFmt, {texType, compCount, byteWidth, dataType }, \ + } + + std::vector test_textures; + + const GLFormat color_tests[] = { + TEST_CASE(TextureType::Regular, GL_RGBA32F, 4, 4, DataType::Float), + TEST_CASE(TextureType::Regular, GL_RGBA32UI, 4, 4, DataType::UInt), + TEST_CASE(TextureType::Regular, GL_RGBA32I, 4, 4, DataType::SInt), + + TEST_CASE(TextureType::Regular, GL_RGB32F, 3, 4, DataType::Float), + TEST_CASE(TextureType::Regular, GL_RGB32UI, 3, 4, DataType::UInt), + TEST_CASE(TextureType::Regular, GL_RGB32I, 3, 4, DataType::SInt), + + TEST_CASE(TextureType::Regular, GL_RG32F, 2, 4, DataType::Float), + TEST_CASE(TextureType::Regular, GL_RG32UI, 2, 4, DataType::UInt), + TEST_CASE(TextureType::Regular, GL_RG32I, 2, 4, DataType::SInt), + + TEST_CASE(TextureType::Regular, GL_R32F, 1, 4, DataType::Float), + TEST_CASE(TextureType::Regular, GL_R32UI, 1, 4, DataType::UInt), + TEST_CASE(TextureType::Regular, GL_R32I, 1, 4, DataType::SInt), + + TEST_CASE(TextureType::Regular, GL_RGBA16F, 4, 2, DataType::Float), + TEST_CASE(TextureType::Regular, GL_RGBA16UI, 4, 2, DataType::UInt), + TEST_CASE(TextureType::Regular, GL_RGBA16I, 4, 2, DataType::SInt), + TEST_CASE(TextureType::Regular, GL_RGBA16, 4, 2, DataType::UNorm), + TEST_CASE(TextureType::Regular, GL_RGBA16_SNORM, 4, 2, DataType::SNorm), + + TEST_CASE(TextureType::Regular, GL_RGB16F, 3, 2, DataType::Float), + TEST_CASE(TextureType::Regular, GL_RGB16UI, 3, 2, DataType::UInt), + TEST_CASE(TextureType::Regular, GL_RGB16I, 3, 2, DataType::SInt), + TEST_CASE(TextureType::Regular, GL_RGB16, 3, 2, DataType::UNorm), + TEST_CASE(TextureType::Regular, GL_RGB16_SNORM, 3, 2, DataType::SNorm), + + TEST_CASE(TextureType::Regular, GL_RG16F, 2, 2, DataType::Float), + TEST_CASE(TextureType::Regular, GL_RG16UI, 2, 2, DataType::UInt), + TEST_CASE(TextureType::Regular, GL_RG16I, 2, 2, DataType::SInt), + TEST_CASE(TextureType::Regular, GL_RG16, 2, 2, DataType::UNorm), + TEST_CASE(TextureType::Regular, GL_RG16_SNORM, 2, 2, DataType::SNorm), + + TEST_CASE(TextureType::Regular, GL_R16F, 1, 2, DataType::Float), + TEST_CASE(TextureType::Regular, GL_R16UI, 1, 2, DataType::UInt), + TEST_CASE(TextureType::Regular, GL_R16I, 1, 2, DataType::SInt), + TEST_CASE(TextureType::Regular, GL_R16, 1, 2, DataType::UNorm), + TEST_CASE(TextureType::Regular, GL_R16_SNORM, 1, 2, DataType::SNorm), + + TEST_CASE(TextureType::Regular, GL_RGBA8UI, 4, 1, DataType::UInt), + TEST_CASE(TextureType::Regular, GL_RGBA8I, 4, 1, DataType::SInt), + TEST_CASE(TextureType::Regular, GL_RGBA8, 4, 1, DataType::UNorm), + TEST_CASE(TextureType::Regular, GL_SRGB8_ALPHA8, 4, 1, DataType::UNorm), + TEST_CASE(TextureType::Regular, GL_RGBA8_SNORM, 4, 1, DataType::SNorm), + + TEST_CASE(TextureType::Regular, GL_RGB8UI, 3, 1, DataType::UInt), + TEST_CASE(TextureType::Regular, GL_RGB8I, 3, 1, DataType::SInt), + TEST_CASE(TextureType::Regular, GL_RGB8, 3, 1, DataType::UNorm), + TEST_CASE(TextureType::Regular, GL_SRGB8, 3, 1, DataType::UNorm), + TEST_CASE(TextureType::Regular, GL_RGB8_SNORM, 3, 1, DataType::SNorm), + + TEST_CASE(TextureType::Regular, GL_RG8UI, 2, 1, DataType::UInt), + TEST_CASE(TextureType::Regular, GL_RG8I, 2, 1, DataType::SInt), + TEST_CASE(TextureType::Regular, GL_RG8, 2, 1, DataType::UNorm), + TEST_CASE(TextureType::Regular, GL_SRG8_EXT, 1, 1, DataType::UNorm), + TEST_CASE(TextureType::Regular, GL_RG8_SNORM, 2, 1, DataType::SNorm), + + TEST_CASE(TextureType::Regular, GL_R8UI, 1, 1, DataType::UInt), + TEST_CASE(TextureType::Regular, GL_R8I, 1, 1, DataType::SInt), + TEST_CASE(TextureType::Regular, GL_R8, 1, 1, DataType::UNorm), + TEST_CASE(TextureType::Regular, GL_SR8_EXT, 1, 1, DataType::UNorm), + TEST_CASE(TextureType::Regular, GL_R8_SNORM, 1, 1, DataType::SNorm), + + TEST_CASE(TextureType::Unknown, GL_RGB565, 0, 0, DataType::UNorm), + TEST_CASE(TextureType::Unknown, GL_RGB5_A1, 0, 0, DataType::UNorm), + + TEST_CASE(TextureType::Unknown, GL_RGB10_A2, 0, 0, DataType::UNorm), + TEST_CASE(TextureType::Unknown, GL_RGB10_A2UI, 0, 0, DataType::UInt), + + TEST_CASE(TextureType::Unknown, GL_RGBA4, 0, 0, DataType::UNorm), + + // formats we don't support in RenderDoc currently + // TEST_CASE(TextureType::Unknown, GL_RGB4, 0, 0, DataType::UNorm), + // TEST_CASE(TextureType::Unknown, GL_RGB5, 0, 0, DataType::UNorm), + // TEST_CASE(TextureType::Unknown, GL_RGB10, 0, 0, DataType::UNorm), + + TEST_CASE(TextureType::Unknown, GL_R11F_G11F_B10F, 0, 0, DataType::UNorm), + + TEST_CASE(TextureType::R9G9B9E5, GL_RGB9_E5, 0, 0, DataType::Float), + + TEST_CASE(TextureType::BC1, GL_COMPRESSED_RGB_S3TC_DXT1_EXT, 0, 0, DataType::UNorm), + TEST_CASE(TextureType::BC1, GL_COMPRESSED_SRGB_S3TC_DXT1_EXT, 0, 0, DataType::UNorm), + TEST_CASE(TextureType::BC1, GL_COMPRESSED_RGBA_S3TC_DXT1_EXT, 0, 0, DataType::UNorm), + TEST_CASE(TextureType::BC1, GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT1_EXT, 0, 0, DataType::UNorm), + + TEST_CASE(TextureType::BC2, GL_COMPRESSED_RGBA_S3TC_DXT3_EXT, 0, 0, DataType::UNorm), + TEST_CASE(TextureType::BC2, GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT3_EXT, 0, 0, DataType::UNorm), + + TEST_CASE(TextureType::BC3, GL_COMPRESSED_RGBA_S3TC_DXT5_EXT, 0, 0, DataType::UNorm), + TEST_CASE(TextureType::BC3, GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT, 0, 0, DataType::UNorm), + + TEST_CASE(TextureType::BC4, GL_COMPRESSED_RED_RGTC1_EXT, 0, 0, DataType::UNorm), + TEST_CASE(TextureType::BC4, GL_COMPRESSED_SIGNED_RED_RGTC1_EXT, 0, 0, DataType::SNorm), + + TEST_CASE(TextureType::BC5, GL_COMPRESSED_RED_GREEN_RGTC2_EXT, 0, 0, DataType::UNorm), + TEST_CASE(TextureType::BC5, GL_COMPRESSED_SIGNED_RED_GREEN_RGTC2_EXT, 0, 0, DataType::SNorm), + + TEST_CASE(TextureType::BC6, GL_COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT_EXT, 0, 0, DataType::UNorm), + TEST_CASE(TextureType::BC6, GL_COMPRESSED_RGB_BPTC_SIGNED_FLOAT_EXT, 0, 0, DataType::SNorm), + + TEST_CASE(TextureType::BC7, GL_COMPRESSED_RGBA_BPTC_UNORM, 0, 0, DataType::UNorm), + TEST_CASE(TextureType::BC7, GL_COMPRESSED_SRGB_ALPHA_BPTC_UNORM, 0, 0, DataType::UNorm), + }; + + for(GLFormat f : color_tests) + { + if(f.internalFormat == GL_SR8_EXT && !GLAD_GL_EXT_texture_sRGB_R8) + continue; + + if(f.internalFormat == GL_SRG8_EXT && !GLAD_GL_EXT_texture_sRGB_RG8) + continue; + + AddSupportedTests(f, test_textures, false); + } + + // finally add the depth tests + const GLFormat depth_tests[] = { + TEST_CASE(TextureType::Unknown, GL_DEPTH32F_STENCIL8, 0, 0, DataType::Float), + TEST_CASE(TextureType::Unknown, GL_DEPTH_COMPONENT32F, 0, 0, DataType::Float), + + TEST_CASE(TextureType::Unknown, GL_DEPTH24_STENCIL8, 0, 0, DataType::Float), + TEST_CASE(TextureType::Unknown, GL_DEPTH_COMPONENT24, 0, 0, DataType::Float), + + TEST_CASE(TextureType::Unknown, GL_DEPTH_COMPONENT16, 0, 0, DataType::Float), + }; + + for(GLFormat f : depth_tests) + AddSupportedTests(f, test_textures, true); + + GLuint renderFBO = MakeFBO(); + glBindFramebuffer(GL_FRAMEBUFFER, renderFBO); + + for(TestCase &t : test_textures) + { + if(QueryFormatBool(t.target, t.fmt.internalFormat, GL_INTERNALFORMAT_SUPPORTED) && + QueryFormatBool(t.target, t.fmt.internalFormat, GL_FRAGMENT_TEXTURE)) + { + FinaliseTest(t); + } + } + + popMarker(); + + GLuint msprog[(size_t)DataType::Count]; + + msprog[(size_t)DataType::Float] = msprog[(size_t)DataType::UNorm] = + msprog[(size_t)DataType::SNorm] = MakeProgram(blitVertex, pixelMSFloat); + msprog[(size_t)DataType::UInt] = MakeProgram(blitVertex, pixelMSUInt); + msprog[(size_t)DataType::SInt] = MakeProgram(blitVertex, pixelMSSInt); + + GLuint msdepthprog = MakeProgram(blitVertex, pixelMSDepth); + + for(TestCase &t : test_textures) + { + if(!t.tex || t.hasData) + continue; + + if(!t.canRender && !t.canDepth && !t.canStencil) + { + TEST_ERROR("Need data for test %s, but it's not a renderable/depthable format", + t.fmt.name.c_str()); + continue; + } + + glFramebufferTexture(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, 0, 0); + glFramebufferTexture(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, 0, 0); + + if(t.canDepth || t.canStencil) + { + glEnable(GL_DEPTH_TEST); + glEnable(GL_STENCIL_TEST); + glDepthMask(0xff); + glDepthFunc(GL_ALWAYS); + glStencilFunc(GL_ALWAYS, 0, 0xff); + glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE); + } + else + { + glDisable(GL_DEPTH_TEST); + glDisable(GL_STENCIL_TEST); + } + + pushMarker("Render data for " + t.fmt.name + " " + MakeName(t)); + + t.hasData = true; + + bool srgb = false; + switch(t.fmt.internalFormat) + { + // only need to handle renderable SRGB formats here + case GL_SRGB8: + case GL_SRGB8_ALPHA8: srgb = true; break; + default: break; + } + + int flags = 0; + + if(t.fmt.cfg.data == DataType::SNorm) + flags |= 1; + if(srgb) + flags |= 2; + + GLuint slices = t.isArray ? texSlices : 1u; + GLuint mips = t.isMSAA || t.isRect ? 1u : texMips; + + for(GLuint mp = 0; mp < mips; mp++) + { + GLuint SlicesOrDepth = slices; + if(t.dim == 3) + SlicesOrDepth >>= mp; + + for(GLuint sl = 0; sl < SlicesOrDepth; sl++) + { + if(t.canDepth || t.canStencil) + { + GLenum attach = GL_NONE; + if(t.canDepth && t.canStencil) + attach = GL_DEPTH_STENCIL_ATTACHMENT; + else if(t.canDepth) + attach = GL_DEPTH_ATTACHMENT; + else if(t.canStencil) + attach = GL_STENCIL_ATTACHMENT; + + if(t.dim == 3 || t.isArray) + glFramebufferTextureLayer(GL_FRAMEBUFFER, attach, t.tex, mp, sl); + else + glFramebufferTexture(GL_FRAMEBUFFER, attach, t.tex, mp); + + glClearBufferfi(GL_DEPTH_STENCIL, 0, 0.0, 0); + + GLuint p = msdepthprog; + + glUseProgram(p); + + glUniform1ui(glGetUniformLocation(p, "texWidth"), texWidth); + + glUniform1ui(glGetUniformLocation(p, "slice"), sl); + glUniform1ui(glGetUniformLocation(p, "mip"), mp); + glUniform1ui(glGetUniformLocation(p, "flags"), flags); + glUniform1ui(glGetUniformLocation(p, "zlayer"), t.dim == 3 ? sl : 0); + + glViewport(0, 0, texWidth, texHeight); + + uint32_t sampleCount = t.isMSAA ? texSamples : 1; + + // need to do each sample separately to let us vary the stencil value + for(uint32_t sm = 0; sm < sampleCount; sm++) + { + glSampleMaski(0, 1 << sm); + + glStencilFunc(GL_ALWAYS, 100 + (mp + sm) * 10, 0xff); + + glDrawArrays(GL_TRIANGLES, 0, 3); + + // clip off the diagonal + glUniform1ui(glGetUniformLocation(p, "flags"), 1); + + glStencilFunc(GL_ALWAYS, 10 + (mp + sm) * 10, 0xff); + + glDrawArrays(GL_TRIANGLES, 0, 3); + } + } + else + { + if(t.dim == 3 || t.isArray) + glFramebufferTextureLayer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, t.tex, mp, sl); + else + glFramebufferTexture(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, t.tex, mp); + + GLuint p = msprog[(size_t)t.fmt.cfg.data]; + + glUseProgram(p); + + glUniform1ui(glGetUniformLocation(p, "texWidth"), texWidth); + + glUniform1ui(glGetUniformLocation(p, "slice"), t.dim == 3 ? 0 : sl); + glUniform1ui(glGetUniformLocation(p, "mip"), mp); + glUniform1ui(glGetUniformLocation(p, "flags"), flags); + glUniform1ui(glGetUniformLocation(p, "zlayer"), t.dim == 3 ? sl : 0); + + glViewport(0, 0, texWidth, texHeight); + + glDrawArrays(GL_TRIANGLES, 0, 3); + } + } + } + + popMarker(); + } + + glDisable(GL_DEPTH_TEST); + glDisable(GL_STENCIL_TEST); + + GLuint fbo = MakeFBO(); + glBindFramebuffer(GL_FRAMEBUFFER, fbo); + + // Color render texture + GLuint colattach = MakeTexture(); + glBindTexture(GL_TEXTURE_2D, colattach); + glTexStorage2D(GL_TEXTURE_2D, 1, GL_RGBA32F, screenWidth, screenHeight); + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, colattach, 0); + + while(Running()) + { + glBindFramebuffer(GL_FRAMEBUFFER, fbo); + + float col[] = {0.4f, 0.5f, 0.6f, 1.0f}; + glClearBufferfv(GL_COLOR, 0, col); + + glBindVertexArray(vao); + + GLsizei viewX = 0, viewY = screenHeight - 10; + glEnable(GL_SCISSOR_TEST); + + for(size_t i = 0; i < test_textures.size(); i++) + { + if(i == 0 || test_textures[i].fmt.internalFormat != test_textures[i - 1].fmt.internalFormat) + { + if(i != 0) + popMarker(); + + pushMarker(test_textures[i].fmt.name); + } + + setMarker(MakeName(test_textures[i])); + + glViewport(viewX, viewY, 10, 10); + glScissor(viewX + 1, viewY + 1, 8, 8); + + glUseProgram(GetProgram(test_textures[i])); + + if(test_textures[i].tex) + { + glBindTextureUnit(0, test_textures[i].tex); + glDrawArrays(GL_TRIANGLES, 0, 3); + } + else + { + setMarker("UNSUPPORTED"); + } + + // advance to next viewport + viewX += 10; + if(viewX + 10 > (float)screenWidth) + { + viewX = 0; + viewY -= 10; + } + } + + // pop the last format region + popMarker(); + + glViewport(0, 0, GLsizei(screenWidth), GLsizei(screenHeight)); + glDisable(GL_SCISSOR_TEST); + + // blit to the screen for a nicer preview + glBindFramebuffer(GL_READ_FRAMEBUFFER, fbo); + glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); + + glBlitFramebuffer(0, 0, screenWidth, screenHeight, 0, 0, screenWidth, screenHeight, + GL_COLOR_BUFFER_BIT, GL_NEAREST); + + Present(); + } + + return 0; + } +}; + +REGISTER_TEST(); diff --git a/util/test/demos/test_common.cpp b/util/test/demos/test_common.cpp index 74b08c3b4..646929b5c 100644 --- a/util/test/demos/test_common.cpp +++ b/util/test/demos/test_common.cpp @@ -492,4 +492,4 @@ bool GraphicsTest::FrameLimit() return false; return true; -} +} \ No newline at end of file diff --git a/util/test/demos/test_common.h b/util/test/demos/test_common.h index 5b4735e2f..30d6e0fd6 100644 --- a/util/test/demos/test_common.h +++ b/util/test/demos/test_common.h @@ -121,6 +121,8 @@ struct Vec4f z = Z; w = W; } + bool operator==(const Vec4f &o) { return x == o.x && y == o.y && z == o.z && w == o.w; } + bool operator!=(const Vec4f &o) { return !(*this == o); } float x, y, z, w; }; @@ -333,3 +335,62 @@ void DebugPrint(const char *fmt, ...); DEBUG_BREAK(); \ exit(0); \ } while(0) + +namespace TextureZoo +{ +enum class DataType +{ + Float, + UNorm, + SNorm, + UInt, + SInt, + Count, +}; + +enum class TextureType +{ + Unknown, + Regular, + R9G9B9E5, + G4R4, + A4R4G4B4, + R4G4B4A4, + R5G6B5, + R5G5B5A1, + A1R5G5B5, + RGB10A2, + BC1, + BC2, + BC3, + BC4, + BC5, + BC6, + BC7, +}; + +static const uint32_t texWidth = 8; +static const uint32_t texHeight = 8; +static const uint32_t texDepth = 10; +static const uint32_t texMips = 3; +static const uint32_t texSlices = 2; +static const uint32_t texSamples = 2; + +struct TexConfig +{ + TextureType type; + uint32_t componentCount; + uint32_t componentBytes; + DataType data; +}; + +struct TexData +{ + std::vector byteData; + uint32_t rowPitch = 0; + uint32_t slicePitch = 0; +}; + +void MakeData(TexData &data, const TexConfig &cfg, Vec4i dimensions, uint32_t mip, uint32_t slice); + +}; // namespace TextureZoo \ No newline at end of file diff --git a/util/test/demos/texture_zoo.cpp b/util/test/demos/texture_zoo.cpp new file mode 100644 index 000000000..7b9fb1f49 --- /dev/null +++ b/util/test/demos/texture_zoo.cpp @@ -0,0 +1,820 @@ +/****************************************************************************** +* The MIT License (MIT) +* +* Copyright (c) 2019 Baldur Karlsson +* +* 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. +******************************************************************************/ + +#include +#include "test_common.h" + +namespace TextureZoo +{ +// quick and dirty half conversion, doesn't handle NaN/inf/subnormal/truncation/rounding +uint16_t MakeHalf(float f) +{ + bool sign = f < 0.0f; + f = sign ? -f : f; + + if(f < 1e-15f) + return 0; + + int exp; + f = frexpf(f, &exp); + + uint32_t mantissa; + memcpy(&mantissa, &f, sizeof(mantissa)); + mantissa = (mantissa & 0x007fffff) >> 13; + + uint16_t ret = mantissa & 0x3ff; + ret |= ((exp + 14) << 10); + if(sign) + ret |= 0x8000; + + return ret; +} + +void MakePixel(byte *data, const TexConfig &cfg, uint32_t x, uint32_t y, uint32_t z, uint32_t mip, + uint32_t slice) +{ + // each 3D slice cycles the x + x += z; + x %= std::max(1U, texWidth >> mip); + + if(cfg.data == DataType::Float || cfg.data == DataType::UNorm || cfg.data == DataType::SNorm) + { + // start points for each component + const float vals[] = { + 0.1f, 0.35f, 0.6f, 0.85f, + }; + + for(uint32_t c = 0; c < cfg.componentCount; c++) + { + uint32_t idx = c; + + // pixels off the diagonal invert the colors + if(x != y) + idx = 3 - idx; + + // subsequent slices add a coarse checkerboard pattern of inverted colors + if((slice % 3 > 0) && (((x / 2) % 2) != ((y / 2) % 2))) + idx = 3 - idx; + + float f = vals[idx]; + + // subsequent mips are shifted up a bit + f += 0.075f * mip; + + // Signed normals are negative + if(cfg.data == DataType::SNorm) + f = -f; + + // if it's a full float, just copy + if(cfg.componentBytes == 4) + { + memcpy(data, &f, cfg.componentBytes); + } + else if(cfg.componentBytes == 2) + { + uint16_t h; + if(cfg.data == DataType::Float) + h = MakeHalf(f); + else if(cfg.data == DataType::UNorm) + h = uint16_t(f * 0xffff); + else if(cfg.data == DataType::SNorm) + h = int16_t(f * 0x7fff); + memcpy(data, &h, cfg.componentBytes); + } + else if(cfg.componentBytes == 1) + { + uint8_t b; + if(cfg.data == DataType::UNorm) + b = uint8_t(f * 0xff); + else if(cfg.data == DataType::SNorm) + b = int8_t(f * 0x7f); + memcpy(data, &b, cfg.componentBytes); + } + else + { + TEST_ERROR("Unexpected component bytes %d in float", cfg.componentBytes); + } + + data += cfg.componentBytes; + } + } + else if(cfg.data == DataType::UInt || cfg.data == DataType::SInt) + { + // same pattern as above but with integer values + const int32_t vals[] = { + 10, 40, 70, 100, + }; + + for(uint32_t c = 0; c < cfg.componentCount; c++) + { + uint32_t idx = c; + + // pixels off the diagonal invert the colors + if(x != y) + idx = 3 - idx; + + if((slice % 3 > 0) && (((x / 2) % 2) != ((y / 2) % 2))) + idx = 3 - idx; + + int32_t val = vals[idx]; + + val += 10 * mip; + + // Signed ints are negative + if(cfg.data == DataType::SInt) + val = -val; + + // because the values are below one byte and we're little-endian we can just copy the + // right number of bytes from val + memcpy(data, &val, cfg.componentBytes); + + data += cfg.componentBytes; + } + } +} + +void MakeData(TexData &data, const TexConfig &cfg, Vec4i dimensions, uint32_t mip, uint32_t slice) +{ + uint32_t mipWidth = std::max(1, dimensions.x >> mip); + uint32_t mipHeight = std::max(1, dimensions.y >> mip); + uint32_t mipDepth = std::max(1, dimensions.z >> mip); + + if(cfg.type == TextureType::Unknown) + { + data = TexData(); + return; + } + else if(cfg.type == TextureType::Regular) + { + uint32_t pixelPitch = cfg.componentBytes * cfg.componentCount; + data.rowPitch = pixelPitch * mipWidth; + data.slicePitch = data.rowPitch * mipHeight; + + data.byteData.resize(data.slicePitch * mipDepth); + + byte *out = data.byteData.data(); + + for(uint32_t z = 0; z < mipDepth; z++) + { + for(uint32_t y = 0; y < mipHeight; y++) + { + for(uint32_t x = 0; x < mipWidth; x++) + { + MakePixel(out, cfg, x, y, z, mip, slice); + out += pixelPitch; + } + } + } + } + else + { + bool bc1 = false, bc2alpha = false, bc3alpha = false, bc6 = false, bc7 = false, sharedExp = false; + int bc4channels = 0; + uint32_t nybblePattern = 0; + bool rgb5 = false; + int alphabitPlace = 0; + bool rgb10a2 = false; + + switch(cfg.type) + { + case TextureType::BC1: bc1 = true; break; + case TextureType::BC2: + bc1 = true; + bc2alpha = true; + break; + case TextureType::BC3: + bc1 = true; + bc3alpha = true; + break; + case TextureType::BC4: bc4channels = 1; break; + case TextureType::BC5: bc4channels = 2; break; + case TextureType::BC6: bc6 = true; break; + case TextureType::BC7: bc7 = true; break; + case TextureType::R9G9B9E5: sharedExp = true; break; + case TextureType::G4R4: nybblePattern = 0x12; break; + case TextureType::A4R4G4B4: nybblePattern = 0x1234; break; + case TextureType::R4G4B4A4: nybblePattern = 0x4321; break; + case TextureType::R5G6B5: + rgb5 = true; + alphabitPlace = 0; + break; + case TextureType::R5G5B5A1: + rgb5 = true; + alphabitPlace = 1; + break; + case TextureType::A1R5G5B5: + rgb5 = true; + alphabitPlace = 2; + break; + case TextureType::RGB10A2: rgb10a2 = true; break; + default: data = TexData(); return; + } + + // get float data so we can do the best possible job of truncating to the desired bit width + TexConfig floatcfg = {TextureType::Regular, 4, 4, DataType::Float}; + TexData floatdata; + + if(rgb10a2 && cfg.data == DataType::UInt) + floatcfg.data = cfg.data; + + MakeData(floatdata, floatcfg, dimensions, mip, slice); + + Vec4f *srcPixels = (Vec4f *)floatdata.byteData.data(); + Vec4i *srcPixelsI = (Vec4i *)floatdata.byteData.data(); + + if(rgb10a2) + { + uint32_t pixelPitch = 4; + data.rowPitch = pixelPitch * mipWidth; + data.slicePitch = data.rowPitch * mipHeight; + + data.byteData.resize(data.slicePitch * mipDepth); + + uint32_t *out = (uint32_t *)data.byteData.data(); + for(uint32_t z = 0; z < mipDepth; z++) + { + for(uint32_t y = 0; y < mipHeight; y++) + { + for(uint32_t x = 0; x < mipWidth; x++) + { + uint32_t encodedPixel = 0; + + if(cfg.data == DataType::UInt) + { + int32_t rgba[4]; + rgba[0] = srcPixelsI[y * mipWidth + x].x; + rgba[1] = srcPixelsI[y * mipWidth + x].y; + rgba[2] = srcPixelsI[y * mipWidth + x].z; + rgba[3] = srcPixelsI[y * mipWidth + x].w; + + encodedPixel |= (rgba[0] & 0x3ff) << 0; + encodedPixel |= (rgba[1] & 0x3ff) << 10; + encodedPixel |= (rgba[2] & 0x3ff) << 20; + encodedPixel |= (std::min(rgba[3], 3) & 0x3) << 30; + } + else + { + float rgba[4]; + rgba[0] = srcPixels[y * mipWidth + x].x; + rgba[1] = srcPixels[y * mipWidth + x].y; + rgba[2] = srcPixels[y * mipWidth + x].z; + rgba[3] = srcPixels[y * mipWidth + x].w; + + encodedPixel |= uint32_t(round(rgba[0] * 0x3ff)) << 0; + encodedPixel |= uint32_t(round(rgba[1] * 0x3ff)) << 10; + encodedPixel |= uint32_t(round(rgba[2] * 0x3ff)) << 20; + encodedPixel |= uint32_t(round(rgba[3] * 0x3)) << 30; + } + + *out = encodedPixel; + out++; + } + } + + srcPixels += mipWidth * mipHeight; + srcPixelsI += mipWidth * mipHeight; + } + } + else if(nybblePattern || rgb5) + { + uint32_t pixelPitch = 2; + data.rowPitch = pixelPitch * mipWidth; + data.slicePitch = data.rowPitch * mipHeight; + + data.byteData.resize(data.slicePitch * mipDepth); + + uint8_t *out = data.byteData.data(); + + for(uint32_t z = 0; z < mipDepth; z++) + { + for(uint32_t y = 0; y < mipHeight; y++) + { + for(uint32_t x = 0; x < mipWidth; x++) + { + float rgb[4]; + rgb[0] = srcPixels[y * mipWidth + x].x; + rgb[1] = srcPixels[y * mipWidth + x].y; + rgb[2] = srcPixels[y * mipWidth + x].z; + rgb[3] = srcPixels[y * mipWidth + x].w; + + if(rgb5) + { + bool alpha = rgb[3] >= 0.5f; + + uint16_t encodedPixel = 0; + + if(alphabitPlace == 0) + { + encodedPixel |= uint16_t(rgb[2] * 31) << 0; + encodedPixel |= uint16_t(rgb[1] * 63) << 5; + encodedPixel |= uint16_t(rgb[0] * 31) << 11; + } + else + { + encodedPixel |= uint16_t(rgb[2] * 31) << 0; + encodedPixel |= uint16_t(rgb[1] * 31) << 5; + encodedPixel |= uint16_t(rgb[0] * 31) << 10; + + if(alphabitPlace == 1) + { + if(alpha) + encodedPixel |= 0x8000; + } + else + { + encodedPixel <<= 1; + if(alpha) + encodedPixel |= 0x1; + } + } + + memcpy(out, &encodedPixel, sizeof(encodedPixel)); + out += 2; + } + else + { + uint8_t encodedPixel = 0; + + encodedPixel |= uint8_t(rgb[((nybblePattern & 0x000f) >> 0) - 1] * 15) << 0; + encodedPixel |= uint8_t(rgb[((nybblePattern & 0x00f0) >> 4) - 1] * 15) << 4; + + *out = encodedPixel; + out++; + + if(nybblePattern & 0xff00) + { + encodedPixel = 0; + encodedPixel |= uint8_t(rgb[((nybblePattern & 0x0f00) >> 8) - 1] * 15) << 0; + encodedPixel |= uint8_t(rgb[((nybblePattern & 0xf000) >> 12) - 1] * 15) << 4; + + *out = encodedPixel; + out++; + } + } + } + } + + srcPixels += mipWidth * mipHeight; + } + } + else if(sharedExp) + { + uint32_t pixelPitch = 4; + data.rowPitch = pixelPitch * mipWidth; + data.slicePitch = data.rowPitch * mipHeight; + + data.byteData.resize(data.slicePitch * mipDepth); + + uint32_t *out = (uint32_t *)data.byteData.data(); + + for(uint32_t z = 0; z < mipDepth; z++) + { + for(uint32_t y = 0; y < mipHeight; y++) + { + for(uint32_t x = 0; x < mipWidth; x++) + { + float rgb[3]; + rgb[0] = srcPixels[y * mipWidth + x].x; + rgb[1] = srcPixels[y * mipWidth + x].y; + rgb[2] = srcPixels[y * mipWidth + x].z; + + uint32_t encodedPixel = 0; + + int exp = -10; + // we pick the highest exponent, losing bits off the bottom of any value that + // needs a lower one, rather than picking a lower one and having to saturate + // values that need a higher one + for(int channel = 0; channel < 3; channel++) + { + int e = 0; + frexpf(rgb[channel], &e); + exp = std::max(exp, e); + } + + for(int channel = 0; channel < 3; channel++) + encodedPixel |= uint32_t(rgb[channel] * 511.0 / (1 << exp)) << (9 * channel); + + encodedPixel |= (exp + 15) << 27; + + *out = encodedPixel; + out++; + } + } + + srcPixels += mipWidth * mipHeight; + } + } + else + { + // these don't change, but make the code easier to read + const uint32_t blockWidth = 4; + const uint32_t blockHeight = 4; + + uint32_t blockSize; + + // 0.5 byte per pixel + if(cfg.type == TextureType::BC1 || cfg.type == TextureType::BC4) + blockSize = 8; + else + blockSize = 16; + + data.rowPitch = blockSize * std::max(1U, mipWidth / blockWidth); + data.slicePitch = data.rowPitch * std::max(1U, mipHeight / blockHeight); + + data.byteData.resize(data.slicePitch * mipDepth); + + byte *out = (byte *)data.byteData.data(); + + const Vec4f invalid(999001.0f, 999002.0f, -999003.0f, -999004.0f); + + // compress each slice separately + for(uint32_t z = 0; z < mipDepth; z++) + { + // block compressed - iterate over the pixels in block size + for(uint32_t y = 0; y < mipHeight; y += blockHeight) + { + for(uint32_t x = 0; x < mipWidth; x += blockWidth) + { + Vec4f blockPixels[blockWidth * blockHeight] = {}; + + // copy all the in-range pixels into the block data + for(uint32_t by = 0; by < blockHeight; by++) + { + for(uint32_t bx = 0; bx < blockWidth; bx++) + { + if(x + bx >= mipWidth || y + by >= mipHeight) + { + blockPixels[by * blockWidth + bx] = invalid; + } + else + { + blockPixels[by * blockWidth + bx] = srcPixels[(y + by) * mipWidth + (x + bx)]; + } + } + } + + // we should have at most two unique pixels. The pattern is structured to allow + // that, since any other colour can't be uniquely represented in all compressed + // formats (even interpolated values) + Vec4f a = invalid, b = invalid; + uint32_t bc1bitmask = 0; + uint64_t bc4bitmask = 0; + + // BC1 and BC4 both share A = 0 and B = 0 codes + enum class BCCode : uint64_t + { + A = 0, + B = 1, + }; + + // iterate the pixels in the block in ascending bitmask order + for(uint32_t p = 0; p < blockWidth * blockHeight; p++) + { + if(blockPixels[p] == invalid) + { + // out of bounds pixel (think of a 2x2 mip), store as A - whatever A is. + bc1bitmask |= uint32_t(BCCode::A) << (p * 2); + bc4bitmask |= uint64_t(BCCode::A) << (p * 3); + } + else if(a == invalid) + { + // A hasn't been found yet, let's use this pixel for that + a = blockPixels[p]; + bc1bitmask |= uint32_t(BCCode::A) << (p * 2); + bc4bitmask |= uint64_t(BCCode::A) << (p * 3); + } + else if(blockPixels[p] == a) + { + // if A has been found then re-use it before assigning to B + bc1bitmask |= uint32_t(BCCode::A) << (p * 2); + bc4bitmask |= uint64_t(BCCode::A) << (p * 3); + } + else if(b == invalid) + { + // B hasn't been found yet, let's use this pixel for that + b = blockPixels[p]; + bc1bitmask |= uint32_t(BCCode::B) << (p * 2); + bc4bitmask |= uint64_t(BCCode::B) << (p * 3); + } + else if(blockPixels[p] == b) + { + bc1bitmask |= uint32_t(BCCode::B) << (p * 2); + bc4bitmask |= uint64_t(BCCode::B) << (p * 3); + } + else + { + TEST_ERROR("Found pixel that isn't A, or B!"); + } + } + + byte a8[4], b8[4]; + uint16_t aHalf[4], bHalf[4]; + int16_t *aHalfS = (int16_t *)aHalf; + int16_t *bHalfS = (int16_t *)bHalf; + + uint16_t a565 = 0; + uint16_t b565 = 0; + + if(cfg.data == DataType::SNorm) + { + int8_t *ia8 = (int8_t *)a8; + int8_t *ib8 = (int8_t *)b8; + + ia8[0] = int8_t(round(a.x * -127.0f)); + ia8[1] = int8_t(round(a.y * -127.0f)); + ia8[2] = int8_t(round(a.z * -127.0f)); + ia8[3] = int8_t(round(a.w * -127.0f)); + + ib8[0] = int8_t(round(b.x * -127.0f)); + ib8[1] = int8_t(round(b.y * -127.0f)); + ib8[2] = int8_t(round(b.z * -127.0f)); + ib8[3] = int8_t(round(b.w * -127.0f)); + + aHalf[0] = MakeHalf(-a.x); + aHalf[1] = MakeHalf(-a.y); + aHalf[2] = MakeHalf(-a.z); + aHalf[3] = MakeHalf(-a.w); + + bHalf[0] = MakeHalf(-b.x); + bHalf[1] = MakeHalf(-b.y); + bHalf[2] = MakeHalf(-b.z); + bHalf[3] = MakeHalf(-b.w); + } + else + { + a8[0] = byte(round(a.x * 255.0f)); + a8[1] = byte(round(a.y * 255.0f)); + a8[2] = byte(round(a.z * 255.0f)); + a8[3] = byte(round(a.w * 255.0f)); + + // red + a565 |= byte(round(a.x * 31.0f)) << 11; + // green + a565 |= byte(round(a.y * 63.0f)) << 5; + // blue + a565 |= byte(round(a.z * 31.0f)) << 0; + + b8[0] = byte(round(b.x * 255.0f)); + b8[1] = byte(round(b.y * 255.0f)); + b8[2] = byte(round(b.z * 255.0f)); + b8[3] = byte(round(b.w * 255.0f)); + + // red + b565 |= byte(round(b.x * 31.0f)) << 11; + // green + b565 |= byte(round(b.y * 63.0f)) << 5; + // blue + b565 |= byte(round(b.z * 31.0f)) << 0; + + aHalf[0] = MakeHalf(a.x); + aHalf[1] = MakeHalf(a.y); + aHalf[2] = MakeHalf(a.z); + aHalf[3] = MakeHalf(a.w); + + bHalf[0] = MakeHalf(b.x); + bHalf[1] = MakeHalf(b.y); + bHalf[2] = MakeHalf(b.z); + bHalf[3] = MakeHalf(b.w); + } + + struct BC1 + { + uint16_t a565; + uint16_t b565; + uint32_t bitmask; + }; + + static_assert(sizeof(BC1) == 8, "BC1 struct is mis-sized"); + + struct BC4 + { + uint64_t a : 8; + uint64_t b : 8; + uint64_t bitmask : 48; + }; + + static_assert(sizeof(BC4) == 8, "BC4 struct is mis-sized"); + + if(bc2alpha) + { + uint64_t alphaBits = 0; + + for(uint32_t p = 0; p < blockWidth * blockHeight; p++) + { + BCCode code = BCCode((bc1bitmask & (0x3 << (p * 2))) >> (p * 2)); + if(code == BCCode::A) + alphaBits |= uint64_t(a8[3] >> 4) << (p * 4); + else if(code == BCCode::B) + alphaBits |= uint64_t(b8[3] >> 4) << (p * 4); + } + + memcpy(out, &alphaBits, sizeof(alphaBits)); + out += sizeof(alphaBits); + } + else if(bc3alpha) + { + // basically the same layout just a different meaning for codes above 1, which + // we + // don't use + BC4 *alpha = (BC4 *)out; + alpha->a = a8[3]; + alpha->b = b8[3]; + alpha->bitmask = bc4bitmask; + out += sizeof(BC4); + } + + if(bc1) + { + BC1 *rgb = (BC1 *)out; + // we don't care about color0 <= color1 order + rgb->a565 = a565; + rgb->b565 = b565; + rgb->bitmask = bc1bitmask; + out += sizeof(BC1); + } + + for(int ch = 0; ch < bc4channels; ch++) + { + BC4 *alpha = (BC4 *)out; + alpha->a = a8[ch]; + alpha->b = b8[ch]; + alpha->bitmask = bc4bitmask; + out += sizeof(BC4); + } + + uint64_t bc67indexbits = 0; + + if(bc6 || bc7) + { + for(uint32_t p = 0; p < blockWidth * blockHeight; p++) + { + BCCode code = BCCode((bc1bitmask & (0x3 << (p * 2))) >> (p * 2)); + + if(p == 0) + { + // the first colour we came across should have been assigned code A. We + // require this, because we're missing a bit from the first index + TEST_ASSERT(code == BCCode::A, "First code must be code A when encoding BC6"); + } + else + { + if(code == BCCode::A) + { + bc67indexbits |= uint64_t(0) << ((p * 4) - 1); + } + else if(code == BCCode::B) + { + bc67indexbits |= uint64_t(15) << ((p * 4) - 1); + } + } + } + } + + if(bc6) + { + byte mode = 0x03; + // mode 3: no transformed endpoints, 0 partition bits, 10 endpoint bits per + // channel, no delta bits. + + uint16_t bias = 0; + + if(cfg.data == DataType::SNorm) + { + // final quantize step, the absolute value gets scaled a little + for(int ch = 0; ch < 3; ch++) + { + bool negA = (aHalf[ch] & 0x8000) != 0; + bool negB = (bHalf[ch] & 0x8000) != 0; + + int16_t valA = int16_t(((aHalf[ch] & 0x7fff) * 32) / 31); + int16_t valB = int16_t(((bHalf[ch] & 0x7fff) * 32) / 31); + + aHalfS[ch] = (negA ? -valA : valA); + bHalfS[ch] = (negB ? -valB : valB); + } + + bias = 63; + } + else + { + // final quantize step, such that max representable half float is 65504.0 + // (which gets mapped to 0xffff) + for(int ch = 0; ch < 3; ch++) + { + aHalf[ch] = uint32_t(aHalf[ch] * 64) / 31; + bHalf[ch] = uint32_t(bHalf[ch] * 64) / 31; + } + + bias = 15; + } + + uint64_t colorbits = 0; + + byte colorbit65 = 0; + + // 10 bits for each value, RGB for A then RGB for B + colorbits |= uint64_t((aHalf[0] + bias) >> 6) << 0; + colorbits |= uint64_t((aHalf[1] + bias) >> 6) << 10; + colorbits |= uint64_t((aHalf[2] + bias) >> 6) << 20; + + colorbits |= uint64_t((bHalf[0] + bias) >> 6) << 30; + colorbits |= uint64_t((bHalf[1] + bias) >> 6) << 40; + colorbits |= uint64_t((bHalf[2] + bias) >> 6) << 50; // overflows by 1 bit + + colorbit65 = (bHalf[2] >> 15) & 0x1; + + uint64_t block[2]; + + // first 64 bits are mode, and 59 of the color bits. + block[0] = mode << 0; + block[0] |= colorbits << 5; + + // second 64-bit is the top bit of the colors bits, then the index bits + block[1] = (bc67indexbits << 1) | colorbit65; + + memcpy(out, block, sizeof(block)); + out += sizeof(block); + } + +#define ROUND_7BIT(x) ((x) >> 1) +#define LO_BIT(x) ((x)&0x1) + + if(bc7) + { + byte mode = 0x40; + // x1000000 = mode 6: no partition bits, no rotation bits, no index selection + // bit. + // 7 color bits, 7 alpha bits, 1 endpoint p-bit, 0 shared p-bits, 4 index bits, + // 0 secondary index bits + + // color is stored R0, R1, G0, G1, B0, B1 because we only have one subset + uint64_t colorbits = 0; + colorbits |= uint64_t(ROUND_7BIT(a8[0])) << 0; + colorbits |= uint64_t(ROUND_7BIT(b8[0])) << 7; + colorbits |= uint64_t(ROUND_7BIT(a8[1])) << 14; + colorbits |= uint64_t(ROUND_7BIT(b8[1])) << 21; + colorbits |= uint64_t(ROUND_7BIT(a8[2])) << 28; + colorbits |= uint64_t(ROUND_7BIT(b8[2])) << 35; + + uint64_t alphabits = 0; + alphabits |= uint64_t(ROUND_7BIT(a8[3])) << 0; + alphabits |= uint64_t(ROUND_7BIT(b8[3])) << 7; + + byte endpointA = 0; + byte endpointB = 0; + // take a vote, if more than two of the original values have the low bit set, + // set + // the endpoint. The tie-break is towards zero because we're wanting *more* than + // two (so exactly two means 0) + + if(LO_BIT(a8[0]) + LO_BIT(a8[1]) + LO_BIT(a8[2]) + LO_BIT(a8[3]) > 2) + endpointA = 1; + + if(LO_BIT(b8[0]) + LO_BIT(b8[1]) + LO_BIT(b8[2]) + LO_BIT(b8[3]) > 2) + endpointB = 1; + + uint64_t block[2]; + + // first 64 bits are mode, color, alpha, and endpoint A + block[0] = mode << 0; + block[0] |= colorbits << 7; + block[0] |= alphabits << (7 + 42); + block[0] |= uint64_t(endpointA & 0x1) << (7 + 42 + 14); + + // second 64-bit is endpoint B, then the index bits + block[1] = (bc67indexbits << 1) | endpointB; + + memcpy(out, block, sizeof(block)); + out += sizeof(block); + } + } + } + + srcPixels += floatdata.slicePitch / sizeof(Vec4f); + } + } + } +} + +}; // namespace TextureZoo \ No newline at end of file diff --git a/util/test/demos/vk/vk_texture_zoo.cpp b/util/test/demos/vk/vk_texture_zoo.cpp new file mode 100644 index 000000000..c4042339c --- /dev/null +++ b/util/test/demos/vk/vk_texture_zoo.cpp @@ -0,0 +1,1464 @@ +/****************************************************************************** + * The MIT License (MIT) + * + * Copyright (c) 2018-2019 Baldur Karlsson + * + * 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. + ******************************************************************************/ + +#include "vk_test.h" + +using namespace TextureZoo; + +RD_TEST(VK_Texture_Zoo, VulkanGraphicsTest) +{ + static constexpr const char *Description = + "Tests all possible combinations of texture type and format that are supported."; + + std::string blitVertex = R"EOSHADER( +#version 420 core + +void main() +{ + const vec4 verts[4] = vec4[4](vec4(-1.0, -1.0, 0.5, 1.0), vec4(3.0, -1.0, 0.5, 1.0), + vec4(-1.0, 3.0, 0.5, 1.0), vec4(1.0, 1.0, 0.5, 1.0)); + + gl_Position = verts[gl_VertexIndex]; +} + +)EOSHADER"; + + std::string pixelTemplate = R"EOSHADER( +#version 420 core + +layout(binding = 0) uniform &texdecl intex; + +layout(location = 0, index = 0) out vec4 Color; + +void main() +{ + Color = vec4(texelFetch(intex, ¶ms)); +} +)EOSHADER"; + + std::string pixelMSFloat = R"EOSHADER( +#version 420 core + +layout(push_constant) uniform PushData { + uint slice; + uint mip; + uint flags; + uint zlayer; +} push; + +float srgb2linear(float f) +{ + if (f <= 0.04045f) + return f / 12.92f; + else + return pow((f + 0.055f) / 1.055f, 2.4f); +} + +layout(location = 0, index = 0) out vec4 Color; + +void main() +{ + uint x = uint(gl_FragCoord.x); + uint y = uint(gl_FragCoord.y); + + vec4 ret = vec4(0.1f, 0.35f, 0.6f, 0.85f); + + // each 3D slice cycles the x. This only affects the primary diagonal + uint offs_x = (x + push.zlayer) % max(1u, TEX_WIDTH >> push.mip); + + // pixels off the diagonal invert the colors + if(offs_x != y) + ret = ret.wzyx; + + // second slice adds a coarse checkerboard pattern of inversion + if(push.slice > 0 && (((x / 2) % 2) != ((y / 2) % 2))) + ret = ret.wzyx; + + // second sample/mip is shifted up a bit. MSAA textures have no mips, + // textures with mips have no samples. + ret += 0.075f.xxxx * (gl_SampleID + push.mip); + + // Signed normals are negative + if((push.flags & 1) != 0) + ret = -ret; + + // undo SRGB curve applied in output merger, to match the textures we just blat values into + // without conversion (which are then interpreted as srgb implicitly) + if((push.flags & 2) != 0) + { + ret.r = srgb2linear(ret.r); + ret.g = srgb2linear(ret.g); + ret.b = srgb2linear(ret.b); + } + + // BGR flip - same as above, for BGRA textures + if((push.flags & 4) != 0) + ret.rgb = ret.bgr; + + // put red into alpha, because that's what we did in manual upload + if((push.flags & 8) != 0) + ret.a = ret.r; + + Color = ret; +} + +)EOSHADER"; + + std::string pixelMSDepth = R"EOSHADER( +#version 420 core + +layout(push_constant) uniform PushData { + uint slice; + uint mip; + uint flags; + uint zlayer; +} push; + +void main() +{ + uint x = uint(gl_FragCoord.x); + uint y = uint(gl_FragCoord.y); + + float ret = 0.1f; + + // each 3D slice cycles the x. This only affects the primary diagonal + uint offs_x = (x + push.zlayer) % max(1u, TEX_WIDTH >> push.mip); + + // pixels off the diagonal invert the colors + // second slice adds a coarse checkerboard pattern of inversion + if((offs_x != y) != (push.slice > 0 && (((x / 2) % 2) != ((y / 2) % 2)))) + { + ret = 0.85f; + + // so we can fill stencil data, clip off the inverted values + if(push.flags == 1) + discard; + } + + // second sample/mip is shifted up a bit. MSAA textures have no mips, + // textures with mips have no samples. + ret += 0.075f * (gl_SampleID + push.mip); + + gl_FragDepth = ret; +} + +)EOSHADER"; + + std::string pixelMSUInt = R"EOSHADER( +#version 420 core + +layout(push_constant) uniform PushData { + uint slice; + uint mip; + uint flags; + uint zlayer; +} push; + +layout(location = 0, index = 0) out uvec4 Color; + +void main() +{ + uint x = uint(gl_FragCoord.x); + uint y = uint(gl_FragCoord.y); + + uvec4 ret = uvec4(10, 40, 70, 100); + + // each 3D slice cycles the x. This only affects the primary diagonal + uint offs_x = (x + push.zlayer) % max(1u, TEX_WIDTH >> push.mip); + + // pixels off the diagonal invert the colors + if(offs_x != y) + ret = ret.wzyx; + + // second slice adds a coarse checkerboard pattern of inversion + if(push.slice > 0 && (((x / 2) % 2) != ((y / 2) % 2))) + ret = ret.wzyx; + + // second sample/mip is shifted up a bit. MSAA textures have no mips, + // textures with mips have no samples. + ret += uvec4(10, 10, 10, 10) * (gl_SampleID + push.mip); + + Color = ret; +} + +)EOSHADER"; + + std::string pixelMSSInt = R"EOSHADER( +#version 420 core + +layout(push_constant) uniform PushData { + uint slice; + uint mip; + uint flags; + uint zlayer; +} push; + +layout(location = 0, index = 0) out ivec4 Color; + +void main() +{ + uint x = uint(gl_FragCoord.x); + uint y = uint(gl_FragCoord.y); + + ivec4 ret = ivec4(10, 40, 70, 100); + + // each 3D slice cycles the x. This only affects the primary diagonal + uint offs_x = (x + push.zlayer) % max(1u, TEX_WIDTH >> push.mip); + + // pixels off the diagonal invert the colors + if(offs_x != y) + ret = ret.wzyx; + + // second slice adds a coarse checkerboard pattern of inversion + if(push.slice > 0 && (((x / 2) % 2) != ((y / 2) % 2))) + ret = ret.wzyx; + + // second sample/mip is shifted up a bit. MSAA textures have no mips, + // textures with mips have no samples. + ret += ivec4(10 * (gl_SampleID + push.mip)); + + Color = -ret; +} + +)EOSHADER"; + + struct VKFormat + { + const std::string name; + VkFormat texFmt; + VkFormat viewFmt; + TexConfig cfg; + }; + + struct TestCase + { + VKFormat fmt; + uint32_t dim; + bool isArray; + bool canRender; + bool isDepth; + bool isMSAA; + bool hasData; + AllocatedImage res; + VkImageView view; + VkDescriptorSet set; + VkImageViewType viewType; + }; + + std::string MakeName(const TestCase &test) + { + std::string name = "Texture " + std::to_string(test.dim) + "D"; + + if(test.isMSAA) + name += " MSAA"; + if(test.isArray) + name += " Array"; + + return name; + } + + VkPipelineLayout layout; + VkPipelineShaderStageCreateInfo vs; + VkRenderPass rp; + + VkPipeline GetPSO(const TestCase &test) + { + static std::map PSOs; + + uint32_t key = uint32_t(test.fmt.cfg.data); + key |= test.dim << 6; + key |= test.isMSAA ? 0x80000 : 0; + key |= test.isArray ? 0x100000 : 0; + + VkPipeline ret = PSOs[key]; + if(!ret) + { + std::string texType = "sampler" + std::to_string(test.dim) + "D"; + if(test.isMSAA) + texType += "MS"; + if(test.dim < 3 && test.isArray) + texType += "Array"; + + std::string typemod = ""; + + if(test.fmt.cfg.data == DataType::UInt) + typemod = "u"; + else if(test.fmt.cfg.data == DataType::SInt) + typemod = "i"; + + std::string src = pixelTemplate; + + uint32_t dim = test.dim + (test.isArray ? 1 : 0); + + if(dim == 1) + src.replace(src.find("¶ms"), 7, "int(0), 0"); + else if(dim == 2) + src.replace(src.find("¶ms"), 7, "ivec2(0), 0"); + else if(dim == 3) + src.replace(src.find("¶ms"), 7, "ivec3(0), 0"); + + src.replace(src.find("&texdecl"), 8, typemod + texType); + + vkh::GraphicsPipelineCreateInfo pipeCreateInfo; + + pipeCreateInfo.inputAssemblyState.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP; + + pipeCreateInfo.layout = layout; + pipeCreateInfo.renderPass = rp; + + pipeCreateInfo.stages = { + vs, CompileShaderModule(src, ShaderLang::glsl, ShaderStage::frag, "main"), + }; + + ret = PSOs[key] = createGraphicsPipeline(pipeCreateInfo); + } + + return ret; + } + + VkDeviceSize CurOffset = 0; + AllocatedBuffer uploadBuf; + byte *CurBuf = NULL; + + bool SetData(VkCommandBuffer cmd, const TestCase &test) + { + uint32_t slices = test.isArray ? texSlices : 1; + uint32_t mips = test.isMSAA ? 1 : texMips; + + Vec4i dim(texWidth, texHeight, texDepth); + + if(test.dim < 3) + dim.z = 1; + if(test.dim < 2) + dim.y = 1; + + TexData data; + + for(uint32_t s = 0; s < slices; s++) + { + for(uint32_t m = 0; m < mips; m++) + { + MakeData(data, test.fmt.cfg, dim, m, s); + + if(data.byteData.empty()) + return false; + + if(s == 0 && m == 0) + { + vkh::cmdPipelineBarrier( + cmd, + { + vkh::ImageMemoryBarrier(0, VK_ACCESS_TRANSFER_WRITE_BIT, VK_IMAGE_LAYOUT_UNDEFINED, + VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, test.res.image), + }); + } + + VkBufferImageCopy copy = {}; + copy.bufferOffset = CurOffset; + copy.bufferRowLength = 0; + copy.bufferImageHeight = 0; + copy.imageExtent.width = std::max(1, dim.x >> m); + copy.imageExtent.height = std::max(1, dim.y >> m); + copy.imageExtent.depth = std::max(1, dim.z >> m); + copy.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + copy.imageSubresource.baseArrayLayer = s; + copy.imageSubresource.layerCount = 1; + copy.imageSubresource.mipLevel = m; + + memcpy(CurBuf + CurOffset, data.byteData.data(), data.byteData.size()); + + CurOffset += data.byteData.size(); + + CurOffset = AlignUp(CurOffset, (VkDeviceSize)256); + + vkCmdCopyBufferToImage(cmd, uploadBuf.buffer, test.res.image, + VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, ©); + } + } + + vkh::cmdPipelineBarrier( + cmd, { + vkh::ImageMemoryBarrier(VK_ACCESS_TRANSFER_WRITE_BIT, VK_ACCESS_SHADER_READ_BIT, + VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, test.res.image), + }); + + return true; + } + + VkDescriptorSetLayout setlayout; + + TestCase FinaliseTest(VkCommandBuffer cmd, TestCase test) + { + VkImageCreateFlags flags = + (test.fmt.texFmt != test.fmt.viewFmt) ? VK_IMAGE_CREATE_MUTABLE_FORMAT_BIT : 0; + + VkImageUsageFlags usage = VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT; + + if(test.isDepth) + usage |= VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT; + else if(test.canRender) + usage |= VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT; + + uint32_t w = texWidth, h = texHeight, d = texDepth; + if(test.dim < 3) + d = 0; + if(test.dim < 2) + h = 0; + + if(test.dim == 1) + { + h = d = 0; + test.viewType = test.isArray ? VK_IMAGE_VIEW_TYPE_1D_ARRAY : VK_IMAGE_VIEW_TYPE_1D; + } + else if(test.dim == 2) + { + d = 0; + test.viewType = test.isArray ? VK_IMAGE_VIEW_TYPE_2D_ARRAY : VK_IMAGE_VIEW_TYPE_2D; + } + else if(test.dim == 3) + { + // need this so we can render to slices + flags |= VK_IMAGE_CREATE_2D_ARRAY_COMPATIBLE_BIT; + test.viewType = VK_IMAGE_VIEW_TYPE_3D; + } + + VkImageAspectFlags viewAspect = VK_IMAGE_ASPECT_COLOR_BIT; + + if(test.isDepth) + { + if(test.fmt.cfg.data == DataType::UInt) + viewAspect = VK_IMAGE_ASPECT_STENCIL_BIT; + else + viewAspect = VK_IMAGE_ASPECT_DEPTH_BIT; + } + + test.res = AllocatedImage( + this, + vkh::ImageCreateInfo( + w, h, d, test.fmt.texFmt, usage, test.isMSAA ? 1 : texMips, test.isArray ? texSlices : 1, + test.isMSAA ? VkSampleCountFlagBits(texSamples) : VK_SAMPLE_COUNT_1_BIT, flags), + VmaAllocationCreateInfo({0, VMA_MEMORY_USAGE_GPU_ONLY})); + test.view = createImageView(vkh::ImageViewCreateInfo( + test.res.image, test.viewType, test.fmt.viewFmt, {}, vkh::ImageSubresourceRange(viewAspect))); + test.set = allocateDescriptorSet(setlayout); + vkh::updateDescriptorSets( + device, { + vkh::WriteDescriptorSet(test.set, 0, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, + {vkh::DescriptorImageInfo(test.view)}), + }); + + setName(test.res.image, MakeName(test) + " " + test.fmt.name); + + if(!test.isMSAA) + { + pushMarker(cmd, "Set data for " + test.fmt.name + " " + MakeName(test)); + + test.hasData = SetData(cmd, test); + + popMarker(cmd); + } + + return test; + } + + void AddSupportedTests(const VKFormat &f, std::vector &test_textures, bool depthMode) + { + VkCommandBuffer cmd = GetCommandBuffer(); + + vkBeginCommandBuffer(cmd, vkh::CommandBufferBeginInfo()); + + CurOffset = 0; + + VkFormatProperties props = {}, props2 = {}; + vkGetPhysicalDeviceFormatProperties(phys, f.texFmt, &props); + vkGetPhysicalDeviceFormatProperties(phys, f.viewFmt, &props2); + + // only check what is supported by both formats + props.optimalTilingFeatures &= props2.optimalTilingFeatures; + + bool viewCast = (f.texFmt != f.viewFmt); + + bool renderable = (props.optimalTilingFeatures & VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT) != 0; + bool depth = (props.optimalTilingFeatures & VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT) != 0; + + VkImageUsageFlags usage = VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT; + + if(renderable) + usage |= VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT; + if(depth) + usage |= VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT; + + VkImageFormatProperties props1D = {}, props2D = {}, props3D = {}; + VkResult vkr = VK_SUCCESS; + vkr = vkGetPhysicalDeviceImageFormatProperties( + phys, f.viewFmt, VK_IMAGE_TYPE_1D, VK_IMAGE_TILING_OPTIMAL, usage, + viewCast ? VK_IMAGE_CREATE_MUTABLE_FORMAT_BIT : 0, &props1D); + if(vkr != VK_SUCCESS) + props1D = {}; + vkr = vkGetPhysicalDeviceImageFormatProperties( + phys, f.viewFmt, VK_IMAGE_TYPE_2D, VK_IMAGE_TILING_OPTIMAL, usage, + viewCast ? VK_IMAGE_CREATE_MUTABLE_FORMAT_BIT : 0, &props2D); + if(vkr != VK_SUCCESS) + props2D = {}; + vkr = vkGetPhysicalDeviceImageFormatProperties( + phys, f.viewFmt, VK_IMAGE_TYPE_3D, VK_IMAGE_TILING_OPTIMAL, usage, + VK_IMAGE_CREATE_2D_ARRAY_COMPATIBLE_BIT | (viewCast ? VK_IMAGE_CREATE_MUTABLE_FORMAT_BIT : 0), + &props3D); + if(vkr != VK_SUCCESS) + props3D = {}; + + // rendering to depth 3D textures is broken on NV, fixed in a future driver version (guess made, + // will be updated once fix ships) + if(depth && physProperties.vendorID == PCI_VENDOR_NV && + physProperties.driverVersion < VK_MAKE_VERSION_NV(445, 0, 0, 0)) + props3D = {}; + + if(!renderable && !depth) + props2D.sampleCounts = VK_SAMPLE_COUNT_1_BIT; + + if((props.optimalTilingFeatures & VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT) || depth) + { + // TODO: disable 1D depth textures for now, we don't support displaying them + if(!depthMode) + { + if(props1D.maxExtent.width >= texWidth) + { + test_textures.push_back(FinaliseTest(cmd, {f, 1, false, renderable, depth})); + test_textures.push_back(FinaliseTest(cmd, {f, 1, true, renderable, depth})); + } + else + { + test_textures.push_back({f, 1, false}); + test_textures.push_back({f, 1, true}); + } + } + + if(props2D.maxExtent.width >= texWidth) + { + test_textures.push_back(FinaliseTest(cmd, {f, 2, false, renderable, depth})); + test_textures.push_back(FinaliseTest(cmd, {f, 2, true, renderable, depth})); + } + else + { + test_textures.push_back({f, 2, false}); + test_textures.push_back({f, 2, true}); + } + + if(props3D.maxExtent.width >= texWidth) + { + test_textures.push_back(FinaliseTest(cmd, {f, 3, false, renderable, depth})); + } + else + { + test_textures.push_back({f, 3, false}); + } + + // TODO: we don't support MSAA<->Array copies for these odd sized pixels, and I suspect + // drivers don't tend to support the formats anyway. Disable for now + if((f.cfg.type != TextureType::Regular || f.cfg.componentCount != 3) && + props2D.sampleCounts & texSamples) + { + test_textures.push_back(FinaliseTest(cmd, {f, 2, false, true, depth, true})); + test_textures.push_back(FinaliseTest(cmd, {f, 2, true, true, depth, true})); + } + else + { + test_textures.push_back({f, 2, false, true, depth, true}); + test_textures.push_back({f, 2, true, true, depth, true}); + } + } + else + { + test_textures.push_back({f, 2, false}); + + if(props1D.maxExtent.width >= texWidth || props2D.maxExtent.width >= texWidth || + props3D.maxExtent.width >= texWidth) + { + TEST_ERROR("Format %d can't be loaded in shader but can be a texture!", f.texFmt); + } + } + + vkEndCommandBuffer(cmd); + + Submit(99, 99, {cmd}); + vkDeviceWaitIdle(device); + } + + void Prepare(int argc, char **argv) + { + devExts.push_back(VK_KHR_MAINTENANCE1_EXTENSION_NAME); + + features.sampleRateShading = true; + + VulkanGraphicsTest::Prepare(argc, argv); + } + + int main() + { + // initialise, create window, create context, etc + if(!Init()) + return 3; + + VkSampler sampler = VK_NULL_HANDLE; + + VkSamplerCreateInfo sampInfo = {VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO}; + sampInfo.magFilter = VK_FILTER_LINEAR; + sampInfo.minFilter = VK_FILTER_LINEAR; + + vkCreateSampler(device, &sampInfo, NULL, &sampler); + + setlayout = createDescriptorSetLayout(vkh::DescriptorSetLayoutCreateInfo({ + {0, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1, VK_SHADER_STAGE_FRAGMENT_BIT, &sampler}, + })); + + layout = createPipelineLayout(vkh::PipelineLayoutCreateInfo( + {setlayout}, {vkh::PushConstantRange(VK_SHADER_STAGE_ALL, 0, 16)})); + + vs = CompileShaderModule(blitVertex, ShaderLang::glsl, ShaderStage::vert, "main"); + + AllocatedImage fltTex( + this, + vkh::ImageCreateInfo(mainWindow->scissor.extent.width, mainWindow->scissor.extent.height, 0, + VK_FORMAT_R32G32B32A32_SFLOAT, + VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT), + VmaAllocationCreateInfo({0, VMA_MEMORY_USAGE_GPU_ONLY})); + + VkImageView fltView = createImageView(vkh::ImageViewCreateInfo( + fltTex.image, VK_IMAGE_VIEW_TYPE_2D, VK_FORMAT_R32G32B32A32_SFLOAT)); + + vkh::RenderPassCreator renderPassCreateInfo; + + renderPassCreateInfo.attachments.push_back(vkh::AttachmentDescription( + VK_FORMAT_R32G32B32A32_SFLOAT, VK_IMAGE_LAYOUT_UNDEFINED, + VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, VK_ATTACHMENT_LOAD_OP_CLEAR)); + + renderPassCreateInfo.addSubpass( + {VkAttachmentReference({0, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL})}); + + renderPassCreateInfo.dependencies.push_back(vkh::SubpassDependency( + 0, VK_SUBPASS_EXTERNAL, VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, + VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT, + VK_ACCESS_SHADER_READ_BIT)); + + rp = createRenderPass(renderPassCreateInfo); + + VkFramebuffer framebuffer = + createFramebuffer(vkh::FramebufferCreateInfo(rp, {fltView}, mainWindow->scissor.extent)); + + uploadBuf = + AllocatedBuffer(this, vkh::BufferCreateInfo(1024 * 1024, VK_BUFFER_USAGE_TRANSFER_SRC_BIT), + VmaAllocationCreateInfo({0, VMA_MEMORY_USAGE_CPU_TO_GPU})); + + CurBuf = uploadBuf.map(); + +#define TEST_CASE_NAME(texFmt, viewFmt) \ + (texFmt == viewFmt) ? std::string(#texFmt + 10) \ + : (std::string(#texFmt + 10) + "->" + (strchr(#viewFmt + 10, '_') + 1)) + +#define TEST_CASE(texType, texFmt, viewFmt, compCount, byteWidth, dataType) \ + { \ + TEST_CASE_NAME(texFmt, viewFmt), texFmt, viewFmt, {texType, compCount, byteWidth, dataType}, \ + } + + std::vector test_textures; + + const VKFormat color_tests[] = { + TEST_CASE(TextureType::Regular, VK_FORMAT_R32G32B32A32_UINT, VK_FORMAT_R32G32B32A32_SFLOAT, + 4, 4, DataType::Float), + TEST_CASE(TextureType::Regular, VK_FORMAT_R32G32B32A32_SFLOAT, VK_FORMAT_R32G32B32A32_UINT, + 4, 4, DataType::UInt), + TEST_CASE(TextureType::Regular, VK_FORMAT_R32G32B32A32_SFLOAT, + VK_FORMAT_R32G32B32A32_SFLOAT, 4, 4, DataType::Float), + TEST_CASE(TextureType::Regular, VK_FORMAT_R32G32B32A32_UINT, VK_FORMAT_R32G32B32A32_UINT, 4, + 4, DataType::UInt), + TEST_CASE(TextureType::Regular, VK_FORMAT_R32G32B32A32_SINT, VK_FORMAT_R32G32B32A32_SINT, 4, + 4, DataType::SInt), + + TEST_CASE(TextureType::Regular, VK_FORMAT_R32G32B32_UINT, VK_FORMAT_R32G32B32_SFLOAT, 3, 4, + DataType::Float), + TEST_CASE(TextureType::Regular, VK_FORMAT_R32G32B32_SFLOAT, VK_FORMAT_R32G32B32_UINT, 3, 4, + DataType::UInt), + TEST_CASE(TextureType::Regular, VK_FORMAT_R32G32B32_SFLOAT, VK_FORMAT_R32G32B32_SFLOAT, 3, + 4, DataType::Float), + TEST_CASE(TextureType::Regular, VK_FORMAT_R32G32B32_UINT, VK_FORMAT_R32G32B32_UINT, 3, 4, + DataType::UInt), + TEST_CASE(TextureType::Regular, VK_FORMAT_R32G32B32_SINT, VK_FORMAT_R32G32B32_SINT, 3, 4, + DataType::SInt), + + TEST_CASE(TextureType::Regular, VK_FORMAT_R32G32_UINT, VK_FORMAT_R32G32_SFLOAT, 2, 4, + DataType::Float), + TEST_CASE(TextureType::Regular, VK_FORMAT_R32G32_SFLOAT, VK_FORMAT_R32G32_UINT, 2, 4, + DataType::UInt), + TEST_CASE(TextureType::Regular, VK_FORMAT_R32G32_SFLOAT, VK_FORMAT_R32G32_SFLOAT, 2, 4, + DataType::Float), + TEST_CASE(TextureType::Regular, VK_FORMAT_R32G32_UINT, VK_FORMAT_R32G32_UINT, 2, 4, + DataType::UInt), + TEST_CASE(TextureType::Regular, VK_FORMAT_R32G32_SINT, VK_FORMAT_R32G32_SINT, 2, 4, + DataType::SInt), + + TEST_CASE(TextureType::Regular, VK_FORMAT_R32_UINT, VK_FORMAT_R32_SFLOAT, 1, 4, + DataType::Float), + TEST_CASE(TextureType::Regular, VK_FORMAT_R32_SFLOAT, VK_FORMAT_R32_UINT, 1, 4, + DataType::UInt), + TEST_CASE(TextureType::Regular, VK_FORMAT_R32_SFLOAT, VK_FORMAT_R32_SFLOAT, 1, 4, + DataType::Float), + TEST_CASE(TextureType::Regular, VK_FORMAT_R32_UINT, VK_FORMAT_R32_UINT, 1, 4, DataType::UInt), + TEST_CASE(TextureType::Regular, VK_FORMAT_R32_SINT, VK_FORMAT_R32_SINT, 1, 4, DataType::SInt), + + TEST_CASE(TextureType::Regular, VK_FORMAT_R16G16B16A16_UINT, VK_FORMAT_R16G16B16A16_SFLOAT, + 4, 2, DataType::Float), + TEST_CASE(TextureType::Regular, VK_FORMAT_R16G16B16A16_SFLOAT, VK_FORMAT_R16G16B16A16_UINT, + 4, 2, DataType::UInt), + TEST_CASE(TextureType::Regular, VK_FORMAT_R16G16B16A16_UINT, VK_FORMAT_R16G16B16A16_UNORM, + 4, 2, DataType::UNorm), + TEST_CASE(TextureType::Regular, VK_FORMAT_R16G16B16A16_SFLOAT, + VK_FORMAT_R16G16B16A16_SFLOAT, 4, 2, DataType::Float), + TEST_CASE(TextureType::Regular, VK_FORMAT_R16G16B16A16_UNORM, VK_FORMAT_R16G16B16A16_UNORM, + 4, 2, DataType::UNorm), + TEST_CASE(TextureType::Regular, VK_FORMAT_R16G16B16A16_UINT, VK_FORMAT_R16G16B16A16_UINT, 4, + 2, DataType::UInt), + TEST_CASE(TextureType::Regular, VK_FORMAT_R16G16B16A16_SNORM, VK_FORMAT_R16G16B16A16_SNORM, + 4, 2, DataType::SNorm), + TEST_CASE(TextureType::Regular, VK_FORMAT_R16G16B16A16_SINT, VK_FORMAT_R16G16B16A16_SINT, 4, + 2, DataType::SInt), + TEST_CASE(TextureType::Regular, VK_FORMAT_R16G16B16A16_USCALED, + VK_FORMAT_R16G16B16A16_USCALED, 4, 2, DataType::UInt), + TEST_CASE(TextureType::Regular, VK_FORMAT_R16G16B16A16_SSCALED, + VK_FORMAT_R16G16B16A16_SSCALED, 4, 2, DataType::SInt), + + TEST_CASE(TextureType::Regular, VK_FORMAT_R16G16B16_UINT, VK_FORMAT_R16G16B16_SFLOAT, 3, 2, + DataType::Float), + TEST_CASE(TextureType::Regular, VK_FORMAT_R16G16B16_SFLOAT, VK_FORMAT_R16G16B16_UINT, 3, 2, + DataType::UInt), + TEST_CASE(TextureType::Regular, VK_FORMAT_R16G16B16_UINT, VK_FORMAT_R16G16B16_UNORM, 3, 2, + DataType::UNorm), + TEST_CASE(TextureType::Regular, VK_FORMAT_R16G16B16_SFLOAT, VK_FORMAT_R16G16B16_SFLOAT, 3, + 2, DataType::Float), + TEST_CASE(TextureType::Regular, VK_FORMAT_R16G16B16_UNORM, VK_FORMAT_R16G16B16_UNORM, 3, 2, + DataType::UNorm), + TEST_CASE(TextureType::Regular, VK_FORMAT_R16G16B16_UINT, VK_FORMAT_R16G16B16_UINT, 3, 2, + DataType::UInt), + TEST_CASE(TextureType::Regular, VK_FORMAT_R16G16B16_SNORM, VK_FORMAT_R16G16B16_SNORM, 3, 2, + DataType::SNorm), + TEST_CASE(TextureType::Regular, VK_FORMAT_R16G16B16_SINT, VK_FORMAT_R16G16B16_SINT, 3, 2, + DataType::SInt), + TEST_CASE(TextureType::Regular, VK_FORMAT_R16G16B16_USCALED, VK_FORMAT_R16G16B16_USCALED, 3, + 2, DataType::UInt), + TEST_CASE(TextureType::Regular, VK_FORMAT_R16G16B16_SSCALED, VK_FORMAT_R16G16B16_SSCALED, 3, + 2, DataType::SInt), + + TEST_CASE(TextureType::Regular, VK_FORMAT_R16G16_UINT, VK_FORMAT_R16G16_SFLOAT, 2, 2, + DataType::Float), + TEST_CASE(TextureType::Regular, VK_FORMAT_R16G16_SFLOAT, VK_FORMAT_R16G16_UINT, 2, 2, + DataType::UInt), + TEST_CASE(TextureType::Regular, VK_FORMAT_R16G16_UINT, VK_FORMAT_R16G16_UNORM, 2, 2, + DataType::UNorm), + TEST_CASE(TextureType::Regular, VK_FORMAT_R16G16_SFLOAT, VK_FORMAT_R16G16_SFLOAT, 2, 2, + DataType::Float), + TEST_CASE(TextureType::Regular, VK_FORMAT_R16G16_UNORM, VK_FORMAT_R16G16_UNORM, 2, 2, + DataType::UNorm), + TEST_CASE(TextureType::Regular, VK_FORMAT_R16G16_UINT, VK_FORMAT_R16G16_UINT, 2, 2, + DataType::UInt), + TEST_CASE(TextureType::Regular, VK_FORMAT_R16G16_SNORM, VK_FORMAT_R16G16_SNORM, 2, 2, + DataType::SNorm), + TEST_CASE(TextureType::Regular, VK_FORMAT_R16G16_SINT, VK_FORMAT_R16G16_SINT, 2, 2, + DataType::SInt), + TEST_CASE(TextureType::Regular, VK_FORMAT_R16G16_USCALED, VK_FORMAT_R16G16_USCALED, 2, 2, + DataType::UInt), + TEST_CASE(TextureType::Regular, VK_FORMAT_R16G16_SSCALED, VK_FORMAT_R16G16_SSCALED, 2, 2, + DataType::SInt), + + TEST_CASE(TextureType::Regular, VK_FORMAT_R16_UINT, VK_FORMAT_R16_SFLOAT, 1, 2, + DataType::Float), + TEST_CASE(TextureType::Regular, VK_FORMAT_R16_SFLOAT, VK_FORMAT_R16_UINT, 1, 2, + DataType::UInt), + TEST_CASE(TextureType::Regular, VK_FORMAT_R16_UINT, VK_FORMAT_R16_UNORM, 1, 2, + DataType::UNorm), + TEST_CASE(TextureType::Regular, VK_FORMAT_R16_SFLOAT, VK_FORMAT_R16_SFLOAT, 1, 2, + DataType::Float), + TEST_CASE(TextureType::Regular, VK_FORMAT_R16_UNORM, VK_FORMAT_R16_UNORM, 1, 2, + DataType::UNorm), + TEST_CASE(TextureType::Regular, VK_FORMAT_R16_UINT, VK_FORMAT_R16_UINT, 1, 2, DataType::UInt), + TEST_CASE(TextureType::Regular, VK_FORMAT_R16_SNORM, VK_FORMAT_R16_SNORM, 1, 2, + DataType::SNorm), + TEST_CASE(TextureType::Regular, VK_FORMAT_R16_SINT, VK_FORMAT_R16_SINT, 1, 2, DataType::SInt), + TEST_CASE(TextureType::Regular, VK_FORMAT_R16_USCALED, VK_FORMAT_R16_USCALED, 1, 2, + DataType::UInt), + TEST_CASE(TextureType::Regular, VK_FORMAT_R16_SSCALED, VK_FORMAT_R16_SSCALED, 1, 2, + DataType::SInt), + + TEST_CASE(TextureType::Regular, VK_FORMAT_R8G8B8A8_UINT, VK_FORMAT_R8G8B8A8_UNORM, 4, 1, + DataType::UNorm), + TEST_CASE(TextureType::Regular, VK_FORMAT_R8G8B8A8_UNORM, VK_FORMAT_R8G8B8A8_UNORM, 4, 1, + DataType::UNorm), + TEST_CASE(TextureType::Regular, VK_FORMAT_R8G8B8A8_SRGB, VK_FORMAT_R8G8B8A8_SRGB, 4, 1, + DataType::UNorm), + TEST_CASE(TextureType::Regular, VK_FORMAT_R8G8B8A8_UINT, VK_FORMAT_R8G8B8A8_UINT, 4, 1, + DataType::UInt), + TEST_CASE(TextureType::Regular, VK_FORMAT_R8G8B8A8_SNORM, VK_FORMAT_R8G8B8A8_SNORM, 4, 1, + DataType::SNorm), + TEST_CASE(TextureType::Regular, VK_FORMAT_R8G8B8A8_SINT, VK_FORMAT_R8G8B8A8_SINT, 4, 1, + DataType::SInt), + TEST_CASE(TextureType::Regular, VK_FORMAT_R8G8B8A8_USCALED, VK_FORMAT_R8G8B8A8_USCALED, 4, + 1, DataType::UInt), + TEST_CASE(TextureType::Regular, VK_FORMAT_R8G8B8A8_SSCALED, VK_FORMAT_R8G8B8A8_SSCALED, 4, + 1, DataType::SInt), + + TEST_CASE(TextureType::Regular, VK_FORMAT_R8G8B8_UINT, VK_FORMAT_R8G8B8_UNORM, 3, 1, + DataType::UNorm), + TEST_CASE(TextureType::Regular, VK_FORMAT_R8G8B8_UNORM, VK_FORMAT_R8G8B8_UNORM, 3, 1, + DataType::UNorm), + TEST_CASE(TextureType::Regular, VK_FORMAT_R8G8B8_SRGB, VK_FORMAT_R8G8B8_SRGB, 3, 1, + DataType::UNorm), + TEST_CASE(TextureType::Regular, VK_FORMAT_R8G8B8_UINT, VK_FORMAT_R8G8B8_UINT, 3, 1, + DataType::UInt), + TEST_CASE(TextureType::Regular, VK_FORMAT_R8G8B8_SNORM, VK_FORMAT_R8G8B8_SNORM, 3, 1, + DataType::SNorm), + TEST_CASE(TextureType::Regular, VK_FORMAT_R8G8B8_SINT, VK_FORMAT_R8G8B8_SINT, 3, 1, + DataType::SInt), + TEST_CASE(TextureType::Regular, VK_FORMAT_R8G8B8_USCALED, VK_FORMAT_R8G8B8_USCALED, 3, 1, + DataType::UInt), + TEST_CASE(TextureType::Regular, VK_FORMAT_R8G8B8_SSCALED, VK_FORMAT_R8G8B8_SSCALED, 3, 1, + DataType::SInt), + + TEST_CASE(TextureType::Regular, VK_FORMAT_R8G8_UINT, VK_FORMAT_R8G8_UNORM, 2, 1, + DataType::UNorm), + TEST_CASE(TextureType::Regular, VK_FORMAT_R8G8_UNORM, VK_FORMAT_R8G8_UNORM, 2, 1, + DataType::UNorm), + TEST_CASE(TextureType::Regular, VK_FORMAT_R8G8_SRGB, VK_FORMAT_R8G8_SRGB, 2, 1, + DataType::UNorm), + TEST_CASE(TextureType::Regular, VK_FORMAT_R8G8_UINT, VK_FORMAT_R8G8_UINT, 2, 1, + DataType::UInt), + TEST_CASE(TextureType::Regular, VK_FORMAT_R8G8_SNORM, VK_FORMAT_R8G8_SNORM, 2, 1, + DataType::SNorm), + TEST_CASE(TextureType::Regular, VK_FORMAT_R8G8_SINT, VK_FORMAT_R8G8_SINT, 2, 1, + DataType::SInt), + TEST_CASE(TextureType::Regular, VK_FORMAT_R8G8_USCALED, VK_FORMAT_R8G8_USCALED, 2, 1, + DataType::UInt), + TEST_CASE(TextureType::Regular, VK_FORMAT_R8G8_SSCALED, VK_FORMAT_R8G8_SSCALED, 2, 1, + DataType::SInt), + + TEST_CASE(TextureType::Regular, VK_FORMAT_R8_UINT, VK_FORMAT_R8_UNORM, 1, 1, DataType::UNorm), + TEST_CASE(TextureType::Regular, VK_FORMAT_R8_UNORM, VK_FORMAT_R8_UNORM, 1, 1, DataType::UNorm), + TEST_CASE(TextureType::Regular, VK_FORMAT_R8_SRGB, VK_FORMAT_R8_SRGB, 1, 1, DataType::UNorm), + TEST_CASE(TextureType::Regular, VK_FORMAT_R8_UINT, VK_FORMAT_R8_UINT, 1, 1, DataType::UInt), + TEST_CASE(TextureType::Regular, VK_FORMAT_R8_SNORM, VK_FORMAT_R8_SNORM, 1, 1, DataType::SNorm), + TEST_CASE(TextureType::Regular, VK_FORMAT_R8_SINT, VK_FORMAT_R8_SINT, 1, 1, DataType::SInt), + TEST_CASE(TextureType::Regular, VK_FORMAT_R8_USCALED, VK_FORMAT_R8_USCALED, 1, 1, + DataType::UInt), + TEST_CASE(TextureType::Regular, VK_FORMAT_R8_SSCALED, VK_FORMAT_R8_SSCALED, 1, 1, + DataType::SInt), + + TEST_CASE(TextureType::Regular, VK_FORMAT_B8G8R8A8_UINT, VK_FORMAT_B8G8R8A8_UNORM, 4, 1, + DataType::UNorm), + TEST_CASE(TextureType::Regular, VK_FORMAT_B8G8R8A8_UINT, VK_FORMAT_B8G8R8A8_SRGB, 4, 1, + DataType::UNorm), + TEST_CASE(TextureType::Regular, VK_FORMAT_B8G8R8A8_UNORM, VK_FORMAT_B8G8R8A8_UNORM, 4, 1, + DataType::UNorm), + TEST_CASE(TextureType::Regular, VK_FORMAT_B8G8R8A8_SRGB, VK_FORMAT_B8G8R8A8_SRGB, 4, 1, + DataType::UNorm), + TEST_CASE(TextureType::Regular, VK_FORMAT_B8G8R8A8_SNORM, VK_FORMAT_B8G8R8A8_SNORM, 4, 1, + DataType::SNorm), + TEST_CASE(TextureType::Regular, VK_FORMAT_B8G8R8A8_UINT, VK_FORMAT_B8G8R8A8_UINT, 4, 1, + DataType::UInt), + TEST_CASE(TextureType::Regular, VK_FORMAT_B8G8R8A8_SINT, VK_FORMAT_B8G8R8A8_SINT, 4, 1, + DataType::SInt), + TEST_CASE(TextureType::Regular, VK_FORMAT_B8G8R8A8_USCALED, VK_FORMAT_B8G8R8A8_USCALED, 4, + 1, DataType::UInt), + TEST_CASE(TextureType::Regular, VK_FORMAT_B8G8R8A8_SSCALED, VK_FORMAT_B8G8R8A8_SSCALED, 4, + 1, DataType::UInt), + + TEST_CASE(TextureType::Regular, VK_FORMAT_A8B8G8R8_UINT_PACK32, + VK_FORMAT_A8B8G8R8_UNORM_PACK32, 4, 1, DataType::UNorm), + TEST_CASE(TextureType::Regular, VK_FORMAT_A8B8G8R8_UINT_PACK32, + VK_FORMAT_A8B8G8R8_SRGB_PACK32, 4, 1, DataType::UNorm), + TEST_CASE(TextureType::Regular, VK_FORMAT_A8B8G8R8_UNORM_PACK32, + VK_FORMAT_A8B8G8R8_UNORM_PACK32, 4, 1, DataType::UNorm), + TEST_CASE(TextureType::Regular, VK_FORMAT_A8B8G8R8_SRGB_PACK32, + VK_FORMAT_A8B8G8R8_SRGB_PACK32, 4, 1, DataType::UNorm), + TEST_CASE(TextureType::Regular, VK_FORMAT_A8B8G8R8_SNORM_PACK32, + VK_FORMAT_A8B8G8R8_SNORM_PACK32, 4, 1, DataType::SNorm), + TEST_CASE(TextureType::Regular, VK_FORMAT_A8B8G8R8_UINT_PACK32, + VK_FORMAT_A8B8G8R8_UINT_PACK32, 4, 1, DataType::UInt), + TEST_CASE(TextureType::Regular, VK_FORMAT_A8B8G8R8_SINT_PACK32, + VK_FORMAT_A8B8G8R8_SINT_PACK32, 4, 1, DataType::SInt), + TEST_CASE(TextureType::Regular, VK_FORMAT_A8B8G8R8_USCALED_PACK32, + VK_FORMAT_A8B8G8R8_USCALED_PACK32, 4, 1, DataType::UInt), + TEST_CASE(TextureType::Regular, VK_FORMAT_A8B8G8R8_SSCALED_PACK32, + VK_FORMAT_A8B8G8R8_SSCALED_PACK32, 4, 1, DataType::UInt), + + TEST_CASE(TextureType::BC1, VK_FORMAT_BC1_RGBA_UNORM_BLOCK, VK_FORMAT_BC1_RGBA_SRGB_BLOCK, + 0, 0, DataType::UNorm), + TEST_CASE(TextureType::BC1, VK_FORMAT_BC1_RGBA_SRGB_BLOCK, VK_FORMAT_BC1_RGBA_UNORM_BLOCK, + 0, 0, DataType::UNorm), + TEST_CASE(TextureType::BC1, VK_FORMAT_BC1_RGB_UNORM_BLOCK, VK_FORMAT_BC1_RGB_UNORM_BLOCK, 0, + 0, DataType::UNorm), + TEST_CASE(TextureType::BC1, VK_FORMAT_BC1_RGB_SRGB_BLOCK, VK_FORMAT_BC1_RGB_SRGB_BLOCK, 0, + 0, DataType::UNorm), + TEST_CASE(TextureType::BC1, VK_FORMAT_BC1_RGBA_UNORM_BLOCK, VK_FORMAT_BC1_RGBA_UNORM_BLOCK, + 0, 0, DataType::UNorm), + TEST_CASE(TextureType::BC1, VK_FORMAT_BC1_RGBA_SRGB_BLOCK, VK_FORMAT_BC1_RGBA_SRGB_BLOCK, 0, + 0, DataType::UNorm), + + TEST_CASE(TextureType::BC2, VK_FORMAT_BC2_UNORM_BLOCK, VK_FORMAT_BC2_SRGB_BLOCK, 0, 0, + DataType::UNorm), + TEST_CASE(TextureType::BC2, VK_FORMAT_BC2_SRGB_BLOCK, VK_FORMAT_BC2_UNORM_BLOCK, 0, 0, + DataType::UNorm), + TEST_CASE(TextureType::BC2, VK_FORMAT_BC2_UNORM_BLOCK, VK_FORMAT_BC2_UNORM_BLOCK, 0, 0, + DataType::UNorm), + TEST_CASE(TextureType::BC2, VK_FORMAT_BC2_SRGB_BLOCK, VK_FORMAT_BC2_SRGB_BLOCK, 0, 0, + DataType::UNorm), + + TEST_CASE(TextureType::BC3, VK_FORMAT_BC3_UNORM_BLOCK, VK_FORMAT_BC3_SRGB_BLOCK, 0, 0, + DataType::UNorm), + TEST_CASE(TextureType::BC3, VK_FORMAT_BC3_SRGB_BLOCK, VK_FORMAT_BC3_UNORM_BLOCK, 0, 0, + DataType::UNorm), + TEST_CASE(TextureType::BC3, VK_FORMAT_BC3_UNORM_BLOCK, VK_FORMAT_BC3_UNORM_BLOCK, 0, 0, + DataType::UNorm), + TEST_CASE(TextureType::BC3, VK_FORMAT_BC3_SRGB_BLOCK, VK_FORMAT_BC3_SRGB_BLOCK, 0, 0, + DataType::UNorm), + + TEST_CASE(TextureType::BC4, VK_FORMAT_BC4_SNORM_BLOCK, VK_FORMAT_BC4_UNORM_BLOCK, 0, 0, + DataType::UNorm), + TEST_CASE(TextureType::BC4, VK_FORMAT_BC4_UNORM_BLOCK, VK_FORMAT_BC4_SNORM_BLOCK, 0, 0, + DataType::SNorm), + TEST_CASE(TextureType::BC4, VK_FORMAT_BC4_UNORM_BLOCK, VK_FORMAT_BC4_UNORM_BLOCK, 0, 0, + DataType::UNorm), + TEST_CASE(TextureType::BC4, VK_FORMAT_BC4_SNORM_BLOCK, VK_FORMAT_BC4_SNORM_BLOCK, 0, 0, + DataType::SNorm), + + TEST_CASE(TextureType::BC5, VK_FORMAT_BC5_SNORM_BLOCK, VK_FORMAT_BC5_UNORM_BLOCK, 0, 0, + DataType::UNorm), + TEST_CASE(TextureType::BC5, VK_FORMAT_BC5_UNORM_BLOCK, VK_FORMAT_BC5_SNORM_BLOCK, 0, 0, + DataType::SNorm), + TEST_CASE(TextureType::BC5, VK_FORMAT_BC5_UNORM_BLOCK, VK_FORMAT_BC5_UNORM_BLOCK, 0, 0, + DataType::UNorm), + TEST_CASE(TextureType::BC5, VK_FORMAT_BC5_SNORM_BLOCK, VK_FORMAT_BC5_SNORM_BLOCK, 0, 0, + DataType::SNorm), + + TEST_CASE(TextureType::BC6, VK_FORMAT_BC6H_SFLOAT_BLOCK, VK_FORMAT_BC6H_UFLOAT_BLOCK, 0, 0, + DataType::Float), + TEST_CASE(TextureType::BC6, VK_FORMAT_BC6H_UFLOAT_BLOCK, VK_FORMAT_BC6H_SFLOAT_BLOCK, 0, 0, + DataType::SNorm), + TEST_CASE(TextureType::BC6, VK_FORMAT_BC6H_UFLOAT_BLOCK, VK_FORMAT_BC6H_UFLOAT_BLOCK, 0, 0, + DataType::Float), + TEST_CASE(TextureType::BC6, VK_FORMAT_BC6H_SFLOAT_BLOCK, VK_FORMAT_BC6H_SFLOAT_BLOCK, 0, 0, + DataType::SNorm), + + TEST_CASE(TextureType::BC7, VK_FORMAT_BC7_SRGB_BLOCK, VK_FORMAT_BC7_UNORM_BLOCK, 0, 0, + DataType::UNorm), + TEST_CASE(TextureType::BC7, VK_FORMAT_BC7_UNORM_BLOCK, VK_FORMAT_BC7_SRGB_BLOCK, 0, 0, + DataType::UNorm), + TEST_CASE(TextureType::BC7, VK_FORMAT_BC7_UNORM_BLOCK, VK_FORMAT_BC7_UNORM_BLOCK, 0, 0, + DataType::UNorm), + TEST_CASE(TextureType::BC7, VK_FORMAT_BC7_SRGB_BLOCK, VK_FORMAT_BC7_SRGB_BLOCK, 0, 0, + DataType::UNorm), + + TEST_CASE(TextureType::R9G9B9E5, VK_FORMAT_E5B9G9R9_UFLOAT_PACK32, + VK_FORMAT_E5B9G9R9_UFLOAT_PACK32, 0, 0, DataType::Float), + TEST_CASE(TextureType::G4R4, VK_FORMAT_R4G4_UNORM_PACK8, VK_FORMAT_R4G4_UNORM_PACK8, 0, 0, + DataType::UNorm), + TEST_CASE(TextureType::A4R4G4B4, VK_FORMAT_R4G4B4A4_UNORM_PACK16, + VK_FORMAT_R4G4B4A4_UNORM_PACK16, 0, 0, DataType::UNorm), + TEST_CASE(TextureType::A4R4G4B4, VK_FORMAT_B4G4R4A4_UNORM_PACK16, + VK_FORMAT_B4G4R4A4_UNORM_PACK16, 0, 0, DataType::UNorm), + TEST_CASE(TextureType::R5G6B5, VK_FORMAT_R5G6B5_UNORM_PACK16, VK_FORMAT_R5G6B5_UNORM_PACK16, + 0, 0, DataType::UNorm), + TEST_CASE(TextureType::R5G6B5, VK_FORMAT_B5G6R5_UNORM_PACK16, VK_FORMAT_B5G6R5_UNORM_PACK16, + 0, 0, DataType::UNorm), + TEST_CASE(TextureType::A1R5G5B5, VK_FORMAT_R5G5B5A1_UNORM_PACK16, + VK_FORMAT_R5G5B5A1_UNORM_PACK16, 0, 0, DataType::UNorm), + TEST_CASE(TextureType::A1R5G5B5, VK_FORMAT_B5G5R5A1_UNORM_PACK16, + VK_FORMAT_B5G5R5A1_UNORM_PACK16, 0, 0, DataType::UNorm), + TEST_CASE(TextureType::R5G5B5A1, VK_FORMAT_A1R5G5B5_UNORM_PACK16, + VK_FORMAT_A1R5G5B5_UNORM_PACK16, 0, 0, DataType::UNorm), + + TEST_CASE(TextureType::RGB10A2, VK_FORMAT_A2R10G10B10_UINT_PACK32, + VK_FORMAT_A2R10G10B10_UNORM_PACK32, 1, 4, DataType::UNorm), + TEST_CASE(TextureType::RGB10A2, VK_FORMAT_A2R10G10B10_UNORM_PACK32, + VK_FORMAT_A2R10G10B10_UNORM_PACK32, 1, 4, DataType::UNorm), + TEST_CASE(TextureType::RGB10A2, VK_FORMAT_A2R10G10B10_SNORM_PACK32, + VK_FORMAT_A2R10G10B10_SNORM_PACK32, 1, 4, DataType::SNorm), + TEST_CASE(TextureType::RGB10A2, VK_FORMAT_A2R10G10B10_USCALED_PACK32, + VK_FORMAT_A2R10G10B10_USCALED_PACK32, 1, 4, DataType::UInt), + TEST_CASE(TextureType::RGB10A2, VK_FORMAT_A2R10G10B10_SSCALED_PACK32, + VK_FORMAT_A2R10G10B10_SSCALED_PACK32, 1, 4, DataType::SInt), + TEST_CASE(TextureType::RGB10A2, VK_FORMAT_A2R10G10B10_UINT_PACK32, + VK_FORMAT_A2R10G10B10_UINT_PACK32, 1, 4, DataType::UInt), + TEST_CASE(TextureType::RGB10A2, VK_FORMAT_A2R10G10B10_SINT_PACK32, + VK_FORMAT_A2R10G10B10_SINT_PACK32, 1, 4, DataType::SInt), + + TEST_CASE(TextureType::Unknown, VK_FORMAT_A2B10G10R10_UINT_PACK32, + VK_FORMAT_A2B10G10R10_UNORM_PACK32, 1, 4, DataType::UNorm), + TEST_CASE(TextureType::Unknown, VK_FORMAT_A2B10G10R10_UNORM_PACK32, + VK_FORMAT_A2B10G10R10_UNORM_PACK32, 1, 4, DataType::UNorm), + TEST_CASE(TextureType::Unknown, VK_FORMAT_A2B10G10R10_SNORM_PACK32, + VK_FORMAT_A2B10G10R10_SNORM_PACK32, 1, 4, DataType::SNorm), + TEST_CASE(TextureType::Unknown, VK_FORMAT_A2B10G10R10_USCALED_PACK32, + VK_FORMAT_A2B10G10R10_USCALED_PACK32, 1, 4, DataType::UInt), + TEST_CASE(TextureType::Unknown, VK_FORMAT_A2B10G10R10_SSCALED_PACK32, + VK_FORMAT_A2B10G10R10_SSCALED_PACK32, 1, 4, DataType::SInt), + TEST_CASE(TextureType::Unknown, VK_FORMAT_A2B10G10R10_UINT_PACK32, + VK_FORMAT_A2B10G10R10_UINT_PACK32, 1, 4, DataType::UInt), + TEST_CASE(TextureType::Unknown, VK_FORMAT_A2B10G10R10_SINT_PACK32, + VK_FORMAT_A2B10G10R10_SINT_PACK32, 1, 4, DataType::SInt), + + TEST_CASE(TextureType::Unknown, VK_FORMAT_B10G11R11_UFLOAT_PACK32, + VK_FORMAT_B10G11R11_UFLOAT_PACK32, 0, 0, DataType::Float), + }; + + for(VKFormat f : color_tests) + AddSupportedTests(f, test_textures, false); + + // finally add the depth tests + const VKFormat depth_tests[] = { + TEST_CASE(TextureType::Unknown, VK_FORMAT_D32_SFLOAT_S8_UINT, VK_FORMAT_D32_SFLOAT_S8_UINT, + 0, 0, DataType::Float), + TEST_CASE(TextureType::Unknown, VK_FORMAT_D32_SFLOAT_S8_UINT, VK_FORMAT_D32_SFLOAT_S8_UINT, + 0, 0, DataType::UInt), + + TEST_CASE(TextureType::Unknown, VK_FORMAT_D24_UNORM_S8_UINT, VK_FORMAT_D24_UNORM_S8_UINT, 0, + 0, DataType::UNorm), + TEST_CASE(TextureType::Unknown, VK_FORMAT_D24_UNORM_S8_UINT, VK_FORMAT_D24_UNORM_S8_UINT, 0, + 0, DataType::UInt), + + TEST_CASE(TextureType::Unknown, VK_FORMAT_D16_UNORM_S8_UINT, VK_FORMAT_D16_UNORM_S8_UINT, 0, + 0, DataType::UNorm), + TEST_CASE(TextureType::Unknown, VK_FORMAT_D16_UNORM_S8_UINT, VK_FORMAT_D16_UNORM_S8_UINT, 0, + 0, DataType::UInt), + + TEST_CASE(TextureType::Unknown, VK_FORMAT_D32_SFLOAT, VK_FORMAT_D32_SFLOAT, 0, 0, + DataType::Float), + + TEST_CASE(TextureType::Unknown, VK_FORMAT_X8_D24_UNORM_PACK32, + VK_FORMAT_X8_D24_UNORM_PACK32, 0, 0, DataType::Float), + + TEST_CASE(TextureType::Unknown, VK_FORMAT_D16_UNORM, VK_FORMAT_D16_UNORM, 0, 0, + DataType::Float), + }; + + for(VKFormat f : depth_tests) + AddSupportedTests(f, test_textures, true); + + uploadBuf.unmap(); + + vkh::GraphicsPipelineCreateInfo pipeCreateInfo; + + pipeCreateInfo.inputAssemblyState.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP; + + pipeCreateInfo.layout = layout; + + pipeCreateInfo.stages = { + vs, vs, + }; + + pipeCreateInfo.depthStencilState.depthCompareOp = VK_COMPARE_OP_ALWAYS; + pipeCreateInfo.depthStencilState.front.compareOp = VK_COMPARE_OP_ALWAYS; + + renderPassCreateInfo.attachments[0].finalLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; + + VkPipelineShaderStageCreateInfo msps[(size_t)DataType::Count]; + + std::map macros; + macros["TEX_WIDTH"] = std::to_string(texWidth); + + msps[(size_t)DataType::Float] = msps[(size_t)DataType::UNorm] = msps[(size_t)DataType::SNorm] = + CompileShaderModule(pixelMSFloat, ShaderLang::glsl, ShaderStage::frag, "main", macros); + msps[(size_t)DataType::UInt] = + CompileShaderModule(pixelMSUInt, ShaderLang::glsl, ShaderStage::frag, "main", macros); + msps[(size_t)DataType::SInt] = + CompileShaderModule(pixelMSSInt, ShaderLang::glsl, ShaderStage::frag, "main", macros); + + VkPipelineShaderStageCreateInfo msdepthps = + CompileShaderModule(pixelMSDepth, ShaderLang::glsl, ShaderStage::frag, "main", macros); + + for(TestCase &t : test_textures) + { + if(t.res.image == VK_NULL_HANDLE || t.hasData) + continue; + + VkCommandBuffer cmd = GetCommandBuffer(); + + vkBeginCommandBuffer(cmd, vkh::CommandBufferBeginInfo()); + + if(!t.canRender && !t.isDepth) + { + TEST_WARN("Need data for test %s %s, but it's not a renderable/depthable format", + t.fmt.name.c_str(), MakeName(t).c_str()); + + vkh::cmdPipelineBarrier( + cmd, { + vkh::ImageMemoryBarrier(0, VK_ACCESS_SHADER_READ_BIT, VK_IMAGE_LAYOUT_UNDEFINED, + VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, t.res.image, + vkh::ImageSubresourceRange(VK_IMAGE_ASPECT_COLOR_BIT)), + }); + + vkEndCommandBuffer(cmd); + + Submit(99, 99, {cmd}); + CHECK_VKR(vkDeviceWaitIdle(device)); + + continue; + } + + pipeCreateInfo.depthStencilState.depthTestEnable = t.isDepth; + pipeCreateInfo.depthStencilState.depthWriteEnable = t.isDepth; + pipeCreateInfo.depthStencilState.stencilTestEnable = t.isDepth; + + pipeCreateInfo.multisampleState.sampleShadingEnable = t.isMSAA; + pipeCreateInfo.multisampleState.minSampleShading = t.isMSAA ? 1.0f : 0.0f; + + bool tex3d = t.dim == 3; + + uint32_t mipLevels = texMips, sampleCount = 1; + + if(t.isMSAA) + { + mipLevels = 1; + sampleCount = texSamples; + } + + pushMarker(cmd, "Render data for " + t.fmt.name + " " + MakeName(t)); + + t.hasData = true; + + bool srgb = false, bgra = false; + switch(t.fmt.viewFmt) + { + case VK_FORMAT_B4G4R4A4_UNORM_PACK16: + case VK_FORMAT_B5G6R5_UNORM_PACK16: + case VK_FORMAT_B5G5R5A1_UNORM_PACK16: + case VK_FORMAT_B8G8R8_UNORM: + case VK_FORMAT_B8G8R8_SNORM: + case VK_FORMAT_B8G8R8_USCALED: + case VK_FORMAT_B8G8R8_SSCALED: + case VK_FORMAT_B8G8R8_UINT: + case VK_FORMAT_B8G8R8_SINT: + case VK_FORMAT_B8G8R8A8_UNORM: + case VK_FORMAT_B8G8R8A8_SNORM: + case VK_FORMAT_B8G8R8A8_USCALED: + case VK_FORMAT_B8G8R8A8_SSCALED: + case VK_FORMAT_B8G8R8A8_UINT: + case VK_FORMAT_B8G8R8A8_SINT: + case VK_FORMAT_A2R10G10B10_UNORM_PACK32: + case VK_FORMAT_A2R10G10B10_SNORM_PACK32: + case VK_FORMAT_A2R10G10B10_USCALED_PACK32: + case VK_FORMAT_A2R10G10B10_SSCALED_PACK32: + case VK_FORMAT_A2R10G10B10_UINT_PACK32: + case VK_FORMAT_A2R10G10B10_SINT_PACK32: bgra = true; break; + + case VK_FORMAT_B8G8R8_SRGB: + case VK_FORMAT_B8G8R8A8_SRGB: + bgra = true; + srgb = true; + break; + + case VK_FORMAT_R8_SRGB: + case VK_FORMAT_R8G8_SRGB: + case VK_FORMAT_R8G8B8_SRGB: + case VK_FORMAT_R8G8B8A8_SRGB: + case VK_FORMAT_A8B8G8R8_SRGB_PACK32: srgb = true; break; + default: break; + } + + int flags = 0; + + if(t.fmt.cfg.data == DataType::SNorm) + flags |= 1; + if(srgb) + flags |= 2; + if(bgra) + flags |= 4; + + renderPassCreateInfo.attachments[0].format = t.fmt.viewFmt; + + VkAttachmentReference *attRef = + (VkAttachmentReference *)renderPassCreateInfo.subpasses[0].pColorAttachments; + + if(t.isDepth) + { + pipeCreateInfo.stages[1] = msdepthps; + + renderPassCreateInfo.dependencies[0].srcStageMask = + VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT; + renderPassCreateInfo.dependencies[0].srcAccessMask = + VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT; + + renderPassCreateInfo.subpasses[0].colorAttachmentCount = 0; + renderPassCreateInfo.subpasses[0].pDepthStencilAttachment = + renderPassCreateInfo.subpasses[0].pColorAttachments; + + attRef->layout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; + + pipeCreateInfo.dynamicState.dynamicStates = { + VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR, VK_DYNAMIC_STATE_STENCIL_REFERENCE, + }; + } + else + { + pipeCreateInfo.stages[1] = msps[(size_t)t.fmt.cfg.data]; + + renderPassCreateInfo.dependencies[0].srcStageMask = + VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; + renderPassCreateInfo.dependencies[0].srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT; + + renderPassCreateInfo.subpasses[0].colorAttachmentCount = 1; + renderPassCreateInfo.subpasses[0].pDepthStencilAttachment = NULL; + + attRef->layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; + + pipeCreateInfo.dynamicState.dynamicStates = { + VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR, + }; + } + + pipeCreateInfo.multisampleState.rasterizationSamples = VkSampleCountFlagBits(sampleCount); + renderPassCreateInfo.attachments[0].samples = VkSampleCountFlagBits(sampleCount); + + VkRenderPass tempRP; + CHECK_VKR(vkCreateRenderPass(device, renderPassCreateInfo, NULL, &tempRP)); + + pipeCreateInfo.renderPass = tempRP; + + VkViewport view = {0.0f, 0.0f, texWidth, texHeight, 0.0f, 1.0f}; + VkRect2D scissor = {{}, {texWidth, texHeight}}; + vkCmdSetViewport(cmd, 0, 1, &view); + vkCmdSetScissor(cmd, 0, 1, &scissor); + + // keep all PSOs/Framebuffers alive until the end of the loop where we submit the command + // buffer + std::vector tempPipes; + std::vector tempViews; + std::vector tempFBs; + + for(uint32_t mp = 0; mp < mipLevels; mp++) + { + uint32_t numSlices = 1; + if(tex3d) + numSlices = std::max(1U, texDepth >> mp); + else if(t.isArray) + numSlices = texSlices; + + scissor.extent.width = std::max(1U, texWidth >> mp); + scissor.extent.height = std::max(1U, texHeight >> mp); + + if(t.dim == 1) + scissor.extent.height = 1; + + for(uint32_t sl = 0; sl < numSlices; sl++) + { + VkImageView tempView; + VkFramebuffer tempFB; + + VkImageViewType viewType = t.viewType; + if(viewType == VK_IMAGE_VIEW_TYPE_3D) + viewType = VK_IMAGE_VIEW_TYPE_2D_ARRAY; + + VkImageAspectFlags aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + + if(t.isDepth) + { + aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT; + + if(t.fmt.viewFmt == VK_FORMAT_S8_UINT || t.fmt.viewFmt == VK_FORMAT_D16_UNORM_S8_UINT || + t.fmt.viewFmt == VK_FORMAT_D24_UNORM_S8_UINT || + t.fmt.viewFmt == VK_FORMAT_D32_SFLOAT_S8_UINT) + aspectMask |= VK_IMAGE_ASPECT_STENCIL_BIT; + } + + vkCreateImageView( + device, vkh::ImageViewCreateInfo(t.res.image, viewType, t.fmt.viewFmt, {}, + vkh::ImageSubresourceRange(aspectMask, mp, 1, sl, 1)), + NULL, &tempView); + vkCreateFramebuffer( + device, vkh::FramebufferCreateInfo(tempRP, {tempView}, scissor.extent), NULL, &tempFB); + + tempViews.push_back(tempView); + tempFBs.push_back(tempFB); + + if(t.isDepth) + { + vkCmdBeginRenderPass( + cmd, vkh::RenderPassBeginInfo(tempRP, tempFB, scissor, {vkh::ClearValue(0.0f, 0)}), + VK_SUBPASS_CONTENTS_INLINE); + + VkSampleMask sampleMask = 1; + pipeCreateInfo.multisampleState.pSampleMask = &sampleMask; + + // need to do each sample separately to let us vary the stencil value + for(uint32_t sm = 0; sm < sampleCount; sm++) + { + sampleMask = 1 << sm; + + VkPipeline pipe; + vkCreateGraphicsPipelines(device, VK_NULL_HANDLE, 1, pipeCreateInfo, NULL, &pipe); + tempPipes.push_back(pipe); + + vkCmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, pipe); + + Vec4i params(sl, mp); + vkCmdPushConstants(cmd, layout, VK_SHADER_STAGE_ALL, 0, sizeof(params), ¶ms); + + vkCmdSetStencilReference(cmd, VK_STENCIL_FACE_FRONT_AND_BACK, 100 + (mp + sm) * 10); + + vkCmdDraw(cmd, 4, 1, 0, 0); + + // clip off the diagonal + params.z = 1; + vkCmdPushConstants(cmd, layout, VK_SHADER_STAGE_ALL, 0, sizeof(params), ¶ms); + + vkCmdSetStencilReference(cmd, VK_STENCIL_FACE_FRONT_AND_BACK, 10 + (mp + sm) * 10); + + vkCmdDraw(cmd, 4, 1, 0, 0); + } + + vkCmdEndRenderPass(cmd); + } + else + { + vkCmdBeginRenderPass( + cmd, vkh::RenderPassBeginInfo(tempRP, tempFB, scissor, {vkh::ClearValue()}), + VK_SUBPASS_CONTENTS_INLINE); + + VkPipeline pipe; + vkCreateGraphicsPipelines(device, VK_NULL_HANDLE, 1, pipeCreateInfo, NULL, &pipe); + tempPipes.push_back(pipe); + + vkCmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, pipe); + + Vec4i params(tex3d ? 0 : sl, mp, flags, tex3d ? sl : 0); + vkCmdPushConstants(cmd, layout, VK_SHADER_STAGE_ALL, 0, sizeof(params), ¶ms); + + vkCmdDraw(cmd, 4, 1, 0, 0); + + vkCmdEndRenderPass(cmd); + } + } + } + + popMarker(cmd); + + vkEndCommandBuffer(cmd); + + Submit(99, 99, {cmd}); + CHECK_VKR(vkDeviceWaitIdle(device)); + + vkDestroyRenderPass(device, tempRP, NULL); + for(VkFramebuffer fb : tempFBs) + vkDestroyFramebuffer(device, fb, NULL); + for(VkImageView v : tempViews) + vkDestroyImageView(device, v, NULL); + for(VkPipeline pipe : tempPipes) + vkDestroyPipeline(device, pipe, NULL); + } + + while(Running()) + { + VkCommandBuffer cmd = GetCommandBuffer(); + + vkBeginCommandBuffer(cmd, vkh::CommandBufferBeginInfo()); + + VkImage swapimg = + StartUsingBackbuffer(cmd, VK_ACCESS_TRANSFER_WRITE_BIT, VK_IMAGE_LAYOUT_GENERAL); + + vkCmdBeginRenderPass(cmd, vkh::RenderPassBeginInfo(rp, framebuffer, mainWindow->scissor, + {vkh::ClearValue(0.4f, 0.5f, 0.6f, 1.0f)}), + VK_SUBPASS_CONTENTS_INLINE); + + VkViewport view = {0.0f, 0.0f, 10.0f, 10.0f, 0.0f, 1.0f}; + for(size_t i = 0; i < test_textures.size(); i++) + { + if(i == 0 || test_textures[i].fmt.texFmt != test_textures[i - 1].fmt.texFmt || + test_textures[i].fmt.viewFmt != test_textures[i - 1].fmt.viewFmt || + test_textures[i].fmt.cfg.data != test_textures[i - 1].fmt.cfg.data) + { + if(i != 0) + popMarker(cmd); + + pushMarker(cmd, test_textures[i].fmt.name); + } + + setMarker(cmd, MakeName(test_textures[i])); + + VkRect2D scissor = { + {(int32_t)view.x + 1, (int32_t)view.y + 1}, + {(uint32_t)view.width - 2, (uint32_t)view.height - 2}, + }; + vkCmdSetViewport(cmd, 0, 1, &view); + vkCmdSetScissor(cmd, 0, 1, &scissor); + + vkCmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, GetPSO(test_textures[i])); + + if(test_textures[i].set != VK_NULL_HANDLE) + { + vkh::cmdBindDescriptorSets(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, layout, 0, + {test_textures[i].set}, {}); + vkCmdDraw(cmd, 4, 1, 0, 0); + } + else + { + setMarker(cmd, "UNSUPPORTED"); + } + + // advance to next viewport + view.x += view.width; + if(view.x + view.width > (float)screenWidth) + { + view.x = 0; + view.y += view.height; + } + } + + // pop the last format region + popMarker(cmd); + + vkCmdEndRenderPass(cmd); + + VkImageBlit region = {}; + region.srcSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + region.srcSubresource.layerCount = 1; + region.dstSubresource = region.srcSubresource; + region.srcOffsets[1].x = mainWindow->scissor.extent.width; + region.srcOffsets[1].y = mainWindow->scissor.extent.height; + region.srcOffsets[1].z = 1; + region.dstOffsets[1] = region.srcOffsets[1]; + + vkCmdBlitImage(cmd, fltTex.image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, swapimg, + VK_IMAGE_LAYOUT_GENERAL, 1, ®ion, VK_FILTER_LINEAR); + + FinishUsingBackbuffer(cmd, VK_ACCESS_TRANSFER_WRITE_BIT, VK_IMAGE_LAYOUT_GENERAL); + + vkEndCommandBuffer(cmd); + + Submit(0, 1, {cmd}); + + Present(); + } + + return 0; + } +}; + +REGISTER_TEST(); diff --git a/util/test/rdtest/__init__.py b/util/test/rdtest/__init__.py index 0c093db45..a5fb09da4 100644 --- a/util/test/rdtest/__init__.py +++ b/util/test/rdtest/__init__.py @@ -3,3 +3,4 @@ from .capture import * from .runner import * from .analyse import * from .testcase import * +from .shared.Texture_Zoo import * diff --git a/util/test/rdtest/runner.py b/util/test/rdtest/runner.py index 5cd50b901..d7eace3a9 100644 --- a/util/test/rdtest/runner.py +++ b/util/test/rdtest/runner.py @@ -341,6 +341,23 @@ def vulkan_register(): rd.UpdateVulkanLayerRegistration(True) +def launch_remote_server(): + # Fork the interpreter to run the test, in case it crashes we can catch it. + # We can re-run with the same parameters + args = sys.argv.copy() + args.insert(0, sys.executable) + + # Add parameter to run the remote server itself + args.append('--internal_remote_server') + + subprocess.Popen(args) + return + + +def become_remote_server(): + rd.BecomeRemoteServer('localhost', None, None) + + def internal_run_test(test_name): testcases = get_tests() diff --git a/util/test/rdtest/shared/Texture_Zoo.py b/util/test/rdtest/shared/Texture_Zoo.py new file mode 100644 index 000000000..a5d36a377 --- /dev/null +++ b/util/test/rdtest/shared/Texture_Zoo.py @@ -0,0 +1,479 @@ +import renderdoc as rd +import rdtest +from typing import List, Tuple +import time +import os + + +# Not a real test, re-used by API-specific tests +class Texture_Zoo(): + def __init__(self): + self.proxied = False + self.fake_msaa = False + self.textures = {} + self.filename = '' + self.textures = {} + self.controller: rd.ReplayController + self.controller = None + self.opengl_mode = False + + def sub(self, mip: int, slice: int, sample: int): + if self.fake_msaa: + return rd.Subresource(mip, slice * 2 + sample, 0) + else: + return rd.Subresource(mip, slice, sample) + + def pick(self, tex: rd.ResourceId, x: int, y: int, sub: rd.Subresource, typeCast: rd.CompType): + if self.opengl_mode: + y = max(1, self.textures[tex].height >> sub.mip) - 1 - y + + return self.controller.PickPixel(tex, x, y, sub, typeCast) + + TEST_CAPTURE = 0 + TEST_DDS = 1 + TEST_PNG = 2 + + def check_test(self, fmt_name: str, name: str, test_mode: int): + pipe: rd.PipeState = self.controller.GetPipelineState() + + image_view = (test_mode != Texture_Zoo.TEST_CAPTURE) + + if image_view: + bound_res: rd.BoundResource = pipe.GetOutputTargets()[0] + else: + bound_res: rd.BoundResource = pipe.GetReadOnlyResources(rd.ShaderStage.Pixel)[0].resources[0] + + texs = self.controller.GetTextures() + for t in texs: + self.textures[t.resourceId] = t + + tex_id: rd.ResourceId = bound_res.resourceId + tex: rd.TextureDescription = self.textures[tex_id] + + comp_type: rd.CompType = tex.format.compType + if bound_res.typeCast != rd.CompType.Typeless: + comp_type = bound_res.typeCast + + # When not running proxied, save non-typecasted textures to disk + if not image_view and not self.proxied and (tex.format.compType == comp_type or + tex.format.type == rd.ResourceFormatType.D24S8 or + tex.format.type == rd.ResourceFormatType.D32S8): + save_data = rd.TextureSave() + save_data.resourceId = tex_id + save_data.destType = rd.FileType.DDS + save_data.sample.mapToArray = True + + self.textures[self.filename] = tex + + path = rdtest.get_tmp_path(self.filename + '.dds') + + success: bool = self.controller.SaveTexture(save_data, path) + + if not success: + try: + os.remove(path) + except Exception: + pass + + save_data.destType = rd.FileType.PNG + save_data.slice.sliceIndex = 0 + save_data.sample.sampleIndex = 0 + path = path.replace('.dds', '.png') + + if comp_type == rd.CompType.UInt: + save_data.comp.blackPoint = 0.0 + save_data.comp.whitePoint = 255.0 + elif comp_type == rd.CompType.SInt: + save_data.comp.blackPoint = -255.0 + save_data.comp.whitePoint = 0.0 + elif comp_type == rd.CompType.SNorm: + save_data.comp.blackPoint = -1.0 + save_data.comp.whitePoint = 0.0 + + success: bool = self.controller.SaveTexture(save_data, path) + + if not success: + try: + os.remove(path) + except Exception: + pass + + value0 = [] + + comp_count = tex.format.compCount + + # When viewing PNGs only compare the components that the original texture had + if test_mode == Texture_Zoo.TEST_PNG: + comp_count = self.textures[self.filename] + tex.msSamp = 0 + tex.arraysize = 1 + tex.depth = 1 + self.fake_msaa = 'MSAA' in name + elif test_mode == Texture_Zoo.TEST_DDS: + tex.arraysize = self.textures[self.filename].arraysize + tex.msSamp = self.textures[self.filename].msSamp + self.fake_msaa = 'MSAA' in name + + # HACK: We don't properly support BGRX, so just drop the alpha channel. We can't set this to compCount = 3 + # internally because that's a 24-bit format with no padding... + if 'B8G8R8X8' in fmt_name: + comp_count = 3 + + # Completely ignore the alpha for BC1, our encoder doesn't pay attention to it + if tex.format.type == rd.ResourceFormatType.BC1: + comp_count = 3 + + # Calculate format-appropriate epsilon + eps_significand = 1.0 + # Account for the sRGB curve by more generous epsilon + + if comp_type == rd.CompType.UNormSRGB: + eps_significand = 2.5 + # Similarly SNorm essentially loses a bit of accuracy due to us only using negative values + elif comp_type == rd.CompType.SNorm: + eps_significand = 2.0 + + if tex.format.type == rd.ResourceFormatType.R4G4B4A4 or tex.format.type == rd.ResourceFormatType.R4G4: + eps = (eps_significand / 15.0) + elif rd.ResourceFormatType.BC1 <= tex.format.type <= rd.ResourceFormatType.BC3: + eps = (eps_significand / 15.0) # 4-bit precision in some channels + elif tex.format.type == rd.ResourceFormatType.R5G5B5A1 or tex.format.type == rd.ResourceFormatType.R5G6B5: + eps = (eps_significand / 31.0) + elif tex.format.type == rd.ResourceFormatType.R11G11B10: + eps = (eps_significand / 31.0) # 5-bit mantissa in blue + elif tex.format.type == rd.ResourceFormatType.R9G9B9E5: + eps = (eps_significand / 63.0) # we have 9 bits of data, but might lose 2-3 due to shared exponent + elif tex.format.type == rd.ResourceFormatType.BC6 and tex.format.compType == rd.CompType.SNorm: + eps = (eps_significand / 63.0) # Lose a bit worth of precision for the signed version + elif rd.ResourceFormatType.BC4 <= tex.format.type <= rd.ResourceFormatType.BC7: + eps = (eps_significand / 127.0) + elif tex.format.compByteWidth == 1: + eps = (eps_significand / 255.0) + elif comp_type == rd.CompType.Depth and tex.format.compCount == 2: + eps = (eps_significand / 255.0) # stencil is only 8-bit + elif tex.format.type == rd.ResourceFormatType.R10G10B10A2: + eps = (eps_significand / 1023.0) + else: + # half-floats have 11-bit mantissa. This epsilon is tight enough that we can be sure + # any remaining errors are implementation inaccuracy and not our bug + eps = (eps_significand / 2047.0) + + for mp in range(tex.mips): + for sl in range(max(tex.arraysize, max(1, tex.depth >> mp))): + z = 0 + if tex.depth > 1: + z = sl + + for sm in range(tex.msSamp): + for x in range(max(1, tex.width >> mp)): + for y in range(max(1, tex.height >> mp)): + picked: rd.PixelValue = self.pick(tex_id, x, y, self.sub(mp, sl, sm), comp_type) + + # each 3D slice cycles the x. This only affects the primary diagonal + offs_x = (x + z) % max(1, tex.width >> mp) + + # The diagonal inverts the colors + inverted = (offs_x != y) + + # second slice adds a coarse checkerboard pattern of inversion + if tex.arraysize > 1 and sl == 1 and ((int(x / 2) % 2) != (int(y / 2) % 2)): + inverted = not inverted + + if comp_type == rd.CompType.UInt or comp_type == rd.CompType.SInt: + expected = [10, 40, 70, 100] + + if inverted: + expected = list(reversed(expected)) + + expected = [c + 10 * (sm + mp) for c in expected] + + if comp_type == rd.CompType.SInt: + picked = picked.intValue + else: + picked = picked.uintValue + elif (tex.format.type == rd.ResourceFormatType.D16S8 or + tex.format.type == rd.ResourceFormatType.D24S8 or + tex.format.type == rd.ResourceFormatType.D32S8): + # depth/stencil is a bit special + expected = [0.1, 10, 100, 0.85] + + if inverted: + expected = list(reversed(expected)) + + expected[0] += 0.075 * (sm + mp) + expected[1] += 10 * (sm + mp) + + # Normalise stencil value + expected[1] = expected[1] / 255.0 + + picked = picked.floatValue + else: + expected = [0.1, 0.35, 0.6, 0.85] + + if inverted: + expected = list(reversed(expected)) + + expected = [c + 0.075 * (sm + mp) for c in expected] + + picked = picked.floatValue + + # SNorm/SInt is negative + if comp_type == rd.CompType.SNorm or comp_type == rd.CompType.SInt: + expected = [-c for c in expected] + + # BGRA textures have a swizzle applied + if tex.format.BGRAOrder(): + expected[0:3] = reversed(expected[0:3]) + + # alpha channel in 10:10:10:2 has extremely low precision, and the ULP requirements mean + # we basically can't trust anything between 0 and 1 on float formats. Just round in that + # case as it still lets us differentiate between alpha 0.0-0.5 and 0.5-1.0 + if tex.format.type == rd.ResourceFormatType.R10G10B10A2: + if comp_type == rd.CompType.UInt: + expected[3] = min(3, expected[3]) + else: + expected[3] = round(expected[3]) * 1.0 + picked[3] = round(picked[3]) * 1.0 + + # Handle 1-bit alpha + if tex.format.type == rd.ResourceFormatType.R5G5B5A1: + expected[3] = 1.0 if expected[3] >= 0.5 else 0.0 + picked[3] = 1.0 if picked[3] >= 0.5 else 0.0 + + # A8 picked values come out in alpha, but we want to compare against the single channel + if tex.format.type == rd.ResourceFormatType.A8: + picked[0] = picked[3] + + # Clamp to number of components in the texture + expected = expected[0:comp_count] + picked = picked[0:comp_count] + + if mp == 0 and sl == 0 and sm == 0 and x == 0 and y == 0: + value0 = picked + + # For SRGB textures picked values will come out as linear + def srgb2linear(f): + if f <= 0.04045: + return f / 12.92 + else: + return ((f + 0.055) / 1.055) ** 2.4 + + if comp_type == rd.CompType.UNormSRGB: + expected[0:3] = [srgb2linear(x) for x in expected[0:3]] + + if test_mode == Texture_Zoo.TEST_PNG: + orig_comp = self.textures[self.filename].format.compType + if orig_comp == rd.CompType.SNorm or orig_comp == rd.CompType.SInt: + expected = [1.0 - x for x in expected] + + if not rdtest.value_compare(picked, expected, eps): + raise rdtest.TestFailureException( + "At ({},{}) of slice {}, mip {}, sample {} of {} {} got {}. Expected {}".format( + x, y, sl, mp, sm, name, fmt_name, picked, expected)) + + if not image_view: + output_tex = pipe.GetOutputTargets()[0].resourceId + + # in the test captures pick the output texture, it should be identical to the + # (0,0) pixel in slice 0, mip 0, sample 0 + view: rd.Viewport = pipe.GetViewport(0) + + val: rd.PixelValue = self.pick(pipe.GetOutputTargets()[0].resourceId, int(view.x + view.width / 2), + int(view.y + view.height / 2), rd.Subresource(), rd.CompType.Typeless) + + picked = val.floatValue + + # A8 picked values come out in alpha, but we want to compare against the single channel + if tex.format.type == rd.ResourceFormatType.A8: + picked[0] = picked[3] + + # Clamp to number of components in the texture + picked = picked[0:comp_count] + + # Up-convert any non-float expected values to floats + value0 = [float(x) for x in value0] + + # For depth/stencil images, one of either depth or stencil should match + if comp_type == rd.CompType.Depth and len(value0) == 2: + if picked[0] == 0.0: + value0[0] = 0.0 + # normalise stencil value if it isn't already + if picked[1] > 1.0: + picked[1] /= 255.0 + elif picked[0] > 1.0: + # un-normalised stencil being rendered in red, match against our stencil expectation + picked[0] /= 255.0 + value0[0] = value0[1] + value0[1] = 0.0 + else: + value0[1] = 0.0 + + if not rdtest.value_compare(picked, value0, eps): + raise rdtest.TestFailureException( + "In {} {} Top-left pixel as rendered is {}. Expected {}".format(name, fmt_name, picked, value0)) + + def check_capture_with_controller(self, proxy_api: str): + any_failed = False + + if proxy_api != '': + rdtest.log.print('Running with {} local proxy'.format(proxy_api)) + self.proxied = True + else: + rdtest.log.print('Running on direct replay') + self.proxied = False + + for d in self.controller.GetDrawcalls(): + + # Check each region for the tests within + if d.flags & rd.DrawFlags.PushMarker: + name = '' + tests_run = 0 + + failed = False + + # Iterate over drawcalls in this region + for sub in d.children: + sub: rd.DrawcallDescription + + if sub.flags & rd.DrawFlags.SetMarker: + name = sub.name + + # Check this draw + if sub.flags & rd.DrawFlags.Drawcall: + tests_run = tests_run + 1 + try: + # Set this event as current + self.controller.SetFrameEvent(sub.eventId, True) + + self.filename = (d.name + '@' + name).replace('->', '_') + + self.check_test(d.name, name, Texture_Zoo.TEST_CAPTURE) + except rdtest.TestFailureException as ex: + failed = any_failed = True + rdtest.log.error(str(ex)) + + if not failed: + rdtest.log.success("All {} texture tests for {} are OK".format(tests_run, d.name)) + + if not any_failed: + if proxy_api != '': + rdtest.log.success( + 'All textures are OK with {} as local proxy'.format(proxy_api)) + else: + rdtest.log.success("All textures are OK on direct replay") + else: + raise rdtest.TestFailureException("Some tests were not as expected") + + def check_capture(self, capture_filename: str, controller: rd.ReplayController): + self.controller = controller + + self.opengl_mode = (self.controller.GetAPIProperties().pipelineType == rd.GraphicsAPI.OpenGL) + + failed = False + + try: + # First check with the local controller + self.check_capture_with_controller('') + except rdtest.TestFailureException as ex: + rdtest.log.error(str(ex)) + failed = True + + # Now shut it down + self.controller.Shutdown() + self.controller = None + + # Launch a remote server + rdtest.launch_remote_server() + + # Wait for it to start + time.sleep(0.5) + + ret: Tuple[rd.ReplayStatus, rd.RemoteServer] = rd.CreateRemoteServerConnection('localhost') + status, remote = ret + + proxies = remote.LocalProxies() + + try: + # Try D3D11 and GL as proxies, D3D12/Vulkan technically don't have proxying implemented even though they + # will be listed in proxies + for api in ['D3D11', 'OpenGL']: + if api not in proxies: + continue + + try: + ret: Tuple[rd.ReplayStatus, rd.ReplayController] = remote.OpenCapture(proxies.index(api), + capture_filename, + rd.ReplayOptions(), None) + status, self.controller = ret + + # Now check with the proxy + self.check_capture_with_controller(api) + except ValueError: + continue + except rdtest.TestFailureException as ex: + rdtest.log.error(str(ex)) + failed = True + finally: + self.controller.Shutdown() + self.controller = None + finally: + remote.ShutdownServerAndConnection() + + # Now iterate over all the temp images saved out, load them as captures, and check the texture. + dir_path = rdtest.get_tmp_path('') + + was_opengl = self.opengl_mode + + # We iterate in filename order, so that dds files get opened before png files. + for file in os.scandir(dir_path): + if '.dds' not in file.name and '.png' not in file.name: + continue + + cap = rd.OpenCaptureFile() + status = cap.OpenFile(file.path, 'rdc', None) + + if status != rd.ReplayStatus.Succeeded: + rdtest.log.error("Couldn't open {}".format(file.name)) + failed = True + continue + + ret: Tuple[rd.ReplayStatus, rd.ReplayController] = cap.OpenCapture(rd.ReplayOptions(), None) + status, self.controller = ret + + if status != rd.ReplayStatus.Succeeded: + rdtest.log.error("Couldn't open {}".format(file.name)) + failed = True + continue + + self.filename = file.name.replace('.dds', '').replace('.png', '') + + [a, b] = file.name.replace('.dds', ' (DDS)').replace('.png', ' (PNG)').split('@') + + self.controller.SetFrameEvent(self.controller.GetDrawcalls()[0].eventId, True) + + try: + self.opengl_mode = False + fmt: rd.ResourceFormat = self.controller.GetTextures()[0].format + + is_compressed = (rd.ResourceFormatType.BC1 <= fmt.type <= rd.ResourceFormatType.BC7 or + fmt.type == rd.ResourceFormatType.EAC or fmt.type == rd.ResourceFormatType.ETC2 or + fmt.type == rd.ResourceFormatType.ASTC or fmt.type == rd.ResourceFormatType.PVRTC) + + # OpenGL saves all non-compressed images to disk with a flip, since that's the expected order for + # most formats. The effect of this is that we should apply the opengl_mode workaround for all files + # *except* compressed textures + if was_opengl and not is_compressed: + self.opengl_mode = True + + self.check_test(a, b, Texture_Zoo.TEST_DDS if '.dds' in file.name else Texture_Zoo.TEST_PNG) + + rdtest.log.success("{} loaded with the correct data".format(file.name)) + except rdtest.TestFailureException as ex: + rdtest.log.error(str(ex)) + failed = True + + self.controller.Shutdown() + self.controller = None + + if failed: + raise rdtest.TestFailureException("Some tests were not as expected") diff --git a/util/test/rdtest/testcase.py b/util/test/rdtest/testcase.py index dc6a7bd10..7418a8ece 100644 --- a/util/test/rdtest/testcase.py +++ b/util/test/rdtest/testcase.py @@ -328,7 +328,8 @@ class TestCase: self.check_capture() - self.controller.Shutdown() + if self.controller is not None: + self.controller.Shutdown() def invoketest(self, debugMode): self.run() diff --git a/util/test/rdtest/util.py b/util/test/rdtest/util.py index 5be9c535d..6174e1b18 100644 --- a/util/test/rdtest/util.py +++ b/util/test/rdtest/util.py @@ -250,9 +250,10 @@ FLT_EPSILON = 2.0*1.19209290E-07 def value_compare(ref, data, eps=FLT_EPSILON): - if type(ref) == float: - if type(data) != float: - return False + if type(ref) == float or type(data) == float: + # if the types are different this is probably 0.0 == 0 or something. Just compare straight by casting to floats + if type(data) != type(data): + return float(data) == float(ref) # Special handling for NaNs - NaNs are always equal to NaNs, but NaN is never equal to any other value if math.isnan(ref) and math.isnan(data): diff --git a/util/test/run_tests.py b/util/test/run_tests.py index 0889f442d..0421c6d29 100644 --- a/util/test/run_tests.py +++ b/util/test/run_tests.py @@ -36,6 +36,8 @@ parser.add_argument('--debugger', parser.add_argument('--internal_run_test', help=argparse.SUPPRESS, type=str, required=False) # Internal command, when we re-run as admin to register vulkan layer parser.add_argument('--internal_vulkan_register', help=argparse.SUPPRESS, action="store_true", required=False) +# Internal command, when we re-run as a remote server +parser.add_argument('--internal_remote_server', help=argparse.SUPPRESS, action="store_true", required=False) args = parser.parse_args() if args.renderdoc is not None: @@ -110,6 +112,8 @@ if args.debugger: if args.internal_vulkan_register: rdtest.vulkan_register() +elif args.internal_remote_server: + rdtest.become_remote_server() elif args.internal_run_test is not None: rdtest.internal_run_test(args.internal_run_test) else: diff --git a/util/test/tests/D3D11/D3D11_Texture_Zoo.py b/util/test/tests/D3D11/D3D11_Texture_Zoo.py new file mode 100644 index 000000000..b0818a76a --- /dev/null +++ b/util/test/tests/D3D11/D3D11_Texture_Zoo.py @@ -0,0 +1,19 @@ +import renderdoc as rd +import rdtest +from typing import List, Tuple +import time +import os + + +class D3D11_Texture_Zoo(rdtest.TestCase): + slow_test = True + demos_test_name = 'D3D11_Texture_Zoo' + + def __init__(self): + rdtest.TestCase.__init__(self) + self.zoo_helper = rdtest.Texture_Zoo() + + def check_capture(self): + # This takes ownership of the controller and shuts it down when it's finished + self.zoo_helper.check_capture(self.capture_filename, self.controller) + self.controller = None diff --git a/util/test/tests/D3D12/D3D12_Texture_Zoo.py b/util/test/tests/D3D12/D3D12_Texture_Zoo.py new file mode 100644 index 000000000..d124737db --- /dev/null +++ b/util/test/tests/D3D12/D3D12_Texture_Zoo.py @@ -0,0 +1,19 @@ +import renderdoc as rd +import rdtest +from typing import List, Tuple +import time +import os + + +class D3D12_Texture_Zoo(rdtest.TestCase): + slow_test = True + demos_test_name = 'D3D12_Texture_Zoo' + + def __init__(self): + rdtest.TestCase.__init__(self) + self.zoo_helper = rdtest.Texture_Zoo() + + def check_capture(self): + # This takes ownership of the controller and shuts it down when it's finished + self.zoo_helper.check_capture(self.capture_filename, self.controller) + self.controller = None diff --git a/util/test/tests/GL/GL_Texture_Zoo.py b/util/test/tests/GL/GL_Texture_Zoo.py new file mode 100644 index 000000000..ecd399ad8 --- /dev/null +++ b/util/test/tests/GL/GL_Texture_Zoo.py @@ -0,0 +1,19 @@ +import renderdoc as rd +import rdtest +from typing import List, Tuple +import time +import os + + +class GL_Texture_Zoo(rdtest.TestCase): + slow_test = True + demos_test_name = 'GL_Texture_Zoo' + + def __init__(self): + rdtest.TestCase.__init__(self) + self.zoo_helper = rdtest.Texture_Zoo() + + def check_capture(self): + # This takes ownership of the controller and shuts it down when it's finished + self.zoo_helper.check_capture(self.capture_filename, self.controller) + self.controller = None diff --git a/util/test/tests/Vulkan/VK_Texture_Zoo.py b/util/test/tests/Vulkan/VK_Texture_Zoo.py new file mode 100644 index 000000000..a056335ee --- /dev/null +++ b/util/test/tests/Vulkan/VK_Texture_Zoo.py @@ -0,0 +1,19 @@ +import renderdoc as rd +import rdtest +from typing import List, Tuple +import time +import os + + +class VK_Texture_Zoo(rdtest.TestCase): + slow_test = True + demos_test_name = 'VK_Texture_Zoo' + + def __init__(self): + rdtest.TestCase.__init__(self) + self.zoo_helper = rdtest.Texture_Zoo() + + def check_capture(self): + # This takes ownership of the controller and shuts it down when it's finished + self.zoo_helper.check_capture(self.capture_filename, self.controller) + self.controller = None