1 // Copyright 2021 The Dawn Authors
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 //     http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14 
15 #include "tests/DawnTest.h"
16 
17 #include "utils/ComboRenderPipelineDescriptor.h"
18 #include "utils/TextureUtils.h"
19 #include "utils/WGPUHelpers.h"
20 
21 constexpr static uint32_t kSize = 4;
22 
23 namespace {
24     using TextureFormat = wgpu::TextureFormat;
25     DAWN_TEST_PARAM_STRUCT(ReadOnlyDepthStencilAttachmentTestsParams, TextureFormat);
26 }  // namespace
27 
28 class ReadOnlyDepthStencilAttachmentTests
29     : public DawnTestWithParams<ReadOnlyDepthStencilAttachmentTestsParams> {
30   protected:
31     struct DepthStencilValues {
32         float depthInitValue;
33         uint32_t stencilInitValue;
34         uint32_t stencilRefValue;
35     };
36 
37     std::vector<const char*> GetRequiredFeatures() override {
38         switch (GetParam().mTextureFormat) {
39             case wgpu::TextureFormat::Depth24UnormStencil8:
40                 if (SupportsFeatures({"depth24unorm-stencil8"})) {
41                     mIsFormatSupported = true;
42                     return {"depth24unorm-stencil8"};
43                 }
44 
45                 return {};
46             case wgpu::TextureFormat::Depth32FloatStencil8:
47                 if (SupportsFeatures({"depth32float-stencil8"})) {
48                     mIsFormatSupported = true;
49                     return {"depth32float-stencil8"};
50                 }
51 
52                 return {};
53             default:
54                 mIsFormatSupported = true;
55                 return {};
56         }
57     }
58 
IsFormatSupported() const59     bool IsFormatSupported() const {
60         return mIsFormatSupported;
61     }
62 
CreateRenderPipeline(wgpu::TextureAspect aspect, wgpu::TextureFormat format)63     wgpu::RenderPipeline CreateRenderPipeline(wgpu::TextureAspect aspect,
64                                               wgpu::TextureFormat format) {
65         utils::ComboRenderPipelineDescriptor pipelineDescriptor;
66 
67         // Draw a rectangle via two triangles. The depth value of the top of the rectangle is 0.4.
68         // The depth value of the bottom is 0.0. The depth value gradually change from 0.4 to 0.0
69         // from the top to the bottom. The top part will compare with the depth values and fail to
70         // pass the depth test. The bottom part will compare with the depth values in depth buffer
71         // and pass the depth test, and sample from the depth buffer in fragment shader in the same
72         // pipeline.
73         pipelineDescriptor.vertex.module = utils::CreateShaderModule(device, R"(
74             [[stage(vertex)]]
75             fn main([[builtin(vertex_index)]] VertexIndex : u32) -> [[builtin(position)]] vec4<f32> {
76                 var pos = array<vec3<f32>, 6>(
77                     vec3<f32>(-1.0,  1.0, 0.4),
78                     vec3<f32>(-1.0, -1.0, 0.0),
79                     vec3<f32>( 1.0,  1.0, 0.4),
80                     vec3<f32>( 1.0,  1.0, 0.4),
81                     vec3<f32>(-1.0, -1.0, 0.0),
82                     vec3<f32>( 1.0, -1.0, 0.0));
83                 return vec4<f32>(pos[VertexIndex], 1.0);
84             })");
85 
86         if (aspect == wgpu::TextureAspect::DepthOnly) {
87             pipelineDescriptor.cFragment.module = utils::CreateShaderModule(device, R"(
88                 [[group(0), binding(0)]] var samp : sampler;
89                 [[group(0), binding(1)]] var tex : texture_depth_2d;
90 
91                 [[stage(fragment)]]
92                 fn main([[builtin(position)]] FragCoord : vec4<f32>) -> [[location(0)]] vec4<f32> {
93                     return vec4<f32>(textureSample(tex, samp, FragCoord.xy), 0.0, 0.0, 0.0);
94                 })");
95 
96             // Enable depth test. But depth write is not enabled.
97             wgpu::DepthStencilState* depthStencil = pipelineDescriptor.EnableDepthStencil(format);
98             depthStencil->depthCompare = wgpu::CompareFunction::LessEqual;
99         } else {
100             ASSERT(aspect == wgpu::TextureAspect::StencilOnly);
101             pipelineDescriptor.cFragment.module = utils::CreateShaderModule(device, R"(
102                 [[group(0), binding(0)]] var tex : texture_2d<u32>;
103 
104                 [[stage(fragment)]]
105                 fn main([[builtin(position)]] FragCoord : vec4<f32>) -> [[location(0)]] vec4<f32> {
106 		    var texel = textureLoad(tex, vec2<i32>(FragCoord.xy), 0);
107                     return vec4<f32>(f32(texel[0]) / 255.0, 0.0, 0.0, 0.0);
108                 })");
109 
110             // Enable stencil test. But stencil write is not enabled.
111             wgpu::DepthStencilState* depthStencil = pipelineDescriptor.EnableDepthStencil(format);
112             depthStencil->stencilFront.compare = wgpu::CompareFunction::LessEqual;
113         }
114 
115         return device.CreateRenderPipeline(&pipelineDescriptor);
116     }
117 
CreateTexture(wgpu::TextureFormat format, wgpu::TextureUsage usage)118     wgpu::Texture CreateTexture(wgpu::TextureFormat format, wgpu::TextureUsage usage) {
119         wgpu::TextureDescriptor descriptor = {};
120         descriptor.size = {kSize, kSize, 1};
121         descriptor.format = format;
122         descriptor.usage = usage;
123         return device.CreateTexture(&descriptor);
124     }
125 
DoTest(wgpu::TextureAspect aspect, wgpu::TextureFormat format, wgpu::Texture colorTexture, DepthStencilValues* values)126     void DoTest(wgpu::TextureAspect aspect,
127                 wgpu::TextureFormat format,
128                 wgpu::Texture colorTexture,
129                 DepthStencilValues* values) {
130         wgpu::Texture depthStencilTexture = CreateTexture(
131             format, wgpu::TextureUsage::RenderAttachment | wgpu::TextureUsage::TextureBinding);
132 
133         wgpu::CommandEncoder commandEncoder = device.CreateCommandEncoder();
134 
135         // Note that we must encompass all aspects for texture view used in attachment.
136         wgpu::TextureView depthStencilViewInAttachment = depthStencilTexture.CreateView();
137         utils::ComboRenderPassDescriptor passDescriptorInit({}, depthStencilViewInAttachment);
138         if (aspect == wgpu::TextureAspect::DepthOnly) {
139             passDescriptorInit.cDepthStencilAttachmentInfo.clearDepth = values->depthInitValue;
140         } else {
141             ASSERT(aspect == wgpu::TextureAspect::StencilOnly);
142             passDescriptorInit.cDepthStencilAttachmentInfo.clearStencil = values->stencilInitValue;
143         }
144         wgpu::RenderPassEncoder passInit = commandEncoder.BeginRenderPass(&passDescriptorInit);
145         passInit.EndPass();
146 
147         // Note that we can only select one single aspect for texture view used in bind group.
148         wgpu::TextureViewDescriptor viewDesc = {};
149         viewDesc.aspect = aspect;
150         wgpu::TextureView depthStencilViewInBindGroup = depthStencilTexture.CreateView(&viewDesc);
151 
152         // Create a render pass to initialize the depth/stencil attachment.
153         utils::ComboRenderPassDescriptor passDescriptor({colorTexture.CreateView()},
154                                                         depthStencilViewInAttachment);
155         // Set both aspects to readonly. We have to do this if the format has both aspects, or
156         // it doesn't impact anything if the format has only one aspect.
157         passDescriptor.cDepthStencilAttachmentInfo.depthReadOnly = true;
158         passDescriptor.cDepthStencilAttachmentInfo.depthLoadOp = wgpu::LoadOp::Load;
159         passDescriptor.cDepthStencilAttachmentInfo.depthStoreOp = wgpu::StoreOp::Store;
160         passDescriptor.cDepthStencilAttachmentInfo.stencilReadOnly = true;
161         passDescriptor.cDepthStencilAttachmentInfo.stencilLoadOp = wgpu::LoadOp::Load;
162         passDescriptor.cDepthStencilAttachmentInfo.stencilStoreOp = wgpu::StoreOp::Store;
163 
164         // Create a render pass with readonly depth/stencil attachment. The attachment has already
165         // been initialized. The pipeline in this render pass will sample from the attachment.
166         // The pipeline will read from the attachment to do depth/stencil test too.
167         wgpu::RenderPassEncoder pass = commandEncoder.BeginRenderPass(&passDescriptor);
168         wgpu::RenderPipeline pipeline = CreateRenderPipeline(aspect, format);
169         pass.SetPipeline(pipeline);
170         if (aspect == wgpu::TextureAspect::DepthOnly) {
171             wgpu::BindGroup bindGroup = utils::MakeBindGroup(
172                 device, pipeline.GetBindGroupLayout(0),
173                 {{0, device.CreateSampler()}, {1, depthStencilViewInBindGroup}});
174             pass.SetBindGroup(0, bindGroup);
175         } else {
176             ASSERT(aspect == wgpu::TextureAspect::StencilOnly);
177             wgpu::BindGroup bindGroup = utils::MakeBindGroup(device, pipeline.GetBindGroupLayout(0),
178                                                              {{0, depthStencilViewInBindGroup}});
179             pass.SetBindGroup(0, bindGroup);
180             pass.SetStencilReference(values->stencilRefValue);
181         }
182         pass.Draw(6);
183         pass.EndPass();
184 
185         wgpu::CommandBuffer commands = commandEncoder.Finish();
186         queue.Submit(1, &commands);
187     }
188 
189   private:
190     bool mIsFormatSupported = false;
191 };
192 
193 class ReadOnlyDepthAttachmentTests : public ReadOnlyDepthStencilAttachmentTests {
194   protected:
195     void SetUp() override {
196         ReadOnlyDepthStencilAttachmentTests::SetUp();
197         DAWN_TEST_UNSUPPORTED_IF(!IsFormatSupported());
198     }
199 };
200 
TEST_P(ReadOnlyDepthAttachmentTests, Test)201 TEST_P(ReadOnlyDepthAttachmentTests, Test) {
202     wgpu::Texture colorTexture =
203         CreateTexture(wgpu::TextureFormat::RGBA8Unorm,
204                       wgpu::TextureUsage::RenderAttachment | wgpu::TextureUsage::CopySrc);
205 
206     wgpu::TextureFormat depthFormat = GetParam().mTextureFormat;
207 
208     DepthStencilValues values;
209     values.depthInitValue = 0.2;
210     DoTest(wgpu::TextureAspect::DepthOnly, depthFormat, colorTexture, &values);
211 
212     // The top part is not rendered by the pipeline. Its color is the default clear color for
213     // color attachment.
214     const std::vector<RGBA8> kExpectedTopColors(kSize * kSize / 2, {0, 0, 0, 0});
215     // The bottom part is rendered, whose red channel is sampled from depth attachment, which
216     // is initialized into 0.2.
217     const std::vector<RGBA8> kExpectedBottomColors(kSize * kSize / 2,
218                                                    {static_cast<uint8_t>(0.2 * 255), 0, 0, 0});
219     EXPECT_TEXTURE_EQ(kExpectedTopColors.data(), colorTexture, {0, 0}, {kSize, kSize / 2});
220     EXPECT_TEXTURE_EQ(kExpectedBottomColors.data(), colorTexture, {0, kSize / 2},
221                       {kSize, kSize / 2});
222 }
223 
224 class ReadOnlyStencilAttachmentTests : public ReadOnlyDepthStencilAttachmentTests {
225   protected:
226     void SetUp() override {
227         ReadOnlyDepthStencilAttachmentTests::SetUp();
228         DAWN_TEST_UNSUPPORTED_IF(!IsFormatSupported());
229     }
230 };
231 
TEST_P(ReadOnlyStencilAttachmentTests, Test)232 TEST_P(ReadOnlyStencilAttachmentTests, Test) {
233     wgpu::Texture colorTexture =
234         CreateTexture(wgpu::TextureFormat::RGBA8Unorm,
235                       wgpu::TextureUsage::RenderAttachment | wgpu::TextureUsage::CopySrc);
236 
237     wgpu::TextureFormat stencilFormat = GetParam().mTextureFormat;
238 
239     DepthStencilValues values;
240     values.stencilInitValue = 3;
241     values.stencilRefValue = 2;
242     // stencilRefValue < stencilValue (stencilInitValue), so stencil test passes. The pipeline
243     // samples from stencil buffer and writes into color buffer.
244     DoTest(wgpu::TextureAspect::StencilOnly, stencilFormat, colorTexture, &values);
245     const std::vector<RGBA8> kSampledColors(kSize * kSize, {3, 0, 0, 0});
246     EXPECT_TEXTURE_EQ(kSampledColors.data(), colorTexture, {0, 0}, {kSize, kSize});
247 
248     values.stencilInitValue = 1;
249     // stencilRefValue > stencilValue (stencilInitValue), so stencil test fails. The pipeline
250     // doesn't change color buffer. Sampled data from stencil buffer is discarded.
251     DoTest(wgpu::TextureAspect::StencilOnly, stencilFormat, colorTexture, &values);
252     const std::vector<RGBA8> kInitColors(kSize * kSize, {0, 0, 0, 0});
253     EXPECT_TEXTURE_EQ(kInitColors.data(), colorTexture, {0, 0}, {kSize, kSize});
254 }
255 
256 DAWN_INSTANTIATE_TEST_P(ReadOnlyDepthAttachmentTests,
257                         {D3D12Backend(), VulkanBackend()},
258                         std::vector<wgpu::TextureFormat>(utils::kDepthFormats.begin(),
259                                                          utils::kDepthFormats.end()));
260 DAWN_INSTANTIATE_TEST_P(ReadOnlyStencilAttachmentTests,
261                         {D3D12Backend(), VulkanBackend()},
262                         std::vector<wgpu::TextureFormat>(utils::kStencilFormats.begin(),
263                                                          utils::kStencilFormats.end()));
264