blob: a9ee509c04629d7987ef607419604e58ba160bc6 [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;
Corentin Wallez6b087812020-10-27 15:35:56 +0000114 descriptor.usage = wgpu::TextureUsage::RenderAttachment;
Corentin Wallez11652ff2020-03-20 17:07:20 +0000115 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;
Kai Ninomiya2afea0c2020-07-10 20:33:08 +0000153 desc.cColorAttachments[0].clearColor = {data->clearCycle, 1.0f - data->clearCycle, 0.0f,
154 1.0f};
Corentin Wallez11652ff2020-03-20 17:07:20 +0000155
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
166std::ostream& operator<<(std::ostream& o, const wgpu::SwapChainDescriptor& desc) {
167 // For now only output attachment is possible.
Corentin Wallez6b087812020-10-27 15:35:56 +0000168 ASSERT(desc.usage == wgpu::TextureUsage::RenderAttachment);
169 o << "RenderAttachment ";
Corentin Wallez11652ff2020-03-20 17:07:20 +0000170 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
190void 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
208void 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
257int 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);
Corentin Wallez8a437942020-04-17 16:45:17 +0000311 queue = device.GetDefaultQueue();
Corentin Wallez11652ff2020-03-20 17:07:20 +0000312
313 // The hacky pipeline to render a triangle.
314 utils::ComboRenderPipelineDescriptor pipelineDesc(device);
Corentin Wallez4814bdb2020-11-26 16:39:46 +0000315 pipelineDesc.vertexStage.module = utils::CreateShaderModuleFromWGSL(device, R"(
316 [[builtin(vertex_idx)]] var<in> VertexIndex : u32;
317 [[builtin(position)]] var<out> Position : vec4<f32>;
318 const pos : array<vec2<f32>, 3> = array<vec2<f32>, 3>(
319 vec2<f32>( 0.0, 0.5),
320 vec2<f32>(-0.5, -0.5),
321 vec2<f32>( 0.5, -0.5)
322 );
323 [[stage(vertex)]] fn main() -> void {
324 Position = vec4<f32>(pos[VertexIndex], 0.0, 1.0);
325 return;
Corentin Wallez11652ff2020-03-20 17:07:20 +0000326 })");
Corentin Wallez4814bdb2020-11-26 16:39:46 +0000327 pipelineDesc.cFragmentStage.module = utils::CreateShaderModuleFromWGSL(device, R"(
328 [[location(0)]] var<out> fragColor : vec4<f32>;
329 [[stage(fragment)]] fn main() -> void {
330 fragColor = vec4<f32>(1.0, 0.0, 0.0, 1.0);
331 return;
Corentin Wallez11652ff2020-03-20 17:07:20 +0000332 })");
333 pipelineDesc.colorStateCount = 1;
334 // BGRA shouldn't be hardcoded. Consider having a map[format -> pipeline].
335 pipelineDesc.cColorStates[0].format = wgpu::TextureFormat::BGRA8Unorm;
336 trianglePipeline = device.CreateRenderPipeline(&pipelineDesc);
337
338 // Craete the first window, since the example exits when there are no windows.
339 AddWindow();
340
341 while (windows.size() != 0) {
342 glfwPollEvents();
343
344 for (auto it = windows.begin(); it != windows.end();) {
345 GLFWwindow* window = it->first;
346
347 if (glfwWindowShouldClose(window)) {
348 glfwDestroyWindow(window);
349 it = windows.erase(it);
350 } else {
351 it++;
352 }
353 }
354
355 for (auto& it : windows) {
356 WindowData* data = it.second.get();
357
358 SyncFromWindow(data);
359 if (!IsSameDescriptor(data->currentDesc, data->targetDesc) && !data->latched) {
360 data->swapchain = device.CreateSwapChain(data->surface, &data->targetDesc);
361 data->currentDesc = data->targetDesc;
362 }
363 UpdateTitle(data);
364 DoRender(data);
365 }
366 }
367}