mirror of
https://github.com/wolfpld/tracy.git
synced 2026-10-18 14:55:53 +00:00
relocating webgpu example
This commit is contained in:
164
examples/webgpu/triangle/CMakeLists.txt
Normal file
164
examples/webgpu/triangle/CMakeLists.txt
Normal file
@@ -0,0 +1,164 @@
|
||||
# CMakeLists.txt — WebGPU spinning triangle demo
|
||||
#
|
||||
# macOS:
|
||||
# clang++ -std=c++17 -ObjC++ spinning_triangle.cpp platform/platform_macos.mm \
|
||||
# -I/path/to/wgpu/include -L/path/to/wgpu/lib -lwgpu_native \
|
||||
# -Wl,-rpath,@executable_path \
|
||||
# -framework Cocoa -framework Metal -framework QuartzCore \
|
||||
# -framework Foundation -framework IOKit -framework IOSurface \
|
||||
# -o spinning_triangle
|
||||
#
|
||||
# Windows (MSVC):
|
||||
# cl /std:c++17 spinning_triangle.cpp platform/platform_windows.cpp \
|
||||
# /I\path\to\wgpu\include \path\to\wgpu\lib\wgpu_native.lib \
|
||||
# user32.lib gdi32.lib /Fe:spinning_triangle.exe
|
||||
#
|
||||
# Linux / Wayland:
|
||||
# g++ -std=c++17 spinning_triangle.cpp platform/platform_wayland.cpp \
|
||||
# xdg-shell-protocol.c \
|
||||
# -I/path/to/wgpu/include -L/path/to/wgpu/lib -lwgpu_native \
|
||||
# -lwayland-client -o spinning_triangle
|
||||
|
||||
cmake_minimum_required(VERSION 3.16)
|
||||
project(spinning_triangle LANGUAGES C CXX)
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# WebGPU backend — set WGPU_PATH to your wgpu-native or Dawn installation.
|
||||
# The library name differs between backends:
|
||||
# wgpu-native → wgpu_native
|
||||
# Dawn → webgpu_dawn
|
||||
# ---------------------------------------------------------------------------
|
||||
set(WGPU_PATH "" CACHE PATH "Root of the WebGPU native installation (contains include/ and lib/)")
|
||||
set(WGPU_LIB "webgpu_dawn" CACHE STRING "WebGPU library name (wgpu_native or webgpu_dawn)")
|
||||
|
||||
if(NOT WGPU_PATH)
|
||||
message(FATAL_ERROR "Set WGPU_PATH to the root of your WebGPU native installation.")
|
||||
endif()
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Tracy root — defaults to two directories above this CMakeLists.txt.
|
||||
# ---------------------------------------------------------------------------
|
||||
set(TRACY_DIR "${CMAKE_CURRENT_SOURCE_DIR}/../../..")
|
||||
option(TRACY_ENABLE "Enable Tracy profiling" ON)
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# macOS quarantine — pre-built WebGPU binaries downloaded from the internet
|
||||
# carry a com.apple.quarantine extended attribute that prevents dyld from
|
||||
# loading them ("damaged or incomplete" / Gatekeeper block). Strip it once
|
||||
# at configure time so the linker and the runtime loader can both access the
|
||||
# library directory without further user intervention.
|
||||
# ---------------------------------------------------------------------------
|
||||
if(APPLE)
|
||||
execute_process(
|
||||
COMMAND xattr -dr com.apple.quarantine "${WGPU_PATH}/lib"
|
||||
)
|
||||
endif()
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Platform-specific source and link settings
|
||||
# ---------------------------------------------------------------------------
|
||||
set(PLATFORM_GENERATED_INCLUDES "")
|
||||
|
||||
if(APPLE)
|
||||
set(PLATFORM_SOURCES platform/platform_macos.mm)
|
||||
set(PLATFORM_LIBS
|
||||
"-framework Cocoa"
|
||||
"-framework Metal"
|
||||
"-framework QuartzCore"
|
||||
"-framework Foundation"
|
||||
"-framework IOKit"
|
||||
"-framework IOSurface"
|
||||
)
|
||||
set_source_files_properties(platform/platform_macos.mm
|
||||
PROPERTIES COMPILE_FLAGS "-ObjC++"
|
||||
)
|
||||
elseif(WIN32)
|
||||
set(PLATFORM_SOURCES platform/platform_windows.cpp)
|
||||
set(PLATFORM_LIBS user32 gdi32)
|
||||
else()
|
||||
# Linux / Wayland — generate xdg-shell protocol glue via wayland-scanner.
|
||||
find_package(PkgConfig REQUIRED)
|
||||
pkg_check_modules(WAYLAND_PROTOCOLS REQUIRED wayland-protocols)
|
||||
pkg_get_variable(WAYLAND_PROTOCOLS_DIR wayland-protocols pkgdatadir)
|
||||
find_program(WAYLAND_SCANNER wayland-scanner REQUIRED)
|
||||
|
||||
set(XDG_SHELL_XML "${WAYLAND_PROTOCOLS_DIR}/stable/xdg-shell/xdg-shell.xml")
|
||||
set(XDG_SHELL_H "${CMAKE_CURRENT_BINARY_DIR}/xdg-shell-client-protocol.h")
|
||||
set(XDG_SHELL_C "${CMAKE_CURRENT_BINARY_DIR}/xdg-shell-protocol.c")
|
||||
|
||||
add_custom_command(
|
||||
OUTPUT "${XDG_SHELL_H}"
|
||||
COMMAND "${WAYLAND_SCANNER}" client-header "${XDG_SHELL_XML}" "${XDG_SHELL_H}"
|
||||
DEPENDS "${XDG_SHELL_XML}"
|
||||
COMMENT "Generating xdg-shell-client-protocol.h"
|
||||
VERBATIM
|
||||
)
|
||||
add_custom_command(
|
||||
OUTPUT "${XDG_SHELL_C}"
|
||||
COMMAND "${WAYLAND_SCANNER}" private-code "${XDG_SHELL_XML}" "${XDG_SHELL_C}"
|
||||
DEPENDS "${XDG_SHELL_XML}"
|
||||
COMMENT "Generating xdg-shell-protocol.c"
|
||||
VERBATIM
|
||||
)
|
||||
|
||||
set(PLATFORM_SOURCES
|
||||
platform/platform_wayland.cpp
|
||||
"${XDG_SHELL_C}"
|
||||
"${XDG_SHELL_H}"
|
||||
)
|
||||
set(PLATFORM_LIBS wayland-client)
|
||||
set(PLATFORM_GENERATED_INCLUDES "${CMAKE_CURRENT_BINARY_DIR}")
|
||||
endif()
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Target
|
||||
# ---------------------------------------------------------------------------
|
||||
add_executable(spinning_triangle
|
||||
spinning_triangle.cpp
|
||||
"${TRACY_DIR}/public/TracyClient.cpp"
|
||||
${PLATFORM_SOURCES}
|
||||
)
|
||||
|
||||
# Treat TracyClient.cpp as third-party code — suppress all warnings so that
|
||||
# upstream changes don't pollute our build output.
|
||||
if(MSVC)
|
||||
set_source_files_properties("${TRACY_DIR}/public/TracyClient.cpp"
|
||||
PROPERTIES COMPILE_FLAGS "/w"
|
||||
)
|
||||
else()
|
||||
set_source_files_properties("${TRACY_DIR}/public/TracyClient.cpp"
|
||||
PROPERTIES COMPILE_FLAGS "-w"
|
||||
)
|
||||
endif()
|
||||
|
||||
target_compile_features(spinning_triangle PRIVATE cxx_std_17)
|
||||
|
||||
if(TRACY_ENABLE)
|
||||
target_compile_definitions(spinning_triangle PRIVATE TRACY_ENABLE)
|
||||
endif()
|
||||
|
||||
target_include_directories(spinning_triangle PRIVATE
|
||||
"${WGPU_PATH}/include"
|
||||
"${TRACY_DIR}/public"
|
||||
${PLATFORM_GENERATED_INCLUDES}
|
||||
)
|
||||
|
||||
target_link_directories(spinning_triangle PRIVATE "${WGPU_PATH}/lib")
|
||||
|
||||
target_link_libraries(spinning_triangle PRIVATE
|
||||
${WGPU_LIB}
|
||||
${PLATFORM_LIBS}
|
||||
)
|
||||
|
||||
# Embed the rpath so the binary finds the WebGPU dylib/so next to itself.
|
||||
if(APPLE)
|
||||
set_target_properties(spinning_triangle PROPERTIES
|
||||
BUILD_RPATH "${WGPU_PATH}/lib"
|
||||
INSTALL_RPATH "@executable_path"
|
||||
)
|
||||
elseif(UNIX)
|
||||
set_target_properties(spinning_triangle PROPERTIES
|
||||
BUILD_RPATH "${WGPU_PATH}/lib"
|
||||
INSTALL_RPATH "$ORIGIN"
|
||||
)
|
||||
endif()
|
||||
23
examples/webgpu/triangle/platform/platform.h
Normal file
23
examples/webgpu/triangle/platform/platform.h
Normal file
@@ -0,0 +1,23 @@
|
||||
// platform.h — interface between platform-agnostic code and platform backends
|
||||
//
|
||||
// Each platform_*.mm / platform_*.cpp file implements these five functions.
|
||||
// Exactly one backend must be linked into the final binary.
|
||||
|
||||
#pragma once
|
||||
#include <webgpu/webgpu.h>
|
||||
|
||||
// Initialize the windowing system and create a window of the given dimensions.
|
||||
// Returns true on success.
|
||||
bool platformInit(int width, int height, const char* title);
|
||||
|
||||
// Create a WebGPU surface backed by the platform window.
|
||||
// Must be called after wgpuCreateInstance() and platformInit().
|
||||
WGPUSurface platformCreateSurface(WGPUInstance instance);
|
||||
|
||||
// Elapsed wall-clock time in seconds since platformInit().
|
||||
double platformGetTime();
|
||||
|
||||
// Enter the platform event/render loop.
|
||||
// Calls render() each frame at ~60 fps.
|
||||
// Calls shutdown() exactly once before returning.
|
||||
void platformRunLoop(void (*render)(), void (*shutdown)());
|
||||
120
examples/webgpu/triangle/platform/platform_macos.mm
Normal file
120
examples/webgpu/triangle/platform/platform_macos.mm
Normal file
@@ -0,0 +1,120 @@
|
||||
// platform_macos.mm — macOS backend (Cocoa + CAMetalLayer)
|
||||
//
|
||||
// Compile flags (see spinning_triangle.cpp header for full invocation):
|
||||
// -ObjC++ -framework Cocoa -framework Metal -framework QuartzCore \
|
||||
// -framework Foundation -framework IOKit -framework IOSurface
|
||||
|
||||
#import <Cocoa/Cocoa.h>
|
||||
#import <QuartzCore/CAMetalLayer.h>
|
||||
#include <CoreFoundation/CFDate.h>
|
||||
#include <webgpu/webgpu.h>
|
||||
#include "platform.h"
|
||||
|
||||
static CAMetalLayer* sMetalLayer = nullptr;
|
||||
static CFAbsoluteTime sStartTime = 0;
|
||||
static void (*sRenderCb)() = nullptr;
|
||||
static void (*sShutdownCb)() = nullptr;
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Cocoa app — window, metal layer, render timer
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
@interface AppDelegate : NSObject <NSApplicationDelegate, NSWindowDelegate>
|
||||
@property (strong) NSWindow* window;
|
||||
@property (strong) NSTimer* timer;
|
||||
@end
|
||||
|
||||
@implementation AppDelegate
|
||||
|
||||
- (void)applicationDidFinishLaunching:(NSNotification*)notification {
|
||||
// ~60 fps render loop
|
||||
self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0 / 60.0
|
||||
target:self
|
||||
selector:@selector(tick:)
|
||||
userInfo:nil
|
||||
repeats:YES];
|
||||
[[NSRunLoop currentRunLoop] addTimer:self.timer forMode:NSRunLoopCommonModes];
|
||||
|
||||
[NSEvent addLocalMonitorForEventsMatchingMask:NSEventMaskKeyDown
|
||||
handler:^NSEvent*(NSEvent* event) {
|
||||
if (event.keyCode == 53) { // kVK_Escape
|
||||
[NSApp terminate:nil];
|
||||
return nil;
|
||||
}
|
||||
return event;
|
||||
}];
|
||||
|
||||
[self.window makeKeyAndOrderFront:nil];
|
||||
}
|
||||
|
||||
- (void)tick:(NSTimer*)t {
|
||||
if (sRenderCb) sRenderCb();
|
||||
}
|
||||
|
||||
- (BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication*)app {
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (void)applicationWillTerminate:(NSNotification*)notification {
|
||||
[self.timer invalidate];
|
||||
if (sShutdownCb) sShutdownCb();
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Platform interface implementation
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
bool platformInit(int width, int height, const char* title) {
|
||||
NSApplication* app = [NSApplication sharedApplication];
|
||||
[app setActivationPolicy:NSApplicationActivationPolicyRegular];
|
||||
|
||||
NSRect frame = NSMakeRect(200, 200, width, height);
|
||||
NSWindow* window = [[NSWindow alloc]
|
||||
initWithContentRect:frame
|
||||
styleMask:(NSWindowStyleMaskTitled |
|
||||
NSWindowStyleMaskClosable |
|
||||
NSWindowStyleMaskMiniaturizable)
|
||||
backing:NSBackingStoreBuffered
|
||||
defer:NO];
|
||||
[window setTitle:[NSString stringWithUTF8String:title]];
|
||||
|
||||
// Metal-backed layer
|
||||
NSView* contentView = [window contentView];
|
||||
[contentView setWantsLayer:YES];
|
||||
sMetalLayer = [CAMetalLayer layer];
|
||||
sMetalLayer.frame = contentView.bounds;
|
||||
sMetalLayer.contentsScale = [window backingScaleFactor];
|
||||
sMetalLayer.pixelFormat = MTLPixelFormatBGRA8Unorm;
|
||||
[contentView.layer addSublayer:sMetalLayer];
|
||||
|
||||
AppDelegate* del = [[AppDelegate alloc] init];
|
||||
del.window = window;
|
||||
[app setDelegate:del];
|
||||
|
||||
sStartTime = CFAbsoluteTimeGetCurrent();
|
||||
return true;
|
||||
}
|
||||
|
||||
WGPUSurface platformCreateSurface(WGPUInstance instance) {
|
||||
WGPUSurfaceSourceMetalLayer metalSrc = {};
|
||||
metalSrc.chain.sType = WGPUSType_SurfaceSourceMetalLayer;
|
||||
metalSrc.layer = sMetalLayer;
|
||||
|
||||
WGPUSurfaceDescriptor surfDesc = {};
|
||||
surfDesc.nextInChain = (WGPUChainedStruct*)&metalSrc;
|
||||
return wgpuInstanceCreateSurface(instance, &surfDesc);
|
||||
}
|
||||
|
||||
double platformGetTime() {
|
||||
return CFAbsoluteTimeGetCurrent() - sStartTime;
|
||||
}
|
||||
|
||||
void platformRunLoop(void (*render)(), void (*shutdown)()) {
|
||||
sRenderCb = render;
|
||||
sShutdownCb = shutdown;
|
||||
@autoreleasepool {
|
||||
[[NSApplication sharedApplication] run];
|
||||
}
|
||||
}
|
||||
213
examples/webgpu/triangle/platform/platform_wayland.cpp
Normal file
213
examples/webgpu/triangle/platform/platform_wayland.cpp
Normal file
@@ -0,0 +1,213 @@
|
||||
// platform_wayland.cpp — Linux/Wayland backend
|
||||
//
|
||||
// Dependencies:
|
||||
// libwayland-client, wayland-protocols (for xdg-shell)
|
||||
//
|
||||
// Generate xdg-shell protocol glue before building:
|
||||
// XML=$(pkg-config --variable=pkgdatadir wayland-protocols)/stable/xdg-shell/xdg-shell.xml
|
||||
// wayland-scanner client-header $XML xdg-shell-client-protocol.h
|
||||
// wayland-scanner private-code $XML xdg-shell-protocol.c
|
||||
//
|
||||
// Compile flags (see spinning_triangle.cpp header for full invocation):
|
||||
// g++ -std=c++17 spinning_triangle.cpp platform_wayland.cpp \
|
||||
// xdg-shell-protocol.c \
|
||||
// -I/path/to/wgpu/include -L/path/to/wgpu/lib -lwgpu_native \
|
||||
// $(pkg-config --cflags --libs wayland-client) \
|
||||
// -o spinning_triangle
|
||||
|
||||
#include <wayland-client.h>
|
||||
#include "xdg-shell-client-protocol.h"
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <time.h>
|
||||
#include <webgpu/webgpu.h>
|
||||
#include "platform.h"
|
||||
|
||||
static wl_display* sDisplay = nullptr;
|
||||
static wl_compositor* sCompositor = nullptr;
|
||||
static xdg_wm_base* sWmBase = nullptr;
|
||||
static wl_seat* sSeat = nullptr;
|
||||
static wl_keyboard* sKeyboard = nullptr;
|
||||
static wl_surface* sSurface = nullptr;
|
||||
static xdg_surface* sXdgSurface = nullptr;
|
||||
static xdg_toplevel* sToplevel = nullptr;
|
||||
static bool sConfigured = false;
|
||||
static bool sRunning = false;
|
||||
static struct timespec sStartTime = {};
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// xdg_wm_base listener — ping/pong keepalive
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
static void wmBasePing(void*, xdg_wm_base* wm, uint32_t serial) {
|
||||
xdg_wm_base_pong(wm, serial);
|
||||
}
|
||||
static const xdg_wm_base_listener kWmBaseListener = { wmBasePing };
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// xdg_surface listener — acknowledge configure events
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
static void xdgSurfaceConfigure(void*, xdg_surface* surf, uint32_t serial) {
|
||||
xdg_surface_ack_configure(surf, serial);
|
||||
sConfigured = true;
|
||||
}
|
||||
static const xdg_surface_listener kXdgSurfaceListener = { xdgSurfaceConfigure };
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// xdg_toplevel listener — window close / resize
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
static void toplevelClose(void*, xdg_toplevel*) {
|
||||
sRunning = false;
|
||||
}
|
||||
static void toplevelConfigure(void*, xdg_toplevel*, int32_t, int32_t, wl_array*) {}
|
||||
static const xdg_toplevel_listener kToplevelListener = { toplevelConfigure, toplevelClose };
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Keyboard listener — Escape to quit
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
static void kbdKeymap(void*, wl_keyboard*, uint32_t, int32_t, uint32_t) {}
|
||||
static void kbdEnter(void*, wl_keyboard*, uint32_t, wl_surface*, wl_array*) {}
|
||||
static void kbdLeave(void*, wl_keyboard*, uint32_t, wl_surface*) {}
|
||||
static void kbdKey(void*, wl_keyboard*, uint32_t, uint32_t, uint32_t key, uint32_t state) {
|
||||
// key 1 == KEY_ESC in Linux evdev (linux/input-event-codes.h)
|
||||
if (key == 1 && state == WL_KEYBOARD_KEY_STATE_PRESSED)
|
||||
sRunning = false;
|
||||
}
|
||||
static void kbdModifiers(void*, wl_keyboard*, uint32_t, uint32_t, uint32_t, uint32_t, uint32_t) {}
|
||||
static void kbdRepeatInfo(void*, wl_keyboard*, int32_t, int32_t) {}
|
||||
static const wl_keyboard_listener kKbdListener = {
|
||||
kbdKeymap, kbdEnter, kbdLeave, kbdKey, kbdModifiers, kbdRepeatInfo
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// wl_seat listener — grab keyboard capability
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
static void seatCapabilities(void*, wl_seat* seat, uint32_t caps) {
|
||||
if ((caps & WL_SEAT_CAPABILITY_KEYBOARD) && !sKeyboard) {
|
||||
sKeyboard = wl_seat_get_keyboard(seat);
|
||||
wl_keyboard_add_listener(sKeyboard, &kKbdListener, nullptr);
|
||||
} else if (!(caps & WL_SEAT_CAPABILITY_KEYBOARD) && sKeyboard) {
|
||||
wl_keyboard_release(sKeyboard);
|
||||
sKeyboard = nullptr;
|
||||
}
|
||||
}
|
||||
static void seatName(void*, wl_seat*, const char*) {}
|
||||
static const wl_seat_listener kSeatListener = { seatCapabilities, seatName };
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Registry listener — bind global interfaces
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
static void registryGlobal(void*, wl_registry* reg,
|
||||
uint32_t name, const char* iface, uint32_t ver) {
|
||||
if (strcmp(iface, wl_compositor_interface.name) == 0)
|
||||
sCompositor = (wl_compositor*)wl_registry_bind(reg, name, &wl_compositor_interface, 4);
|
||||
else if (strcmp(iface, xdg_wm_base_interface.name) == 0) {
|
||||
sWmBase = (xdg_wm_base*)wl_registry_bind(reg, name, &xdg_wm_base_interface, 1);
|
||||
xdg_wm_base_add_listener(sWmBase, &kWmBaseListener, nullptr);
|
||||
} else if (strcmp(iface, wl_seat_interface.name) == 0) {
|
||||
sSeat = (wl_seat*)wl_registry_bind(reg, name, &wl_seat_interface, 5);
|
||||
wl_seat_add_listener(sSeat, &kSeatListener, nullptr);
|
||||
}
|
||||
}
|
||||
static void registryGlobalRemove(void*, wl_registry*, uint32_t) {}
|
||||
static const wl_registry_listener kRegistryListener = { registryGlobal, registryGlobalRemove };
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Platform interface implementation
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
bool platformInit(int width, int height, const char* title) {
|
||||
sDisplay = wl_display_connect(nullptr);
|
||||
if (!sDisplay) { fprintf(stderr, "Cannot connect to Wayland display\n"); return false; }
|
||||
|
||||
wl_registry* registry = wl_display_get_registry(sDisplay);
|
||||
wl_registry_add_listener(registry, &kRegistryListener, nullptr);
|
||||
|
||||
// Two roundtrips: first to enumerate globals, second for seat capabilities
|
||||
wl_display_roundtrip(sDisplay);
|
||||
wl_display_roundtrip(sDisplay);
|
||||
|
||||
if (!sCompositor) { fprintf(stderr, "No wl_compositor\n"); return false; }
|
||||
if (!sWmBase) { fprintf(stderr, "No xdg_wm_base\n"); return false; }
|
||||
|
||||
sSurface = wl_compositor_create_surface(sCompositor);
|
||||
sXdgSurface = xdg_wm_base_get_xdg_surface(sWmBase, sSurface);
|
||||
sToplevel = xdg_surface_get_toplevel(sXdgSurface);
|
||||
|
||||
xdg_surface_add_listener(sXdgSurface, &kXdgSurfaceListener, nullptr);
|
||||
xdg_toplevel_add_listener(sToplevel, &kToplevelListener, nullptr);
|
||||
xdg_toplevel_set_title(sToplevel, title);
|
||||
xdg_toplevel_set_app_id(sToplevel, "spinning_triangle");
|
||||
|
||||
wl_surface_commit(sSurface);
|
||||
|
||||
// Wait for the compositor to send the first configure
|
||||
while (!sConfigured) wl_display_dispatch(sDisplay);
|
||||
|
||||
clock_gettime(CLOCK_MONOTONIC, &sStartTime);
|
||||
return true;
|
||||
}
|
||||
|
||||
WGPUSurface platformCreateSurface(WGPUInstance instance) {
|
||||
WGPUSurfaceSourceWaylandSurface waylandSrc = {};
|
||||
waylandSrc.chain.sType = WGPUSType_SurfaceSourceWaylandSurface;
|
||||
waylandSrc.display = sDisplay;
|
||||
waylandSrc.surface = sSurface;
|
||||
|
||||
WGPUSurfaceDescriptor surfDesc = {};
|
||||
surfDesc.nextInChain = (WGPUChainedStruct*)&waylandSrc;
|
||||
return wgpuInstanceCreateSurface(instance, &surfDesc);
|
||||
}
|
||||
|
||||
double platformGetTime() {
|
||||
struct timespec now;
|
||||
clock_gettime(CLOCK_MONOTONIC, &now);
|
||||
return (double)(now.tv_sec - sStartTime.tv_sec)
|
||||
+ (double)(now.tv_nsec - sStartTime.tv_nsec) * 1e-9;
|
||||
}
|
||||
|
||||
void platformRunLoop(void (*render)(), void (*shutdown)()) {
|
||||
// Target ~16.67 ms per frame (60 fps)
|
||||
static const long kFrameNs = 1000000000L / 60;
|
||||
|
||||
sRunning = true;
|
||||
while (sRunning) {
|
||||
struct timespec frameStart;
|
||||
clock_gettime(CLOCK_MONOTONIC, &frameStart);
|
||||
|
||||
// Dispatch pending Wayland events without blocking
|
||||
if (wl_display_dispatch_pending(sDisplay) < 0) break;
|
||||
wl_display_flush(sDisplay);
|
||||
|
||||
if (sRunning) render();
|
||||
|
||||
// Sleep for the remainder of the frame budget
|
||||
struct timespec frameEnd;
|
||||
clock_gettime(CLOCK_MONOTONIC, &frameEnd);
|
||||
long elapsed = (frameEnd.tv_sec - frameStart.tv_sec) * 1000000000L
|
||||
+ (frameEnd.tv_nsec - frameStart.tv_nsec);
|
||||
long remaining = kFrameNs - elapsed;
|
||||
if (remaining > 0) {
|
||||
struct timespec ts = { 0, remaining };
|
||||
nanosleep(&ts, nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
shutdown();
|
||||
|
||||
// Cleanup Wayland objects
|
||||
if (sKeyboard) { wl_keyboard_release(sKeyboard); sKeyboard = nullptr; }
|
||||
if (sToplevel) { xdg_toplevel_destroy(sToplevel); sToplevel = nullptr; }
|
||||
if (sXdgSurface) { xdg_surface_destroy(sXdgSurface); sXdgSurface = nullptr; }
|
||||
if (sSurface) { wl_surface_destroy(sSurface); sSurface = nullptr; }
|
||||
if (sWmBase) { xdg_wm_base_destroy(sWmBase); sWmBase = nullptr; }
|
||||
if (sSeat) { wl_seat_release(sSeat); sSeat = nullptr; }
|
||||
if (sCompositor) { wl_compositor_destroy(sCompositor); sCompositor = nullptr; }
|
||||
wl_display_disconnect(sDisplay);
|
||||
}
|
||||
135
examples/webgpu/triangle/platform/platform_windows.cpp
Normal file
135
examples/webgpu/triangle/platform/platform_windows.cpp
Normal file
@@ -0,0 +1,135 @@
|
||||
// platform_windows.cpp — Windows backend (Win32)
|
||||
//
|
||||
// Compile flags (MSVC, console subsystem):
|
||||
// cl /std:c++17 spinning_triangle.cpp platform_windows.cpp \
|
||||
// /I\path\to\wgpu\include \path\to\wgpu\lib\wgpu_native.lib \
|
||||
// user32.lib gdi32.lib /Fe:spinning_triangle.exe
|
||||
//
|
||||
// MinGW/Clang equivalent:
|
||||
// clang++ -std=c++17 spinning_triangle.cpp platform_windows.cpp \
|
||||
// -I/path/to/wgpu/include -L/path/to/wgpu/lib -lwgpu_native \
|
||||
// -luser32 -lgdi32 -o spinning_triangle.exe
|
||||
|
||||
#ifndef WIN32_LEAN_AND_MEAN
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#endif
|
||||
#include <windows.h>
|
||||
#include <webgpu/webgpu.h>
|
||||
#include <stdio.h>
|
||||
#include "platform.h"
|
||||
|
||||
#pragma comment(lib, "user32.lib")
|
||||
#pragma comment(lib, "gdi32.lib")
|
||||
#pragma comment(lib, "dxguid.lib") // Dawn: WKPDID_D3DDebugObjectName
|
||||
#pragma comment(lib, "OneCore") // Dawn: CompareObjectHandles
|
||||
#pragma comment(lib, "ntdll.lib") // wgpu-native: NtReadFile et al.
|
||||
|
||||
static HWND sHwnd = nullptr;
|
||||
static bool sRunning = false;
|
||||
static LARGE_INTEGER sFreq = {};
|
||||
static LARGE_INTEGER sStartTime = {};
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Win32 window procedure
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
static LRESULT CALLBACK wndProc(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp) {
|
||||
switch (msg) {
|
||||
case WM_KEYDOWN:
|
||||
if (wp == VK_ESCAPE) { sRunning = false; return 0; }
|
||||
break;
|
||||
case WM_CLOSE:
|
||||
case WM_DESTROY:
|
||||
sRunning = false;
|
||||
PostQuitMessage(0);
|
||||
return 0;
|
||||
}
|
||||
return DefWindowProcA(hwnd, msg, wp, lp);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Platform interface implementation
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
bool platformInit(int width, int height, const char* title) {
|
||||
WNDCLASSEXA wc = {};
|
||||
wc.cbSize = sizeof(wc);
|
||||
wc.style = CS_HREDRAW | CS_VREDRAW;
|
||||
wc.lpfnWndProc = wndProc;
|
||||
wc.hInstance = GetModuleHandleA(nullptr);
|
||||
wc.hCursor = LoadCursor(nullptr, IDC_ARROW);
|
||||
wc.lpszClassName = "SpinningTriangle";
|
||||
if (!RegisterClassExA(&wc)) {
|
||||
fprintf(stderr, "RegisterClassExA failed (%lu)\n", GetLastError());
|
||||
return false;
|
||||
}
|
||||
|
||||
// Adjust client area to match the requested dimensions
|
||||
RECT rect = { 0, 0, width, height };
|
||||
AdjustWindowRect(&rect, WS_OVERLAPPEDWINDOW & ~(WS_THICKFRAME | WS_MAXIMIZEBOX), FALSE);
|
||||
|
||||
sHwnd = CreateWindowExA(
|
||||
0, "SpinningTriangle", title,
|
||||
WS_OVERLAPPEDWINDOW & ~(WS_THICKFRAME | WS_MAXIMIZEBOX),
|
||||
CW_USEDEFAULT, CW_USEDEFAULT,
|
||||
rect.right - rect.left, rect.bottom - rect.top,
|
||||
nullptr, nullptr, GetModuleHandleA(nullptr), nullptr);
|
||||
if (!sHwnd) {
|
||||
fprintf(stderr, "CreateWindowExA failed (%lu)\n", GetLastError());
|
||||
return false;
|
||||
}
|
||||
|
||||
ShowWindow(sHwnd, SW_SHOW);
|
||||
UpdateWindow(sHwnd);
|
||||
|
||||
QueryPerformanceFrequency(&sFreq);
|
||||
QueryPerformanceCounter(&sStartTime);
|
||||
return true;
|
||||
}
|
||||
|
||||
WGPUSurface platformCreateSurface(WGPUInstance instance) {
|
||||
WGPUSurfaceSourceWindowsHWND hwndSrc = {};
|
||||
hwndSrc.chain.sType = WGPUSType_SurfaceSourceWindowsHWND;
|
||||
hwndSrc.hinstance = GetModuleHandleA(nullptr);
|
||||
hwndSrc.hwnd = sHwnd;
|
||||
|
||||
WGPUSurfaceDescriptor surfDesc = {};
|
||||
surfDesc.nextInChain = (WGPUChainedStruct*)&hwndSrc;
|
||||
return wgpuInstanceCreateSurface(instance, &surfDesc);
|
||||
}
|
||||
|
||||
double platformGetTime() {
|
||||
LARGE_INTEGER now;
|
||||
QueryPerformanceCounter(&now);
|
||||
return (double)(now.QuadPart - sStartTime.QuadPart) / (double)sFreq.QuadPart;
|
||||
}
|
||||
|
||||
void platformRunLoop(void (*render)(), void (*shutdown)()) {
|
||||
// Target ~16.67 ms per frame (60 fps)
|
||||
static const double kFrameTime = 1.0 / 60.0;
|
||||
|
||||
sRunning = true;
|
||||
while (sRunning) {
|
||||
double frameStart = platformGetTime();
|
||||
|
||||
// Drain the Win32 message queue
|
||||
MSG msg;
|
||||
while (PeekMessageA(&msg, nullptr, 0, 0, PM_REMOVE)) {
|
||||
if (msg.message == WM_QUIT) { sRunning = false; break; }
|
||||
TranslateMessage(&msg);
|
||||
DispatchMessageA(&msg);
|
||||
}
|
||||
|
||||
if (sRunning) render();
|
||||
|
||||
// Sleep for the remainder of the frame budget
|
||||
double elapsed = platformGetTime() - frameStart;
|
||||
if (elapsed < kFrameTime) {
|
||||
DWORD ms = (DWORD)((kFrameTime - elapsed) * 1000.0);
|
||||
if (ms > 0) Sleep(ms);
|
||||
}
|
||||
}
|
||||
|
||||
shutdown();
|
||||
if (sHwnd) DestroyWindow(sHwnd);
|
||||
}
|
||||
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