Files
tracy/examples/webgpu/triangle/platform/platform_wayland.cpp
2026-06-09 09:06:48 -07:00

214 lines
9.0 KiB
C++

// 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);
}