1 // Copyright 2020 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/WGPUHelpers.h"
19 
20 namespace {
21     struct CreateReadyPipelineTask {
22         wgpu::ComputePipeline computePipeline = nullptr;
23         wgpu::RenderPipeline renderPipeline = nullptr;
24         bool isCompleted = false;
25         std::string message;
26     };
27 }  // anonymous namespace
28 
29 class CreateReadyPipelineTest : public DawnTest {
30   protected:
31     CreateReadyPipelineTask task;
32 };
33 
34 // Verify the basic use of CreateReadyComputePipeline works on all backends.
TEST_P(CreateReadyPipelineTest,BasicUseOfCreateReadyComputePipeline)35 TEST_P(CreateReadyPipelineTest, BasicUseOfCreateReadyComputePipeline) {
36     const char* computeShader = R"(
37         #version 450
38         layout(std140, set = 0, binding = 0) buffer SSBO { uint value; } ssbo;
39         void main() {
40             ssbo.value = 1u;
41         })";
42 
43     wgpu::ComputePipelineDescriptor csDesc;
44     csDesc.computeStage.module =
45         utils::CreateShaderModule(device, utils::SingleShaderStage::Compute, computeShader);
46     csDesc.computeStage.entryPoint = "main";
47 
48     device.CreateReadyComputePipeline(
49         &csDesc,
50         [](WGPUCreateReadyPipelineStatus status, WGPUComputePipeline returnPipeline,
51            const char* message, void* userdata) {
52             ASSERT_EQ(WGPUCreateReadyPipelineStatus::WGPUCreateReadyPipelineStatus_Success, status);
53 
54             CreateReadyPipelineTask* task = static_cast<CreateReadyPipelineTask*>(userdata);
55             task->computePipeline = wgpu::ComputePipeline::Acquire(returnPipeline);
56             task->isCompleted = true;
57             task->message = message;
58         },
59         &task);
60 
61     wgpu::BufferDescriptor bufferDesc;
62     bufferDesc.size = sizeof(uint32_t);
63     bufferDesc.usage = wgpu::BufferUsage::Storage | wgpu::BufferUsage::CopySrc;
64     wgpu::Buffer ssbo = device.CreateBuffer(&bufferDesc);
65 
66     wgpu::CommandBuffer commands;
67     {
68         wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
69         wgpu::ComputePassEncoder pass = encoder.BeginComputePass();
70 
71         while (!task.isCompleted) {
72             WaitABit();
73         }
74         ASSERT_TRUE(task.message.empty());
75         ASSERT_NE(nullptr, task.computePipeline.Get());
76         wgpu::BindGroup bindGroup =
77             utils::MakeBindGroup(device, task.computePipeline.GetBindGroupLayout(0),
78                                  {
79                                      {0, ssbo, 0, sizeof(uint32_t)},
80                                  });
81         pass.SetBindGroup(0, bindGroup);
82         pass.SetPipeline(task.computePipeline);
83 
84         pass.Dispatch(1);
85         pass.EndPass();
86 
87         commands = encoder.Finish();
88     }
89 
90     queue.Submit(1, &commands);
91 
92     constexpr uint32_t kExpected = 1u;
93     EXPECT_BUFFER_U32_EQ(kExpected, ssbo, 0);
94 }
95 
96 // Verify CreateReadyComputePipeline() works as expected when there is any error that happens during
97 // the creation of the compute pipeline. The SPEC requires that during the call of
98 // CreateReadyComputePipeline() any error won't be forwarded to the error scope / unhandled error
99 // callback.
TEST_P(CreateReadyPipelineTest,CreateComputePipelineFailed)100 TEST_P(CreateReadyPipelineTest, CreateComputePipelineFailed) {
101     DAWN_SKIP_TEST_IF(IsDawnValidationSkipped());
102 
103     const char* computeShader = R"(
104         #version 450
105         layout(std140, set = 0, binding = 0) buffer SSBO { uint value; } ssbo;
106         void main() {
107             ssbo.value = 1u;
108         })";
109 
110     wgpu::ComputePipelineDescriptor csDesc;
111     csDesc.computeStage.module =
112         utils::CreateShaderModule(device, utils::SingleShaderStage::Compute, computeShader);
113     csDesc.computeStage.entryPoint = "main0";
114 
115     device.CreateReadyComputePipeline(
116         &csDesc,
117         [](WGPUCreateReadyPipelineStatus status, WGPUComputePipeline returnPipeline,
118            const char* message, void* userdata) {
119             ASSERT_EQ(WGPUCreateReadyPipelineStatus::WGPUCreateReadyPipelineStatus_Error, status);
120 
121             CreateReadyPipelineTask* task = static_cast<CreateReadyPipelineTask*>(userdata);
122             task->computePipeline = wgpu::ComputePipeline::Acquire(returnPipeline);
123             task->isCompleted = true;
124             task->message = message;
125         },
126         &task);
127 
128     while (!task.isCompleted) {
129         WaitABit();
130     }
131 
132     ASSERT_FALSE(task.message.empty());
133     ASSERT_EQ(nullptr, task.computePipeline.Get());
134 }
135 
136 // Verify the basic use of CreateReadyRenderPipeline() works on all backends.
TEST_P(CreateReadyPipelineTest,BasicUseOfCreateReadyRenderPipeline)137 TEST_P(CreateReadyPipelineTest, BasicUseOfCreateReadyRenderPipeline) {
138     constexpr wgpu::TextureFormat kRenderAttachmentFormat = wgpu::TextureFormat::RGBA8Unorm;
139 
140     const char* vertexShader = R"(
141         #version 450
142         void main() {
143             gl_Position = vec4(0.f, 0.f, 0.f, 1.f);
144             gl_PointSize = 1.0f;
145         })";
146     const char* fragmentShader = R"(
147         #version 450
148         layout(location = 0) out vec4 o_color;
149         void main() {
150             o_color = vec4(0.f, 1.f, 0.f, 1.f);
151         })";
152 
153     utils::ComboRenderPipelineDescriptor renderPipelineDescriptor(device);
154     wgpu::ShaderModule vsModule =
155         utils::CreateShaderModule(device, utils::SingleShaderStage::Vertex, vertexShader);
156     wgpu::ShaderModule fsModule =
157         utils::CreateShaderModule(device, utils::SingleShaderStage::Fragment, fragmentShader);
158     renderPipelineDescriptor.vertexStage.module = vsModule;
159     renderPipelineDescriptor.cFragmentStage.module = fsModule;
160     renderPipelineDescriptor.cColorStates[0].format = kRenderAttachmentFormat;
161     renderPipelineDescriptor.primitiveTopology = wgpu::PrimitiveTopology::PointList;
162 
163     device.CreateReadyRenderPipeline(
164         &renderPipelineDescriptor,
165         [](WGPUCreateReadyPipelineStatus status, WGPURenderPipeline returnPipeline,
166            const char* message, void* userdata) {
167             ASSERT_EQ(WGPUCreateReadyPipelineStatus::WGPUCreateReadyPipelineStatus_Success, status);
168 
169             CreateReadyPipelineTask* task = static_cast<CreateReadyPipelineTask*>(userdata);
170             task->renderPipeline = wgpu::RenderPipeline::Acquire(returnPipeline);
171             task->isCompleted = true;
172             task->message = message;
173         },
174         &task);
175 
176     wgpu::TextureDescriptor textureDescriptor;
177     textureDescriptor.size = {1, 1, 1};
178     textureDescriptor.format = kRenderAttachmentFormat;
179     textureDescriptor.usage = wgpu::TextureUsage::RenderAttachment | wgpu::TextureUsage::CopySrc;
180     wgpu::Texture outputTexture = device.CreateTexture(&textureDescriptor);
181 
182     utils::ComboRenderPassDescriptor renderPassDescriptor({outputTexture.CreateView()});
183     renderPassDescriptor.cColorAttachments[0].loadOp = wgpu::LoadOp::Clear;
184     renderPassDescriptor.cColorAttachments[0].clearColor = {1.f, 0.f, 0.f, 1.f};
185 
186     wgpu::CommandBuffer commands;
187     {
188         wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
189         wgpu::RenderPassEncoder renderPassEncoder = encoder.BeginRenderPass(&renderPassDescriptor);
190 
191         while (!task.isCompleted) {
192             WaitABit();
193         }
194         ASSERT_TRUE(task.message.empty());
195         ASSERT_NE(nullptr, task.renderPipeline.Get());
196 
197         renderPassEncoder.SetPipeline(task.renderPipeline);
198         renderPassEncoder.Draw(1);
199         renderPassEncoder.EndPass();
200         commands = encoder.Finish();
201     }
202 
203     queue.Submit(1, &commands);
204 
205     EXPECT_PIXEL_RGBA8_EQ(RGBA8(0, 255, 0, 255), outputTexture, 0, 0);
206 }
207 
208 // Verify CreateReadyRenderPipeline() works as expected when there is any error that happens during
209 // the creation of the render pipeline. The SPEC requires that during the call of
210 // CreateReadyRenderPipeline() any error won't be forwarded to the error scope / unhandled error
211 // callback.
TEST_P(CreateReadyPipelineTest,CreateRenderPipelineFailed)212 TEST_P(CreateReadyPipelineTest, CreateRenderPipelineFailed) {
213     DAWN_SKIP_TEST_IF(IsDawnValidationSkipped());
214 
215     constexpr wgpu::TextureFormat kRenderAttachmentFormat = wgpu::TextureFormat::Depth32Float;
216 
217     const char* vertexShader = R"(
218         #version 450
219         void main() {
220             gl_Position = vec4(0.f, 0.f, 0.f, 1.f);
221             gl_PointSize = 1.0f;
222         })";
223     const char* fragmentShader = R"(
224         #version 450
225         layout(location = 0) out vec4 o_color;
226         void main() {
227             o_color = vec4(0.f, 1.f, 0.f, 1.f);
228         })";
229 
230     utils::ComboRenderPipelineDescriptor renderPipelineDescriptor(device);
231     wgpu::ShaderModule vsModule =
232         utils::CreateShaderModule(device, utils::SingleShaderStage::Vertex, vertexShader);
233     wgpu::ShaderModule fsModule =
234         utils::CreateShaderModule(device, utils::SingleShaderStage::Fragment, fragmentShader);
235     renderPipelineDescriptor.vertexStage.module = vsModule;
236     renderPipelineDescriptor.cFragmentStage.module = fsModule;
237     renderPipelineDescriptor.cColorStates[0].format = kRenderAttachmentFormat;
238     renderPipelineDescriptor.primitiveTopology = wgpu::PrimitiveTopology::PointList;
239 
240     device.CreateReadyRenderPipeline(
241         &renderPipelineDescriptor,
242         [](WGPUCreateReadyPipelineStatus status, WGPURenderPipeline returnPipeline,
243            const char* message, void* userdata) {
244             ASSERT_EQ(WGPUCreateReadyPipelineStatus::WGPUCreateReadyPipelineStatus_Error, status);
245 
246             CreateReadyPipelineTask* task = static_cast<CreateReadyPipelineTask*>(userdata);
247             task->renderPipeline = wgpu::RenderPipeline::Acquire(returnPipeline);
248             task->isCompleted = true;
249             task->message = message;
250         },
251         &task);
252 
253     while (!task.isCompleted) {
254         WaitABit();
255     }
256 
257     ASSERT_FALSE(task.message.empty());
258     ASSERT_EQ(nullptr, task.computePipeline.Get());
259 }
260 
261 // Verify there is no error when the device is released before the callback of
262 // CreateReadyComputePipeline() is called.
TEST_P(CreateReadyPipelineTest,ReleaseDeviceBeforeCallbackOfCreateReadyComputePipeline)263 TEST_P(CreateReadyPipelineTest, ReleaseDeviceBeforeCallbackOfCreateReadyComputePipeline) {
264     const char* computeShader = R"(
265         #version 450
266         void main() {
267         })";
268 
269     wgpu::ComputePipelineDescriptor csDesc;
270     csDesc.computeStage.module =
271         utils::CreateShaderModule(device, utils::SingleShaderStage::Compute, computeShader);
272     csDesc.computeStage.entryPoint = "main";
273 
274     device.CreateReadyComputePipeline(
275         &csDesc,
276         [](WGPUCreateReadyPipelineStatus status, WGPUComputePipeline returnPipeline,
277            const char* message, void* userdata) {
278             ASSERT_EQ(WGPUCreateReadyPipelineStatus::WGPUCreateReadyPipelineStatus_DeviceDestroyed,
279                       status);
280 
281             CreateReadyPipelineTask* task = static_cast<CreateReadyPipelineTask*>(userdata);
282             task->computePipeline = wgpu::ComputePipeline::Acquire(returnPipeline);
283             task->isCompleted = true;
284             task->message = message;
285         },
286         &task);
287 }
288 
289 // Verify there is no error when the device is released before the callback of
290 // CreateReadyRenderPipeline() is called.
TEST_P(CreateReadyPipelineTest,ReleaseDeviceBeforeCallbackOfCreateReadyRenderPipeline)291 TEST_P(CreateReadyPipelineTest, ReleaseDeviceBeforeCallbackOfCreateReadyRenderPipeline) {
292     const char* vertexShader = R"(
293         #version 450
294         void main() {
295             gl_Position = vec4(0.f, 0.f, 0.f, 1.f);
296             gl_PointSize = 1.0f;
297         })";
298     const char* fragmentShader = R"(
299         #version 450
300         layout(location = 0) out vec4 o_color;
301         void main() {
302             o_color = vec4(0.f, 1.f, 0.f, 1.f);
303         })";
304 
305     utils::ComboRenderPipelineDescriptor renderPipelineDescriptor(device);
306     wgpu::ShaderModule vsModule =
307         utils::CreateShaderModule(device, utils::SingleShaderStage::Vertex, vertexShader);
308     wgpu::ShaderModule fsModule =
309         utils::CreateShaderModule(device, utils::SingleShaderStage::Fragment, fragmentShader);
310     renderPipelineDescriptor.vertexStage.module = vsModule;
311     renderPipelineDescriptor.cFragmentStage.module = fsModule;
312     renderPipelineDescriptor.cColorStates[0].format = wgpu::TextureFormat::RGBA8Unorm;
313     renderPipelineDescriptor.primitiveTopology = wgpu::PrimitiveTopology::PointList;
314 
315     device.CreateReadyRenderPipeline(
316         &renderPipelineDescriptor,
317         [](WGPUCreateReadyPipelineStatus status, WGPURenderPipeline returnPipeline,
318            const char* message, void* userdata) {
319             ASSERT_EQ(WGPUCreateReadyPipelineStatus::WGPUCreateReadyPipelineStatus_DeviceDestroyed,
320                       status);
321 
322             CreateReadyPipelineTask* task = static_cast<CreateReadyPipelineTask*>(userdata);
323             task->renderPipeline = wgpu::RenderPipeline::Acquire(returnPipeline);
324             task->isCompleted = true;
325             task->message = message;
326         },
327         &task);
328 }
329 
330 DAWN_INSTANTIATE_TEST(CreateReadyPipelineTest,
331                       D3D12Backend(),
332                       MetalBackend(),
333                       OpenGLBackend(),
334                       VulkanBackend());
335