// Copyright 2019 The Dawn Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #include "tests/DawnTest.h" #include "dawn/common/Assert.h" #include "dawn/common/Math.h" #include "dawn/utils/ComboRenderPipelineDescriptor.h" #include "dawn/utils/TextureUtils.h" #include "dawn/utils/WGPUHelpers.h" #include #include // An expectation for float buffer content that can correctly compare different NaN values and // supports a basic tolerance for comparison of finite values. class ExpectFloatWithTolerance : public detail::Expectation { public: ExpectFloatWithTolerance(std::vector expected, float tolerance) : mExpected(std::move(expected)), mTolerance(tolerance) { } testing::AssertionResult Check(const void* data, size_t size) override { ASSERT(size == sizeof(float) * mExpected.size()); const float* actual = static_cast(data); for (size_t i = 0; i < mExpected.size(); ++i) { float expectedValue = mExpected[i]; float actualValue = actual[i]; if (!FloatsMatch(expectedValue, actualValue)) { testing::AssertionResult result = testing::AssertionFailure() << "Expected data[" << i << "] to be close to " << expectedValue << ", actual " << actualValue << std::endl; return result; } } return testing::AssertionSuccess(); } private: bool FloatsMatch(float expected, float actual) { if (std::isnan(expected)) { return std::isnan(actual); } if (std::isinf(expected)) { return std::isinf(actual) && std::signbit(expected) == std::signbit(actual); } if (mTolerance == 0.0f) { return expected == actual; } float error = std::abs(expected - actual); return error < mTolerance; } std::vector mExpected; float mTolerance; }; // An expectation for float16 buffers that can correctly compare NaNs (all NaNs are equivalent). class ExpectFloat16 : public detail::Expectation { public: ExpectFloat16(std::vector expected) : mExpected(std::move(expected)) { } testing::AssertionResult Check(const void* data, size_t size) override { ASSERT(size == sizeof(uint16_t) * mExpected.size()); const uint16_t* actual = static_cast(data); for (size_t i = 0; i < mExpected.size(); ++i) { uint16_t expectedValue = mExpected[i]; uint16_t actualValue = actual[i]; if (!Floats16Match(expectedValue, actualValue)) { testing::AssertionResult result = testing::AssertionFailure() << "Expected data[" << i << "] to be " << expectedValue << ", actual " << actualValue << std::endl; return result; } } return testing::AssertionSuccess(); } private: bool Floats16Match(float expected, float actual) { if (IsFloat16NaN(expected)) { return IsFloat16NaN(actual); } return expected == actual; } std::vector mExpected; }; class TextureFormatTest : public DawnTest { protected: // Structure containing all the information that tests need to know about the format. struct FormatTestInfo { wgpu::TextureFormat format; uint32_t texelByteSize; wgpu::TextureComponentType type; uint32_t componentCount; }; // Returns a reprensentation of a format that can be used to contain the "uncompressed" values // of the format. That the equivalent format with all channels 32bit-sized. FormatTestInfo GetUncompressedFormatInfo(FormatTestInfo formatInfo) { switch (formatInfo.type) { case wgpu::TextureComponentType::Float: return {wgpu::TextureFormat::RGBA32Float, 16, formatInfo.type, 4}; case wgpu::TextureComponentType::Sint: return {wgpu::TextureFormat::RGBA32Sint, 16, formatInfo.type, 4}; case wgpu::TextureComponentType::Uint: return {wgpu::TextureFormat::RGBA32Uint, 16, formatInfo.type, 4}; default: UNREACHABLE(); } } // Return a pipeline that can be used in a full-texture draw to sample from the texture in the // bindgroup and output its decompressed values to the render target. wgpu::RenderPipeline CreateSamplePipeline(FormatTestInfo sampleFormatInfo, FormatTestInfo renderFormatInfo) { utils::ComboRenderPipelineDescriptor desc; wgpu::ShaderModule vsModule = utils::CreateShaderModule(device, R"( @stage(vertex) fn main(@builtin(vertex_index) VertexIndex : u32) -> @builtin(position) vec4 { var pos = array, 3>( vec2(-3.0, -1.0), vec2( 3.0, -1.0), vec2( 0.0, 2.0)); return vec4(pos[VertexIndex], 0.0, 1.0); })"); // Compute the WGSL type of the texture's data. const char* type = utils::GetWGSLColorTextureComponentType(sampleFormatInfo.format); std::ostringstream fsSource; fsSource << "@group(0) @binding(0) var myTexture : texture_2d<" << type << ">;\n"; fsSource << "struct FragmentOut {\n"; fsSource << " @location(0) color : vec4<" << type << ">;\n"; fsSource << R"(}; @stage(fragment) fn main(@builtin(position) FragCoord : vec4) -> FragmentOut { var output : FragmentOut; output.color = textureLoad(myTexture, vec2(FragCoord.xy), 0); return output; })"; wgpu::ShaderModule fsModule = utils::CreateShaderModule(device, fsSource.str().c_str()); desc.vertex.module = vsModule; desc.cFragment.module = fsModule; desc.cTargets[0].format = renderFormatInfo.format; return device.CreateRenderPipeline(&desc); } // The sampling test uploads the sample data in a texture with the sampleFormatInfo.format. // It then samples from it and renders the results in a texture with the // renderFormatInfo.format format. Finally it checks that the data rendered matches // expectedRenderData, using the cutom expectation if present. void DoSampleTest(FormatTestInfo sampleFormatInfo, const void* sampleData, size_t sampleDataSize, FormatTestInfo renderFormatInfo, const void* expectedRenderData, size_t expectedRenderDataSize, detail::Expectation* customExpectation) { // The input data should contain an exact number of texels ASSERT(sampleDataSize % sampleFormatInfo.texelByteSize == 0); uint32_t width = sampleDataSize / sampleFormatInfo.texelByteSize; // The input data must be a multiple of 4 byte in length for WriteBuffer ASSERT(sampleDataSize % 4 == 0); ASSERT(expectedRenderDataSize % 4 == 0); // Create the texture we will sample from wgpu::TextureDescriptor sampleTextureDesc; sampleTextureDesc.usage = wgpu::TextureUsage::CopyDst | wgpu::TextureUsage::TextureBinding; sampleTextureDesc.size = {width, 1, 1}; sampleTextureDesc.format = sampleFormatInfo.format; wgpu::Texture sampleTexture = device.CreateTexture(&sampleTextureDesc); wgpu::Buffer uploadBuffer = utils::CreateBufferFromData(device, sampleData, sampleDataSize, wgpu::BufferUsage::CopySrc); // Create the texture that we will render results to ASSERT(expectedRenderDataSize == width * renderFormatInfo.texelByteSize); wgpu::TextureDescriptor renderTargetDesc; renderTargetDesc.usage = wgpu::TextureUsage::CopySrc | wgpu::TextureUsage::RenderAttachment; renderTargetDesc.size = {width, 1, 1}; renderTargetDesc.format = renderFormatInfo.format; wgpu::Texture renderTarget = device.CreateTexture(&renderTargetDesc); // Create the readback buffer for the data in renderTarget wgpu::BufferDescriptor readbackBufferDesc; readbackBufferDesc.usage = wgpu::BufferUsage::CopyDst | wgpu::BufferUsage::CopySrc; readbackBufferDesc.size = expectedRenderDataSize; wgpu::Buffer readbackBuffer = device.CreateBuffer(&readbackBufferDesc); // Prepare objects needed to sample from texture in the renderpass wgpu::RenderPipeline pipeline = CreateSamplePipeline(sampleFormatInfo, renderFormatInfo); wgpu::BindGroup bindGroup = utils::MakeBindGroup(device, pipeline.GetBindGroupLayout(0), {{0, sampleTexture.CreateView()}}); // Encode commands for the test that fill texture, sample it to render to renderTarget then // copy renderTarget in a buffer so we can read it easily. wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); { wgpu::ImageCopyBuffer bufferView = utils::CreateImageCopyBuffer(uploadBuffer, 0, 256); wgpu::ImageCopyTexture textureView = utils::CreateImageCopyTexture(sampleTexture, 0, {0, 0, 0}); wgpu::Extent3D extent{width, 1, 1}; encoder.CopyBufferToTexture(&bufferView, &textureView, &extent); } utils::ComboRenderPassDescriptor renderPassDesc({renderTarget.CreateView()}); wgpu::RenderPassEncoder renderPass = encoder.BeginRenderPass(&renderPassDesc); renderPass.SetPipeline(pipeline); renderPass.SetBindGroup(0, bindGroup); renderPass.Draw(3); renderPass.End(); { wgpu::ImageCopyBuffer bufferView = utils::CreateImageCopyBuffer(readbackBuffer, 0, 256); wgpu::ImageCopyTexture textureView = utils::CreateImageCopyTexture(renderTarget, 0, {0, 0, 0}); wgpu::Extent3D extent{width, 1, 1}; encoder.CopyTextureToBuffer(&textureView, &bufferView, &extent); } wgpu::CommandBuffer commands = encoder.Finish(); queue.Submit(1, &commands); // For floats use a special expectation that understands how to compare NaNs and support a // tolerance. if (customExpectation != nullptr) { AddBufferExpectation(__FILE__, __LINE__, readbackBuffer, 0, expectedRenderDataSize, customExpectation); } else { EXPECT_BUFFER_U32_RANGE_EQ(static_cast(expectedRenderData), readbackBuffer, 0, expectedRenderDataSize / sizeof(uint32_t)); } } template std::vector ExpandDataTo4Component(const std::vector& originalData, uint32_t originalComponentCount, const std::array& defaultValues) { std::vector result; for (size_t i = 0; i < originalData.size() / originalComponentCount; i++) { for (size_t component = 0; component < 4; component++) { if (component < originalComponentCount) { result.push_back(originalData[i * originalComponentCount + component]); } else { result.push_back(defaultValues[component]); } } } return result; } // Helper functions used to run tests that convert the typeful test objects to typeless void* template void DoFormatSamplingTest(FormatTestInfo formatInfo, const std::vector& textureData, const std::vector& expectedRenderData, detail::Expectation* customExpectation = nullptr) { FormatTestInfo renderFormatInfo = GetUncompressedFormatInfo(formatInfo); // Expand the expected data to be 4 component wide with the default sampling values of // (0, 0, 0, 1) std::array defaultValues = {RenderData(0), RenderData(0), RenderData(0), RenderData(1)}; std::vector expandedRenderData = ExpandDataTo4Component(expectedRenderData, formatInfo.componentCount, defaultValues); DoSampleTest(formatInfo, textureData.data(), textureData.size() * sizeof(TextureData), renderFormatInfo, expandedRenderData.data(), expandedRenderData.size() * sizeof(RenderData), customExpectation); } template void DoFloatFormatSamplingTest(FormatTestInfo formatInfo, const std::vector& textureData, const std::vector& expectedRenderData, float floatTolerance = 0.0f) { // Expand the expected data to be 4 component wide with the default sampling values of // (0, 0, 0, 1) std::array defaultValues = {0.0f, 0.0f, 0.0f, 1.0f}; std::vector expandedRenderData = ExpandDataTo4Component(expectedRenderData, formatInfo.componentCount, defaultValues); // Use a special expectation that understands how to compare NaNs and supports a tolerance. DoFormatSamplingTest(formatInfo, textureData, expectedRenderData, new ExpectFloatWithTolerance(expandedRenderData, floatTolerance)); } template void DoFormatRenderingTest(FormatTestInfo formatInfo, const std::vector& textureData, const std::vector& expectedRenderData, detail::Expectation* customExpectation = nullptr) { FormatTestInfo sampleFormatInfo = GetUncompressedFormatInfo(formatInfo); // Expand the sampling texture data to contain garbage data for unused components to check // that they don't influence the rendering result. std::array garbageValues; garbageValues.fill(13); std::vector expandedTextureData = ExpandDataTo4Component(textureData, formatInfo.componentCount, garbageValues); DoSampleTest(sampleFormatInfo, expandedTextureData.data(), expandedTextureData.size() * sizeof(TextureData), formatInfo, expectedRenderData.data(), expectedRenderData.size() * sizeof(RenderData), customExpectation); } // Below are helper functions for types that are very similar to one another so the logic is // shared. template void DoUnormTest(FormatTestInfo formatInfo) { static_assert(!std::is_signed::value && std::is_integral::value); ASSERT(sizeof(T) * formatInfo.componentCount == formatInfo.texelByteSize); ASSERT(formatInfo.type == wgpu::TextureComponentType::Float); T maxValue = std::numeric_limits::max(); std::vector textureData = {0, 1, maxValue, maxValue}; std::vector uncompressedData = {0.0f, 1.0f / maxValue, 1.0f, 1.0f}; DoFormatSamplingTest(formatInfo, textureData, uncompressedData); DoFormatRenderingTest(formatInfo, uncompressedData, textureData); } template void DoSnormTest(FormatTestInfo formatInfo) { static_assert(std::is_signed::value && std::is_integral::value); ASSERT(sizeof(T) * formatInfo.componentCount == formatInfo.texelByteSize); ASSERT(formatInfo.type == wgpu::TextureComponentType::Float); T maxValue = std::numeric_limits::max(); T minValue = std::numeric_limits::min(); std::vector textureData = {0, 1, -1, maxValue, minValue, T(minValue + 1), 0, 0}; std::vector uncompressedData = { 0.0f, 1.0f / maxValue, -1.0f / maxValue, 1.0f, -1.0f, -1.0f, 0.0f, 0.0f}; DoFloatFormatSamplingTest(formatInfo, textureData, uncompressedData, 0.0001f / maxValue); // Snorm formats aren't renderable because they are not guaranteed renderable in Vulkan } template void DoUintTest(FormatTestInfo formatInfo) { static_assert(!std::is_signed::value && std::is_integral::value); ASSERT(sizeof(T) * formatInfo.componentCount == formatInfo.texelByteSize); ASSERT(formatInfo.type == wgpu::TextureComponentType::Uint); T maxValue = std::numeric_limits::max(); std::vector textureData = {0, 1, maxValue, maxValue}; std::vector uncompressedData = {0, 1, maxValue, maxValue}; DoFormatSamplingTest(formatInfo, textureData, uncompressedData); DoFormatRenderingTest(formatInfo, uncompressedData, textureData); } template void DoSintTest(FormatTestInfo formatInfo) { static_assert(std::is_signed::value && std::is_integral::value); ASSERT(sizeof(T) * formatInfo.componentCount == formatInfo.texelByteSize); ASSERT(formatInfo.type == wgpu::TextureComponentType::Sint); T maxValue = std::numeric_limits::max(); T minValue = std::numeric_limits::min(); std::vector textureData = {0, 1, maxValue, minValue}; std::vector uncompressedData = {0, 1, maxValue, minValue}; DoFormatSamplingTest(formatInfo, textureData, uncompressedData); DoFormatRenderingTest(formatInfo, uncompressedData, textureData); } void DoFloat32Test(FormatTestInfo formatInfo) { ASSERT(sizeof(float) * formatInfo.componentCount == formatInfo.texelByteSize); ASSERT(formatInfo.type == wgpu::TextureComponentType::Float); std::vector textureData = {+0.0f, -0.0f, 1.0f, 1.0e-29f, 1.0e29f, NAN, INFINITY, -INFINITY}; DoFloatFormatSamplingTest(formatInfo, textureData, textureData); DoFormatRenderingTest(formatInfo, textureData, textureData, new ExpectFloatWithTolerance(textureData, 0.0f)); } void DoFloat16Test(FormatTestInfo formatInfo) { ASSERT(sizeof(int16_t) * formatInfo.componentCount == formatInfo.texelByteSize); ASSERT(formatInfo.type == wgpu::TextureComponentType::Float); std::vector uncompressedData = {+0.0f, -0.0f, 1.0f, 1.01e-4f, 1.0e4f, NAN, INFINITY, -INFINITY}; std::vector textureData; for (float value : uncompressedData) { textureData.push_back(Float32ToFloat16(value)); } DoFloatFormatSamplingTest(formatInfo, textureData, uncompressedData, 1.0e-5f); // Use a special expectation that knows that all Float16 NaNs are equivalent. DoFormatRenderingTest(formatInfo, uncompressedData, textureData, new ExpectFloat16(textureData)); } }; // Test the R8Unorm format TEST_P(TextureFormatTest, R8Unorm) { DoUnormTest({wgpu::TextureFormat::R8Unorm, 1, wgpu::TextureComponentType::Float, 1}); } // Test the RG8Unorm format TEST_P(TextureFormatTest, RG8Unorm) { DoUnormTest({wgpu::TextureFormat::RG8Unorm, 2, wgpu::TextureComponentType::Float, 2}); } // Test the RGBA8Unorm format TEST_P(TextureFormatTest, RGBA8Unorm) { DoUnormTest( {wgpu::TextureFormat::RGBA8Unorm, 4, wgpu::TextureComponentType::Float, 4}); } // Test the BGRA8Unorm format TEST_P(TextureFormatTest, BGRA8Unorm) { // TODO(crbug.com/dawn/596): BGRA is unsupported on OpenGL ES; add workaround or validation DAWN_SUPPRESS_TEST_IF(IsOpenGLES()); uint8_t maxValue = std::numeric_limits::max(); std::vector textureData = {maxValue, 1, 0, maxValue}; std::vector uncompressedData = {0.0f, 1.0f / maxValue, 1.0f, 1.0f}; DoFormatSamplingTest({wgpu::TextureFormat::BGRA8Unorm, 4, wgpu::TextureComponentType::Float, 4}, textureData, uncompressedData); DoFormatRenderingTest( {wgpu::TextureFormat::BGRA8Unorm, 4, wgpu::TextureComponentType::Float, 4}, uncompressedData, textureData); } // Test the R8Snorm format TEST_P(TextureFormatTest, R8Snorm) { DoSnormTest({wgpu::TextureFormat::R8Snorm, 1, wgpu::TextureComponentType::Float, 1}); } // Test the RG8Snorm format TEST_P(TextureFormatTest, RG8Snorm) { DoSnormTest({wgpu::TextureFormat::RG8Snorm, 2, wgpu::TextureComponentType::Float, 2}); } // Test the RGBA8Snorm format TEST_P(TextureFormatTest, RGBA8Snorm) { DoSnormTest({wgpu::TextureFormat::RGBA8Snorm, 4, wgpu::TextureComponentType::Float, 4}); } // Test the R8Uint format TEST_P(TextureFormatTest, R8Uint) { DoUintTest({wgpu::TextureFormat::R8Uint, 1, wgpu::TextureComponentType::Uint, 1}); } // Test the RG8Uint format TEST_P(TextureFormatTest, RG8Uint) { DoUintTest({wgpu::TextureFormat::RG8Uint, 2, wgpu::TextureComponentType::Uint, 2}); } // Test the RGBA8Uint format TEST_P(TextureFormatTest, RGBA8Uint) { DoUintTest({wgpu::TextureFormat::RGBA8Uint, 4, wgpu::TextureComponentType::Uint, 4}); } // Test the R16Uint format TEST_P(TextureFormatTest, R16Uint) { DoUintTest({wgpu::TextureFormat::R16Uint, 2, wgpu::TextureComponentType::Uint, 1}); } // Test the RG16Uint format TEST_P(TextureFormatTest, RG16Uint) { DoUintTest({wgpu::TextureFormat::RG16Uint, 4, wgpu::TextureComponentType::Uint, 2}); } // Test the RGBA16Uint format TEST_P(TextureFormatTest, RGBA16Uint) { DoUintTest({wgpu::TextureFormat::RGBA16Uint, 8, wgpu::TextureComponentType::Uint, 4}); } // Test the R32Uint format TEST_P(TextureFormatTest, R32Uint) { DoUintTest({wgpu::TextureFormat::R32Uint, 4, wgpu::TextureComponentType::Uint, 1}); } // Test the RG32Uint format TEST_P(TextureFormatTest, RG32Uint) { DoUintTest({wgpu::TextureFormat::RG32Uint, 8, wgpu::TextureComponentType::Uint, 2}); } // Test the RGBA32Uint format TEST_P(TextureFormatTest, RGBA32Uint) { DoUintTest( {wgpu::TextureFormat::RGBA32Uint, 16, wgpu::TextureComponentType::Uint, 4}); } // Test the R8Sint format TEST_P(TextureFormatTest, R8Sint) { DoSintTest({wgpu::TextureFormat::R8Sint, 1, wgpu::TextureComponentType::Sint, 1}); } // Test the RG8Sint format TEST_P(TextureFormatTest, RG8Sint) { DoSintTest({wgpu::TextureFormat::RG8Sint, 2, wgpu::TextureComponentType::Sint, 2}); } // Test the RGBA8Sint format TEST_P(TextureFormatTest, RGBA8Sint) { DoSintTest({wgpu::TextureFormat::RGBA8Sint, 4, wgpu::TextureComponentType::Sint, 4}); } // Test the R16Sint format TEST_P(TextureFormatTest, R16Sint) { DoSintTest({wgpu::TextureFormat::R16Sint, 2, wgpu::TextureComponentType::Sint, 1}); } // Test the RG16Sint format TEST_P(TextureFormatTest, RG16Sint) { DoSintTest({wgpu::TextureFormat::RG16Sint, 4, wgpu::TextureComponentType::Sint, 2}); } // Test the RGBA16Sint format TEST_P(TextureFormatTest, RGBA16Sint) { DoSintTest({wgpu::TextureFormat::RGBA16Sint, 8, wgpu::TextureComponentType::Sint, 4}); } // Test the R32Sint format TEST_P(TextureFormatTest, R32Sint) { DoSintTest({wgpu::TextureFormat::R32Sint, 4, wgpu::TextureComponentType::Sint, 1}); } // Test the RG32Sint format TEST_P(TextureFormatTest, RG32Sint) { DoSintTest({wgpu::TextureFormat::RG32Sint, 8, wgpu::TextureComponentType::Sint, 2}); } // Test the RGBA32Sint format TEST_P(TextureFormatTest, RGBA32Sint) { DoSintTest({wgpu::TextureFormat::RGBA32Sint, 16, wgpu::TextureComponentType::Sint, 4}); } // Test the R32Float format TEST_P(TextureFormatTest, R32Float) { DoFloat32Test({wgpu::TextureFormat::R32Float, 4, wgpu::TextureComponentType::Float, 1}); } // Test the RG32Float format TEST_P(TextureFormatTest, RG32Float) { DoFloat32Test({wgpu::TextureFormat::RG32Float, 8, wgpu::TextureComponentType::Float, 2}); } // Test the RGBA32Float format TEST_P(TextureFormatTest, RGBA32Float) { DoFloat32Test({wgpu::TextureFormat::RGBA32Float, 16, wgpu::TextureComponentType::Float, 4}); } // Test the R16Float format TEST_P(TextureFormatTest, R16Float) { // TODO(https://crbug.com/swiftshader/147) Rendering INFINITY isn't handled correctly by // swiftshader DAWN_SUPPRESS_TEST_IF(IsVulkan() && IsSwiftshader() || IsANGLE()); DoFloat16Test({wgpu::TextureFormat::R16Float, 2, wgpu::TextureComponentType::Float, 1}); } // Test the RG16Float format TEST_P(TextureFormatTest, RG16Float) { // TODO(https://crbug.com/swiftshader/147) Rendering INFINITY isn't handled correctly by // swiftshader DAWN_SUPPRESS_TEST_IF(IsVulkan() && IsSwiftshader() || IsANGLE()); DoFloat16Test({wgpu::TextureFormat::RG16Float, 4, wgpu::TextureComponentType::Float, 2}); } // Test the RGBA16Float format TEST_P(TextureFormatTest, RGBA16Float) { // TODO(https://crbug.com/swiftshader/147) Rendering INFINITY isn't handled correctly by // swiftshader DAWN_SUPPRESS_TEST_IF(IsVulkan() && IsSwiftshader() || IsANGLE()); DoFloat16Test({wgpu::TextureFormat::RGBA16Float, 8, wgpu::TextureComponentType::Float, 4}); } // Test the RGBA8Unorm format TEST_P(TextureFormatTest, RGBA8UnormSrgb) { uint8_t maxValue = std::numeric_limits::max(); std::vector textureData = {0, 1, maxValue, 64, 35, 68, 152, 168}; std::vector uncompressedData; for (size_t i = 0; i < textureData.size(); i += 4) { uncompressedData.push_back(SRGBToLinear(textureData[i + 0] / float(maxValue))); uncompressedData.push_back(SRGBToLinear(textureData[i + 1] / float(maxValue))); uncompressedData.push_back(SRGBToLinear(textureData[i + 2] / float(maxValue))); // Alpha is linear for sRGB formats uncompressedData.push_back(textureData[i + 3] / float(maxValue)); } DoFloatFormatSamplingTest( {wgpu::TextureFormat::RGBA8UnormSrgb, 4, wgpu::TextureComponentType::Float, 4}, textureData, uncompressedData, 1.0e-3); DoFormatRenderingTest( {wgpu::TextureFormat::RGBA8UnormSrgb, 4, wgpu::TextureComponentType::Float, 4}, uncompressedData, textureData); } // Test the BGRA8UnormSrgb format TEST_P(TextureFormatTest, BGRA8UnormSrgb) { // TODO(cwallez@chromium.org): This format doesn't exist in OpenGL, emulate it using // RGBA8UnormSrgb and swizzling / shader twiddling DAWN_SUPPRESS_TEST_IF(IsOpenGL() || IsOpenGLES()); uint8_t maxValue = std::numeric_limits::max(); std::vector textureData = {0, 1, maxValue, 64, 35, 68, 152, 168}; std::vector uncompressedData; for (size_t i = 0; i < textureData.size(); i += 4) { // Note that R and B are swapped uncompressedData.push_back(SRGBToLinear(textureData[i + 2] / float(maxValue))); uncompressedData.push_back(SRGBToLinear(textureData[i + 1] / float(maxValue))); uncompressedData.push_back(SRGBToLinear(textureData[i + 0] / float(maxValue))); // Alpha is linear for sRGB formats uncompressedData.push_back(textureData[i + 3] / float(maxValue)); } DoFloatFormatSamplingTest( {wgpu::TextureFormat::BGRA8UnormSrgb, 4, wgpu::TextureComponentType::Float, 4}, textureData, uncompressedData, 1.0e-3); DoFormatRenderingTest( {wgpu::TextureFormat::BGRA8UnormSrgb, 4, wgpu::TextureComponentType::Float, 4}, uncompressedData, textureData); } // Test the RGB10A2Unorm format TEST_P(TextureFormatTest, RGB10A2Unorm) { auto MakeRGB10A2 = [](uint32_t r, uint32_t g, uint32_t b, uint32_t a) -> uint32_t { ASSERT((r & 0x3FF) == r); ASSERT((g & 0x3FF) == g); ASSERT((b & 0x3FF) == b); ASSERT((a & 0x3) == a); return r | g << 10 | b << 20 | a << 30; }; std::vector textureData = {MakeRGB10A2(0, 0, 0, 0), MakeRGB10A2(1023, 1023, 1023, 1), MakeRGB10A2(243, 576, 765, 2), MakeRGB10A2(0, 0, 0, 3)}; // clang-format off std::vector uncompressedData = { 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f, 1 / 3.0f, 243 / 1023.0f, 576 / 1023.0f, 765 / 1023.0f, 2 / 3.0f, 0.0f, 0.0f, 0.0f, 1.0f }; // clang-format on DoFloatFormatSamplingTest( {wgpu::TextureFormat::RGB10A2Unorm, 4, wgpu::TextureComponentType::Float, 4}, textureData, uncompressedData, 1.0e-5); DoFormatRenderingTest( {wgpu::TextureFormat::RGB10A2Unorm, 4, wgpu::TextureComponentType::Float, 4}, uncompressedData, textureData); } // Test the RG11B10Ufloat format TEST_P(TextureFormatTest, RG11B10Ufloat) { constexpr uint32_t kFloat11Zero = 0; constexpr uint32_t kFloat11Infinity = 0x7C0; constexpr uint32_t kFloat11Nan = 0x7C1; constexpr uint32_t kFloat11One = 0x3C0; constexpr uint32_t kFloat10Zero = 0; constexpr uint32_t kFloat10Infinity = 0x3E0; constexpr uint32_t kFloat10Nan = 0x3E1; constexpr uint32_t kFloat10One = 0x1E0; auto MakeRG11B10 = [](uint32_t r, uint32_t g, uint32_t b) { ASSERT((r & 0x7FF) == r); ASSERT((g & 0x7FF) == g); ASSERT((b & 0x3FF) == b); return r | g << 11 | b << 22; }; // Test each of (0, 1, INFINITY, NaN) for each component but never two with the same value at a // time. std::vector textureData = { MakeRG11B10(kFloat11Zero, kFloat11Infinity, kFloat10Nan), MakeRG11B10(kFloat11Infinity, kFloat11Nan, kFloat10One), MakeRG11B10(kFloat11Nan, kFloat11One, kFloat10Zero), MakeRG11B10(kFloat11One, kFloat11Zero, kFloat10Infinity), }; // This is one of the only 3-channel formats, so we don't have specific testing for them. Alpha // should always be sampled as 1 // clang-format off std::vector uncompressedData = { 0.0f, INFINITY, NAN, 1.0f, INFINITY, NAN, 1.0f, 1.0f, NAN, 1.0f, 0.0f, 1.0f, 1.0f, 0.0f, INFINITY, 1.0f }; // clang-format on DoFloatFormatSamplingTest( {wgpu::TextureFormat::RG11B10Ufloat, 4, wgpu::TextureComponentType::Float, 4}, textureData, uncompressedData); // This format is not renderable. } // Test the RGB9E5Ufloat format TEST_P(TextureFormatTest, RGB9E5Ufloat) { // RGB9E5 is different from other floating point formats because the mantissa doesn't index in // the window defined by the exponent but is instead treated as a pure multiplier. There is // also no Infinity or NaN. The OpenGL 4.6 spec has the best explanation I've found in section // 8.25 "Shared Exponent Texture Color Conversion": // // red = reduint * 2^(expuint - B - N) = reduint * 2^(expuint - 24) // // Where reduint and expuint are the integer values when considering the E5 as a 5bit uint, and // the r9 as a 9bit uint. B the number of bits of the mantissa (9), and N the offset for the // exponent (15). float smallestExponent = std::pow(2.0f, -24.0f); float largestExponent = std::pow(2.0f, float(31 - 24)); auto MakeRGB9E5 = [](uint32_t r, uint32_t g, uint32_t b, uint32_t e) { ASSERT((r & 0x1FF) == r); ASSERT((g & 0x1FF) == g); ASSERT((b & 0x1FF) == b); ASSERT((e & 0x1F) == e); return r | g << 9 | b << 18 | e << 27; }; // Test the smallest largest, and "1" exponents std::vector textureData = { MakeRGB9E5(0, 1, 2, 0b00000), MakeRGB9E5(2, 1, 0, 0b11111), MakeRGB9E5(0, 1, 2, 0b11000), }; // This is one of the only 3-channel formats, so we don't have specific testing for them. Alpha // should always be sampled as 1 // clang-format off std::vector uncompressedData = { 0.0f, smallestExponent, 2.0f * smallestExponent, 1.0f, 2.0f * largestExponent, largestExponent, 0.0f, 1.0f, 0.0f, 1.0f, 2.0f, 1.0f, }; // clang-format on DoFloatFormatSamplingTest( {wgpu::TextureFormat::RGB9E5Ufloat, 4, wgpu::TextureComponentType::Float, 4}, textureData, uncompressedData); // This format is not renderable. } DAWN_INSTANTIATE_TEST(TextureFormatTest, D3D12Backend(), MetalBackend(), OpenGLBackend(), OpenGLESBackend(), VulkanBackend());