• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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/ScopedAutoreleasePool.h"
59 #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 
69 struct 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 
85 static std::unordered_map<GLFWwindow*, std::unique_ptr<WindowData>> windows;
86 static uint64_t windowSerial = 0;
87 
88 static std::unique_ptr<dawn_native::Instance> instance;
89 static wgpu::Device device;
90 static wgpu::Queue queue;
91 static wgpu::RenderPipeline trianglePipeline;
92 
IsSameDescriptor(const wgpu::SwapChainDescriptor & a,const wgpu::SwapChainDescriptor & b)93 bool 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 
98 void OnKeyPress(GLFWwindow* window, int key, int, int action, int);
99 
SyncFromWindow(WindowData * data)100 void 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 
AddWindow()109 void 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;
115     descriptor.usage = wgpu::TextureUsage::RenderAttachment;
116     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 
DoRender(WindowData * data)132 void 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);
144         pass.Draw(3);
145         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;
154         desc.cColorAttachments[0].clearColor = {data->clearCycle, 1.0f - data->clearCycle, 0.0f,
155                                                 1.0f};
156 
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 
operator <<(std::ostream & o,const wgpu::SwapChainDescriptor & desc)167 std::ostream& operator<<(std::ostream& o, const wgpu::SwapChainDescriptor& desc) {
168     // For now only render attachment is possible.
169     ASSERT(desc.usage == wgpu::TextureUsage::RenderAttachment);
170     o << "RenderAttachment ";
171     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 
UpdateTitle(WindowData * data)191 void 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 
OnKeyPress(GLFWwindow * window,int key,int,int action,int)209 void 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 
main(int argc,const char * argv[])258 int 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);
312     queue = device.GetQueue();
313 
314     // The hacky pipeline to render a triangle.
315     utils::ComboRenderPipelineDescriptor pipelineDesc;
316     pipelineDesc.vertex.module = utils::CreateShaderModule(device, R"(
317         [[stage(vertex)]] fn main([[builtin(vertex_index)]] VertexIndex : u32)
318                                -> [[builtin(position)]] vec4<f32> {
319             var pos = array<vec2<f32>, 3>(
320                 vec2<f32>( 0.0,  0.5),
321                 vec2<f32>(-0.5, -0.5),
322                 vec2<f32>( 0.5, -0.5)
323             );
324             return vec4<f32>(pos[VertexIndex], 0.0, 1.0);
325         })");
326     pipelineDesc.cFragment.module = utils::CreateShaderModule(device, R"(
327         [[stage(fragment)]] fn main() -> [[location(0)]] vec4<f32> {
328             return vec4<f32>(1.0, 0.0, 0.0, 1.0);
329         })");
330     // BGRA shouldn't be hardcoded. Consider having a map[format -> pipeline].
331     pipelineDesc.cTargets[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         utils::ScopedAutoreleasePool pool;
339         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 }
365