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 // This is an example to manually test swapchain code. Controls are the following, scoped to the
16 // currently focused window:
17 // - W: creates a new window.
18 // - L: Latches the current swapchain, to check what happens when the window changes but not the
19 // swapchain.
20 // - R: switches the rendering mode, between "The Red Triangle" and color-cycling clears that's
21 // (WARNING) likely seizure inducing.
22 // - D: cycles the divisor for the swapchain size.
23 // - P: switches present modes.
24 //
25 // Closing all the windows exits the example. ^C also works.
26 //
27 // Things to test manually:
28 //
29 // - Basic tests (with the triangle render mode):
30 // - Check the triangle is red on a black background and with the pointy side up.
31 // - Cycle render modes a bunch and check that the triangle background is always solid black.
32 // - Check that rendering triangles to multiple windows works.
33 //
34 // - Present mode single-window tests (with cycling color render mode):
35 // - Check that Fifo cycles at about 1 cycle per second and has no tearing.
36 // - Check that Mailbox cycles faster than Fifo and has no tearing.
37 // - Check that Immediate cycles faster than Fifo, it is allowed to have tearing. (dragging
38 // between two monitors can help see tearing)
39 //
40 // - Present mode multi-window tests, it should have the same results as single-window tests when
41 // all windows are in the same present mode. In mixed present modes only Immediate windows are
42 // allowed to tear.
43 //
44 // - Resizing tests (with the triangle render mode):
45 // - Check that cycling divisors on the triangle produces lower and lower resolution triangles.
46 // - Check latching the swapchain config and resizing the window a bunch (smaller, bigger, and
47 // diagonal aspect ratio).
48 //
49 // - Config change tests:
50 // - Check that cycling between present modes works.
51 // - TODO can't be tested yet: check cycling the same window over multiple devices.
52 // - TODO can't be tested yet: check cycling the same window over multiple formats.
53
54 #include "common/Assert.h"
55 #include "common/Log.h"
56 #include "utils/ComboRenderPipelineDescriptor.h"
57 #include "utils/GLFWUtils.h"
58 #include "utils/WGPUHelpers.h"
59
60 #include <dawn/dawn_proc.h>
61 #include <dawn/webgpu_cpp.h>
62 #include <dawn_native/DawnNative.h>
63 #include "GLFW/glfw3.h"
64
65 #include <memory>
66 #include <unordered_map>
67
68 struct WindowData {
69 GLFWwindow* window = nullptr;
70 uint64_t serial = 0;
71
72 float clearCycle = 1.0f;
73 bool latched = false;
74 bool renderTriangle = true;
75 uint32_t divisor = 1;
76
77 wgpu::Surface surface = nullptr;
78 wgpu::SwapChain swapchain = nullptr;
79
80 wgpu::SwapChainDescriptor currentDesc;
81 wgpu::SwapChainDescriptor targetDesc;
82 };
83
84 static std::unordered_map<GLFWwindow*, std::unique_ptr<WindowData>> windows;
85 static uint64_t windowSerial = 0;
86
87 static std::unique_ptr<dawn_native::Instance> instance;
88 static wgpu::Device device;
89 static wgpu::Queue queue;
90 static wgpu::RenderPipeline trianglePipeline;
91
IsSameDescriptor(const wgpu::SwapChainDescriptor & a,const wgpu::SwapChainDescriptor & b)92 bool IsSameDescriptor(const wgpu::SwapChainDescriptor& a, const wgpu::SwapChainDescriptor& b) {
93 return a.usage == b.usage && a.format == b.format && a.width == b.width &&
94 a.height == b.height && a.presentMode == b.presentMode;
95 }
96
97 void OnKeyPress(GLFWwindow* window, int key, int, int action, int);
98
SyncFromWindow(WindowData * data)99 void SyncFromWindow(WindowData* data) {
100 int width;
101 int height;
102 glfwGetFramebufferSize(data->window, &width, &height);
103
104 data->targetDesc.width = std::max(1u, width / data->divisor);
105 data->targetDesc.height = std::max(1u, height / data->divisor);
106 }
107
AddWindow()108 void AddWindow() {
109 glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API);
110 GLFWwindow* window = glfwCreateWindow(400, 400, "", nullptr, nullptr);
111 glfwSetKeyCallback(window, OnKeyPress);
112
113 wgpu::SwapChainDescriptor descriptor;
114 descriptor.usage = wgpu::TextureUsage::RenderAttachment;
115 descriptor.format = wgpu::TextureFormat::BGRA8Unorm;
116 descriptor.width = 0;
117 descriptor.height = 0;
118 descriptor.presentMode = wgpu::PresentMode::Fifo;
119
120 std::unique_ptr<WindowData> data = std::make_unique<WindowData>();
121 data->window = window;
122 data->serial = windowSerial++;
123 data->surface = utils::CreateSurfaceForWindow(instance->Get(), window);
124 data->currentDesc = descriptor;
125 data->targetDesc = descriptor;
126 SyncFromWindow(data.get());
127
128 windows[window] = std::move(data);
129 }
130
DoRender(WindowData * data)131 void DoRender(WindowData* data) {
132 wgpu::TextureView view = data->swapchain.GetCurrentTextureView();
133 wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
134
135 if (data->renderTriangle) {
136 utils::ComboRenderPassDescriptor desc({view});
137 // Use Load to check the swapchain is lazy cleared (we shouldn't see garbage from previous
138 // frames).
139 desc.cColorAttachments[0].loadOp = wgpu::LoadOp::Load;
140
141 wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&desc);
142 pass.SetPipeline(trianglePipeline);
143 pass.Draw(3);
144 pass.EndPass();
145 } else {
146 data->clearCycle -= 1.0 / 60.f;
147 if (data->clearCycle < 0.0) {
148 data->clearCycle = 1.0f;
149 }
150
151 utils::ComboRenderPassDescriptor desc({view});
152 desc.cColorAttachments[0].loadOp = wgpu::LoadOp::Clear;
153 desc.cColorAttachments[0].clearColor = {data->clearCycle, 1.0f - data->clearCycle, 0.0f,
154 1.0f};
155
156 wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&desc);
157 pass.EndPass();
158 }
159
160 wgpu::CommandBuffer commands = encoder.Finish();
161 queue.Submit(1, &commands);
162
163 data->swapchain.Present();
164 }
165
operator <<(std::ostream & o,const wgpu::SwapChainDescriptor & desc)166 std::ostream& operator<<(std::ostream& o, const wgpu::SwapChainDescriptor& desc) {
167 // For now only output attachment is possible.
168 ASSERT(desc.usage == wgpu::TextureUsage::RenderAttachment);
169 o << "RenderAttachment ";
170 o << desc.width << "x" << desc.height << " ";
171
172 // For now only BGRA is allowed
173 ASSERT(desc.format == wgpu::TextureFormat::BGRA8Unorm);
174 o << "BGRA8Unorm ";
175
176 switch (desc.presentMode) {
177 case wgpu::PresentMode::Immediate:
178 o << "Immediate";
179 break;
180 case wgpu::PresentMode::Fifo:
181 o << "Fifo";
182 break;
183 case wgpu::PresentMode::Mailbox:
184 o << "Mailbox";
185 break;
186 }
187 return o;
188 }
189
UpdateTitle(WindowData * data)190 void UpdateTitle(WindowData* data) {
191 std::ostringstream o;
192
193 o << data->serial << " ";
194 if (data->divisor != 1) {
195 o << "Divisor:" << data->divisor << " ";
196 }
197
198 if (data->latched) {
199 o << "Latched: (" << data->currentDesc << ") ";
200 o << "Target: (" << data->targetDesc << ")";
201 } else {
202 o << "(" << data->currentDesc << ")";
203 }
204
205 glfwSetWindowTitle(data->window, o.str().c_str());
206 }
207
OnKeyPress(GLFWwindow * window,int key,int,int action,int)208 void OnKeyPress(GLFWwindow* window, int key, int, int action, int) {
209 if (action != GLFW_PRESS) {
210 return;
211 }
212
213 ASSERT(windows.count(window) == 1);
214
215 WindowData* data = windows[window].get();
216 switch (key) {
217 case GLFW_KEY_W:
218 AddWindow();
219 break;
220
221 case GLFW_KEY_L:
222 data->latched = !data->latched;
223 UpdateTitle(data);
224 break;
225
226 case GLFW_KEY_R:
227 data->renderTriangle = !data->renderTriangle;
228 UpdateTitle(data);
229 break;
230
231 case GLFW_KEY_D:
232 data->divisor *= 2;
233 if (data->divisor > 32) {
234 data->divisor = 1;
235 }
236 break;
237
238 case GLFW_KEY_P:
239 switch (data->targetDesc.presentMode) {
240 case wgpu::PresentMode::Immediate:
241 data->targetDesc.presentMode = wgpu::PresentMode::Fifo;
242 break;
243 case wgpu::PresentMode::Fifo:
244 data->targetDesc.presentMode = wgpu::PresentMode::Mailbox;
245 break;
246 case wgpu::PresentMode::Mailbox:
247 data->targetDesc.presentMode = wgpu::PresentMode::Immediate;
248 break;
249 }
250 break;
251
252 default:
253 break;
254 }
255 }
256
main(int argc,const char * argv[])257 int main(int argc, const char* argv[]) {
258 // Setup GLFW
259 glfwSetErrorCallback([](int code, const char* message) {
260 dawn::ErrorLog() << "GLFW error " << code << " " << message;
261 });
262 if (!glfwInit()) {
263 return 1;
264 }
265
266 // Choose an adapter we like.
267 // TODO: allow switching the window between devices.
268 DawnProcTable procs = dawn_native::GetProcs();
269 dawnProcSetProcs(&procs);
270
271 instance = std::make_unique<dawn_native::Instance>();
272 instance->DiscoverDefaultAdapters();
273
274 std::vector<dawn_native::Adapter> adapters = instance->GetAdapters();
275 dawn_native::Adapter chosenAdapter;
276 for (dawn_native::Adapter& adapter : adapters) {
277 wgpu::AdapterProperties properties;
278 adapter.GetProperties(&properties);
279 if (properties.backendType != wgpu::BackendType::Null) {
280 chosenAdapter = adapter;
281 break;
282 }
283 }
284 ASSERT(chosenAdapter);
285
286 // Setup the device on that adapter.
287 device = wgpu::Device::Acquire(chosenAdapter.CreateDevice());
288 device.SetUncapturedErrorCallback(
289 [](WGPUErrorType errorType, const char* message, void*) {
290 const char* errorTypeName = "";
291 switch (errorType) {
292 case WGPUErrorType_Validation:
293 errorTypeName = "Validation";
294 break;
295 case WGPUErrorType_OutOfMemory:
296 errorTypeName = "Out of memory";
297 break;
298 case WGPUErrorType_Unknown:
299 errorTypeName = "Unknown";
300 break;
301 case WGPUErrorType_DeviceLost:
302 errorTypeName = "Device lost";
303 break;
304 default:
305 UNREACHABLE();
306 return;
307 }
308 dawn::ErrorLog() << errorTypeName << " error: " << message;
309 },
310 nullptr);
311 queue = device.GetDefaultQueue();
312
313 // The hacky pipeline to render a triangle.
314 utils::ComboRenderPipelineDescriptor pipelineDesc(device);
315 pipelineDesc.vertexStage.module =
316 utils::CreateShaderModule(device, utils::SingleShaderStage::Vertex, R"(
317 #version 450
318 const vec2 pos[3] = vec2[3](vec2(0.0f, 0.5f), vec2(-0.5f, -0.5f), vec2(0.5f, -0.5f));
319 void main() {
320 gl_Position = vec4(pos[gl_VertexIndex], 0.0, 1.0);
321 })");
322 pipelineDesc.cFragmentStage.module =
323 utils::CreateShaderModule(device, utils::SingleShaderStage::Fragment, R"(
324 #version 450
325 layout(location = 0) out vec4 fragColor;
326 void main() {
327 fragColor = vec4(1.0, 0.0, 0.0, 1.0);
328 })");
329 pipelineDesc.colorStateCount = 1;
330 // BGRA shouldn't be hardcoded. Consider having a map[format -> pipeline].
331 pipelineDesc.cColorStates[0].format = wgpu::TextureFormat::BGRA8Unorm;
332 trianglePipeline = device.CreateRenderPipeline(&pipelineDesc);
333
334 // Craete the first window, since the example exits when there are no windows.
335 AddWindow();
336
337 while (windows.size() != 0) {
338 glfwPollEvents();
339
340 for (auto it = windows.begin(); it != windows.end();) {
341 GLFWwindow* window = it->first;
342
343 if (glfwWindowShouldClose(window)) {
344 glfwDestroyWindow(window);
345 it = windows.erase(it);
346 } else {
347 it++;
348 }
349 }
350
351 for (auto& it : windows) {
352 WindowData* data = it.second.get();
353
354 SyncFromWindow(data);
355 if (!IsSameDescriptor(data->currentDesc, data->targetDesc) && !data->latched) {
356 data->swapchain = device.CreateSwapChain(data->surface, &data->targetDesc);
357 data->currentDesc = data->targetDesc;
358 }
359 UpdateTitle(data);
360 DoRender(data);
361 }
362 }
363 }
364