From cc45cf60463b7658b090019621f4abf43337840e Mon Sep 17 00:00:00 2001 From: Marcos Slomp Date: Sun, 14 Jun 2026 17:38:18 -0700 Subject: [PATCH] switch to SDL3 (no cmake fetch, just find_package) --- examples/webgpu/triangle/CMakeLists.txt | 50 +--- .../triangle/platform/platform_macos.mm | 121 ---------- .../triangle/platform/platform_sdl3.cpp | 95 ++++++++ .../triangle/platform/platform_wayland.cpp | 213 ------------------ .../triangle/platform/platform_windows.cpp | 135 ----------- 5 files changed, 103 insertions(+), 511 deletions(-) delete mode 100644 examples/webgpu/triangle/platform/platform_macos.mm create mode 100644 examples/webgpu/triangle/platform/platform_sdl3.cpp delete mode 100644 examples/webgpu/triangle/platform/platform_wayland.cpp delete mode 100644 examples/webgpu/triangle/platform/platform_windows.cpp diff --git a/examples/webgpu/triangle/CMakeLists.txt b/examples/webgpu/triangle/CMakeLists.txt index 1359a379..f9f21a10 100644 --- a/examples/webgpu/triangle/CMakeLists.txt +++ b/examples/webgpu/triangle/CMakeLists.txt @@ -13,7 +13,7 @@ # /I\path\to\wgpu\include \path\to\wgpu\lib\wgpu_native.lib \ # user32.lib gdi32.lib /Fe:spinning_triangle.exe # -# Linux / Wayland: +# Linux (requires libsdl3-dev): # 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 \ @@ -78,13 +78,15 @@ if(APPLE) endif() # --------------------------------------------------------------------------- -# Platform-specific source and link settings +# Platform — SDL3 (cross-platform windowing, must be installed on the system) # --------------------------------------------------------------------------- -set(PLATFORM_GENERATED_INCLUDES "") +find_package(SDL3 REQUIRED) + +set(PLATFORM_SOURCES platform/platform_sdl3.cpp) if(APPLE) - set(PLATFORM_SOURCES platform/platform_macos.mm) set(PLATFORM_LIBS + SDL3::SDL3 "-framework Cocoa" "-framework Metal" "-framework QuartzCore" @@ -92,45 +94,10 @@ if(APPLE) "-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) + set(PLATFORM_LIBS SDL3::SDL3) 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}") + set(PLATFORM_LIBS SDL3::SDL3) endif() # --------------------------------------------------------------------------- @@ -163,7 +130,6 @@ 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") diff --git a/examples/webgpu/triangle/platform/platform_macos.mm b/examples/webgpu/triangle/platform/platform_macos.mm deleted file mode 100644 index dc7cf2cb..00000000 --- a/examples/webgpu/triangle/platform/platform_macos.mm +++ /dev/null @@ -1,121 +0,0 @@ -// 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 -#import -#include -#include -#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 -@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; - }]; - - [NSApp activateIgnoringOtherApps:YES]; - [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]; - } -} diff --git a/examples/webgpu/triangle/platform/platform_sdl3.cpp b/examples/webgpu/triangle/platform/platform_sdl3.cpp new file mode 100644 index 00000000..c3893209 --- /dev/null +++ b/examples/webgpu/triangle/platform/platform_sdl3.cpp @@ -0,0 +1,95 @@ +// platform_sdl3.cpp — SDL3 windowing backend for the WebGPU example +#include "platform.h" // webgpu/webgpu.h first + +#define SDL_MAIN_HANDLED // we don't want SDL_main +#include + +#ifdef __APPLE__ +# include +#endif + +#include +#include + +static SDL_Window* sWin = nullptr; +static std::chrono::steady_clock::time_point sStartTime; +#ifdef __APPLE__ +static SDL_MetalView sMetalView = nullptr; +#endif + +bool platformInit(int width, int height, const char* title) { + if (!SDL_Init(SDL_INIT_VIDEO)) { + fprintf(stderr, "ERROR: SDL_Init failed: %s\n", SDL_GetError()); + return false; + } + + SDL_WindowFlags flags = 0; +#ifdef __APPLE__ + flags |= SDL_WINDOW_METAL; +#endif + + sWin = SDL_CreateWindow(title, width, height, flags); + if (!sWin) { + fprintf(stderr, "ERROR: SDL_CreateWindow failed: %s\n", SDL_GetError()); + SDL_Quit(); + return false; + } + SDL_SetWindowPosition(sWin, SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED); + + sStartTime = std::chrono::steady_clock::now(); + return true; +} + +WGPUSurface platformCreateSurface(WGPUInstance instance) { + WGPUSurfaceDescriptor desc = {}; + SDL_PropertiesID props = SDL_GetWindowProperties(sWin); + +#if defined(__APPLE__) + sMetalView = SDL_Metal_CreateView(sWin); + if (!sMetalView) { + fprintf(stderr, "ERROR: SDL_Metal_CreateView failed\n"); + return nullptr; + } + WGPUSurfaceSourceMetalLayer metalDesc = {}; + metalDesc.chain.sType = WGPUSType_SurfaceSourceMetalLayer; + metalDesc.layer = SDL_Metal_GetLayer(sMetalView); + desc.nextInChain = &metalDesc.chain; +#elif defined(_WIN32) + WGPUSurfaceSourceWindowsHWND hwndDesc = {}; + hwndDesc.chain.sType = WGPUSType_SurfaceSourceWindowsHWND; + hwndDesc.hinstance = SDL_GetPointerProperty(props, SDL_PROP_WINDOW_WIN32_INSTANCE_POINTER, nullptr); + hwndDesc.hwnd = SDL_GetPointerProperty(props, SDL_PROP_WINDOW_WIN32_HWND_POINTER, nullptr); + desc.nextInChain = &hwndDesc.chain; +#else // Linux / X11 + WGPUSurfaceSourceXlibWindow x11Desc = {}; + x11Desc.chain.sType = WGPUSType_SurfaceSourceXlibWindow; + x11Desc.display = SDL_GetPointerProperty(props, SDL_PROP_WINDOW_X11_DISPLAY_POINTER, nullptr); + x11Desc.window = (uint32_t)SDL_GetNumberProperty(props, SDL_PROP_WINDOW_X11_WINDOW_NUMBER, 0); + desc.nextInChain = &x11Desc.chain; +#endif + + return wgpuInstanceCreateSurface(instance, &desc); +} + +double platformGetTime() { + return std::chrono::duration( + std::chrono::steady_clock::now() - sStartTime).count(); +} + +void platformRunLoop(void (*render)(), void (*shutdown)()) { + bool running = true; + while (running) { + SDL_Event e; + while (SDL_PollEvent(&e)) { + if (e.type == SDL_EVENT_QUIT) running = false; + if (e.type == SDL_EVENT_KEY_DOWN && e.key.key == SDLK_ESCAPE) running = false; + } + if (running) render(); + } + shutdown(); +#ifdef __APPLE__ + SDL_Metal_DestroyView(sMetalView); +#endif + SDL_DestroyWindow(sWin); + SDL_Quit(); +} diff --git a/examples/webgpu/triangle/platform/platform_wayland.cpp b/examples/webgpu/triangle/platform/platform_wayland.cpp deleted file mode 100644 index 619abb6e..00000000 --- a/examples/webgpu/triangle/platform/platform_wayland.cpp +++ /dev/null @@ -1,213 +0,0 @@ -// 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 -#include "xdg-shell-client-protocol.h" -#include -#include -#include -#include -#include -#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); -} diff --git a/examples/webgpu/triangle/platform/platform_windows.cpp b/examples/webgpu/triangle/platform/platform_windows.cpp deleted file mode 100644 index 153531ca..00000000 --- a/examples/webgpu/triangle/platform/platform_windows.cpp +++ /dev/null @@ -1,135 +0,0 @@ -// 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 -#include -#include -#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); -}