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 "common/Assert.h"
18 #include "utils/ComboRenderPipelineDescriptor.h"
19 #include "utils/WGPUHelpers.h"
20
21 constexpr static unsigned int kRTSize = 1;
22
23 class DepthClampingTest : public DawnTest {
24 protected:
SetUp()25 void SetUp() override {
26 DawnTest::SetUp();
27 DAWN_TEST_UNSUPPORTED_IF(!SupportsFeatures({"depth-clamping"}));
28
29 wgpu::TextureDescriptor renderTargetDescriptor;
30 renderTargetDescriptor.size = {kRTSize, kRTSize};
31 renderTargetDescriptor.format = wgpu::TextureFormat::RGBA8Unorm;
32 renderTargetDescriptor.usage =
33 wgpu::TextureUsage::RenderAttachment | wgpu::TextureUsage::CopySrc;
34 renderTarget = device.CreateTexture(&renderTargetDescriptor);
35
36 renderTargetView = renderTarget.CreateView();
37
38 wgpu::TextureDescriptor depthDescriptor;
39 depthDescriptor.dimension = wgpu::TextureDimension::e2D;
40 depthDescriptor.size = {kRTSize, kRTSize};
41 depthDescriptor.format = wgpu::TextureFormat::Depth24PlusStencil8;
42 depthDescriptor.usage = wgpu::TextureUsage::RenderAttachment;
43 depthTexture = device.CreateTexture(&depthDescriptor);
44
45 depthTextureView = depthTexture.CreateView();
46
47 vsModule = utils::CreateShaderModule(device, R"(
48 [[block]] struct UBO {
49 color : vec3<f32>;
50 depth : f32;
51 };
52 [[group(0), binding(0)]] var<uniform> ubo : UBO;
53
54 [[stage(vertex)]] fn main() -> [[builtin(position)]] vec4<f32> {
55 return vec4<f32>(0.0, 0.0, ubo.depth, 1.0);
56 })");
57
58 fsModule = utils::CreateShaderModule(device, R"(
59 [[block]] struct UBO {
60 color : vec3<f32>;
61 depth : f32;
62 };
63 [[group(0), binding(0)]] var<uniform> ubo : UBO;
64
65 [[stage(fragment)]] fn main() -> [[location(0)]] vec4<f32> {
66 return vec4<f32>(ubo.color, 1.0);
67 })");
68 }
69
GetRequiredFeatures()70 std::vector<const char*> GetRequiredFeatures() override {
71 std::vector<const char*> requiredFeatures = {};
72 if (SupportsFeatures({"depth-clamping"})) {
73 requiredFeatures.push_back("depth-clamping");
74 }
75 return requiredFeatures;
76 }
77
78 struct TestSpec {
79 wgpu::PrimitiveDepthClampingState* depthClampingState;
80 RGBA8 color;
81 float depth;
82 wgpu::CompareFunction depthCompareFunction;
83 };
84
85 // Each test param represents a pair of triangles with a color, depth, stencil value, and
86 // depthStencil state, one frontfacing, one backfacing Draw the triangles in order and check the
87 // expected colors for the frontfaces and backfaces
DoTest(const std::vector<TestSpec> & testParams,const RGBA8 & expected)88 void DoTest(const std::vector<TestSpec>& testParams, const RGBA8& expected) {
89 wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
90
91 struct TriangleData {
92 float color[3];
93 float depth;
94 };
95
96 utils::ComboRenderPassDescriptor renderPass({renderTargetView}, depthTextureView);
97 wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&renderPass);
98
99 for (size_t i = 0; i < testParams.size(); ++i) {
100 const TestSpec& test = testParams[i];
101
102 TriangleData data = {
103 {static_cast<float>(test.color.r) / 255.f, static_cast<float>(test.color.g) / 255.f,
104 static_cast<float>(test.color.b) / 255.f},
105 test.depth,
106 };
107 // Upload a buffer for each triangle's depth and color data
108 wgpu::Buffer buffer = utils::CreateBufferFromData(device, &data, sizeof(TriangleData),
109 wgpu::BufferUsage::Uniform);
110
111 // Create a pipeline for the triangles with the test spec's params.
112 utils::ComboRenderPipelineDescriptor descriptor;
113 descriptor.primitive.nextInChain = test.depthClampingState;
114 descriptor.primitive.topology = wgpu::PrimitiveTopology::PointList;
115 descriptor.vertex.module = vsModule;
116 descriptor.cFragment.module = fsModule;
117 wgpu::DepthStencilState* depthStencil = descriptor.EnableDepthStencil();
118 depthStencil->depthWriteEnabled = true;
119 depthStencil->depthCompare = test.depthCompareFunction;
120 depthStencil->format = wgpu::TextureFormat::Depth24PlusStencil8;
121
122 wgpu::RenderPipeline pipeline = device.CreateRenderPipeline(&descriptor);
123
124 // Create a bind group for the data
125 wgpu::BindGroup bindGroup = utils::MakeBindGroup(
126 device, pipeline.GetBindGroupLayout(0), {{0, buffer}});
127
128 pass.SetPipeline(pipeline);
129 pass.SetBindGroup(0, bindGroup);
130 pass.Draw(1);
131 }
132 pass.EndPass();
133
134 wgpu::CommandBuffer commands = encoder.Finish();
135 queue.Submit(1, &commands);
136
137 EXPECT_PIXEL_RGBA8_EQ(expected, renderTarget, 0, 0) << "Pixel check failed";
138 }
139
140 wgpu::Texture renderTarget;
141 wgpu::Texture depthTexture;
142 wgpu::TextureView renderTargetView;
143 wgpu::TextureView depthTextureView;
144 wgpu::ShaderModule vsModule;
145 wgpu::ShaderModule fsModule;
146 };
147
148 // Test that fragments beyond the far plane are clamped to 1.0 if depth clamping is enabled.
TEST_P(DepthClampingTest,ClampOnBeyondFarPlane)149 TEST_P(DepthClampingTest, ClampOnBeyondFarPlane) {
150 wgpu::PrimitiveDepthClampingState clampingState;
151 clampingState.clampDepth = true;
152
153 DoTest(
154 {
155 // Draw a red triangle at depth 1.
156 {
157 nullptr, /* depthClampingState */
158 RGBA8(255, 0, 0, 255), /* color */
159 1.f, /* depth */
160 wgpu::CompareFunction::Always,
161 },
162 // Draw a green triangle at depth 2 which should get clamped to 1.
163 {
164 &clampingState,
165 RGBA8(0, 255, 0, 255), /* color */
166 2.f, /* depth */
167 wgpu::CompareFunction::Equal,
168 },
169 },
170 // Since we draw the green triangle with an "equal" depth compare function, the resulting
171 // fragment should be green.
172 RGBA8(0, 255, 0, 255));
173 }
174
175 // Test that fragments beyond the near plane are clamped to 0.0 if depth clamping is enabled.
TEST_P(DepthClampingTest,ClampOnBeyondNearPlane)176 TEST_P(DepthClampingTest, ClampOnBeyondNearPlane) {
177 wgpu::PrimitiveDepthClampingState clampingState;
178 clampingState.clampDepth = true;
179
180 DoTest(
181 {
182 // Draw a red triangle at depth 0.
183 {
184 nullptr, /* depthClampingState */
185 RGBA8(255, 0, 0, 255), /* color */
186 0.f, /* depth */
187 wgpu::CompareFunction::Always,
188 },
189 // Draw a green triangle at depth -1 which should get clamped to 0.
190 {
191 &clampingState,
192 RGBA8(0, 255, 0, 255), /* color */
193 -1.f, /* depth */
194 wgpu::CompareFunction::Equal,
195 },
196 },
197 // Since we draw the green triangle with an "equal" depth compare function, the resulting
198 // fragment should be green.
199 RGBA8(0, 255, 0, 255));
200 }
201
202 // Test that fragments inside the view frustum are unaffected by depth clamping.
TEST_P(DepthClampingTest,ClampOnInsideViewFrustum)203 TEST_P(DepthClampingTest, ClampOnInsideViewFrustum) {
204 wgpu::PrimitiveDepthClampingState clampingState;
205 clampingState.clampDepth = true;
206
207 DoTest(
208 {
209 {
210 &clampingState,
211 RGBA8(0, 255, 0, 255), /* color */
212 0.5f, /* depth */
213 wgpu::CompareFunction::Always,
214 },
215 },
216 RGBA8(0, 255, 0, 255));
217 }
218
219
220 // Test that fragments outside the view frustum are clipped if depth clamping is disabled.
TEST_P(DepthClampingTest,ClampOffOutsideViewFrustum)221 TEST_P(DepthClampingTest, ClampOffOutsideViewFrustum) {
222 wgpu::PrimitiveDepthClampingState clampingState;
223 clampingState.clampDepth = false;
224
225 DoTest(
226 {
227 {
228 &clampingState,
229 RGBA8(0, 255, 0, 255), /* color */
230 2.f, /* depth */
231 wgpu::CompareFunction::Always,
232 },
233 {
234 &clampingState,
235 RGBA8(0, 255, 0, 255), /* color */
236 -1.f, /* depth */
237 wgpu::CompareFunction::Always,
238 },
239 },
240 RGBA8(0, 0, 0, 0));
241 }
242
243 // Test that fragments outside the view frustum are clipped if clampDepth is left unspecified.
TEST_P(DepthClampingTest,ClampUnspecifiedOutsideViewFrustum)244 TEST_P(DepthClampingTest, ClampUnspecifiedOutsideViewFrustum) {
245 DoTest(
246 {
247 {
248 nullptr, /* depthClampingState */
249 RGBA8(0, 255, 0, 255), /* color */
250 -1.f, /* depth */
251 wgpu::CompareFunction::Always,
252 },
253 {
254 nullptr, /* depthClampingState */
255 RGBA8(0, 255, 0, 255), /* color */
256 2.f, /* depth */
257 wgpu::CompareFunction::Always,
258 },
259 },
260 RGBA8(0, 0, 0, 0));
261 }
262
263 // Test that fragments are properly clipped or clamped if multiple render pipelines are used
264 // within the same render pass with differing clampDepth values.
TEST_P(DepthClampingTest,MultipleRenderPipelines)265 TEST_P(DepthClampingTest, MultipleRenderPipelines) {
266 wgpu::PrimitiveDepthClampingState clampingState;
267 clampingState.clampDepth = true;
268
269 wgpu::PrimitiveDepthClampingState clippingState;
270 clippingState.clampDepth = false;
271
272 DoTest(
273 {
274 // Draw green with clamping
275 {
276 &clampingState,
277 RGBA8(0, 255, 0, 255), /* color */
278 2.f, /* depth */
279 wgpu::CompareFunction::Always,
280 },
281 // Draw red with clipping
282 {
283 &clippingState,
284 RGBA8(255, 0, 0, 255), /* color */
285 2.f, /* depth */
286 wgpu::CompareFunction::Always,
287 },
288 },
289 RGBA8(0, 255, 0, 255)); // Result should be green
290 }
291
292 DAWN_INSTANTIATE_TEST(DepthClampingTest,
293 D3D12Backend(),
294 MetalBackend(),
295 OpenGLBackend(),
296 OpenGLESBackend(),
297 VulkanBackend());
298