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