mirror of
https://github.com/wolfpld/tracy.git
synced 2026-09-29 13:35:58 +00:00
relocating webgpu example
This commit is contained in:
364
examples/webgpu/triangle/spinning_triangle.cpp
Normal file
364
examples/webgpu/triangle/spinning_triangle.cpp
Normal file
@@ -0,0 +1,364 @@
|
||||
// spinning_triangle.cpp — platform-agnostic WebGPU spinning triangle demo.
|
||||
|
||||
#include "platform/platform.h"
|
||||
#include <cmath>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <webgpu/webgpu.h>
|
||||
|
||||
#include <tracy/Tracy.hpp>
|
||||
#include <tracy/TracyWebGPU.hpp>
|
||||
|
||||
#ifndef __TRACYWEBGPU_HPP__
|
||||
#define TracyWebGPUCtx void*
|
||||
#define TracyWebGPUSetupDevice(...)
|
||||
#define TracyWebGPUContext(...) nullptr
|
||||
#define TracyWebGPUContextName(...)
|
||||
#define TracyWebGPUNamedZone(...)
|
||||
#define TracyWebGPUCollect(...)
|
||||
#define TracyWebGPUDestroy(...)
|
||||
namespace tracy { struct WebGPUQueueCtx { static void SetupDevice(WGPUDeviceDescriptor) {} }; }
|
||||
#endif//__TRACYWEBGPU_HPP__
|
||||
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Globals
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
static const int kWidth = 800;
|
||||
static const int kHeight = 600;
|
||||
|
||||
static WGPUInstance gInstance = nullptr;
|
||||
static WGPUSurface gSurface = nullptr;
|
||||
static WGPUAdapter gAdapter = nullptr;
|
||||
static WGPUDevice gDevice = nullptr;
|
||||
static WGPUQueue gQueue = nullptr;
|
||||
static WGPURenderPipeline gPipeline = nullptr;
|
||||
static WGPUBuffer gUniformBuf = nullptr;
|
||||
static WGPUBindGroup gBindGroup = nullptr;
|
||||
|
||||
static TracyWebGPUCtx gTracyCtx = nullptr;
|
||||
|
||||
static WGPUTextureFormat gSurfaceFormat = WGPUTextureFormat_BGRA8Unorm;
|
||||
|
||||
// TODO: this can become platformError() instead
|
||||
int error(int code, const char* message) {
|
||||
fprintf(stderr, "ERROR: %s (code: %d)\n", message, code);
|
||||
return code;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// WGSL shader — vertex colours baked in, rotation via a uniform float.
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
static const char* kShaderSource = R"(
|
||||
struct Uniforms {
|
||||
angle: f32,
|
||||
};
|
||||
@group(0) @binding(0) var<uniform> u: Uniforms;
|
||||
|
||||
struct VSOut {
|
||||
@builtin(position) pos: vec4f,
|
||||
@location(0) color: vec3f,
|
||||
};
|
||||
|
||||
@vertex
|
||||
fn vs_main(@builtin(vertex_index) vi: u32) -> VSOut {
|
||||
var positions = array<vec2f, 3>(
|
||||
vec2f( 0.0, 0.5),
|
||||
vec2f(-0.433, -0.25),
|
||||
vec2f( 0.433, -0.25),
|
||||
);
|
||||
var colors = array<vec3f, 3>(
|
||||
vec3f(1.0, 0.0, 0.0),
|
||||
vec3f(0.0, 1.0, 0.0),
|
||||
vec3f(0.0, 0.0, 1.0),
|
||||
);
|
||||
|
||||
let c = cos(u.angle);
|
||||
let s = sin(u.angle);
|
||||
let p = positions[vi];
|
||||
let rotated = vec2f(p.x * c - p.y * s, p.x * s + p.y * c);
|
||||
|
||||
var out: VSOut;
|
||||
out.pos = vec4f(rotated, 0.0, 1.0);
|
||||
out.color = colors[vi];
|
||||
return out;
|
||||
}
|
||||
|
||||
@fragment
|
||||
fn fs_main(@location(0) color: vec3f) -> @location(0) vec4f {
|
||||
return vec4f(color, 1.0);
|
||||
}
|
||||
)";
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Adapter / Device request callbacks (current wgpu-native API)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
static void onAdapterReady(WGPURequestAdapterStatus status,
|
||||
WGPUAdapter adapter,
|
||||
WGPUStringView message,
|
||||
void* userdata1, void* /*userdata2*/) {
|
||||
if (status == WGPURequestAdapterStatus_Success) {
|
||||
*(WGPUAdapter*)userdata1 = adapter;
|
||||
} else {
|
||||
fprintf(stderr, "Adapter request failed: %.*s\n",
|
||||
(int)message.length, message.data);
|
||||
}
|
||||
}
|
||||
|
||||
static void onDeviceReady(WGPURequestDeviceStatus status,
|
||||
WGPUDevice device,
|
||||
WGPUStringView message,
|
||||
void* userdata1, void* /*userdata2*/) {
|
||||
if (status == WGPURequestDeviceStatus_Success) {
|
||||
*(WGPUDevice*)userdata1 = device;
|
||||
} else {
|
||||
fprintf(stderr, "Device request failed: %.*s\n",
|
||||
(int)message.length, message.data);
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// WebGPU init
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
static int initWebGPU() {
|
||||
// Adapter
|
||||
WGPURequestAdapterOptions adapterOpts = {};
|
||||
adapterOpts.compatibleSurface = gSurface;
|
||||
|
||||
WGPURequestAdapterCallbackInfo adapterCB = {};
|
||||
adapterCB.mode = WGPUCallbackMode_AllowProcessEvents;
|
||||
adapterCB.callback = onAdapterReady;
|
||||
adapterCB.userdata1 = &gAdapter;
|
||||
wgpuInstanceRequestAdapter(gInstance, &adapterOpts, adapterCB);
|
||||
while (!gAdapter) { wgpuInstanceProcessEvents(gInstance); }
|
||||
if (!gAdapter) return error(11, "No adapter");
|
||||
|
||||
WGPUUncapturedErrorCallbackInfo errorCB = {};
|
||||
errorCB.callback = [](WGPUDevice const*, WGPUErrorType type,
|
||||
WGPUStringView message, void*, void*) {
|
||||
fprintf(stderr, "[WGPU ERROR] type=%d %.*s\n",
|
||||
(int)type, (int)message.length, message.data);
|
||||
};
|
||||
|
||||
WGPUDeviceDescriptor deviceDesc = {};
|
||||
deviceDesc.uncapturedErrorCallbackInfo = errorCB;
|
||||
|
||||
TracyWebGPUSetupDevice(deviceDesc);
|
||||
|
||||
WGPURequestDeviceCallbackInfo deviceCB = {};
|
||||
deviceCB.mode = WGPUCallbackMode_AllowProcessEvents;
|
||||
deviceCB.callback = onDeviceReady;
|
||||
deviceCB.userdata1 = &gDevice;
|
||||
wgpuAdapterRequestDevice(gAdapter, &deviceDesc, deviceCB);
|
||||
while (!gDevice) { wgpuInstanceProcessEvents(gInstance); }
|
||||
if (!gDevice) return error(12, "No device");
|
||||
|
||||
gQueue = wgpuDeviceGetQueue(gDevice);
|
||||
gTracyCtx = TracyWebGPUContext(gInstance, gDevice, gQueue);
|
||||
TracyWebGPUContextName(gTracyCtx, "WebGPU", 6);
|
||||
|
||||
// Configure surface
|
||||
WGPUSurfaceConfiguration config = {};
|
||||
config.device = gDevice;
|
||||
config.format = gSurfaceFormat;
|
||||
config.usage = WGPUTextureUsage_RenderAttachment;
|
||||
config.alphaMode = WGPUCompositeAlphaMode_Opaque;
|
||||
config.width = kWidth;
|
||||
config.height = kHeight;
|
||||
config.presentMode = WGPUPresentMode_Fifo;
|
||||
wgpuSurfaceConfigure(gSurface, &config);
|
||||
|
||||
// Shader module
|
||||
WGPUShaderSourceWGSL wgslSrc = {};
|
||||
wgslSrc.chain.sType = WGPUSType_ShaderSourceWGSL;
|
||||
wgslSrc.code = { kShaderSource, WGPU_STRLEN };
|
||||
|
||||
WGPUShaderModuleDescriptor smDesc = {};
|
||||
smDesc.nextInChain = (WGPUChainedStruct*)&wgslSrc;
|
||||
WGPUShaderModule shaderMod = wgpuDeviceCreateShaderModule(gDevice, &smDesc);
|
||||
|
||||
// Uniform buffer (one f32 for rotation angle)
|
||||
WGPUBufferDescriptor bufDesc = {};
|
||||
bufDesc.usage = WGPUBufferUsage_Uniform | WGPUBufferUsage_CopyDst;
|
||||
bufDesc.size = sizeof(float);
|
||||
gUniformBuf = wgpuDeviceCreateBuffer(gDevice, &bufDesc);
|
||||
|
||||
// Bind group layout + bind group
|
||||
WGPUBindGroupLayoutEntry bglEntry = {};
|
||||
bglEntry.binding = 0;
|
||||
bglEntry.visibility = WGPUShaderStage_Vertex;
|
||||
bglEntry.buffer.type = WGPUBufferBindingType_Uniform;
|
||||
bglEntry.buffer.minBindingSize = sizeof(float);
|
||||
|
||||
WGPUBindGroupLayoutDescriptor bglDesc = {};
|
||||
bglDesc.entryCount = 1;
|
||||
bglDesc.entries = &bglEntry;
|
||||
WGPUBindGroupLayout bgl = wgpuDeviceCreateBindGroupLayout(gDevice, &bglDesc);
|
||||
|
||||
WGPUBindGroupEntry bgEntry = {};
|
||||
bgEntry.binding = 0;
|
||||
bgEntry.buffer = gUniformBuf;
|
||||
bgEntry.size = sizeof(float);
|
||||
|
||||
WGPUBindGroupDescriptor bgDesc = {};
|
||||
bgDesc.layout = bgl;
|
||||
bgDesc.entryCount = 1;
|
||||
bgDesc.entries = &bgEntry;
|
||||
gBindGroup = wgpuDeviceCreateBindGroup(gDevice, &bgDesc);
|
||||
|
||||
// Pipeline layout
|
||||
WGPUPipelineLayoutDescriptor plDesc = {};
|
||||
plDesc.bindGroupLayoutCount = 1;
|
||||
plDesc.bindGroupLayouts = &bgl;
|
||||
WGPUPipelineLayout pipelineLayout = wgpuDeviceCreatePipelineLayout(gDevice, &plDesc);
|
||||
|
||||
// Render pipeline
|
||||
WGPUColorTargetState colorTarget = {};
|
||||
colorTarget.format = gSurfaceFormat;
|
||||
colorTarget.writeMask = WGPUColorWriteMask_All;
|
||||
|
||||
WGPUFragmentState fragState = {};
|
||||
fragState.module = shaderMod;
|
||||
fragState.entryPoint = { "fs_main", WGPU_STRLEN };
|
||||
fragState.targetCount = 1;
|
||||
fragState.targets = &colorTarget;
|
||||
|
||||
WGPURenderPipelineDescriptor rpDesc = {};
|
||||
rpDesc.layout = pipelineLayout;
|
||||
rpDesc.vertex.module = shaderMod;
|
||||
rpDesc.vertex.entryPoint = { "vs_main", WGPU_STRLEN };
|
||||
rpDesc.primitive.topology = WGPUPrimitiveTopology_TriangleList;
|
||||
rpDesc.multisample.count = 1;
|
||||
rpDesc.multisample.mask = 0xFFFFFFFF;
|
||||
rpDesc.fragment = &fragState;
|
||||
|
||||
gPipeline = wgpuDeviceCreateRenderPipeline(gDevice, &rpDesc);
|
||||
|
||||
// Cleanup intermediates
|
||||
wgpuShaderModuleRelease(shaderMod);
|
||||
wgpuPipelineLayoutRelease(pipelineLayout);
|
||||
wgpuBindGroupLayoutRelease(bgl);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Frame rendering
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
// Returns the surface texture for the current frame, or {.texture=nullptr} on
|
||||
// a skippable condition (timeout, occlusion) or an error.
|
||||
static WGPUSurfaceTexture getWindowSurface() {
|
||||
WGPUSurfaceTexture surfTex = {};
|
||||
wgpuSurfaceGetCurrentTexture(gSurface, &surfTex);
|
||||
if (surfTex.status == WGPUSurfaceGetCurrentTextureStatus_SuccessOptimal ||
|
||||
surfTex.status == WGPUSurfaceGetCurrentTextureStatus_SuccessSuboptimal)
|
||||
return surfTex;
|
||||
|
||||
// Timeout and Occluded are normal OS events (window covered / on a different Space).
|
||||
bool silent = surfTex.status == WGPUSurfaceGetCurrentTextureStatus_Timeout;
|
||||
#ifdef WGPU_H_
|
||||
silent = silent || surfTex.status == (WGPUSurfaceGetCurrentTextureStatus)WGPUSurfaceGetCurrentTextureStatus_Occluded;
|
||||
#endif
|
||||
if (!silent)
|
||||
fprintf(stderr, "Failed to get surface texture (status %d)\n", surfTex.status);
|
||||
if (surfTex.texture) wgpuTextureRelease(surfTex.texture);
|
||||
surfTex.texture = nullptr;
|
||||
return surfTex;
|
||||
}
|
||||
|
||||
static void renderFrame() {
|
||||
ZoneScoped;
|
||||
|
||||
// Update rotation angle
|
||||
float angle = (float)platformGetTime();
|
||||
wgpuQueueWriteBuffer(gQueue, gUniformBuf, 0, &angle, sizeof(float));
|
||||
|
||||
WGPUSurfaceTexture surfTex = getWindowSurface();
|
||||
if (!surfTex.texture) return;
|
||||
|
||||
WGPUTextureView view = wgpuTextureCreateView(surfTex.texture, nullptr);
|
||||
|
||||
// Command encoder
|
||||
WGPUCommandEncoder encoder = wgpuDeviceCreateCommandEncoder(gDevice, nullptr);
|
||||
|
||||
// Render pass
|
||||
WGPURenderPassColorAttachment colorAtt = {};
|
||||
colorAtt.view = view;
|
||||
colorAtt.loadOp = WGPULoadOp_Clear;
|
||||
colorAtt.storeOp = WGPUStoreOp_Store;
|
||||
colorAtt.clearValue = { 0.05, 0.05, 0.08, 1.0 };
|
||||
colorAtt.depthSlice = WGPU_DEPTH_SLICE_UNDEFINED;
|
||||
|
||||
WGPURenderPassDescriptor passDesc = {};
|
||||
passDesc.colorAttachmentCount = 1;
|
||||
passDesc.colorAttachments = &colorAtt;
|
||||
|
||||
{
|
||||
ZoneScopedN("render-pass");
|
||||
TracyWebGPUNamedZone(gTracyCtx, tracyZone, encoder, passDesc, "triangle draw", true);
|
||||
WGPURenderPassEncoder pass = wgpuCommandEncoderBeginRenderPass(encoder, &passDesc);
|
||||
wgpuRenderPassEncoderSetPipeline(pass, gPipeline);
|
||||
wgpuRenderPassEncoderSetBindGroup(pass, 0, gBindGroup, 0, nullptr);
|
||||
wgpuRenderPassEncoderDraw(pass, 3, 1, 0, 0);
|
||||
wgpuRenderPassEncoderEnd(pass);
|
||||
wgpuRenderPassEncoderRelease(pass);
|
||||
}
|
||||
|
||||
// Submit
|
||||
WGPUCommandBuffer cmdBuf = wgpuCommandEncoderFinish(encoder, nullptr);
|
||||
wgpuQueueSubmit(gQueue, 1, &cmdBuf);
|
||||
|
||||
// Present
|
||||
wgpuSurfacePresent(gSurface);
|
||||
|
||||
// Process Events
|
||||
wgpuInstanceProcessEvents(gInstance);
|
||||
TracyWebGPUCollect(gTracyCtx);
|
||||
|
||||
// Cleanup
|
||||
wgpuCommandBufferRelease(cmdBuf);
|
||||
wgpuCommandEncoderRelease(encoder);
|
||||
wgpuTextureViewRelease(view);
|
||||
wgpuTextureRelease(surfTex.texture);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Shutdown
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
static void shutdown() {
|
||||
fprintf(stderr, "application is shutting down...\n");
|
||||
TracyWebGPUDestroy(gTracyCtx);
|
||||
if (gBindGroup) wgpuBindGroupRelease(gBindGroup);
|
||||
if (gUniformBuf) wgpuBufferRelease(gUniformBuf);
|
||||
if (gPipeline) wgpuRenderPipelineRelease(gPipeline);
|
||||
if (gQueue) wgpuQueueRelease(gQueue);
|
||||
if (gDevice) wgpuDeviceRelease(gDevice);
|
||||
if (gAdapter) wgpuAdapterRelease(gAdapter);
|
||||
if (gSurface) wgpuSurfaceRelease(gSurface);
|
||||
if (gInstance) wgpuInstanceRelease(gInstance);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// main
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
int main(int argc, char* argv[]) {
|
||||
if (!platformInit(kWidth, kHeight, "WebGPU Spinning Triangle"))
|
||||
return 1;
|
||||
|
||||
gInstance = wgpuCreateInstance(nullptr);
|
||||
if (!gInstance) return error(2, "Failed to create WebGPU instance.");
|
||||
|
||||
gSurface = platformCreateSurface(gInstance);
|
||||
if (!gSurface) return error(3, "Failed to create surface.");
|
||||
|
||||
if (initWebGPU() != 0) return 4;
|
||||
|
||||
platformRunLoop(renderFrame, shutdown);
|
||||
return 0;
|
||||
}
|
||||
Reference in New Issue
Block a user