| // Copyright 2021 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 <algorithm> |
| #include <vector> |
| |
| #include "dawn/tests/DawnTest.h" |
| #include "dawn/utils/ComboRenderPipelineDescriptor.h" |
| #include "dawn/utils/WGPUHelpers.h" |
| |
| namespace dawn { |
| namespace { |
| |
| using Format = wgpu::TextureFormat; |
| enum class Check { |
| CopyStencil, |
| StencilTest, |
| CopyDepth, |
| DepthTest, |
| SampleDepth, |
| }; |
| |
| std::ostream& operator<<(std::ostream& o, Check check) { |
| switch (check) { |
| case Check::CopyStencil: |
| o << "CopyStencil"; |
| break; |
| case Check::StencilTest: |
| o << "StencilTest"; |
| break; |
| case Check::CopyDepth: |
| o << "CopyDepth"; |
| break; |
| case Check::DepthTest: |
| o << "DepthTest"; |
| break; |
| case Check::SampleDepth: |
| o << "SampleDepth"; |
| break; |
| } |
| return o; |
| } |
| |
| DAWN_TEST_PARAM_STRUCT(DepthStencilLoadOpTestParams, Format, Check); |
| |
| constexpr static uint32_t kRTSize = 16; |
| constexpr uint32_t kMipLevelCount = 2u; |
| constexpr std::array<float, kMipLevelCount> kDepthValues = {0.125f, 0.875f}; |
| constexpr std::array<uint16_t, kMipLevelCount> kU16DepthValues = {8192u, 57343u}; |
| constexpr std::array<uint8_t, kMipLevelCount> kStencilValues = {7u, 3u}; |
| |
| class DepthStencilLoadOpTests : public DawnTestWithParams<DepthStencilLoadOpTestParams> { |
| protected: |
| void SetUp() override { |
| DawnTestWithParams<DepthStencilLoadOpTestParams>::SetUp(); |
| |
| DAWN_TEST_UNSUPPORTED_IF(!mIsFormatSupported); |
| |
| // Readback of Depth/Stencil textures not fully supported on GL right now. |
| // Also depends on glTextureView which is not supported on ES. |
| DAWN_SUPPRESS_TEST_IF(IsOpenGL() || IsOpenGLES()); |
| |
| wgpu::TextureDescriptor descriptor; |
| descriptor.size = {kRTSize, kRTSize}; |
| descriptor.format = GetParam().mFormat; |
| descriptor.mipLevelCount = kMipLevelCount; |
| descriptor.usage = wgpu::TextureUsage::RenderAttachment | wgpu::TextureUsage::CopySrc | |
| wgpu::TextureUsage::TextureBinding; |
| |
| texture = device.CreateTexture(&descriptor); |
| |
| wgpu::TextureViewDescriptor textureViewDesc = {}; |
| textureViewDesc.mipLevelCount = 1; |
| |
| for (uint32_t mipLevel = 0; mipLevel < kMipLevelCount; ++mipLevel) { |
| textureViewDesc.baseMipLevel = mipLevel; |
| textureViews[mipLevel] = texture.CreateView(&textureViewDesc); |
| |
| utils::ComboRenderPassDescriptor renderPassDescriptor({}, textureViews[mipLevel]); |
| renderPassDescriptor.UnsetDepthStencilLoadStoreOpsForFormat(GetParam().mFormat); |
| renderPassDescriptor.cDepthStencilAttachmentInfo.depthClearValue = |
| kDepthValues[mipLevel]; |
| renderPassDescriptor.cDepthStencilAttachmentInfo.stencilClearValue = |
| kStencilValues[mipLevel]; |
| renderPassDescriptors.push_back(renderPassDescriptor); |
| } |
| } |
| |
| std::vector<wgpu::FeatureName> GetRequiredFeatures() override { |
| switch (GetParam().mFormat) { |
| case wgpu::TextureFormat::Depth32FloatStencil8: |
| if (SupportsFeatures({wgpu::FeatureName::Depth32FloatStencil8})) { |
| mIsFormatSupported = true; |
| return {wgpu::FeatureName::Depth32FloatStencil8}; |
| } |
| return {}; |
| default: |
| mIsFormatSupported = true; |
| return {}; |
| } |
| } |
| |
| void CheckMipLevel(uint32_t mipLevel) { |
| uint32_t mipSize = std::max(kRTSize >> mipLevel, 1u); |
| |
| switch (GetParam().mCheck) { |
| case Check::SampleDepth: { |
| DAWN_TEST_UNSUPPORTED_IF(utils::IsStencilOnlyFormat(GetParam().mFormat)); |
| |
| std::vector<float> expectedDepth(mipSize * mipSize, kDepthValues[mipLevel]); |
| ExpectSampledDepthData( |
| texture, mipSize, mipSize, 0, mipLevel, |
| new detail::ExpectEq<float>(expectedDepth.data(), expectedDepth.size(), 0.0001)) |
| << "sample depth mip " << mipLevel; |
| break; |
| } |
| |
| case Check::CopyDepth: { |
| DAWN_TEST_UNSUPPORTED_IF(utils::IsStencilOnlyFormat(GetParam().mFormat)); |
| |
| if (GetParam().mFormat == wgpu::TextureFormat::Depth16Unorm) { |
| std::vector<uint16_t> expectedDepth(mipSize * mipSize, |
| kU16DepthValues[mipLevel]); |
| EXPECT_TEXTURE_EQ(expectedDepth.data(), texture, {0, 0}, {mipSize, mipSize}, |
| mipLevel, wgpu::TextureAspect::DepthOnly, |
| /* bytesPerRow */ 0, /* tolerance */ uint16_t(1)) |
| << "copy depth mip " << mipLevel; |
| } else { |
| std::vector<float> expectedDepth(mipSize * mipSize, kDepthValues[mipLevel]); |
| EXPECT_TEXTURE_EQ(expectedDepth.data(), texture, {0, 0}, {mipSize, mipSize}, |
| mipLevel, wgpu::TextureAspect::DepthOnly) |
| << "copy depth mip " << mipLevel; |
| } |
| |
| break; |
| } |
| |
| case Check::CopyStencil: { |
| std::vector<uint8_t> expectedStencil(mipSize * mipSize, kStencilValues[mipLevel]); |
| EXPECT_TEXTURE_EQ(expectedStencil.data(), texture, {0, 0}, {mipSize, mipSize}, |
| mipLevel, wgpu::TextureAspect::StencilOnly) |
| << "copy stencil mip " << mipLevel; |
| break; |
| } |
| |
| case Check::DepthTest: { |
| DAWN_TEST_UNSUPPORTED_IF(utils::IsStencilOnlyFormat(GetParam().mFormat)); |
| |
| std::vector<float> expectedDepth(mipSize * mipSize, kDepthValues[mipLevel]); |
| ExpectAttachmentDepthTestData(texture, GetParam().mFormat, mipSize, mipSize, 0, |
| mipLevel, expectedDepth) |
| << "depth test mip " << mipLevel; |
| break; |
| } |
| |
| case Check::StencilTest: { |
| ExpectAttachmentStencilTestData(texture, GetParam().mFormat, mipSize, mipSize, 0, |
| mipLevel, kStencilValues[mipLevel]) |
| << "stencil test mip " << mipLevel; |
| break; |
| } |
| } |
| } |
| |
| wgpu::Texture texture; |
| std::array<wgpu::TextureView, kMipLevelCount> textureViews; |
| // Vector instead of array because there is no default constructor. |
| std::vector<utils::ComboRenderPassDescriptor> renderPassDescriptors; |
| |
| private: |
| bool mIsFormatSupported = false; |
| }; |
| |
| } // anonymous namespace |
| |
| // Check that clearing a mip level works at all. |
| TEST_P(DepthStencilLoadOpTests, ClearMip0) { |
| // TODO(crbug.com/dawn/1828): depth16unorm broken on Apple GPUs. |
| DAWN_SUPPRESS_TEST_IF(IsApple() && GetParam().mFormat == wgpu::TextureFormat::Depth16Unorm); |
| |
| wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); |
| encoder.BeginRenderPass(&renderPassDescriptors[0]).End(); |
| wgpu::CommandBuffer commandBuffer = encoder.Finish(); |
| queue.Submit(1, &commandBuffer); |
| |
| CheckMipLevel(0u); |
| } |
| |
| // Check that clearing a non-zero mip level works at all. |
| TEST_P(DepthStencilLoadOpTests, ClearMip1) { |
| wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); |
| encoder.BeginRenderPass(&renderPassDescriptors[1]).End(); |
| wgpu::CommandBuffer commandBuffer = encoder.Finish(); |
| queue.Submit(1, &commandBuffer); |
| |
| CheckMipLevel(1u); |
| } |
| |
| // Clear first mip then the second mip. Check both mip levels. |
| TEST_P(DepthStencilLoadOpTests, ClearBothMip0Then1) { |
| // TODO(crbug.com/dawn/1828): depth16unorm broken on Apple GPUs. |
| DAWN_SUPPRESS_TEST_IF(IsApple() && GetParam().mFormat == wgpu::TextureFormat::Depth16Unorm); |
| |
| wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); |
| encoder.BeginRenderPass(&renderPassDescriptors[0]).End(); |
| encoder.BeginRenderPass(&renderPassDescriptors[1]).End(); |
| wgpu::CommandBuffer commandBuffer = encoder.Finish(); |
| queue.Submit(1, &commandBuffer); |
| |
| CheckMipLevel(0u); |
| CheckMipLevel(1u); |
| } |
| |
| // Clear second mip then the first mip. Check both mip levels. |
| TEST_P(DepthStencilLoadOpTests, ClearBothMip1Then0) { |
| // TODO(crbug.com/dawn/1828): depth16unorm broken on Apple GPUs. |
| DAWN_SUPPRESS_TEST_IF(IsApple() && GetParam().mFormat == wgpu::TextureFormat::Depth16Unorm); |
| |
| wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); |
| encoder.BeginRenderPass(&renderPassDescriptors[1]).End(); |
| encoder.BeginRenderPass(&renderPassDescriptors[0]).End(); |
| wgpu::CommandBuffer commandBuffer = encoder.Finish(); |
| queue.Submit(1, &commandBuffer); |
| |
| CheckMipLevel(0u); |
| CheckMipLevel(1u); |
| } |
| |
| namespace { |
| |
| auto GenerateParam() { |
| auto params1 = MakeParamGenerator<DepthStencilLoadOpTestParams>( |
| {D3D12Backend(), D3D12Backend({}, {"use_d3d12_render_pass"}), MetalBackend(), |
| OpenGLBackend(), OpenGLESBackend(), VulkanBackend()}, |
| {wgpu::TextureFormat::Depth32Float, wgpu::TextureFormat::Depth16Unorm}, |
| {Check::CopyDepth, Check::DepthTest, Check::SampleDepth}); |
| |
| auto params2 = MakeParamGenerator<DepthStencilLoadOpTestParams>( |
| {D3D12Backend(), D3D12Backend({}, {"use_d3d12_render_pass"}), MetalBackend(), |
| MetalBackend({"metal_use_combined_depth_stencil_format_for_stencil8"}), |
| MetalBackend( |
| {"metal_use_both_depth_and_stencil_attachments_for_combined_depth_stencil_formats"}), |
| OpenGLBackend(), OpenGLESBackend(), VulkanBackend()}, |
| {wgpu::TextureFormat::Depth24PlusStencil8, wgpu::TextureFormat::Depth32FloatStencil8, |
| wgpu::TextureFormat::Stencil8}, |
| {Check::CopyStencil, Check::StencilTest, Check::DepthTest, Check::SampleDepth}); |
| |
| std::vector<DepthStencilLoadOpTestParams> allParams; |
| allParams.insert(allParams.end(), params1.begin(), params1.end()); |
| allParams.insert(allParams.end(), params2.begin(), params2.end()); |
| |
| return allParams; |
| } |
| |
| INSTANTIATE_TEST_SUITE_P(, |
| DepthStencilLoadOpTests, |
| ::testing::ValuesIn(GenerateParam()), |
| DawnTestBase::PrintToStringParamName("DepthStencilLoadOpTests")); |
| GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(DepthStencilLoadOpTests); |
| |
| class StencilClearValueOverflowTest : public DepthStencilLoadOpTests {}; |
| |
| // Test when stencilClearValue overflows uint8_t (>255), only the last 8 bits will be applied as the |
| // stencil clear value in encoder.BeginRenderPass() (currently Dawn only supports 8-bit stencil |
| // format). |
| TEST_P(StencilClearValueOverflowTest, StencilClearValueOverFlowUint8) { |
| constexpr uint32_t kOverflowedStencilValue = kStencilValues[0] + 0x100; |
| renderPassDescriptors[0].cDepthStencilAttachmentInfo.stencilClearValue = |
| kOverflowedStencilValue; |
| |
| wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); |
| encoder.BeginRenderPass(&renderPassDescriptors[0]).End(); |
| wgpu::CommandBuffer commandBuffer = encoder.Finish(); |
| queue.Submit(1, &commandBuffer); |
| |
| CheckMipLevel(0u); |
| } |
| |
| // Test when stencilClearValue overflows uint16_t(>65535), only the last 8 bits will be applied as |
| // the stencil clear value in encoder.BeginRenderPass() (currently Dawn only supports 8-bit stencil |
| // format). |
| TEST_P(StencilClearValueOverflowTest, StencilClearValueOverFlowUint16) { |
| constexpr uint32_t kOverflowedStencilValue = kStencilValues[0] + 0x10000; |
| renderPassDescriptors[0].cDepthStencilAttachmentInfo.stencilClearValue = |
| kOverflowedStencilValue; |
| |
| wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); |
| encoder.BeginRenderPass(&renderPassDescriptors[0]).End(); |
| wgpu::CommandBuffer commandBuffer = encoder.Finish(); |
| queue.Submit(1, &commandBuffer); |
| |
| CheckMipLevel(0u); |
| } |
| |
| DAWN_INSTANTIATE_TEST_P(StencilClearValueOverflowTest, |
| {D3D12Backend(), D3D12Backend({}, {"use_d3d12_render_pass"}), |
| MetalBackend(), OpenGLBackend(), OpenGLESBackend(), VulkanBackend()}, |
| {wgpu::TextureFormat::Depth24PlusStencil8, |
| wgpu::TextureFormat::Depth32FloatStencil8, wgpu::TextureFormat::Stencil8}, |
| {Check::CopyStencil, Check::StencilTest}); |
| |
| // Regression tests to reproduce a flaky failure when running whole WebGPU CTS on Intel Gen12 GPUs. |
| // See crbug.com/dawn/1487 for more details. |
| using SupportsTextureBinding = bool; |
| DAWN_TEST_PARAM_STRUCT(DepthTextureClearTwiceTestParams, Format, SupportsTextureBinding); |
| |
| class DepthTextureClearTwiceTest : public DawnTestWithParams<DepthTextureClearTwiceTestParams> { |
| public: |
| void RecordClearDepthAspectAtLevel(wgpu::CommandEncoder encoder, |
| wgpu::Texture depthTexture, |
| uint32_t level, |
| float clearValue) { |
| wgpu::TextureViewDescriptor viewDescriptor = {}; |
| viewDescriptor.baseArrayLayer = 0; |
| viewDescriptor.arrayLayerCount = 1; |
| viewDescriptor.baseMipLevel = level; |
| viewDescriptor.mipLevelCount = 1; |
| wgpu::TextureView view = depthTexture.CreateView(&viewDescriptor); |
| |
| wgpu::RenderPassDescriptor renderPassDescriptor = {}; |
| renderPassDescriptor.colorAttachmentCount = 0; |
| |
| wgpu::RenderPassDepthStencilAttachment depthStencilAttachment = {}; |
| depthStencilAttachment.view = view; |
| depthStencilAttachment.depthClearValue = clearValue; |
| depthStencilAttachment.depthLoadOp = wgpu::LoadOp::Clear; |
| depthStencilAttachment.depthStoreOp = wgpu::StoreOp::Store; |
| |
| if (!utils::IsDepthOnlyFormat(GetParam().mFormat)) { |
| depthStencilAttachment.stencilLoadOp = wgpu::LoadOp::Load; |
| depthStencilAttachment.stencilStoreOp = wgpu::StoreOp::Store; |
| } |
| |
| renderPassDescriptor.depthStencilAttachment = &depthStencilAttachment; |
| wgpu::RenderPassEncoder renderPass = encoder.BeginRenderPass(&renderPassDescriptor); |
| renderPass.End(); |
| } |
| |
| protected: |
| std::vector<wgpu::FeatureName> GetRequiredFeatures() override { |
| switch (GetParam().mFormat) { |
| case wgpu::TextureFormat::Depth32FloatStencil8: |
| if (SupportsFeatures({wgpu::FeatureName::Depth32FloatStencil8})) { |
| mIsFormatSupported = true; |
| return {wgpu::FeatureName::Depth32FloatStencil8}; |
| } |
| return {}; |
| default: |
| mIsFormatSupported = true; |
| return {}; |
| } |
| } |
| |
| bool mIsFormatSupported = false; |
| }; |
| |
| TEST_P(DepthTextureClearTwiceTest, ClearDepthAspectTwice) { |
| DAWN_SUPPRESS_TEST_IF(!mIsFormatSupported); |
| |
| constexpr uint32_t kSize = 64; |
| constexpr uint32_t kLevelCount = 5; |
| |
| wgpu::TextureFormat depthFormat = GetParam().mFormat; |
| |
| wgpu::TextureDescriptor descriptor; |
| descriptor.size = {kSize, kSize}; |
| descriptor.format = depthFormat; |
| descriptor.mipLevelCount = kLevelCount; |
| |
| // The toggle "d3d12_force_initialize_copyable_depth_stencil_texture_on_creation" is not related |
| // to this test as we don't specify wgpu::TextureUsage::CopyDst in the test. |
| descriptor.usage = wgpu::TextureUsage::RenderAttachment; |
| if (GetParam().mSupportsTextureBinding) { |
| descriptor.usage |= wgpu::TextureUsage::TextureBinding; |
| } |
| wgpu::Texture depthTexture = device.CreateTexture(&descriptor); |
| |
| // First, clear all the subresources to 0. |
| { |
| constexpr float kClearValue = 0.f; |
| wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); |
| for (uint32_t level = 0; level < kLevelCount; ++level) { |
| RecordClearDepthAspectAtLevel(encoder, depthTexture, level, kClearValue); |
| } |
| wgpu::CommandBuffer commandBuffer = encoder.Finish(); |
| queue.Submit(1, &commandBuffer); |
| } |
| |
| // Then, clear several mipmap levels to 0.8. |
| { |
| constexpr float kClearValue = 0.8f; |
| constexpr std::array<uint32_t, 2> kLevelsToSet = {2u, 4u}; |
| wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); |
| for (uint32_t level : kLevelsToSet) { |
| RecordClearDepthAspectAtLevel(encoder, depthTexture, level, kClearValue); |
| } |
| wgpu::CommandBuffer commandBuffer = encoder.Finish(); |
| queue.Submit(1, &commandBuffer); |
| } |
| |
| // Check if the data in remaining mipmap levels is still 0. |
| { |
| constexpr std::array<uint32_t, 3> kLevelsToTest = {0, 1u, 3u}; |
| for (uint32_t level : kLevelsToTest) { |
| uint32_t sizeAtLevel = kSize >> level; |
| std::vector<float> expectedValue(sizeAtLevel * sizeAtLevel, 0.f); |
| ExpectAttachmentDepthTestData(depthTexture, GetParam().mFormat, sizeAtLevel, |
| sizeAtLevel, 0, level, expectedValue); |
| } |
| } |
| } |
| |
| DAWN_INSTANTIATE_TEST_P(DepthTextureClearTwiceTest, |
| {D3D12Backend(), MetalBackend(), OpenGLBackend(), OpenGLESBackend(), |
| VulkanBackend()}, |
| {wgpu::TextureFormat::Depth16Unorm, wgpu::TextureFormat::Depth24Plus, |
| wgpu::TextureFormat::Depth32Float, |
| wgpu::TextureFormat::Depth32FloatStencil8, |
| wgpu::TextureFormat::Depth24PlusStencil8}, |
| {true, false}); |
| |
| } // anonymous namespace |
| } // namespace dawn |