-
-
Save nickav/88c7a1344fe68da8a45ab25c4795b237 to your computer and use it in GitHub Desktop.
setting up and using WebGPU in C using Dawn and GLFW
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// NOTE(nick): | |
// | |
// 1. Get Dawn: | |
// | |
// on Windows get a prebuilt copy of dawn from: https://github.com/mmozeiko/build-dawn | |
// | |
// on MacOS build from source with: | |
// git clone https://github.com/google/dawn | |
// cd dawn | |
// mkdir -p out/Release | |
// cd out/Release | |
// cmake ../.. -DCMAKE_BUILD_TYPE=Release | |
// make | |
// | |
// Place the library in the same directory as your source files. | |
// | |
// 2. Build | |
// | |
// on MacOS rename to the file to .m and build with: | |
// libs="$(pkg-config --cflags --libs glfw3) -framework Cocoa -framework AppKit -framework QuartzCore -L. -lwebgpu_dawn" | |
// clang -g -I glfwwebgpu.m $libs -o main | |
// | |
#include <stdio.h> | |
#include <assert.h> | |
#include <stdbool.h> | |
#ifdef _WIN32 | |
#define WIN32_LEAN_AND_MEAN | |
#define VC_EXTRALEAN | |
#define NOMINMAX | |
#include <windows.h> | |
#pragma comment (lib, "gdi32") | |
#pragma comment (lib, "user32") | |
#pragma comment (lib, "webgpu_dawn") | |
#endif // _WIN32 | |
#include "webgpu.h" | |
#include "glfw3webgpu.c" | |
#include <GLFW/glfw3.h> | |
#define WEBGPU_STR(str) (WGPUStringView) { .data = str, .length = sizeof(str) - 1 } | |
#define Unused(x) ((void)(x)) | |
#ifdef __APPLE__ | |
static void FatalError(WGPUStringView message) | |
{ | |
char msg[1024]; | |
snprintf(msg, sizeof(msg), "%.*s", (int)message.length, message.data); | |
NSAlert *alert = [[NSAlert alloc] init]; | |
[alert setMessageText:@"Fatal Error"]; | |
[alert setInformativeText:[NSString stringWithUTF8String:msg]]; | |
[alert setAlertStyle:NSAlertStyleCritical]; | |
[alert runModal]; | |
abort(); | |
} | |
#endif // __APPLE__ | |
#ifdef _WIN32 | |
static void FatalError(WGPUStringView message) | |
{ | |
char msg[1024]; | |
snprintf(msg, sizeof(msg), "%.*s", (int)message.length, message.data); | |
MessageBoxA(NULL, msg, "Error", MB_ICONEXCLAMATION); | |
ExitProcess(0); | |
} | |
#endif // _WIN32 | |
static void OnDeviceError(const WGPUDevice *device, WGPUErrorType type, WGPUStringView message, void* userdata1, void* userdata2) | |
{ | |
FatalError(message); | |
} | |
static bool adapterRequestEnded = false; | |
static void OnAdapterRequestEnded(WGPURequestAdapterStatus status, WGPUAdapter adapter, WGPUStringView message, void *userData) | |
{ | |
if (status == WGPURequestAdapterStatus_Success) | |
{ | |
WGPUAdapter *result = (WGPUAdapter*)userData; | |
if (*result == NULL) | |
{ | |
*result = adapter; | |
} | |
} | |
else | |
{ | |
printf("Could not get WebGPU adapter!\n"); | |
FatalError(message); | |
} | |
adapterRequestEnded = true; | |
} | |
struct Vertex | |
{ | |
float position[2]; | |
float uv[2]; | |
float color[3]; | |
}; | |
const uint32_t kVertexStride = sizeof(struct Vertex); | |
static const struct Vertex kVertexData[] = | |
{ | |
{ { -0.00f, +0.75f }, { 25.0f, 50.0f }, { 1, 0, 0 } }, | |
{ { +0.75f, -0.50f }, { 0.0f, 0.0f }, { 0, 1, 0 } }, | |
{ { -0.75f, -0.50f }, { 50.0f, 0.0f }, { 0, 0, 1 } }, | |
}; | |
const uint32_t kTextureWidth = 2; | |
const uint32_t kTextureHeight = 2; | |
const WGPUTextureFormat kSwapChainFormat = WGPUTextureFormat_BGRA8Unorm; | |
void wgpuPollEvents(WGPUDevice device, bool yieldToWebBrowser) | |
{ | |
Unused(device); | |
Unused(yieldToWebBrowser); | |
#if defined(WEBGPU_BACKEND_DAWN) | |
wgpuDeviceTick(device); | |
#elif defined(WEBGPU_BACKEND_WGPU) | |
wgpuDevicePoll(device, false, NULL); | |
#elif defined(WEBGPU_BACKEND_EMSCRIPTEN) | |
if (yieldToWebBrowser) { | |
emscripten_sleep(100); | |
} | |
#endif | |
} | |
int main() | |
{ | |
glfwInit(); | |
glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); | |
glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE); | |
GLFWwindow *window = glfwCreateWindow(1280, 800, "WebGPU Window", NULL, NULL); | |
WGPUInstance instance = wgpuCreateInstance(NULL); | |
WGPUSurface surface = glfwGetWGPUSurface(instance, window); | |
WGPURequestAdapterOptions options = {}; | |
options.nextInChain = NULL; | |
options.compatibleSurface = surface; | |
WGPUAdapter adapter = NULL; | |
{ | |
adapterRequestEnded = false; | |
WGPURequestAdapterOptions options = | |
{ | |
.compatibleSurface = surface, | |
// .powerPreference = WGPUPowerPreference_HighPerformance, | |
}; | |
wgpuInstanceRequestAdapter(instance, &options, &OnAdapterRequestEnded, &adapter); | |
// when using Emscripten, we need to hand the control back to the browser until the adapter is ready. | |
#ifdef __EMSCRIPTEN__ | |
while (!adapterRequestEnded) | |
{ | |
emscripten_sleep(100); | |
} | |
#endif // __EMSCRIPTEN__ | |
if (!adapter) | |
{ | |
FatalError(WEBGPU_STR("Failed to get WebGPU adapter")); | |
} | |
} | |
{ | |
WGPUAdapterInfo info = {0}; | |
wgpuAdapterGetInfo(adapter, &info); | |
const char* adapter_types[] = | |
{ | |
[WGPUAdapterType_DiscreteGPU] = "Discrete GPU", | |
[WGPUAdapterType_IntegratedGPU] = "Integrated GPU", | |
[WGPUAdapterType_CPU] = "CPU", | |
[WGPUAdapterType_Unknown] = "unknown", | |
}; | |
char temp[1024]; | |
snprintf(temp, sizeof(temp), | |
"Device = %.*s\n" | |
"Description = %.*s\n" | |
"Vendor = %.*s\n" | |
"Architecture = %.*s\n" | |
"Adapter Type = %s\n", | |
(int)info.device.length, info.device.data, | |
(int)info.description.length, info.description.data, | |
(int)info.vendor.length, info.vendor.data, | |
(int)info.architecture.length, info.architecture.data, | |
adapter_types[info.adapterType]); | |
printf("%s", temp); | |
} | |
WGPUDevice device = NULL; | |
{ | |
// if you want to be sure device will support things you'll use, you can specify requirements here: | |
//WGPUSupportedLimits supported = { 0 }; | |
//wgpuAdapterGetLimits(adapter, &supported); | |
//supported.limits.maxTextureDimension2D = kTextureWidth; | |
//supported.limits.maxBindGroups = 1; | |
//supported.limits.maxBindingsPerBindGroup = 3; // uniform buffer for vertex shader, and texture + sampler for fragment | |
//supported.limits.maxSampledTexturesPerShaderStage = 1; | |
//supported.limits.maxSamplersPerShaderStage = 1; | |
//supported.limits.maxUniformBuffersPerShaderStage = 1; | |
//supported.limits.maxUniformBufferBindingSize = 4 * 4 * sizeof(float); // 4x4 matrix | |
//supported.limits.maxVertexBuffers = 1; | |
//supported.limits.maxBufferSize = sizeof(kVertexData); | |
//supported.limits.maxVertexAttributes = 3; // pos, texcoord, color | |
//supported.limits.maxVertexBufferArrayStride = kVertexStride; | |
//supported.limits.maxColorAttachments = 1; | |
WGPUDeviceDescriptor desc = | |
{ | |
// notify on errors | |
.uncapturedErrorCallbackInfo2.callback = &OnDeviceError, | |
// extra features: https://dawn.googlesource.com/dawn/+/refs/heads/main/src/dawn/native/Features.cpp | |
//.requiredFeaturesCount = n | |
//.requiredFeatures = (WGPUFeatureName[]) { ... } | |
//.requiredLimits = &(WGPURequiredLimits) { .limits = supported.limits }, | |
}; | |
device = wgpuAdapterCreateDevice(adapter, &desc); | |
assert(device && "Failed to create WebGPU device"); | |
} | |
WGPUQueue queue = wgpuDeviceGetQueue(device); | |
WGPUBuffer vbuffer; | |
{ | |
WGPUBufferDescriptor desc = | |
{ | |
.usage = WGPUBufferUsage_Vertex | WGPUBufferUsage_CopyDst, | |
.size = sizeof(kVertexData), | |
}; | |
vbuffer = wgpuDeviceCreateBuffer(device, &desc); | |
wgpuQueueWriteBuffer(queue, vbuffer, 0, kVertexData, sizeof(kVertexData)); | |
} | |
// uniform buffer for one 4x4 float matrix | |
WGPUBuffer ubuffer; | |
{ | |
WGPUBufferDescriptor desc = | |
{ | |
.usage = WGPUBufferUsage_Uniform | WGPUBufferUsage_CopyDst, | |
.size = 4 * 4 * sizeof(float), // 4x4 matrix | |
}; | |
ubuffer = wgpuDeviceCreateBuffer(device, &desc); | |
} | |
WGPUTextureView texture_view; | |
{ | |
// checkerboard texture, with 50% transparency on black colors | |
unsigned int pixels[] = | |
{ | |
0x80000000, 0xffffffff, | |
0xffffffff, 0x80000000, | |
}; | |
WGPUTextureDescriptor desc = | |
{ | |
.usage = WGPUTextureUsage_TextureBinding | WGPUTextureUsage_CopyDst, | |
.dimension = WGPUTextureDimension_2D, | |
.size = { kTextureWidth, kTextureHeight, 1 }, | |
.format = WGPUTextureFormat_RGBA8Unorm, | |
.mipLevelCount = 1, | |
.sampleCount = 1, | |
}; | |
WGPUTexture texture = wgpuDeviceCreateTexture(device, &desc); | |
// upload pixels | |
WGPUImageCopyTexture dst = | |
{ | |
.texture = texture, | |
.mipLevel = 0, | |
}; | |
WGPUTextureDataLayout src = | |
{ | |
.offset = 0, | |
.bytesPerRow = kTextureWidth * 4, // 4 bytes per pixel | |
.rowsPerImage = kTextureHeight, | |
}; | |
wgpuQueueWriteTexture(queue, &dst, pixels, sizeof(pixels), &src, &desc.size); | |
WGPUTextureViewDescriptor view_desc = | |
{ | |
.format = desc.format, | |
.dimension = WGPUTextureViewDimension_2D, | |
.baseMipLevel = 0, | |
.mipLevelCount = 1, | |
.baseArrayLayer = 0, | |
.arrayLayerCount = 1, | |
.aspect = WGPUTextureAspect_All, | |
}; | |
texture_view = wgpuTextureCreateView(texture, &view_desc); | |
wgpuTextureRelease(texture); | |
} | |
// compile shader | |
WGPUShaderModule shaders; | |
{ | |
#if 1 | |
// shader in WebGPU Shading Language (WGSL) | |
static const char wgsl[] = | |
"struct VertexIn { \n" | |
" @location(0) aPos : vec2f, \n" | |
" @location(1) aTex : vec2f, \n" | |
" @location(2) aCol : vec3f \n" | |
"} \n" | |
" \n" | |
"struct VertexOut { \n" | |
" @builtin(position) vPos : vec4f, \n" | |
" @location(0) vTex : vec2f, \n" | |
" @location(1) vCol : vec3f \n" | |
"} \n" | |
" \n" | |
"@group(0) @binding(0) var<uniform> uTransform : mat4x4f; \n" | |
"@group(0) @binding(1) var myTexture : texture_2d<f32>; \n" | |
"@group(0) @binding(2) var mySampler : sampler; \n" | |
" \n" | |
"@vertex \n" | |
"fn vs(in : VertexIn) -> VertexOut { \n" | |
" var out : VertexOut; \n" | |
" out.vPos = uTransform * vec4f(in.aPos, 0.0, 1.0); \n" | |
" out.vTex = in.aTex; \n" | |
" out.vCol = in.aCol; \n" | |
" return out; \n" | |
"} \n" | |
" \n" | |
"@fragment \n" | |
"fn fs(in : VertexOut) -> @location(0) vec4f { \n" | |
" var tex : vec4f = textureSample(myTexture, mySampler, in.vTex); \n" | |
" return vec4f(in.vCol, 1.0) * tex; \n" | |
"} \n" | |
; | |
WGPUShaderModuleDescriptor desc = | |
{ | |
.nextInChain = &((WGPUShaderSourceWGSL) | |
{ | |
.chain.sType = WGPUSType_ShaderSourceWGSL, | |
.code = WEBGPU_STR(wgsl), | |
}).chain, | |
}; | |
#else | |
// alternative way of using SPIR-V shader binary code as input, non-standard for WebGPU spec | |
uint32_t code[4096]; | |
// to create SPIR-V binary from WGSL source, first save code above into "shader.wgsl" file | |
// then run "tint.exe shader.wgsl -o shader.spv" to get "shader.spv" output file | |
// you can also create "shader.spv" file by compiling HLSL/GLSL code to SPIR-V using custom tools | |
FILE* f = fopen("shader.spv", "rb"); | |
int code_size = (int)fread(code, sizeof(code[0]), sizeof(code)/sizeof(code[0]), f); | |
fclose(f); | |
WGPUShaderModuleDescriptor desc = | |
{ | |
.nextInChain = &((WGPUShaderSourceSPIRV) | |
{ | |
.chain.sType = WGPUSType_ShaderSourceSPIRV, | |
.codeSize = code_size, | |
.code = code, | |
}).chain, | |
}; | |
#endif | |
shaders = wgpuDeviceCreateShaderModule(device, &desc); | |
// to get compiler error messages explicitly use wgpuShaderModuleGetCompilationInfo | |
// otherwise they will be reported with device error callback | |
} | |
WGPUSampler sampler; | |
{ | |
WGPUSamplerDescriptor desc = | |
{ | |
.addressModeU = WGPUAddressMode_Repeat, | |
.addressModeV = WGPUAddressMode_Repeat, | |
.addressModeW = WGPUAddressMode_Repeat, | |
.magFilter = WGPUFilterMode_Nearest, | |
.minFilter = WGPUFilterMode_Nearest, | |
.mipmapFilter = WGPUMipmapFilterMode_Nearest, | |
.lodMinClamp = 0.f, | |
.lodMaxClamp = 1.f, | |
.compare = WGPUCompareFunction_Undefined, | |
.maxAnisotropy = 1, | |
}; | |
sampler = wgpuDeviceCreateSampler(device, &desc); | |
} | |
WGPUBindGroup bind_group; | |
WGPURenderPipeline pipeline; | |
{ | |
WGPUBindGroupLayoutDescriptor bind_group_layout_desc = | |
{ | |
.entryCount = 3, | |
.entries = (WGPUBindGroupLayoutEntry[]) | |
{ | |
// uniform buffer for vertex shader | |
{ | |
.binding = 0, | |
.visibility = WGPUShaderStage_Vertex, | |
.buffer.type = WGPUBufferBindingType_Uniform, | |
}, | |
// texture for fragment shader | |
{ | |
.binding = 1, | |
.visibility = WGPUShaderStage_Fragment, | |
.texture.sampleType = WGPUTextureSampleType_Float, | |
.texture.viewDimension = WGPUTextureViewDimension_2D, | |
.texture.multisampled = 0, | |
}, | |
// sampler for fragment shader | |
{ | |
.binding = 2, | |
.visibility = WGPUShaderStage_Fragment, | |
.sampler.type = WGPUSamplerBindingType_Filtering, | |
}, | |
}, | |
}; | |
WGPUBindGroupLayout bind_group_layout = wgpuDeviceCreateBindGroupLayout(device, &bind_group_layout_desc); | |
WGPUPipelineLayoutDescriptor pipeline_layout_desc = | |
{ | |
.bindGroupLayoutCount = 1, | |
.bindGroupLayouts = (WGPUBindGroupLayout[]) { bind_group_layout }, | |
}; | |
WGPUPipelineLayout pipeline_layout = wgpuDeviceCreatePipelineLayout(device, &pipeline_layout_desc); | |
WGPURenderPipelineDescriptor pipeline_desc = | |
{ | |
.layout = pipeline_layout, | |
// draw triangle list, no index buffer, no culling | |
.primitive = | |
{ | |
.topology = WGPUPrimitiveTopology_TriangleList, | |
// .stripIndexFormat = WGPUIndexFormat_Uint16, | |
.frontFace = WGPUFrontFace_CCW, | |
.cullMode = WGPUCullMode_None, | |
}, | |
// vertex shader | |
.vertex = | |
{ | |
.module = shaders, | |
.entryPoint = WEBGPU_STR("vs"), | |
.bufferCount = 1, | |
.buffers = (WGPUVertexBufferLayout[]) | |
{ | |
// one vertex buffer as input | |
{ | |
.arrayStride = kVertexStride, | |
.stepMode = WGPUVertexStepMode_Vertex, | |
.attributeCount = 3, | |
.attributes = (WGPUVertexAttribute[]) | |
{ | |
{ WGPUVertexFormat_Float32x2, offsetof(struct Vertex, position), 0 }, | |
{ WGPUVertexFormat_Float32x2, offsetof(struct Vertex, uv), 1 }, | |
{ WGPUVertexFormat_Float32x3, offsetof(struct Vertex, color), 2 }, | |
}, | |
}, | |
}, | |
}, | |
// fragment shader | |
.fragment = &(WGPUFragmentState) | |
{ | |
.module = shaders, | |
.entryPoint = WEBGPU_STR("fs"), | |
.targetCount = 1, | |
.targets = (WGPUColorTargetState[]) | |
{ | |
// writing to one output, with alpha-blending enabled | |
{ | |
.format = kSwapChainFormat, | |
.blend = &(WGPUBlendState) | |
{ | |
.color.operation = WGPUBlendOperation_Add, | |
.color.srcFactor = WGPUBlendFactor_SrcAlpha, | |
.color.dstFactor = WGPUBlendFactor_OneMinusSrcAlpha, | |
.alpha.operation = WGPUBlendOperation_Add, | |
.alpha.srcFactor = WGPUBlendFactor_SrcAlpha, | |
.alpha.dstFactor = WGPUBlendFactor_OneMinusSrcAlpha, | |
}, | |
.writeMask = WGPUColorWriteMask_All, | |
}, | |
}, | |
}, | |
// if depth/stencil buffer usage/testing is needed | |
//.depthStencil = &(WGPUDepthStencilState) { ... }, | |
// no multisampling | |
.multisample = | |
{ | |
.count = 1, | |
.mask = 0xffffffff, | |
.alphaToCoverageEnabled = 0, | |
}, | |
}; | |
pipeline = wgpuDeviceCreateRenderPipeline(device, &pipeline_desc); | |
wgpuPipelineLayoutRelease(pipeline_layout); | |
WGPUBindGroupDescriptor bind_group_desc = | |
{ | |
.layout = bind_group_layout, | |
.entryCount = 3, | |
.entries = (WGPUBindGroupEntry[]) | |
{ | |
// uniform buffer for vertex shader | |
{ .binding = 0, .buffer = ubuffer, .offset = 0, .size = 4 * 4 * sizeof(float) }, | |
// texure for fragment shader | |
{ .binding = 1, .textureView = texture_view }, | |
// sampler for fragment shader | |
{ .binding = 2, .sampler = sampler }, | |
}, | |
}; | |
bind_group = wgpuDeviceCreateBindGroup(device, &bind_group_desc); | |
wgpuBindGroupLayoutRelease(bind_group_layout); | |
} | |
// release resources that are now owned by pipeline and will not be used in this code later | |
wgpuSamplerRelease(sampler); | |
wgpuTextureViewRelease(texture_view); | |
glfwSwapInterval(1); | |
float angle = 0; | |
int current_width = 0; | |
int current_height = 0; | |
float then = glfwGetTime(); | |
bool swap_chain = false; | |
while (!glfwWindowShouldClose(window)) | |
{ | |
glfwPollEvents(); | |
wgpuPollEvents(device, false); | |
int width, height; | |
glfwGetFramebufferSize(window, &width, &height); | |
// process all internal async events or error callbacks | |
// this is native-code specific functionality, because in browser's WebGPU everything works with JS event loop | |
wgpuInstanceProcessEvents(instance); | |
// resize swap chain if needed | |
if (!swap_chain || width != current_width || height != current_height) | |
{ | |
if (swap_chain) | |
{ | |
// release old swap chain | |
wgpuSurfaceUnconfigure(surface); | |
swap_chain = false; | |
} | |
// resize to new size for non-zero window size | |
if (width != 0 && height != 0) | |
{ | |
WGPUSurfaceConfiguration config = | |
{ | |
.device = device, | |
.format = kSwapChainFormat, | |
.usage = WGPUTextureUsage_RenderAttachment, | |
.width = width, | |
.height = height, | |
.presentMode = WGPUPresentMode_Fifo, // WGPUPresentMode_Mailbox // WGPUPresentMode_Immediate | |
}; | |
wgpuSurfaceConfigure(surface, &config); | |
swap_chain = true; | |
} | |
current_width = width; | |
current_height = height; | |
} | |
if (swap_chain) | |
{ | |
float now = glfwGetTime(); | |
float delta = then - now; | |
then = now; | |
// update 4x4 rotation matrix in uniform | |
{ | |
angle += delta * 2.0f * (float)M_PI / 20.0f; // full rotation in 20 seconds | |
angle = fmodf(angle, 2.0f * (float)M_PI); | |
float aspect = (float)height / width; | |
float matrix[16] = | |
{ | |
cosf(angle) * aspect, -sinf(angle), 0, 0, | |
sinf(angle) * aspect, cosf(angle), 0, 0, | |
0, 0, 0, 0, | |
0, 0, 0, 1, | |
}; | |
wgpuQueueWriteBuffer(queue, ubuffer, 0, matrix, sizeof(matrix)); | |
} | |
WGPUSurfaceTexture surfaceTex; | |
wgpuSurfaceGetCurrentTexture(surface, &surfaceTex); | |
if (surfaceTex.status != WGPUSurfaceGetCurrentTextureStatus_Success) | |
{ | |
FatalError(WEBGPU_STR("Cannot acquire next swap chain texture!")); | |
} | |
WGPUTextureViewDescriptor surfaceViewDesc = | |
{ | |
.format = wgpuTextureGetFormat(surfaceTex.texture), | |
.dimension = WGPUTextureViewDimension_2D, | |
.mipLevelCount = 1, | |
.arrayLayerCount = 1, | |
.aspect = WGPUTextureAspect_All, | |
.usage = WGPUTextureUsage_RenderAttachment, | |
}; | |
WGPUTextureView surfaceView = wgpuTextureCreateView(surfaceTex.texture, &surfaceViewDesc); | |
assert(surfaceView); | |
WGPURenderPassDescriptor desc = | |
{ | |
.colorAttachmentCount = 1, | |
.colorAttachments = (WGPURenderPassColorAttachment[]) | |
{ | |
// one output to write to, initially cleared with background color | |
{ | |
.view = surfaceView, | |
.depthSlice = WGPU_DEPTH_SLICE_UNDEFINED, | |
.loadOp = WGPULoadOp_Clear, | |
.storeOp = WGPUStoreOp_Store, | |
.clearValue = { 0.392, 0.584, 0.929, 1.0 }, // r,g,b,a | |
}, | |
}, | |
}; | |
WGPUCommandEncoder encoder = wgpuDeviceCreateCommandEncoder(device, NULL); | |
// encode render pass | |
WGPURenderPassEncoder pass = wgpuCommandEncoderBeginRenderPass(encoder, &desc); | |
{ | |
wgpuRenderPassEncoderSetViewport(pass, 0.f, 0.f, (float)width, (float)height, 0.f, 1.f); | |
// draw the triangle using 3 vertices in vertex buffer | |
wgpuRenderPassEncoderSetPipeline(pass, pipeline); | |
wgpuRenderPassEncoderSetBindGroup(pass, 0, bind_group, 0, NULL); | |
wgpuRenderPassEncoderSetVertexBuffer(pass, 0, vbuffer, 0, WGPU_WHOLE_SIZE); | |
wgpuRenderPassEncoderDraw(pass, 3, 1, 0, 0); | |
} | |
wgpuRenderPassEncoderEnd(pass); | |
// submit to queue | |
WGPUCommandBuffer command = wgpuCommandEncoderFinish(encoder, NULL); | |
wgpuQueueSubmit(queue, 1, &command); | |
wgpuCommandBufferRelease(command); | |
wgpuCommandEncoderRelease(encoder); | |
wgpuTextureViewRelease(surfaceView); | |
// present to output | |
wgpuSurfacePresent(surface); | |
} | |
} | |
return 0; | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* This is an extension of GLFW for WebGPU, abstracting away the details of | |
* OS-specific operations. | |
* | |
* This file is part of the "Learn WebGPU for C++" book. | |
* https://eliemichel.github.io/LearnWebGPU | |
* | |
* Most of this code comes from the wgpu-native triangle example: | |
* https://github.com/gfx-rs/wgpu-native/blob/master/examples/triangle/main.c | |
* | |
* MIT License | |
* Copyright (c) 2022-2023 Elie Michel and the wgpu-native authors | |
* | |
* Permission is hereby granted, free of charge, to any person obtaining a copy | |
* of this software and associated documentation files (the "Software"), to deal | |
* in the Software without restriction, including without limitation the rights | |
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |
* copies of the Software, and to permit persons to whom the Software is | |
* furnished to do so, subject to the following conditions: | |
* | |
* The above copyright notice and this permission notice shall be included in all | |
* copies or substantial portions of the Software. | |
* | |
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | |
* SOFTWARE. | |
*/ | |
#include <webgpu/webgpu.h> | |
#define WGPU_TARGET_MACOS 1 | |
#define WGPU_TARGET_LINUX_X11 2 | |
#define WGPU_TARGET_WINDOWS 3 | |
#define WGPU_TARGET_LINUX_WAYLAND 4 | |
#define WGPU_TARGET_EMSCRIPTEN 5 | |
#if defined(__EMSCRIPTEN__) | |
#define WGPU_TARGET WGPU_TARGET_EMSCRIPTEN | |
#elif defined(_WIN32) | |
#define WGPU_TARGET WGPU_TARGET_WINDOWS | |
#elif defined(__APPLE__) | |
#define WGPU_TARGET WGPU_TARGET_MACOS | |
#elif defined(_GLFW_WAYLAND) | |
#define WGPU_TARGET WGPU_TARGET_LINUX_WAYLAND | |
#else | |
#define WGPU_TARGET WGPU_TARGET_LINUX_X11 | |
#endif | |
#if WGPU_TARGET == WGPU_TARGET_MACOS | |
#include <Foundation/Foundation.h> | |
#include <QuartzCore/CAMetalLayer.h> | |
#endif | |
#include <GLFW/glfw3.h> | |
#if WGPU_TARGET == WGPU_TARGET_MACOS | |
#define GLFW_EXPOSE_NATIVE_COCOA | |
#elif WGPU_TARGET == WGPU_TARGET_LINUX_X11 | |
#define GLFW_EXPOSE_NATIVE_X11 | |
#elif WGPU_TARGET == WGPU_TARGET_LINUX_WAYLAND | |
#define GLFW_EXPOSE_NATIVE_WAYLAND | |
#elif WGPU_TARGET == WGPU_TARGET_WINDOWS | |
#define GLFW_EXPOSE_NATIVE_WIN32 | |
#endif | |
#if !defined(__EMSCRIPTEN__) | |
#include <GLFW/glfw3native.h> | |
#endif | |
WGPUSurface glfwGetWGPUSurface(WGPUInstance instance, GLFWwindow* window) { | |
#if WGPU_TARGET == WGPU_TARGET_MACOS | |
{ | |
id metal_layer = [CAMetalLayer layer]; | |
NSWindow* ns_window = glfwGetCocoaWindow(window); | |
[ns_window.contentView setWantsLayer: YES]; | |
[ns_window.contentView setLayer: metal_layer]; | |
WGPUSurfaceDescriptorFromMetalLayer fromMetalLayer = {}; | |
fromMetalLayer.chain.next = NULL; | |
fromMetalLayer.chain.sType = WGPUSType_SurfaceSourceMetalLayer; | |
fromMetalLayer.layer = metal_layer; | |
WGPUSurfaceDescriptor surfaceDescriptor = {}; | |
surfaceDescriptor.nextInChain = &fromMetalLayer.chain; | |
// surfaceDescriptor.label = NULL; | |
return wgpuInstanceCreateSurface(instance, &surfaceDescriptor); | |
} | |
#elif WGPU_TARGET == WGPU_TARGET_LINUX_X11 | |
{ | |
Display* x11_display = glfwGetX11Display(); | |
Window x11_window = glfwGetX11Window(window); | |
WGPUSurfaceDescriptorFromXlibWindow fromXlibWindow; | |
fromXlibWindow.chain.next = NULL; | |
fromXlibWindow.chain.sType = WGPUSType_SurfaceDescriptorFromXlibWindow; | |
fromXlibWindow.display = x11_display; | |
fromXlibWindow.window = x11_window; | |
WGPUSurfaceDescriptor surfaceDescriptor; | |
surfaceDescriptor.nextInChain = &fromXlibWindow.chain; | |
surfaceDescriptor.label = NULL; | |
return wgpuInstanceCreateSurface(instance, &surfaceDescriptor); | |
} | |
#elif WGPU_TARGET == WGPU_TARGET_LINUX_WAYLAND | |
{ | |
struct wl_display* wayland_display = glfwGetWaylandDisplay(); | |
struct wl_surface* wayland_surface = glfwGetWaylandWindow(window); | |
WGPUSurfaceDescriptorFromWaylandSurface fromWaylandSurface; | |
fromWaylandSurface.chain.next = NULL; | |
fromWaylandSurface.chain.sType = WGPUSType_SurfaceDescriptorFromWaylandSurface; | |
fromWaylandSurface.display = wayland_display; | |
fromWaylandSurface.surface = wayland_surface; | |
WGPUSurfaceDescriptor surfaceDescriptor; | |
surfaceDescriptor.nextInChain = &fromWaylandSurface.chain; | |
surfaceDescriptor.label = NULL; | |
return wgpuInstanceCreateSurface(instance, &surfaceDescriptor); | |
} | |
#elif WGPU_TARGET == WGPU_TARGET_WINDOWS | |
{ | |
HWND hwnd = glfwGetWin32Window(window); | |
HINSTANCE hinstance = GetModuleHandle(NULL); | |
WGPUSurfaceDescriptorFromWindowsHWND fromWindowsHWND; | |
fromWindowsHWND.chain.next = NULL; | |
fromWindowsHWND.chain.sType = WGPUSType_SurfaceDescriptorFromWindowsHWND; | |
fromWindowsHWND.hinstance = hinstance; | |
fromWindowsHWND.hwnd = hwnd; | |
WGPUSurfaceDescriptor surfaceDescriptor; | |
surfaceDescriptor.nextInChain = &fromWindowsHWND.chain; | |
surfaceDescriptor.label = NULL; | |
return wgpuInstanceCreateSurface(instance, &surfaceDescriptor); | |
} | |
#elif WGPU_TARGET == WGPU_TARGET_EMSCRIPTEN | |
{ | |
WGPUSurfaceDescriptorFromCanvasHTMLSelector fromCanvasHTMLSelector; | |
fromCanvasHTMLSelector.chain.next = NULL; | |
fromCanvasHTMLSelector.chain.sType = WGPUSType_SurfaceDescriptorFromCanvasHTMLSelector; | |
fromCanvasHTMLSelector.selector = "canvas"; | |
WGPUSurfaceDescriptor surfaceDescriptor; | |
surfaceDescriptor.nextInChain = &fromCanvasHTMLSelector.chain; | |
surfaceDescriptor.label = NULL; | |
return wgpuInstanceCreateSurface(instance, &surfaceDescriptor); | |
} | |
#else | |
#error "Unsupported WGPU_TARGET" | |
#endif | |
} | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment