blob: 1c7d9ddade3dc9757ea96725c90d1516f70ba9ff [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"
Austin Eng700a5fb2021-06-24 19:21:31 +000058#include "utils/ScopedAutoreleasePool.h"
Corentin Wallez11652ff2020-03-20 17:07:20 +000059#include "utils/WGPUHelpers.h"
60
61#include <dawn/dawn_proc.h>
62#include <dawn/webgpu_cpp.h>
63#include <dawn_native/DawnNative.h>
64#include "GLFW/glfw3.h"
65
66#include <memory>
67#include <unordered_map>
68
69struct WindowData {
70 GLFWwindow* window = nullptr;
71 uint64_t serial = 0;
72
73 float clearCycle = 1.0f;
74 bool latched = false;
75 bool renderTriangle = true;
76 uint32_t divisor = 1;
77
78 wgpu::Surface surface = nullptr;
79 wgpu::SwapChain swapchain = nullptr;
80
81 wgpu::SwapChainDescriptor currentDesc;
82 wgpu::SwapChainDescriptor targetDesc;
83};
84
85static std::unordered_map<GLFWwindow*, std::unique_ptr<WindowData>> windows;
86static uint64_t windowSerial = 0;
87
88static std::unique_ptr<dawn_native::Instance> instance;
89static wgpu::Device device;
90static wgpu::Queue queue;
91static wgpu::RenderPipeline trianglePipeline;
92
93bool IsSameDescriptor(const wgpu::SwapChainDescriptor& a, const wgpu::SwapChainDescriptor& b) {
94 return a.usage == b.usage && a.format == b.format && a.width == b.width &&
95 a.height == b.height && a.presentMode == b.presentMode;
96}
97
98void OnKeyPress(GLFWwindow* window, int key, int, int action, int);
99
100void SyncFromWindow(WindowData* data) {
101 int width;
102 int height;
103 glfwGetFramebufferSize(data->window, &width, &height);
104
105 data->targetDesc.width = std::max(1u, width / data->divisor);
106 data->targetDesc.height = std::max(1u, height / data->divisor);
107}
108
109void AddWindow() {
110 glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API);
111 GLFWwindow* window = glfwCreateWindow(400, 400, "", nullptr, nullptr);
112 glfwSetKeyCallback(window, OnKeyPress);
113
114 wgpu::SwapChainDescriptor descriptor;
Corentin Wallez6b087812020-10-27 15:35:56 +0000115 descriptor.usage = wgpu::TextureUsage::RenderAttachment;
Corentin Wallez11652ff2020-03-20 17:07:20 +0000116 descriptor.format = wgpu::TextureFormat::BGRA8Unorm;
117 descriptor.width = 0;
118 descriptor.height = 0;
119 descriptor.presentMode = wgpu::PresentMode::Fifo;
120
121 std::unique_ptr<WindowData> data = std::make_unique<WindowData>();
122 data->window = window;
123 data->serial = windowSerial++;
124 data->surface = utils::CreateSurfaceForWindow(instance->Get(), window);
125 data->currentDesc = descriptor;
126 data->targetDesc = descriptor;
127 SyncFromWindow(data.get());
128
129 windows[window] = std::move(data);
130}
131
132void DoRender(WindowData* data) {
133 wgpu::TextureView view = data->swapchain.GetCurrentTextureView();
134 wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
135
136 if (data->renderTriangle) {
137 utils::ComboRenderPassDescriptor desc({view});
138 // Use Load to check the swapchain is lazy cleared (we shouldn't see garbage from previous
139 // frames).
140 desc.cColorAttachments[0].loadOp = wgpu::LoadOp::Load;
141
142 wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&desc);
143 pass.SetPipeline(trianglePipeline);
Corentin Wallez67b1ad72020-03-31 16:21:35 +0000144 pass.Draw(3);
Corentin Wallez11652ff2020-03-20 17:07:20 +0000145 pass.EndPass();
146 } else {
147 data->clearCycle -= 1.0 / 60.f;
148 if (data->clearCycle < 0.0) {
149 data->clearCycle = 1.0f;
150 }
151
152 utils::ComboRenderPassDescriptor desc({view});
153 desc.cColorAttachments[0].loadOp = wgpu::LoadOp::Clear;
Kai Ninomiya2afea0c2020-07-10 20:33:08 +0000154 desc.cColorAttachments[0].clearColor = {data->clearCycle, 1.0f - data->clearCycle, 0.0f,
155 1.0f};
Corentin Wallez11652ff2020-03-20 17:07:20 +0000156
157 wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&desc);
158 pass.EndPass();
159 }
160
161 wgpu::CommandBuffer commands = encoder.Finish();
162 queue.Submit(1, &commands);
163
164 data->swapchain.Present();
165}
166
167std::ostream& operator<<(std::ostream& o, const wgpu::SwapChainDescriptor& desc) {
Corentin Wallez49a1f722021-01-22 19:51:37 +0000168 // For now only render attachment is possible.
Corentin Wallez6b087812020-10-27 15:35:56 +0000169 ASSERT(desc.usage == wgpu::TextureUsage::RenderAttachment);
170 o << "RenderAttachment ";
Corentin Wallez11652ff2020-03-20 17:07:20 +0000171 o << desc.width << "x" << desc.height << " ";
172
173 // For now only BGRA is allowed
174 ASSERT(desc.format == wgpu::TextureFormat::BGRA8Unorm);
175 o << "BGRA8Unorm ";
176
177 switch (desc.presentMode) {
178 case wgpu::PresentMode::Immediate:
179 o << "Immediate";
180 break;
181 case wgpu::PresentMode::Fifo:
182 o << "Fifo";
183 break;
184 case wgpu::PresentMode::Mailbox:
185 o << "Mailbox";
186 break;
187 }
188 return o;
189}
190
191void UpdateTitle(WindowData* data) {
192 std::ostringstream o;
193
194 o << data->serial << " ";
195 if (data->divisor != 1) {
196 o << "Divisor:" << data->divisor << " ";
197 }
198
199 if (data->latched) {
200 o << "Latched: (" << data->currentDesc << ") ";
201 o << "Target: (" << data->targetDesc << ")";
202 } else {
203 o << "(" << data->currentDesc << ")";
204 }
205
206 glfwSetWindowTitle(data->window, o.str().c_str());
207}
208
209void OnKeyPress(GLFWwindow* window, int key, int, int action, int) {
210 if (action != GLFW_PRESS) {
211 return;
212 }
213
214 ASSERT(windows.count(window) == 1);
215
216 WindowData* data = windows[window].get();
217 switch (key) {
218 case GLFW_KEY_W:
219 AddWindow();
220 break;
221
222 case GLFW_KEY_L:
223 data->latched = !data->latched;
224 UpdateTitle(data);
225 break;
226
227 case GLFW_KEY_R:
228 data->renderTriangle = !data->renderTriangle;
229 UpdateTitle(data);
230 break;
231
232 case GLFW_KEY_D:
233 data->divisor *= 2;
234 if (data->divisor > 32) {
235 data->divisor = 1;
236 }
237 break;
238
239 case GLFW_KEY_P:
240 switch (data->targetDesc.presentMode) {
241 case wgpu::PresentMode::Immediate:
242 data->targetDesc.presentMode = wgpu::PresentMode::Fifo;
243 break;
244 case wgpu::PresentMode::Fifo:
245 data->targetDesc.presentMode = wgpu::PresentMode::Mailbox;
246 break;
247 case wgpu::PresentMode::Mailbox:
248 data->targetDesc.presentMode = wgpu::PresentMode::Immediate;
249 break;
250 }
251 break;
252
253 default:
254 break;
255 }
256}
257
258int main(int argc, const char* argv[]) {
259 // Setup GLFW
260 glfwSetErrorCallback([](int code, const char* message) {
261 dawn::ErrorLog() << "GLFW error " << code << " " << message;
262 });
263 if (!glfwInit()) {
264 return 1;
265 }
266
267 // Choose an adapter we like.
268 // TODO: allow switching the window between devices.
269 DawnProcTable procs = dawn_native::GetProcs();
270 dawnProcSetProcs(&procs);
271
272 instance = std::make_unique<dawn_native::Instance>();
273 instance->DiscoverDefaultAdapters();
274
275 std::vector<dawn_native::Adapter> adapters = instance->GetAdapters();
276 dawn_native::Adapter chosenAdapter;
277 for (dawn_native::Adapter& adapter : adapters) {
278 wgpu::AdapterProperties properties;
279 adapter.GetProperties(&properties);
280 if (properties.backendType != wgpu::BackendType::Null) {
281 chosenAdapter = adapter;
282 break;
283 }
284 }
285 ASSERT(chosenAdapter);
286
287 // Setup the device on that adapter.
288 device = wgpu::Device::Acquire(chosenAdapter.CreateDevice());
289 device.SetUncapturedErrorCallback(
290 [](WGPUErrorType errorType, const char* message, void*) {
291 const char* errorTypeName = "";
292 switch (errorType) {
293 case WGPUErrorType_Validation:
294 errorTypeName = "Validation";
295 break;
296 case WGPUErrorType_OutOfMemory:
297 errorTypeName = "Out of memory";
298 break;
299 case WGPUErrorType_Unknown:
300 errorTypeName = "Unknown";
301 break;
302 case WGPUErrorType_DeviceLost:
303 errorTypeName = "Device lost";
304 break;
305 default:
306 UNREACHABLE();
307 return;
308 }
309 dawn::ErrorLog() << errorTypeName << " error: " << message;
310 },
311 nullptr);
Corentin Wallez6d315da2021-02-04 15:33:42 +0000312 queue = device.GetQueue();
Corentin Wallez11652ff2020-03-20 17:07:20 +0000313
314 // The hacky pipeline to render a triangle.
Brandon Jones41c87d92021-05-21 05:01:38 +0000315 utils::ComboRenderPipelineDescriptor pipelineDesc;
Corentin Wallez7aec4ae2021-03-24 15:55:32 +0000316 pipelineDesc.vertex.module = utils::CreateShaderModule(device, R"(
James Price9e0debd2021-04-13 14:52:44 +0000317 [[stage(vertex)]] fn main([[builtin(vertex_index)]] VertexIndex : u32)
318 -> [[builtin(position)]] vec4<f32> {
Corentin Wallezb86e45f2021-06-17 21:36:11 +0000319 var pos = array<vec2<f32>, 3>(
Ben Clayton36edf8d2021-06-17 11:25:09 +0000320 vec2<f32>( 0.0, 0.5),
321 vec2<f32>(-0.5, -0.5),
322 vec2<f32>( 0.5, -0.5)
323 );
James Price9e0debd2021-04-13 14:52:44 +0000324 return vec4<f32>(pos[VertexIndex], 0.0, 1.0);
Corentin Wallez11652ff2020-03-20 17:07:20 +0000325 })");
Corentin Wallez7aec4ae2021-03-24 15:55:32 +0000326 pipelineDesc.cFragment.module = utils::CreateShaderModule(device, R"(
James Price9e0debd2021-04-13 14:52:44 +0000327 [[stage(fragment)]] fn main() -> [[location(0)]] vec4<f32> {
328 return vec4<f32>(1.0, 0.0, 0.0, 1.0);
Corentin Wallez11652ff2020-03-20 17:07:20 +0000329 })");
Corentin Wallez11652ff2020-03-20 17:07:20 +0000330 // BGRA shouldn't be hardcoded. Consider having a map[format -> pipeline].
Corentin Wallez9895c272021-03-18 16:46:58 +0000331 pipelineDesc.cTargets[0].format = wgpu::TextureFormat::BGRA8Unorm;
Brandon Jones41c87d92021-05-21 05:01:38 +0000332 trianglePipeline = device.CreateRenderPipeline(&pipelineDesc);
Corentin Wallez11652ff2020-03-20 17:07:20 +0000333
334 // Craete the first window, since the example exits when there are no windows.
335 AddWindow();
336
337 while (windows.size() != 0) {
Austin Eng700a5fb2021-06-24 19:21:31 +0000338 utils::ScopedAutoreleasePool pool;
Corentin Wallez11652ff2020-03-20 17:07:20 +0000339 glfwPollEvents();
340
341 for (auto it = windows.begin(); it != windows.end();) {
342 GLFWwindow* window = it->first;
343
344 if (glfwWindowShouldClose(window)) {
345 glfwDestroyWindow(window);
346 it = windows.erase(it);
347 } else {
348 it++;
349 }
350 }
351
352 for (auto& it : windows) {
353 WindowData* data = it.second.get();
354
355 SyncFromWindow(data);
356 if (!IsSameDescriptor(data->currentDesc, data->targetDesc) && !data->latched) {
357 data->swapchain = device.CreateSwapChain(data->surface, &data->targetDesc);
358 data->currentDesc = data->targetDesc;
359 }
360 UpdateTitle(data);
361 DoRender(data);
362 }
363 }
364}