// Copyright 2020 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 #include #include #include "dawn/common/Constants.h" #include "dawn/common/Math.h" #include "dawn/tests/DawnTest.h" #include "dawn/utils/ComboRenderPipelineDescriptor.h" #include "dawn/utils/TestUtils.h" #include "dawn/utils/TextureUtils.h" #include "dawn/utils/WGPUHelpers.h" namespace { using TextureFormat = wgpu::TextureFormat; DAWN_TEST_PARAM_STRUCT(DepthStencilCopyTestParams, TextureFormat); constexpr std::array kValidDepthCopyTextureFormats = { wgpu::TextureFormat::Depth16Unorm, wgpu::TextureFormat::Depth32Float, wgpu::TextureFormat::Depth32FloatStencil8, }; constexpr std::array kValidDepthCopyFromBufferFormats = { wgpu::TextureFormat::Depth16Unorm, }; } // namespace class DepthStencilCopyTests : public DawnTestWithParams { protected: void SetUp() override { DawnTestWithParams::SetUp(); DAWN_TEST_UNSUPPORTED_IF(!mIsFormatSupported); // Draw a square in the bottom left quarter of the screen. mVertexModule = utils::CreateShaderModule(device, R"( @vertex fn main(@builtin(vertex_index) VertexIndex : u32) -> @builtin(position) vec4 { var pos = array, 6>( vec2(-1.0, -1.0), vec2( 0.0, -1.0), vec2(-1.0, 0.0), vec2(-1.0, 0.0), vec2( 0.0, -1.0), vec2( 0.0, 0.0)); return vec4(pos[VertexIndex], 0.0, 1.0); })"); } std::vector GetRequiredFeatures() override { switch (GetParam().mTextureFormat) { case wgpu::TextureFormat::Depth32FloatStencil8: if (SupportsFeatures({wgpu::FeatureName::Depth32FloatStencil8})) { mIsFormatSupported = true; return {wgpu::FeatureName::Depth32FloatStencil8}; } return {}; default: mIsFormatSupported = true; return {}; } } bool IsValidDepthCopyTextureFormat() { switch (GetParam().mTextureFormat) { case wgpu::TextureFormat::Depth16Unorm: case wgpu::TextureFormat::Depth32Float: case wgpu::TextureFormat::Depth32FloatStencil8: return true; default: return false; } } wgpu::Texture CreateTexture(uint32_t width, uint32_t height, wgpu::TextureUsage usage, uint32_t mipLevelCount = 1) { wgpu::TextureDescriptor texDescriptor = {}; texDescriptor.size = {width, height, 1}; texDescriptor.format = GetParam().mTextureFormat; texDescriptor.usage = usage; texDescriptor.mipLevelCount = mipLevelCount; return device.CreateTexture(&texDescriptor); } wgpu::Texture CreateDepthStencilTexture(uint32_t width, uint32_t height, wgpu::TextureUsage usage, uint32_t mipLevelCount = 1) { wgpu::TextureDescriptor texDescriptor = {}; texDescriptor.size = {width, height, 1}; texDescriptor.format = GetParam().mTextureFormat; texDescriptor.usage = usage; texDescriptor.mipLevelCount = mipLevelCount; return device.CreateTexture(&texDescriptor); } wgpu::Texture CreateDepthTexture(uint32_t width, uint32_t height, wgpu::TextureUsage usage, uint32_t mipLevelCount = 1) { wgpu::TextureDescriptor texDescriptor = {}; texDescriptor.size = {width, height, 1}; texDescriptor.format = GetParam().mTextureFormat; texDescriptor.usage = usage; texDescriptor.mipLevelCount = mipLevelCount; return device.CreateTexture(&texDescriptor); } // Initialize the depth/stencil values for the texture using a render pass. // The texture will be cleared to the "clear" values, and then bottom left corner will // be written with the "region" values. void InitializeDepthStencilTextureRegion(wgpu::Texture texture, float clearDepth, float regionDepth, uint8_t clearStencil, uint8_t regionStencil, uint32_t mipLevel = 0) { wgpu::TextureFormat format = GetParam().mTextureFormat; // Create the render pass used for the initialization. utils::ComboRenderPipelineDescriptor renderPipelineDesc; renderPipelineDesc.vertex.module = mVertexModule; renderPipelineDesc.cFragment.targetCount = 0; wgpu::DepthStencilState* depthStencil = renderPipelineDesc.EnableDepthStencil(format); if (utils::IsStencilOnlyFormat(format)) { depthStencil->depthCompare = wgpu::CompareFunction::Always; renderPipelineDesc.cFragment.module = utils::CreateShaderModule(device, R"( @fragment fn main() {} )"); } else { depthStencil->depthWriteEnabled = true; renderPipelineDesc.cFragment.module = utils::CreateShaderModule(device, std::string(R"( @fragment fn main() -> @builtin(frag_depth) f32 { return )" + std::to_string(regionDepth) + R"(; })") .c_str()); } if (!utils::IsDepthOnlyFormat(format)) { depthStencil->stencilFront.passOp = wgpu::StencilOperation::Replace; } wgpu::RenderPipeline pipeline = device.CreateRenderPipeline(&renderPipelineDesc); // Build the render pass used for initialization. wgpu::TextureViewDescriptor viewDesc = {}; viewDesc.baseMipLevel = mipLevel; viewDesc.mipLevelCount = 1; utils::ComboRenderPassDescriptor renderPassDesc({}, texture.CreateView(&viewDesc)); renderPassDesc.UnsetDepthStencilLoadStoreOpsForFormat(format); renderPassDesc.cDepthStencilAttachmentInfo.depthClearValue = clearDepth; renderPassDesc.cDepthStencilAttachmentInfo.stencilClearValue = clearStencil; // Draw the quad (two triangles) wgpu::CommandEncoder commandEncoder = device.CreateCommandEncoder(); wgpu::RenderPassEncoder pass = commandEncoder.BeginRenderPass(&renderPassDesc); pass.SetPipeline(pipeline); pass.SetStencilReference(regionStencil); pass.Draw(6); pass.End(); wgpu::CommandBuffer commands = commandEncoder.Finish(); queue.Submit(1, &commands); } wgpu::Texture CreateInitializeDepthStencilTextureAndCopyT2T(float clearDepth, float regionDepth, uint8_t clearStencil, uint8_t regionStencil, uint32_t width, uint32_t height, wgpu::TextureUsage usage, uint32_t mipLevel = 0) { wgpu::Texture src = CreateDepthStencilTexture( width, height, wgpu::TextureUsage::RenderAttachment | wgpu::TextureUsage::CopySrc, mipLevel + 1); wgpu::Texture dst = CreateDepthStencilTexture( width, height, usage | wgpu::TextureUsage::CopyDst, mipLevel + 1); InitializeDepthStencilTextureRegion(src, clearDepth, regionDepth, clearStencil, regionStencil, mipLevel); // Perform a T2T copy of all aspects { wgpu::CommandEncoder commandEncoder = device.CreateCommandEncoder(); wgpu::ImageCopyTexture srcView = utils::CreateImageCopyTexture(src, mipLevel, {0, 0, 0}); wgpu::ImageCopyTexture dstView = utils::CreateImageCopyTexture(dst, mipLevel, {0, 0, 0}); wgpu::Extent3D copySize = {width >> mipLevel, height >> mipLevel, 1}; commandEncoder.CopyTextureToTexture(&srcView, &dstView, ©Size); wgpu::CommandBuffer commands = commandEncoder.Finish(); queue.Submit(1, &commands); } return dst; } uint32_t BufferSizeForTextureCopy(uint32_t width, uint32_t height, uint32_t depth, wgpu::TextureFormat format = wgpu::TextureFormat::RGBA8Unorm, wgpu::TextureAspect aspect = wgpu::TextureAspect::All) { uint32_t bytesPerPixel = 0; switch (format) { case wgpu::TextureFormat::Depth24PlusStencil8: { ASSERT(aspect == wgpu::TextureAspect::StencilOnly); bytesPerPixel = 1; break; } case wgpu::TextureFormat::Depth32FloatStencil8: { switch (aspect) { case wgpu::TextureAspect::DepthOnly: bytesPerPixel = 4; break; case wgpu::TextureAspect::StencilOnly: bytesPerPixel = 1; break; default: UNREACHABLE(); break; } break; } default: bytesPerPixel = utils::GetTexelBlockSizeInBytes(format); break; } uint32_t bytesPerRow = Align(width * bytesPerPixel, kTextureBytesPerRowAlignment); return (bytesPerRow * (height - 1) + width * bytesPerPixel) * depth; } wgpu::ShaderModule mVertexModule; private: bool mIsFormatSupported = false; }; // Test copying both aspects in a T2T copy, then copying only stencil. TEST_P(DepthStencilCopyTests, T2TBothAspectsThenCopyStencil) { // TODO(crbug.com/dawn/1497): glReadPixels: GL error: HIGH: Invalid format and type combination. DAWN_SUPPRESS_TEST_IF(IsANGLE()); // TODO(crbug.com/dawn/667): Work around some platforms' inability to read back stencil. DAWN_TEST_UNSUPPORTED_IF(HasToggleEnabled("disable_depth_stencil_read")); constexpr uint32_t kWidth = 4; constexpr uint32_t kHeight = 4; wgpu::Texture texture = CreateInitializeDepthStencilTextureAndCopyT2T( 0.1f, 0.3f, 1u, 3u, kWidth, kHeight, wgpu::TextureUsage::CopySrc | wgpu::TextureUsage::RenderAttachment); // Check the stencil std::vector expectedData = { 1u, 1u, 1u, 1u, // 1u, 1u, 1u, 1u, // 3u, 3u, 1u, 1u, // 3u, 3u, 1u, 1u, // }; EXPECT_TEXTURE_EQ(expectedData.data(), texture, {0, 0}, {kWidth, kHeight}, 0, wgpu::TextureAspect::StencilOnly); } // Test that part of a non-renderable stencil aspect can be copied. Notably, // this test has different behavior on some platforms than T2TBothAspectsThenCopyStencil. TEST_P(DepthStencilCopyTests, T2TBothAspectsThenCopyNonRenderableStencil) { // TODO(crbug.com/dawn/1497): glReadPixels: GL error: HIGH: Invalid format and type combination. DAWN_SUPPRESS_TEST_IF(IsANGLE()); // TODO(crbug.com/dawn/667): Work around some platforms' inability to read back stencil. DAWN_TEST_UNSUPPORTED_IF(HasToggleEnabled("disable_depth_stencil_read")); constexpr uint32_t kWidth = 4; constexpr uint32_t kHeight = 4; wgpu::Texture texture = CreateInitializeDepthStencilTextureAndCopyT2T( 0.1f, 0.3f, 1u, 3u, kWidth, kHeight, wgpu::TextureUsage::CopySrc); // Check the stencil std::vector expectedData = { 1u, 1u, 1u, 1u, // 1u, 1u, 1u, 1u, // 3u, 3u, 1u, 1u, // 3u, 3u, 1u, 1u, // }; EXPECT_TEXTURE_EQ(expectedData.data(), texture, {0, 0}, {kWidth, kHeight}, 0, wgpu::TextureAspect::StencilOnly); } // Test that part of a non-renderable, non-zero mip stencil aspect can be copied. Notably, // this test has different behavior on some platforms than T2TBothAspectsThenCopyStencil. TEST_P(DepthStencilCopyTests, T2TBothAspectsThenCopyNonRenderableNonZeroMipStencil) { // TODO(crbug.com/dawn/1497): glReadPixels: GL error: HIGH: Invalid format and type combination. DAWN_SUPPRESS_TEST_IF(IsANGLE()); // TODO(crbug.com/dawn/667): Work around some platforms' inability to read back stencil. DAWN_TEST_UNSUPPORTED_IF(HasToggleEnabled("disable_depth_stencil_read")); wgpu::Texture texture = CreateInitializeDepthStencilTextureAndCopyT2T( 0.1f, 0.3f, 1u, 3u, 9, 9, wgpu::TextureUsage::CopySrc, 1); // Check the stencil std::vector expectedData = { 1u, 1u, 1u, 1u, // 1u, 1u, 1u, 1u, // 3u, 3u, 1u, 1u, // 3u, 3u, 1u, 1u, // }; EXPECT_TEXTURE_EQ(expectedData.data(), texture, {0, 0}, {4, 4}, 1, wgpu::TextureAspect::StencilOnly); } // Test copying both aspects in a T2T copy, then copying only depth. TEST_P(DepthStencilCopyTests, T2TBothAspectsThenCopyDepth) { DAWN_TEST_UNSUPPORTED_IF(!IsValidDepthCopyTextureFormat()); constexpr uint32_t kWidth = 4; constexpr uint32_t kHeight = 4; wgpu::Texture texture = CreateInitializeDepthStencilTextureAndCopyT2T( 0.1f, 0.3f, 1u, 3u, kWidth, kHeight, wgpu::TextureUsage::RenderAttachment); // Check the depth ExpectAttachmentDepthTestData(texture, GetParam().mTextureFormat, kWidth, kHeight, 0, 0, { 0.1, 0.1, 0.1, 0.1, // 0.1, 0.1, 0.1, 0.1, // 0.3, 0.3, 0.1, 0.1, // 0.3, 0.3, 0.1, 0.1, // }); } // Test copying both aspects in a T2T copy, then copying only depth at a nonzero mip. TEST_P(DepthStencilCopyTests, T2TBothAspectsThenCopyNonZeroMipDepth) { DAWN_TEST_UNSUPPORTED_IF(!IsValidDepthCopyTextureFormat()); wgpu::Texture texture = CreateInitializeDepthStencilTextureAndCopyT2T( 0.1f, 0.3f, 1u, 3u, 8, 8, wgpu::TextureUsage::RenderAttachment, 1); // Check the depth ExpectAttachmentDepthTestData(texture, GetParam().mTextureFormat, 4, 4, 0, 1, { 0.1, 0.1, 0.1, 0.1, // 0.1, 0.1, 0.1, 0.1, // 0.3, 0.3, 0.1, 0.1, // 0.3, 0.3, 0.1, 0.1, // }); } // Test copying both aspects in a T2T copy, then copying stencil, then copying depth TEST_P(DepthStencilCopyTests, T2TBothAspectsThenCopyStencilThenDepth) { DAWN_TEST_UNSUPPORTED_IF(!IsValidDepthCopyTextureFormat()); // TODO(crbug.com/dawn/667): Work around some platforms' inability to read back stencil. DAWN_TEST_UNSUPPORTED_IF(HasToggleEnabled("disable_depth_stencil_read")); constexpr uint32_t kWidth = 4; constexpr uint32_t kHeight = 4; wgpu::Texture texture = CreateInitializeDepthStencilTextureAndCopyT2T( 0.1f, 0.3f, 1u, 3u, kWidth, kHeight, wgpu::TextureUsage::CopySrc | wgpu::TextureUsage::RenderAttachment); // Check the stencil std::vector expectedData = { 1u, 1u, 1u, 1u, // 1u, 1u, 1u, 1u, // 3u, 3u, 1u, 1u, // 3u, 3u, 1u, 1u, // }; EXPECT_TEXTURE_EQ(expectedData.data(), texture, {0, 0}, {kWidth, kHeight}, 0, wgpu::TextureAspect::StencilOnly); // Check the depth ExpectAttachmentDepthTestData(texture, GetParam().mTextureFormat, kWidth, kHeight, 0, 0, { 0.1, 0.1, 0.1, 0.1, // 0.1, 0.1, 0.1, 0.1, // 0.3, 0.3, 0.1, 0.1, // 0.3, 0.3, 0.1, 0.1, // }); } // Test copying both aspects in a T2T copy, then copying depth, then copying stencil TEST_P(DepthStencilCopyTests, T2TBothAspectsThenCopyDepthThenStencil) { DAWN_TEST_UNSUPPORTED_IF(!IsValidDepthCopyTextureFormat()); // TODO(crbug.com/dawn/667): Work around the fact that some platforms are unable to read // stencil. DAWN_TEST_UNSUPPORTED_IF(HasToggleEnabled("disable_depth_stencil_read")); constexpr uint32_t kWidth = 4; constexpr uint32_t kHeight = 4; wgpu::Texture texture = CreateInitializeDepthStencilTextureAndCopyT2T( 0.1f, 0.3f, 1u, 3u, kWidth, kHeight, wgpu::TextureUsage::CopySrc | wgpu::TextureUsage::RenderAttachment); // Check the depth ExpectAttachmentDepthTestData(texture, GetParam().mTextureFormat, kWidth, kHeight, 0, 0, { 0.1, 0.1, 0.1, 0.1, // 0.1, 0.1, 0.1, 0.1, // 0.3, 0.3, 0.1, 0.1, // 0.3, 0.3, 0.1, 0.1, // }); // Check the stencil std::vector expectedData = { 1u, 1u, 1u, 1u, // 1u, 1u, 1u, 1u, // 3u, 3u, 1u, 1u, // 3u, 3u, 1u, 1u, // }; EXPECT_TEXTURE_EQ(expectedData.data(), texture, {0, 0}, {kWidth, kHeight}, 0, wgpu::TextureAspect::StencilOnly); } class DepthCopyTests : public DepthStencilCopyTests { public: void DoCopyFromDepthTest(uint32_t bufferCopyOffset, float initDepth, uint32_t textureWidth, uint32_t textureHeight, uint32_t testLevel) { // TODO(crbug.com/dawn/1237): Depth16Unorm test failed on OpenGL and OpenGLES which says // Invalid format and type combination in glReadPixels DAWN_TEST_UNSUPPORTED_IF(GetParam().mTextureFormat == wgpu::TextureFormat::Depth16Unorm && (IsOpenGL() || IsOpenGLES())); // TODO(crbug.com/dawn/1291): These tests are failing on GLES (both native and ANGLE) // when using Tint/GLSL. DAWN_TEST_UNSUPPORTED_IF(IsOpenGLES()); uint32_t mipLevelCount = testLevel + 1; wgpu::Texture texture = CreateTexture( textureWidth, textureHeight, wgpu::TextureUsage::RenderAttachment | wgpu::TextureUsage::CopySrc, mipLevelCount); InitializeDepthStencilTextureRegion(texture, 0.f, initDepth, 0, 0, testLevel); uint32_t copyWidth = textureWidth >> testLevel; uint32_t copyHeight = textureHeight >> testLevel; wgpu::Extent3D copySize = {copyWidth, copyHeight, 1}; constexpr uint32_t kBytesPerRow = kTextureBytesPerRowAlignment; wgpu::BufferDescriptor bufferDescriptor = {}; bufferDescriptor.usage = wgpu::BufferUsage::CopySrc | wgpu::BufferUsage::CopyDst; bufferDescriptor.size = bufferCopyOffset + BufferSizeForTextureCopy(copyWidth, copyHeight, 1, GetParam().mTextureFormat, wgpu::TextureAspect::DepthOnly); wgpu::Buffer destinationBuffer = device.CreateBuffer(&bufferDescriptor); wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); wgpu::ImageCopyTexture imageCopyTexture = utils::CreateImageCopyTexture( texture, testLevel, {0, 0, 0}, wgpu::TextureAspect::DepthOnly); wgpu::ImageCopyBuffer imageCopyBuffer = utils::CreateImageCopyBuffer( destinationBuffer, bufferCopyOffset, kBytesPerRow, copyHeight); encoder.CopyTextureToBuffer(&imageCopyTexture, &imageCopyBuffer, ©Size); wgpu::CommandBuffer commandBuffer = encoder.Finish(); queue.Submit(1, &commandBuffer); if (GetParam().mTextureFormat == wgpu::TextureFormat::Depth16Unorm) { uint16_t expected = FloatToUnorm(initDepth); std::vector expectedData = { 0, 0, 0, 0, // 0, 0, 0, 0, // expected, expected, 0, 0, // expected, expected, 0, 0, // }; for (uint32_t y = 0; y < copyHeight; ++y) { EXPECT_BUFFER_U16_RANGE_EQ(expectedData.data() + copyWidth * y, destinationBuffer, bufferCopyOffset + y * kBytesPerRow, copyWidth); } } else { std::vector expectedData = { 0.0, 0.0, 0.0, 0.0, // 0.0, 0.0, 0.0, 0.0, // initDepth, initDepth, 0.0, 0.0, // initDepth, initDepth, 0.0, 0.0, // }; for (uint32_t y = 0; y < copyHeight; ++y) { EXPECT_BUFFER_FLOAT_RANGE_EQ(expectedData.data() + copyWidth * y, destinationBuffer, bufferCopyOffset + y * kBytesPerRow, copyWidth); } } } }; // Test copying the depth-only aspect into a buffer. TEST_P(DepthCopyTests, FromDepthAspect) { constexpr float kInitDepth = 0.2f; constexpr uint32_t kBufferCopyOffset = 0; constexpr uint32_t kWidth = 4; constexpr uint32_t kHeight = 4; constexpr uint32_t kTestLevel = 0; DoCopyFromDepthTest(kBufferCopyOffset, kInitDepth, kWidth, kHeight, kTestLevel); } // Test copying the depth-only aspect into a buffer at a non-zero offset. TEST_P(DepthCopyTests, FromDepthAspectToBufferAtNonZeroOffset) { constexpr float kInitDepth = 0.2f; constexpr uint32_t kWidth = 4; constexpr uint32_t kHeight = 4; constexpr uint32_t kTestLevel = 0; constexpr std::array kBufferCopyOffsets = {4u, 512u}; for (uint32_t offset : kBufferCopyOffsets) { DoCopyFromDepthTest(offset, kInitDepth, kWidth, kHeight, kTestLevel); } } // Test copying the non-zero mip, depth-only aspect into a buffer. TEST_P(DepthCopyTests, FromNonZeroMipDepthAspect) { constexpr float kInitDepth = 0.2f; constexpr uint32_t kBufferCopyOffset = 0; constexpr uint32_t kWidth = 9; constexpr uint32_t kHeight = 9; constexpr uint32_t kTestLevel = 1; DoCopyFromDepthTest(kBufferCopyOffset, kInitDepth, kWidth, kHeight, kTestLevel); } class DepthCopyFromBufferTests : public DepthStencilCopyTests { public: void DoTest(uint32_t bufferCopyOffset, bool hasRenderAttachmentUsage) { // TODO(crbug.com/dawn/1237): Depth16Unorm test failed on OpenGL and OpenGLES which says // Invalid format and type combination in glReadPixels DAWN_TEST_UNSUPPORTED_IF(GetParam().mTextureFormat == wgpu::TextureFormat::Depth16Unorm && (IsOpenGL() || IsOpenGLES())); constexpr uint32_t kWidth = 8; constexpr uint32_t kHeight = 1; wgpu::TextureUsage textureUsage = wgpu::TextureUsage::CopyDst | wgpu::TextureUsage::CopySrc; // On D3D12 backend the resource flag D3D12_RESOURCE_FLAG_ALLOW_DEPTH_STENCIL is set only // when wgpu::TextureUsage::RenderAttachment is set on the creation of a depth stencil // texture. if (hasRenderAttachmentUsage) { textureUsage |= wgpu::TextureUsage::RenderAttachment; } wgpu::Texture destTexture = CreateTexture(kWidth, kHeight, textureUsage); wgpu::BufferDescriptor descriptor; descriptor.size = bufferCopyOffset + BufferSizeForTextureCopy(kWidth, kHeight, 1, GetParam().mTextureFormat); descriptor.usage = wgpu::BufferUsage::CopySrc; descriptor.mappedAtCreation = true; wgpu::Buffer srcBuffer = device.CreateBuffer(&descriptor); constexpr uint32_t kBytesPerRow = kTextureBytesPerRowAlignment; wgpu::ImageCopyBuffer imageCopyBuffer = utils::CreateImageCopyBuffer(srcBuffer, bufferCopyOffset, kBytesPerRow, kHeight); wgpu::ImageCopyTexture imageCopyTexture = utils::CreateImageCopyTexture( destTexture, 0, {0, 0, 0}, wgpu::TextureAspect::DepthOnly); wgpu::Extent3D extent = {kWidth, kHeight, 1}; constexpr float kInitDepth = 0.2f; // This expectation is the test as it performs the CopyTextureToBuffer. if (GetParam().mTextureFormat == wgpu::TextureFormat::Depth16Unorm) { uint16_t expected = FloatToUnorm(kInitDepth); std::vector expectedData = { 0, 0, expected, expected, 0, 0, expected, expected, }; size_t expectedSize = expectedData.size() * sizeof(uint16_t); memcpy(srcBuffer.GetMappedRange(bufferCopyOffset, expectedSize), expectedData.data(), expectedSize); srcBuffer.Unmap(); wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); encoder.CopyBufferToTexture(&imageCopyBuffer, &imageCopyTexture, &extent); wgpu::CommandBuffer commands = encoder.Finish(); queue.Submit(1, &commands); EXPECT_TEXTURE_EQ(expectedData.data(), destTexture, {0, 0}, {kWidth, kHeight}, 0, wgpu::TextureAspect::DepthOnly); } else { std::vector expectedData = { 0.0, 0.0, kInitDepth, kInitDepth, 0.0, 0.0, kInitDepth, kInitDepth, }; size_t expectedSize = expectedData.size() * sizeof(float); memcpy(srcBuffer.GetMappedRange(bufferCopyOffset, expectedSize), expectedData.data(), expectedSize); srcBuffer.Unmap(); wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); encoder.CopyBufferToTexture(&imageCopyBuffer, &imageCopyTexture, &extent); wgpu::CommandBuffer commands = encoder.Finish(); queue.Submit(1, &commands); EXPECT_TEXTURE_EQ(expectedData.data(), destTexture, {0, 0}, {kWidth, kHeight}, 0, wgpu::TextureAspect::DepthOnly); } } }; // Test copying the depth-only aspect from a buffer. TEST_P(DepthCopyFromBufferTests, BufferToDepthAspect) { constexpr uint32_t kBufferCopyOffset = 0; constexpr bool kIsRenderable = false; DoTest(kBufferCopyOffset, kIsRenderable); } // Test copying the depth-only aspect from a buffer at a non-zero offset. TEST_P(DepthCopyFromBufferTests, BufferToNonRenderableDepthAspectAtNonZeroOffset) { constexpr std::array kBufferCopyOffsets = {8, 512}; constexpr bool kIsRenderable = false; for (uint32_t offset : kBufferCopyOffsets) { DoTest(offset, kIsRenderable); } } // Test copying the depth-only aspect from a buffer at a non-zero offset. TEST_P(DepthCopyFromBufferTests, BufferToRenderableDepthAspectAtNonZeroOffset) { constexpr std::array kBufferCopyOffsets = {8, 512}; constexpr bool kIsRenderable = true; for (uint32_t offset : kBufferCopyOffsets) { DoTest(offset, kIsRenderable); } } class StencilCopyTests : public DepthStencilCopyTests { public: void DoCopyFromStencilTest(uint32_t bufferCopyOffset, uint32_t textureWidth, uint32_t textureHeight, uint32_t testLevel) { // TODO(crbug.com/dawn/1497): glReadPixels: GL error: HIGH: Invalid format and type // combination. DAWN_SUPPRESS_TEST_IF(IsANGLE()); // TODO(crbug.com/dawn/667): Work around the fact that some platforms are unable to read // stencil. DAWN_TEST_UNSUPPORTED_IF(HasToggleEnabled("disable_depth_stencil_read")); uint32_t mipLevelCount = testLevel + 1; wgpu::Texture depthStencilTexture = CreateDepthStencilTexture( textureWidth, textureHeight, wgpu::TextureUsage::RenderAttachment | wgpu::TextureUsage::CopySrc, mipLevelCount); InitializeDepthStencilTextureRegion(depthStencilTexture, 0.f, 0.3f, 0u, 1u, testLevel); std::vector expectedData = { 0u, 0u, 0u, 0u, // 0u, 0u, 0u, 0u, // 1u, 1u, 0u, 0u, // 1u, 1u, 0u, 0u, // }; uint32_t copyWidth = textureWidth >> testLevel; uint32_t copyHeight = textureHeight >> testLevel; ASSERT_EQ(expectedData.size(), copyWidth * copyHeight); wgpu::Extent3D copySize = {copyWidth, copyHeight, 1}; constexpr uint32_t kBytesPerRow = kTextureBytesPerRowAlignment; wgpu::BufferDescriptor bufferDescriptor = {}; bufferDescriptor.usage = wgpu::BufferUsage::CopySrc | wgpu::BufferUsage::CopyDst; bufferDescriptor.size = bufferCopyOffset + BufferSizeForTextureCopy(copyWidth, copyHeight, 1, GetParam().mTextureFormat, wgpu::TextureAspect::StencilOnly); wgpu::Buffer destinationBuffer = device.CreateBuffer(&bufferDescriptor); wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); wgpu::ImageCopyTexture imageCopyTexture = utils::CreateImageCopyTexture( depthStencilTexture, testLevel, {0, 0, 0}, wgpu::TextureAspect::StencilOnly); wgpu::ImageCopyBuffer imageCopyBuffer = utils::CreateImageCopyBuffer( destinationBuffer, bufferCopyOffset, kBytesPerRow, copyHeight); encoder.CopyTextureToBuffer(&imageCopyTexture, &imageCopyBuffer, ©Size); wgpu::CommandBuffer commandBuffer = encoder.Finish(); queue.Submit(1, &commandBuffer); for (uint32_t y = 0; y < copyHeight; ++y) { EXPECT_BUFFER_U8_RANGE_EQ(expectedData.data() + copyWidth * y, destinationBuffer, bufferCopyOffset + y * kBytesPerRow, copyWidth); } } void DoCopyToStencilTest(uint32_t bufferCopyOffset) { // Copies to a single aspect are unsupported on OpenGL. DAWN_TEST_UNSUPPORTED_IF(IsOpenGL()); DAWN_TEST_UNSUPPORTED_IF(IsOpenGLES()); // TODO(crbug.com/dawn/1273): Fails on Win11 with D3D12 debug layer and full validation DAWN_SUPPRESS_TEST_IF(IsD3D12() && IsBackendValidationEnabled()); // Create a stencil texture constexpr uint32_t kWidth = 4; constexpr uint32_t kHeight = 4; const bool hasDepth = !utils::IsStencilOnlyFormat(GetParam().mTextureFormat); wgpu::Texture depthStencilTexture = CreateDepthStencilTexture( kWidth, kHeight, wgpu::TextureUsage::RenderAttachment | wgpu::TextureUsage::CopySrc | wgpu::TextureUsage::CopyDst); if (hasDepth) { wgpu::CommandEncoder commandEncoder = device.CreateCommandEncoder(); // Clear depth to 0.7, so we can check that the stencil copy doesn't mutate the depth. utils::ComboRenderPassDescriptor passDescriptor({}, depthStencilTexture.CreateView()); passDescriptor.UnsetDepthStencilLoadStoreOpsForFormat(GetParam().mTextureFormat); passDescriptor.cDepthStencilAttachmentInfo.depthClearValue = 0.7; wgpu::RenderPassEncoder pass = commandEncoder.BeginRenderPass(&passDescriptor); pass.End(); wgpu::CommandBuffer commands = commandEncoder.Finish(); queue.Submit(1, &commands); } std::vector stencilData = { 1u, 2u, 3u, 4u, // 5u, 6u, 7u, 8u, // 9u, 10u, 11u, 12u, // 13u, 14u, 15u, 16u, // }; // After copying stencil data in, we will decrement stencil values in the bottom left // of the screen. This is the expected result. std::vector expectedStencilData = { 1u, 2u, 3u, 4u, // 5u, 6u, 7u, 8u, // 8u, 9u, 11u, 12u, // 12u, 13u, 15u, 16u, // }; // Upload the stencil data. { wgpu::BufferDescriptor descriptor; descriptor.size = bufferCopyOffset + BufferSizeForTextureCopy( kWidth, kHeight, 1, GetParam().mTextureFormat, wgpu::TextureAspect::StencilOnly); descriptor.usage = wgpu::BufferUsage::CopySrc; descriptor.mappedAtCreation = true; wgpu::Buffer srcBuffer = device.CreateBuffer(&descriptor); uint8_t* mappedPtr = static_cast(srcBuffer.GetMappedRange(bufferCopyOffset)); constexpr uint32_t kBytesPerRow = kTextureBytesPerRowAlignment; for (uint32_t y = 0; y < kHeight; ++y) { memcpy(mappedPtr + y * kBytesPerRow, stencilData.data() + y * kWidth, kWidth); } srcBuffer.Unmap(); wgpu::CommandEncoder commandEncoder = device.CreateCommandEncoder(); wgpu::ImageCopyBuffer imageCopyBuffer = utils::CreateImageCopyBuffer(srcBuffer, bufferCopyOffset, kBytesPerRow, kHeight); wgpu::ImageCopyTexture imageCopyTexture = utils::CreateImageCopyTexture( depthStencilTexture, 0, {0, 0, 0}, wgpu::TextureAspect::StencilOnly); wgpu::Extent3D copySize = {kWidth, kHeight, 1}; commandEncoder.CopyBufferToTexture(&imageCopyBuffer, &imageCopyTexture, ©Size); wgpu::CommandBuffer commandBuffer = commandEncoder.Finish(); queue.Submit(1, &commandBuffer); } // Decrement the stencil value in a render pass to ensure the data is visible to the // pipeline. { wgpu::CommandEncoder commandEncoder = device.CreateCommandEncoder(); // Create a render pipline which decrements the stencil value for passing fragments. // A quad is drawn in the bottom left. utils::ComboRenderPipelineDescriptor renderPipelineDesc; renderPipelineDesc.vertex.module = mVertexModule; renderPipelineDesc.cFragment.module = utils::CreateShaderModule(device, R"( @fragment fn main() { })"); renderPipelineDesc.cFragment.targetCount = 0; wgpu::DepthStencilState* depthStencil = renderPipelineDesc.EnableDepthStencil(GetParam().mTextureFormat); depthStencil->stencilFront.passOp = wgpu::StencilOperation::DecrementClamp; if (!hasDepth) { depthStencil->depthWriteEnabled = false; depthStencil->depthCompare = wgpu::CompareFunction::Always; } wgpu::RenderPipeline pipeline = device.CreateRenderPipeline(&renderPipelineDesc); // Create a render pass which loads the stencil. We want to load the values we // copied in. Also load the canary depth values so they're not lost. utils::ComboRenderPassDescriptor passDescriptor({}, depthStencilTexture.CreateView()); passDescriptor.cDepthStencilAttachmentInfo.stencilLoadOp = wgpu::LoadOp::Load; passDescriptor.cDepthStencilAttachmentInfo.depthLoadOp = wgpu::LoadOp::Load; passDescriptor.UnsetDepthStencilLoadStoreOpsForFormat(GetParam().mTextureFormat); // Draw the quad in the bottom left (two triangles). wgpu::RenderPassEncoder pass = commandEncoder.BeginRenderPass(&passDescriptor); pass.SetPipeline(pipeline); pass.Draw(6); pass.End(); wgpu::CommandBuffer commands = commandEncoder.Finish(); queue.Submit(1, &commands); } // Copy back the stencil data and check it is correct. EXPECT_TEXTURE_EQ(expectedStencilData.data(), depthStencilTexture, {0, 0}, {kWidth, kHeight}, 0, wgpu::TextureAspect::StencilOnly); if (hasDepth) { ExpectAttachmentDepthTestData(depthStencilTexture, GetParam().mTextureFormat, kWidth, kHeight, 0, 0, { 0.7, 0.7, 0.7, 0.7, // 0.7, 0.7, 0.7, 0.7, // 0.7, 0.7, 0.7, 0.7, // 0.7, 0.7, 0.7, 0.7, // }); } } }; // Test copying the stencil-only aspect into a buffer. TEST_P(StencilCopyTests, FromStencilAspect) { constexpr uint32_t kWidth = 4; constexpr uint32_t kHeight = 4; constexpr uint32_t kTestLevel = 0; constexpr uint32_t kBufferCopyOffset = 0; DoCopyFromStencilTest(kBufferCopyOffset, kWidth, kHeight, kTestLevel); } // Test copying the stencil-only aspect into a buffer at a non-zero offset TEST_P(StencilCopyTests, FromStencilAspectAtNonZeroOffset) { constexpr uint32_t kWidth = 4; constexpr uint32_t kHeight = 4; constexpr uint32_t kTestLevel = 0; constexpr std::array kBufferCopyOffsets = {4u, 512u}; for (uint32_t offset : kBufferCopyOffsets) { DoCopyFromStencilTest(offset, kWidth, kHeight, kTestLevel); } } // Test copying the non-zero mip, stencil-only aspect into a buffer. TEST_P(StencilCopyTests, FromNonZeroMipStencilAspect) { constexpr uint32_t kWidth = 9; constexpr uint32_t kHeight = 9; constexpr uint32_t kTestLevel = 1; constexpr uint32_t kBufferCopyOffset = 0; DoCopyFromStencilTest(kBufferCopyOffset, kWidth, kHeight, kTestLevel); } // Test copying to the stencil-aspect of a texture TEST_P(StencilCopyTests, ToStencilAspect) { constexpr uint32_t kBufferCopyOffset = 0; DoCopyToStencilTest(kBufferCopyOffset); } // Test copying to the stencil-aspect of a texture at non-zero offset TEST_P(StencilCopyTests, ToStencilAspectAtNonZeroOffset) { constexpr std::array kBufferCopyOffsets = {8, 512}; for (uint32_t offset : kBufferCopyOffsets) { DoCopyToStencilTest(offset); } } // Test uploading to the non-zero mip, stencil-only aspect of a texture, // and then checking the contents with a stencil test. TEST_P(StencilCopyTests, CopyNonzeroMipThenReadWithStencilTest) { // Copies to a single aspect are unsupported on OpenGL. DAWN_TEST_UNSUPPORTED_IF(IsOpenGL()); DAWN_TEST_UNSUPPORTED_IF(IsOpenGLES()); // Create a stencil texture constexpr uint32_t kWidth = 4; constexpr uint32_t kHeight = 4; constexpr uint32_t kMipLevel = 1; wgpu::Texture depthStencilTexture = CreateDepthStencilTexture(kWidth, kHeight, wgpu::TextureUsage::RenderAttachment | wgpu::TextureUsage::CopySrc | wgpu::TextureUsage::CopyDst, kMipLevel + 1); std::vector stencilData = { 7u, 7u, // 7u, 7u, // }; // Upload the stencil data. { wgpu::TextureDataLayout dataLayout = {}; dataLayout.bytesPerRow = kWidth >> kMipLevel; wgpu::ImageCopyTexture imageCopyTexture = utils::CreateImageCopyTexture( depthStencilTexture, 1, {0, 0, 0}, wgpu::TextureAspect::StencilOnly); wgpu::Extent3D copySize = {kWidth >> kMipLevel, kHeight >> kMipLevel, 1}; queue.WriteTexture(&imageCopyTexture, stencilData.data(), stencilData.size(), &dataLayout, ©Size); } // Check the stencil contents. ExpectAttachmentStencilTestData(depthStencilTexture, GetParam().mTextureFormat, kWidth >> kMipLevel, kWidth >> kMipLevel, 0u, kMipLevel, 7u); } DAWN_INSTANTIATE_TEST_P(DepthStencilCopyTests, {D3D12Backend(), MetalBackend(), MetalBackend({"use_blit_for_buffer_to_depth_texture_copy", "use_blit_for_buffer_to_stencil_texture_copy"}), OpenGLBackend(), OpenGLESBackend(), // Test with the vulkan_use_s8 toggle forced on and off. VulkanBackend({"vulkan_use_s8"}, {}), VulkanBackend({}, {"vulkan_use_s8"})}, std::vector(utils::kDepthAndStencilFormats.begin(), utils::kDepthAndStencilFormats.end())); DAWN_INSTANTIATE_TEST_P(DepthCopyTests, {D3D12Backend(), D3D12Backend({"d3d12_use_temp_buffer_in_depth_stencil_texture_and_buffer_" "copy_with_non_zero_buffer_offset"}), MetalBackend(), OpenGLBackend(), OpenGLESBackend(), VulkanBackend()}, std::vector(kValidDepthCopyTextureFormats.begin(), kValidDepthCopyTextureFormats.end())); DAWN_INSTANTIATE_TEST_P(DepthCopyFromBufferTests, {D3D12Backend(), D3D12Backend({"d3d12_use_temp_buffer_in_depth_stencil_texture_and_buffer_" "copy_with_non_zero_buffer_offset"}), MetalBackend(), MetalBackend({"use_blit_for_buffer_to_depth_texture_copy"}), OpenGLBackend(), OpenGLESBackend(), VulkanBackend()}, std::vector(kValidDepthCopyFromBufferFormats.begin(), kValidDepthCopyFromBufferFormats.end())); DAWN_INSTANTIATE_TEST_P( StencilCopyTests, {D3D12Backend(), D3D12Backend({"d3d12_use_temp_buffer_in_depth_stencil_texture_and_buffer_" "copy_with_non_zero_buffer_offset"}), MetalBackend(), MetalBackend({"metal_use_combined_depth_stencil_format_for_stencil8"}), MetalBackend( {"metal_use_both_depth_and_stencil_attachments_for_combined_depth_stencil_formats"}), MetalBackend({"use_blit_for_buffer_to_stencil_texture_copy"}), OpenGLBackend(), OpenGLESBackend(), // Test with the vulkan_use_s8 toggle forced on and off. VulkanBackend({"vulkan_use_s8"}, {}), VulkanBackend({}, {"vulkan_use_s8"})}, std::vector(utils::kStencilFormats.begin(), utils::kStencilFormats.end()));