blob: ee924d3dba8c71faad69ed0e20165af8eee26c81 [file] [log] [blame]
Corentin Wallez11652ff2020-03-20 17:07:20 +00001// 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
68struct 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
84static std::unordered_map<GLFWwindow*, std::unique_ptr<WindowData>> windows;
85static uint64_t windowSerial = 0;
86
87static std::unique_ptr<dawn_native::Instance> instance;
88static wgpu::Device device;
89static wgpu::Queue queue;
90static wgpu::RenderPipeline trianglePipeline;
91
92bool 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
97void OnKeyPress(GLFWwindow* window, int key, int, int action, int);
98
99void 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
108void 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::OutputAttachment;
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
131void 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);
Corentin Wallez67b1ad72020-03-31 16:21:35 +0000143 pass.Draw(3);
Corentin Wallez11652ff2020-03-20 17:07:20 +0000144 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;
Austin Eng80a18682020-03-23 20:10:53 +0000153 desc.cColorAttachments[0].clearColor = {data->clearCycle, 1.0f - data->clearCycle, 0.0f, 1.0f};
Corentin Wallez11652ff2020-03-20 17:07:20 +0000154
155 wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&desc);
156 pass.EndPass();
157 }
158
159 wgpu::CommandBuffer commands = encoder.Finish();
160 queue.Submit(1, &commands);
161
162 data->swapchain.Present();
163}
164
165std::ostream& operator<<(std::ostream& o, const wgpu::SwapChainDescriptor& desc) {
166 // For now only output attachment is possible.
167 ASSERT(desc.usage == wgpu::TextureUsage::OutputAttachment);
168 o << "OutputAttachment ";
169 o << desc.width << "x" << desc.height << " ";
170
171 // For now only BGRA is allowed
172 ASSERT(desc.format == wgpu::TextureFormat::BGRA8Unorm);
173 o << "BGRA8Unorm ";
174
175 switch (desc.presentMode) {
176 case wgpu::PresentMode::Immediate:
177 o << "Immediate";
178 break;
179 case wgpu::PresentMode::Fifo:
180 o << "Fifo";
181 break;
182 case wgpu::PresentMode::Mailbox:
183 o << "Mailbox";
184 break;
185 }
186 return o;
187}
188
189void UpdateTitle(WindowData* data) {
190 std::ostringstream o;
191
192 o << data->serial << " ";
193 if (data->divisor != 1) {
194 o << "Divisor:" << data->divisor << " ";
195 }
196
197 if (data->latched) {
198 o << "Latched: (" << data->currentDesc << ") ";
199 o << "Target: (" << data->targetDesc << ")";
200 } else {
201 o << "(" << data->currentDesc << ")";
202 }
203
204 glfwSetWindowTitle(data->window, o.str().c_str());
205}
206
207void OnKeyPress(GLFWwindow* window, int key, int, int action, int) {
208 if (action != GLFW_PRESS) {
209 return;
210 }
211
212 ASSERT(windows.count(window) == 1);
213
214 WindowData* data = windows[window].get();
215 switch (key) {
216 case GLFW_KEY_W:
217 AddWindow();
218 break;
219
220 case GLFW_KEY_L:
221 data->latched = !data->latched;
222 UpdateTitle(data);
223 break;
224
225 case GLFW_KEY_R:
226 data->renderTriangle = !data->renderTriangle;
227 UpdateTitle(data);
228 break;
229
230 case GLFW_KEY_D:
231 data->divisor *= 2;
232 if (data->divisor > 32) {
233 data->divisor = 1;
234 }
235 break;
236
237 case GLFW_KEY_P:
238 switch (data->targetDesc.presentMode) {
239 case wgpu::PresentMode::Immediate:
240 data->targetDesc.presentMode = wgpu::PresentMode::Fifo;
241 break;
242 case wgpu::PresentMode::Fifo:
243 data->targetDesc.presentMode = wgpu::PresentMode::Mailbox;
244 break;
245 case wgpu::PresentMode::Mailbox:
246 data->targetDesc.presentMode = wgpu::PresentMode::Immediate;
247 break;
248 }
249 break;
250
251 default:
252 break;
253 }
254}
255
256int main(int argc, const char* argv[]) {
257 // Setup GLFW
258 glfwSetErrorCallback([](int code, const char* message) {
259 dawn::ErrorLog() << "GLFW error " << code << " " << message;
260 });
261 if (!glfwInit()) {
262 return 1;
263 }
264
265 // Choose an adapter we like.
266 // TODO: allow switching the window between devices.
267 DawnProcTable procs = dawn_native::GetProcs();
268 dawnProcSetProcs(&procs);
269
270 instance = std::make_unique<dawn_native::Instance>();
271 instance->DiscoverDefaultAdapters();
272
273 std::vector<dawn_native::Adapter> adapters = instance->GetAdapters();
274 dawn_native::Adapter chosenAdapter;
275 for (dawn_native::Adapter& adapter : adapters) {
276 wgpu::AdapterProperties properties;
277 adapter.GetProperties(&properties);
278 if (properties.backendType != wgpu::BackendType::Null) {
279 chosenAdapter = adapter;
280 break;
281 }
282 }
283 ASSERT(chosenAdapter);
284
285 // Setup the device on that adapter.
286 device = wgpu::Device::Acquire(chosenAdapter.CreateDevice());
287 device.SetUncapturedErrorCallback(
288 [](WGPUErrorType errorType, const char* message, void*) {
289 const char* errorTypeName = "";
290 switch (errorType) {
291 case WGPUErrorType_Validation:
292 errorTypeName = "Validation";
293 break;
294 case WGPUErrorType_OutOfMemory:
295 errorTypeName = "Out of memory";
296 break;
297 case WGPUErrorType_Unknown:
298 errorTypeName = "Unknown";
299 break;
300 case WGPUErrorType_DeviceLost:
301 errorTypeName = "Device lost";
302 break;
303 default:
304 UNREACHABLE();
305 return;
306 }
307 dawn::ErrorLog() << errorTypeName << " error: " << message;
308 },
309 nullptr);
Corentin Wallez8a437942020-04-17 16:45:17 +0000310 queue = device.GetDefaultQueue();
Corentin Wallez11652ff2020-03-20 17:07:20 +0000311
312 // The hacky pipeline to render a triangle.
313 utils::ComboRenderPipelineDescriptor pipelineDesc(device);
314 pipelineDesc.vertexStage.module =
315 utils::CreateShaderModule(device, utils::SingleShaderStage::Vertex, R"(
316 #version 450
317 const vec2 pos[3] = vec2[3](vec2(0.0f, 0.5f), vec2(-0.5f, -0.5f), vec2(0.5f, -0.5f));
318 void main() {
319 gl_Position = vec4(pos[gl_VertexIndex], 0.0, 1.0);
320 })");
321 pipelineDesc.cFragmentStage.module =
322 utils::CreateShaderModule(device, utils::SingleShaderStage::Fragment, R"(
323 #version 450
324 layout(location = 0) out vec4 fragColor;
325 void main() {
326 fragColor = vec4(1.0, 0.0, 0.0, 1.0);
327 })");
328 pipelineDesc.colorStateCount = 1;
329 // BGRA shouldn't be hardcoded. Consider having a map[format -> pipeline].
330 pipelineDesc.cColorStates[0].format = wgpu::TextureFormat::BGRA8Unorm;
331 trianglePipeline = device.CreateRenderPipeline(&pipelineDesc);
332
333 // Craete the first window, since the example exits when there are no windows.
334 AddWindow();
335
336 while (windows.size() != 0) {
337 glfwPollEvents();
338
339 for (auto it = windows.begin(); it != windows.end();) {
340 GLFWwindow* window = it->first;
341
342 if (glfwWindowShouldClose(window)) {
343 glfwDestroyWindow(window);
344 it = windows.erase(it);
345 } else {
346 it++;
347 }
348 }
349
350 for (auto& it : windows) {
351 WindowData* data = it.second.get();
352
353 SyncFromWindow(data);
354 if (!IsSameDescriptor(data->currentDesc, data->targetDesc) && !data->latched) {
355 data->swapchain = device.CreateSwapChain(data->surface, &data->targetDesc);
356 data->currentDesc = data->targetDesc;
357 }
358 UpdateTitle(data);
359 DoRender(data);
360 }
361 }
362}