Skip to content

Commit 9a58ddf

Browse files
committed
Continue next C++ wrapper chapter
1 parent 755a96e commit 9a58ddf

2 files changed

Lines changed: 338 additions & 9 deletions

File tree

next/getting-started/cpp-wrapper.md

Lines changed: 337 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ C++ wrapper <span class="bullet">🟠</span>
44
```{lit-setup}
55
:tangle-root: 028 - C++ Wrapper - Next
66
:parent: 025 - First Color - Next
7+
:debug:
78
```
89

910
*Resulting code:* [`step028-next`](https://github.com/eliemichel/LearnWebGPU-Code/tree/step028-next)
@@ -48,8 +49,10 @@ For now we place this in the `main.cpp`, but you could also create a dedicated `
4849

4950
We now incrementally see the niceties that this wrapper introduces. Keep in mind that the C++ wrapper types are (almost) always **compatible with the C API**, so you can migrate your code **progressively**.
5051

51-
Namespace
52-
---------
52+
Core features
53+
-------------
54+
55+
### Namespace
5356

5457
The C interface could not make use of **namespaces**, since they only exist in C++, so you may have noticed that every single function starts with `wgpu` and every single structure starts with `WGPU`. A more C++ idiomatic way of doing this is to enclose all these functions into a **namespace**.
5558

@@ -78,8 +81,7 @@ using namespace wgpu;
7881
Instance instance = createInstance();
7982
```
8083

81-
Objects
82-
-------
84+
### Objects
8385

8486
Beyond namespace, most functions are also **prefixed by the type of their first argument**, for instance:
8587

@@ -139,8 +141,7 @@ Buffer buffer = device.createBuffer(descriptor);
139141
As you can see, descriptors can also be **initialized** using the generic `wgpu::Default` object instead of the long struct-specific INIT macro.
140142
```
141143

142-
Scoped enumerations
143-
-------------------
144+
### Scoped enumerations
144145

145146
Because enums are *unscoped* by default, the C API is forced to **prefix all values** that an enumeration can take with the name of the enum, leading to quite long names:
146147

@@ -176,8 +177,27 @@ wgpu::RequestAdapterStatus::Success;
176177
The actual implementation use a little trickery so that enum names are scoped, but implicitly converted to and from the original WebGPU enum values.
177178
```
178179

179-
Capturing closures
180-
------------------
180+
### String View
181+
182+
Instead of writing conversion functions like `toStdStringView` and `toWgpuStringView`, the wrapper defines a `wgpu::StringView` type that is able to automatically convert:
183+
184+
```C++
185+
deviceDesc.defaultQueue.label = toWgpuStringView("The Default Queue");
186+
// [...]
187+
WGPUStringView message = /* ... */;
188+
std::cerr << "Uncaptured Error: " << toStdStringView(message) << std::endl;
189+
```
190+
191+
becomes:
192+
193+
```C++
194+
deviceDesc.defaultQueue.label = StringView("The Default Queue");
195+
// [...]
196+
StringView message = /* ... */;
197+
std::cerr << "Uncaptured Error: " << message << std::endl;
198+
```
199+
200+
### Capturing closures
181201

182202
Many asynchronous operations use callbacks. In order to provide some context to the callback's body, there are always two `void *userdata` arguments passed around. This can be simplified in C++ by using capturing closures.
183203

@@ -223,6 +243,21 @@ buffer.mapAsync(buffer, MapMode::Read, 0, 16, CallbackMode::AllowProcessEvents,
223243
A little difference between the C and C++ versions above is that the C version allocates the `Context` statically on the stack, while rooms for the C++ lambda gets allocated dynamically in the heap. If this bothers you, the wrapper provides an in-between that enables using the object notation but still expects you to create the callback info structure yourself.
224244
```
225245

246+
Extensions
247+
----------
248+
249+
Besides the core zero-overhead features described above, the wrapper provides some utility features. When these are used, they add a bit of runtime code, but that likely corresponds to what you would manually write without using them.
250+
251+
### Synchronous adapter and device requests
252+
253+
As we have seen in the early chapters, it can be convenient to get a version of `Instance::requestAdapter` and `Adapter::requestDevice` that are blocking instead of asynchronous. The wrapper provides a `Instance::requestAdapterSync` and a `Adapter::requestDeviceSync` that are equivalent to the utility functions that we previously introduced.
254+
255+
### Object pretty printing
256+
257+
Object wrappers (Instance, Adapter, Device, etc.) provide an overload of `operator<<` so that printing them with `std::cout` does not just give a pointer address but also prefixes it with the type name, like `<wgpu::Device 0x1234567>` instead of just `0x1234567`.
258+
259+
If you want to avoid this, just cast the object back to the raw type before printing it: `std::cout << (WGPUDevice)device < std::endl`.
260+
226261
Conclusion
227262
----------
228263

@@ -231,3 +266,297 @@ From now on, I will be providing two versions of each code snippet: one that use
231266
Congratulations, you've made it to **the end of the "Getting Started" section**! It was not a small thing, you are now well equipped to explore and understand the various documentation about WebGPU. From here, you can decide to either move on to the [Graphics](../basic-3d-rendering/index.md) section and draw your first triangle, or go to the [Compute](../basic-compute/index.md) section and start playing with tensors.
232267

233268
*Resulting code:* [`step028-next`](https://github.com/eliemichel/LearnWebGPU-Code/tree/step028-next)
269+
270+
271+
```{lit} C++, Includes (replace, hidden)
272+
#include "webgpu-utils.h"
273+
274+
#define WEBGPU_CPP_IMPLEMENTATION // NEW
275+
#include <webgpu/webgpu.hpp> // NEW
276+
#include <GLFW/glfw3.h>
277+
#include <glfw3webgpu.h>
278+
279+
#ifdef __EMSCRIPTEN__
280+
# include <emscripten.h>
281+
#endif
282+
283+
#include <iostream>
284+
#include <thread>
285+
#include <chrono>
286+
#include <vector>
287+
#include <string_view>
288+
289+
using namespace wgpu; // NEW
290+
```
291+
292+
```{lit} C++, Private methods (replace, hidden)
293+
private:
294+
TextureView GetNextSurfaceView(); // NEW
295+
```
296+
297+
```{lit} C++, Application attributes (replace, hidden)
298+
GLFWwindow *window;
299+
wgpu::Instance instance; // NEW
300+
wgpu::Device device; // NEW
301+
wgpu::Queue queue; // NEW
302+
wgpu::Surface surface; // NEW
303+
```
304+
305+
```{lit} C++, Initialize (replace, hidden)
306+
{{Open window and get adapter}}
307+
308+
{{Request device}}
309+
310+
queue = device.getQueue(); // NEW
311+
312+
{{Surface Configuration}}
313+
314+
// We no longer need to access the adapter
315+
adapter.release(); // NEW
316+
```
317+
318+
```{lit} C++, Open window and get adapter (replace, hidden)
319+
// Open window
320+
glfwInit();
321+
glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); // <-- extra info for glfwCreateWindow
322+
glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE);
323+
window = glfwCreateWindow(640, 480, "Learn WebGPU", nullptr, nullptr);
324+
325+
// Create instance ('instance' is now declared at the class level)
326+
instance = createInstance(); // NEW
327+
328+
// Get adapter
329+
std::cout << "Requesting adapter..." << std::endl;
330+
{{Request adapter}}
331+
std::cout << "Got adapter: " << adapter << std::endl;
332+
```
333+
334+
```{lit} C++, Request adapter (replace, hidden)
335+
{{Get the surface}}
336+
RequestAdapterOptions adapterOpts = Default; // NEW
337+
adapterOpts.compatibleSurface = surface;
338+
Adapter adapter = requestAdapterSync(instance, &adapterOpts); // TODO
339+
```
340+
341+
```{lit} C++, Request device (replace, hidden)
342+
std::cout << "Requesting device..." << std::endl;
343+
DeviceDescriptor deviceDesc = Default; // NEW
344+
{{Build device descriptor}}
345+
device = requestDeviceSync(instance, adapter, &deviceDesc); // TODO
346+
std::cout << "Got device: " << device << std::endl;
347+
```
348+
349+
```{lit} C++, Build device descriptor (replace, hidden)
350+
deviceDesc.label = StringView("My Device"); // NEW
351+
352+
std::vector<FeatureName> features; // NEW
353+
{{List required features}}
354+
deviceDesc.requiredFeatureCount = features.size();
355+
deviceDesc.requiredFeatures = (WGPUFeatureName*)features.data(); // NEW
356+
357+
// Make sure 'features' lives until the call to wgpuAdapterRequestDevice!
358+
Limits requiredLimits = Default; // NEW
359+
{{Specify required limits}}
360+
deviceDesc.requiredLimits = &requiredLimits;
361+
362+
// Make sure that the 'requiredLimits' variable lives until the call to wgpuAdapterRequestDevice!
363+
deviceDesc.defaultQueue.label = StringView("The Default Queue"); // NEW
364+
365+
{{Device Lost Callback}}
366+
367+
{{Device Error Callback}}
368+
```
369+
370+
```{lit} C++, Device Lost Callback (replace, hidden)
371+
auto onDeviceLost = []( // TODO: setter
372+
WGPUDevice const * device,
373+
WGPUDeviceLostReason reason,
374+
struct WGPUStringView message,
375+
void* /* userdata1 */,
376+
void* /* userdata2 */
377+
) {
378+
// All we do is display a message when the device is lost
379+
std::cout
380+
<< "Device " << device << " was lost: reason " << reason
381+
<< " (" << StringView(message) << ")" // NEW
382+
<< std::endl;
383+
};
384+
deviceDesc.deviceLostCallbackInfo.callback = onDeviceLost;
385+
deviceDesc.deviceLostCallbackInfo.mode = WGPUCallbackMode_AllowProcessEvents;
386+
```
387+
388+
```{lit} C++, Device Error Callback (replace, hidden)
389+
auto onDeviceError = []( // TODO: setter
390+
WGPUDevice const * device,
391+
WGPUErrorType type,
392+
struct WGPUStringView message,
393+
void* /* userdata1 */,
394+
void* /* userdata2 */
395+
) {
396+
std::cout
397+
<< "Uncaptured error in device " << device << ": type " << type
398+
<< " (" << StringView(message) << ")" // NEW
399+
<< std::endl;
400+
};
401+
deviceDesc.uncapturedErrorCallbackInfo.callback = onDeviceError;
402+
```
403+
404+
```{lit} C++, Surface Configuration (replace, hidden)
405+
SurfaceConfiguration config = Default; // NEW
406+
{{Describe the surface configuration}}
407+
surface.configure(config); // NEW
408+
```
409+
410+
```{lit} C++, Describe the surface configuration (replace, hidden)
411+
// Configuration of the textures created for the underlying swap chain
412+
config.width = 640;
413+
config.height = 480;
414+
config.device = device;
415+
{{Describe surface format}}
416+
config.presentMode = PresentMode::Fifo; // NEW
417+
config.alphaMode = CompositeAlphaMode::Auto; // NEW
418+
```
419+
420+
```{lit} C++, Describe surface format (replace, hidden)
421+
SurfaceCapabilities capabilities = Default; // NEW
422+
423+
// We get the capabilities for a pair of (surface, adapter).
424+
// If it works, this populates the `capabilities` structure
425+
Status status = surface.getCapabilities(adapter, &capabilities); // NEW
426+
if (status != Status::Success) { // NEW
427+
return false;
428+
}
429+
430+
// From the capabilities, we get the preferred format: it is always the first one!
431+
// (NB: There is always at least 1 format if the GetCapabilities was successful)
432+
config.format = capabilities.formats[0];
433+
434+
// We no longer need to access the capabilities, so we release their memory.
435+
capabilities.freeMembers(); // NEW
436+
```
437+
438+
```{lit} C++, Terminate (replace, hidden)
439+
surface.unconfigure(); // NEW
440+
queue.release(); // NEW
441+
{{Destroy surface}}
442+
device.release(); // NEW
443+
glfwDestroyWindow(window);
444+
glfwTerminate();
445+
```
446+
447+
```{lit} C++, Destroy surface (replace, hidden)
448+
surface.release(); // NEW
449+
```
450+
451+
```{lit} C++, Application implementation (replace, hidden)
452+
bool Application::Initialize() {
453+
// Move the whole initialization here
454+
{{Initialize}}
455+
return true;
456+
}
457+
458+
void Application::Terminate() {
459+
// Move all the release/destroy/terminate calls here
460+
{{Terminate}}
461+
}
462+
463+
void Application::MainLoop() {
464+
glfwPollEvents();
465+
instance.processEvents(); // NEW
466+
467+
{{Main loop content}}
468+
}
469+
470+
bool Application::IsRunning() {
471+
return !glfwWindowShouldClose(window);
472+
}
473+
474+
{{GetNextSurfaceView method}}
475+
```
476+
477+
```{lit} C++, Get the next target texture view (replace, hidden)
478+
// Get the next target texture view
479+
TextureView targetView = GetNextSurfaceView(); // NEW
480+
if (!targetView) return; // no surface texture, we skip this frame
481+
```
482+
483+
```{lit} C++, Create Command Encoder (replace, hidden)
484+
CommandEncoderDescriptor encoderDesc = Default; // NEW
485+
encoderDesc.label = StringView("My command encoder"); // NEW
486+
CommandEncoder encoder = device.createCommandEncoder(encoderDesc); // NEW
487+
```
488+
489+
```{lit} C++, Encode Render Pass (replace, hidden)
490+
RenderPassDescriptor renderPassDesc = Default; // NEW
491+
{{Describe Render Pass}}
492+
493+
RenderPassEncoder renderPass = encoder.beginRenderPass(renderPassDesc); // NEW
494+
{{Use Render Pass}}
495+
renderPass.end(); // NEW
496+
renderPass.release(); // NEW
497+
```
498+
499+
```{lit} C++, Describe Render Pass (replace, hidden)
500+
RenderPassColorAttachment renderPassColorAttachment = Default; // NEW
501+
{{Describe the attachment}}
502+
renderPassDesc.colorAttachmentCount = 1;
503+
renderPassDesc.colorAttachments = &renderPassColorAttachment;
504+
```
505+
506+
```{lit} C++, Describe the attachment (replace, hidden)
507+
renderPassColorAttachment.view = targetView;
508+
renderPassColorAttachment.loadOp = LoadOp::Clear; // NEW
509+
renderPassColorAttachment.storeOp = StoreOp::Store; // NEW
510+
renderPassColorAttachment.clearValue = Color{ 1.0, 0.8, 0.55, 1.0 }; // NEW
511+
```
512+
513+
```{lit} C++, Finish encoding and submit (replace, hidden)
514+
CommandBufferDescriptor cmdBufferDescriptor = Default; // NEW
515+
cmdBufferDescriptor.label = StringView("Command buffer"); // NEW
516+
CommandBuffer command = encoder.finish(cmdBufferDescriptor); // NEW
517+
encoder.release(); // NEW // release encoder after it's finished
518+
519+
// Finally submit the command queue
520+
std::cout << "Submitting command..." << std::endl;
521+
queue.submit(command); // NEW
522+
command.release(); // NEW
523+
std::cout << "Command submitted." << std::endl;
524+
```
525+
526+
```{lit} C++, Present the surface onto the window (replace, hidden)
527+
targetView.release(); // NEW
528+
#ifndef __EMSCRIPTEN__
529+
surface.present(); // NEW
530+
#endif
531+
```
532+
533+
```{lit} C++, GetNextSurfaceView method (replace, hidden)
534+
TextureView Application::GetNextSurfaceView() { // NEW
535+
{{Get the next surface texture}}
536+
{{Create surface texture view}}
537+
{{Release the texture}}
538+
return targetView;
539+
}
540+
```
541+
542+
```{lit} C++, Get the next surface texture (replace, hidden)
543+
SurfaceTexture surfaceTexture = Default; // NEW
544+
surface.getCurrentTexture(&surfaceTexture); // NEW
545+
if (
546+
surfaceTexture.status != SurfaceGetCurrentTextureStatus::SuccessOptimal && // NEW
547+
surfaceTexture.status != SurfaceGetCurrentTextureStatus::SuccessSuboptimal
548+
) {
549+
return nullptr;
550+
}
551+
```
552+
553+
```{lit} C++, Create surface texture view (replace, hidden)
554+
TextureViewDescriptor viewDescriptor = Default; // NEW
555+
viewDescriptor.label = StringView("Surface texture view"); // NEW
556+
viewDescriptor.dimension = TextureViewDimension::_2D; // NEW // not to confuse with 2DArray
557+
TextureView targetView = Texture(surfaceTexture.texture).createView(viewDescriptor); // NEW, TODO
558+
```
559+
560+
```{lit} C++, Release the texture (replace, hidden)
561+
Texture(surfaceTexture.texture).release(); // NEW, TODO
562+
```

next/getting-started/first-color.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -202,7 +202,7 @@ private:
202202
To get the texture to draw onto, we use `wgpuSurfaceGetCurrentTexture`. The "surface texture" is not really an object but rather a **container** for the **multiple things that this function returns**. It is thus up to us to create the `WGPUSurfaceTexture` container, which we pass to the function to write into it:
203203

204204
```{lit} C++, Get the next surface texture
205-
WGPUSurfaceTexture surfaceTexture;
205+
WGPUSurfaceTexture surfaceTexture = WGPU_SURFACE_TEXTURE_INIT;
206206
wgpuSurfaceGetCurrentTexture(surface, &surfaceTexture);
207207
```
208208

0 commit comments

Comments
 (0)