Use WebGPU
Before going into the design of Arche in terms of rendering, let's look at how to use WebGPU. The reason for this layer is that it will determine how we construct and destruct a series of objects. The construction and destruction of objects are the basis of all programs.
Arche-cppâ
In Arche-cpp, we develop WebGPU applications based on Dawn, and through Git Submodule, we can download the corresponding code when pulling the repository. The specific operations are not expanded here. Specific to calling WebGPU, there are two core header files:
#include <webgpu/webgpu.h> // C Header
#include <webgpu/webgpu_cpp.h> // C++ Header
The former corresponds to the C header file, and the latter uses C++ to encapsulate it. For the C type of WebGPU, such
as WGPUBuffer
is an Opaque Pointer:
typedef struct WGPUBufferImpl* WGPUBuffer;
Concrete objects can be constructed through WGPUDevice
related functions:
typedef WGPUBuffer (*WGPUProcDeviceCreateBuffer)(WGPUDevice device, WGPUBufferDescriptor const * descriptor);
You can see that these functions are also function pointers. Using C types makes it easy to bind with other languages, such as JavaScript, but in actual use, it is not particularly convenient. So Dawn provides a wrapper version of C++.
In C++, the equivalent Buffer
declaration is as follows:
class Buffer : public ObjectBase<Buffer, WGPUBuffer> {
public:
using ObjectBase::ObjectBase;
using ObjectBase::operator=;
void Destroy() const;
void const * GetConstMappedRange(size_t offset = 0, size_t size = 0) const;
void * GetMappedRange(size_t offset = 0, size_t size = 0) const;
void MapAsync(MapMode mode, size_t offset, size_t size, BufferMapCallback callback, void * userdata) const;
void SetLabel(char const * label) const;
void Unmap() const;
private:
friend ObjectBase<Buffer, WGPUBuffer>;
static void WGPUReference(WGPUBuffer handle);
static void WGPURelease(WGPUBuffer handle);
};
You can see that this type inherits ObjectBase, and provides two static functions, WGPUReference
and WGPURelease
,
recursively through singular templates. In fact, all similar classes are inherited in this way. from ObjectBase
It can be seen that a series of constructors are implemented:
class ObjectBase {
public:
ObjectBase() = default;
ObjectBase(CType handle): mHandle(handle) {
if (mHandle) Derived::WGPUReference(mHandle);
}
~ObjectBase() {
if (mHandle) Derived::WGPURelease(mHandle);
}
ObjectBase(ObjectBase const& other)
: ObjectBase(other.Get()) {
}
Derived& operator=(ObjectBase const& other) {
if (&other != this) {
if (mHandle) Derived::WGPURelease(mHandle);
mHandle = other.mHandle;
if (mHandle) Derived::WGPUReference(mHandle);
}
return static_cast<Derived&>(*this);
}
ObjectBase(ObjectBase&& other) {
mHandle = other.mHandle;
other.mHandle = 0;
}
Derived& operator=(ObjectBase&& other) {
if (&other != this) {
if (mHandle) Derived::WGPURelease(mHandle);
mHandle = other.mHandle;
other.mHandle = 0;
}
return static_cast<Derived&>(*this);
}
ObjectBase(std::nullptr_t) {}
Derived& operator=(std::nullptr_t) {
if (mHandle != nullptr) {
Derived::WGPURelease(mHandle);
mHandle = nullptr;
}
return static_cast<Derived&>(*this);
}
};
It can be seen that when these types are constructed, mHandle is stored internally, and when they are
destructed, Derived::WGPURelease
is automatically called to release these types, which is in line with the C++
construction principle RAII (Resource Acquisition Is Initialization).
Therefore, using the C++ version of the interface can automatically destruct the object, and there will be
no memory leak problem. Therefore, Arche-cpp uses the C++ interface to build the entire engine.
tip
Sometimes some projects use the C-type WebGPU interface, such as the back-end implementation provided by IMGUI,
so the C pointer saved by the C++ object can be taken out through Get
, but to avoid these projects from destructing
these pointers, otherwise C++ A pointer in a type becomes a wild pointer before it is destructed:
void GUI::draw(ImDrawData* drawData,
wgpu::RenderPassEncoder& passEncoder) {
ImGui_ImplWGPU_RenderDrawData(drawData, passEncoder.Get());
}
Arche.jsâ
The standard developer of WebGPU provides a TypeScript Type package,
namely @webgpu/types . All types in this package have been encapsulated
as interface
, for example:
interface GPUBuffer extends GPUObjectBase {
/**
* Maps the given range of the {@link GPUBuffer} and resolves the returned {@link Promise} when the
* {@link GPUBuffer}'s content is ready to be accessed with {@link GPUBuffer#getMappedRange}.
* @param mode - Whether the buffer should be mapped for reading or writing.
* @param offset - Offset in bytes into the buffer to the start of the range to map.
* @param size - Size in bytes of the range to map.
*/
mapAsync(
mode: GPUMapModeFlags,
offset?: GPUSize64,
size?: GPUSize64
): Promise<undefined>;
/**
* Returns a {@link ArrayBuffer} with the contents of the {@link GPUBuffer} in the given mapped range.
* @param offset - Offset in bytes into the buffer to return buffer contents from.
* @param size - Size in bytes of the {@link ArrayBuffer} to return.
*/
getMappedRange(
offset?: GPUSize64,
size?: GPUSize64
): ArrayBuffer;
/**
* Unmaps the mapped range of the {@link GPUBuffer} and makes it's contents available for use by the
* GPU again.
*/
unmap(): undefined;
/**
* Destroys the {@link GPUBuffer}.
*/
destroy(): undefined;
}
For types like GPUBuffer, using interface
is not too problematic because objects of these types are constructed
via GPUDevice
rather than new
an object directly. However, for many Descriptors For the object, if it needs to be
constructed in a way similar to Json every time, the garbage collection mechanism will be triggered implicitly. Since it
is impossible to new
an interface
directly, it is best to implement an interface
that conforms to the interface
. class
:
export class BufferDescriptor implements GPUBufferDescriptor {
label?: string;
mappedAtCreation?: boolean;
size: GPUSize64;
usage: GPUBufferUsageFlags;
}
These types can be cached or used as static types to cache intermediate variables, thus reducing the number of triggers for garbage collection mechanisms.