Skip to content

Instantly share code, notes, and snippets.

@DevFelixFaber
Created July 10, 2024 19:11
Show Gist options
  • Save DevFelixFaber/6947def19c3cad9a278c88a880be8738 to your computer and use it in GitHub Desktop.
Save DevFelixFaber/6947def19c3cad9a278c88a880be8738 to your computer and use it in GitHub Desktop.
Wayland OpenGL Example Application. Includes Keyboard input (with key-repeat), mouse events, setting the mouse cursor image/icon, retrieving the refreshrate, and measuring render time.
#define _GNU_SOURCE
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <assert.h>
#include <string.h>
#include <time.h>
#include <poll.h>
#include <sys/timerfd.h>
#include <sys/mman.h>
#include <xkbcommon/xkbcommon.h>
#include <linux/input-event-codes.h> // Mouse buttons
#include <wayland-client.h>
#include <wayland-cursor.h>
#include <wayland-egl.h>
#include "generated/xdg_shell.h"
#include "generated/xdg_shell.c"
// NOTE: I use this definition to get "more modern" OpenGL function prototypes.
// They most likely are available on every platform, but it's technically not guaranteed.
// Not used in this example, but this can become relevant once there are more OpenGL function calls.
#define GL_GLEXT_PROTOTYPES
#include <GL/gl.h>
#include <EGL/egl.h>
// Nicer primitives
#include <inttypes.h>
#include <stddef.h>
#include <stdint.h>
typedef int8_t s8;
typedef int16_t s16;
typedef int32_t s32;
typedef int64_t s64;
typedef uint8_t u8;
typedef uint16_t u16;
typedef uint32_t u32;
typedef uint64_t u64;
typedef s32 b32;
typedef float f32;
typedef double f64;
#define ARRAYCOUNT(Array) (sizeof(Array) / sizeof((Array)[0]))
#define MIN(a,b) ((a) < (b) ? (a) : (b))
#define MAX(a,b) ((a) > (b) ? (a) : (b))
// Structure definitions
typedef struct wayland_output wayland_output;
struct wayland_output
{
b32 IsInitialized;
b32 DisplaysWindow;
struct wl_output *WlOutput;
double RefreshRateHz;
u32 NameID;
};
typedef struct wayland_context wayland_context;
struct wayland_context
{
// Wayland
struct wl_display *WlDisplay;
struct wl_registry *WlRegistry;
struct wl_compositor *WlCompositor;
struct wl_surface *WlSurface;
struct wl_egl_window *WlEglWindow;
struct wl_seat *WlSeat;
struct wl_shm *WlShm;
struct wl_keyboard *WlKeyboard;
struct wl_pointer *WlPointer;
wayland_output WaylandOutputs[16]; // Assuming that 16 Monitors are enough
struct wl_cursor_theme *WlCursorTheme;
struct wl_cursor *WlCursor;
struct wl_surface *WlCursorSurface;
struct wl_buffer *WlCursorBuffer;
struct wl_cursor_image *WlCursorImage;
// XDG (rendering to surface)
struct xdg_wm_base *XdgBase;
struct xdg_toplevel *XdgToplevel;
struct xdg_surface *XdgSurface;
// XKB (Keyboard input)
struct xkb_context *XkbContext;
struct xkb_keymap *XkbKeymap;
struct xkb_state *XkbState;
// Egl (OpenGL context)
EGLContext EglContext;
EGLDisplay EglDisplay;
EGLSurface EglSurface;
// Input for key repeat
int TimerFd;
struct itimerspec TimerProperties;
u32 KeyCode;
u32 KeyState;
// Some window properties
double WindowCurrentRefreshRate; // Max of all refreshrates the window is displayed on
s32 WindowCurrentWidth;
s32 WindowCurrentHeight;
s32 WindowWantsWidth;
s32 WindowWantsHeight;
b32 OpenGLNeedsResize;
b32 WindowFullscreen;
b32 WantsToExit;
// TODO: Start of your custom properties
};
// Functions
// This function is called on every key event
static void
ProcessKeyUpdate(wayland_context *Context, u32 Key, u32 KeyState)
{
char Utf8Character[5] = { 0 };
u32 XkbScancode = Key+8; // Something something translation from evdev
xkb_keysym_t Symbol = xkb_state_key_get_one_sym(Context->XkbState, XkbScancode);
s32 BytesNeededToWrite = xkb_state_key_get_utf8(Context->XkbState, XkbScancode, Utf8Character, sizeof(Utf8Character));
b32 IsPressed = (KeyState == WL_KEYBOARD_KEY_STATE_PRESSED);
char AsciiChar = Utf8Character[0];
if (Symbol == XKB_KEY_Escape)
{
Context->WantsToExit = 1;
}
}
// NOTE: Mouse events are generated in the WaylandCallbackPointer!
// Might need to be gathered or processed separately.
static void
WaylandCallbackUnused() // for C2X: use (...)
{
// noop;
}
static void
WaylandCallbackKeyboardKeymap(void *Data, struct wl_keyboard *WlKeyboard, u32 Format, s32 Fd, u32 Size)
{
wayland_context *Context = Data;
assert(Format == WL_KEYBOARD_KEYMAP_FORMAT_XKB_V1);
char *MapShm = mmap(0, Size, PROT_READ, MAP_PRIVATE, Fd, 0);
assert(MapShm != MAP_FAILED);
Context->XkbKeymap = xkb_keymap_new_from_string(Context->XkbContext, MapShm, XKB_KEYMAP_FORMAT_TEXT_V1, XKB_KEYMAP_COMPILE_NO_FLAGS);
munmap(MapShm, Size);
close(Fd);
Context->XkbState = xkb_state_new(Context->XkbKeymap);
}
static void
WaylandCallbackKeyboardKey(void *Data, struct wl_keyboard *WlKeyboard, u32 Serial, u32 Time, u32 Key, u32 State)
{
// Store latest keypress for keyrepeat
wayland_context *ProgramState = Data;
if (Key == ProgramState->KeyCode && State == WL_KEYBOARD_KEY_STATE_RELEASED)
{
// Released key that would be repeating
struct itimerspec DisabledTimer = { 0 };
struct itimerspec Unused = { 0 };
int ReturnValue = timerfd_settime(ProgramState->TimerFd, 0, &DisabledTimer, &Unused);
assert(ReturnValue == 0);
}
else if (State == WL_KEYBOARD_KEY_STATE_PRESSED)
{
ProgramState->KeyCode = Key;
ProgramState->KeyState = State;
struct itimerspec Unused = { 0 };
int ReturnValue = timerfd_settime(ProgramState->TimerFd, 0, &ProgramState->TimerProperties, &Unused);
assert(ReturnValue == 0);
}
ProcessKeyUpdate(Data, Key, State);
}
static void
WaylandCallbackKeyboardModifiers(void *Data, struct wl_keyboard *WlKeyboard,
u32 serial, u32 ModsDepressed, u32 ModsLatched, u32 ModsLocked, u32 Group)
{
wayland_context *State = Data;
xkb_state_update_mask(State->XkbState, ModsDepressed, ModsLatched, ModsLocked, 0, 0, Group);
}
static void
WaylandCallbackKeyboardRepeatInfo(void *Data, struct wl_keyboard *WlKeyboard, s32 Rate, s32 Delay)
{
wayland_context *State = Data;
u64 NsKeyDelay = (u64)Delay * (u64)1000llu * (u64)1000llu;
u64 NsKeyRepeat = ((u64)1000llu * (u64)1000llu * (u64)1000llu / (u64)Rate);
struct timespec InitialExpiration = {
.tv_nsec = (long int)NsKeyDelay,
};
struct timespec PeriodicExpiration = {
.tv_nsec = (long int)NsKeyRepeat,
};
struct itimerspec TimerProperties = {
.it_value = InitialExpiration,
.it_interval = PeriodicExpiration,
};
State->TimerProperties = TimerProperties;
}
static struct wl_keyboard_listener GLOBALWlKeyboardListener = {
.keymap = WaylandCallbackKeyboardKeymap,
.enter = WaylandCallbackUnused,
.leave = WaylandCallbackUnused,
.key = WaylandCallbackKeyboardKey,
.modifiers = WaylandCallbackKeyboardModifiers,
.repeat_info = WaylandCallbackKeyboardRepeatInfo,
};
static void
WaylandCallbackPointerEnter(void *Data, struct wl_pointer *WlPointer,
u32 Serial, struct wl_surface *WlSurface,
wl_fixed_t SurfaceX, wl_fixed_t SurfaceY)
{
// Set default cursor
wayland_context *Context = Data;
wl_pointer_set_cursor(WlPointer, Serial, Context->WlCursorSurface,
(int)Context->WlCursorImage->hotspot_x,
(int)Context->WlCursorImage->hotspot_y);
}
static void
WaylandCallbackPointerMotion(void *Data, struct wl_pointer *WlPointer,
u32 Time, wl_fixed_t SurfaceX, wl_fixed_t SurfaceY)
{
wayland_context *Context = Data;
int X = wl_fixed_to_int(SurfaceX);
int Y = wl_fixed_to_int(SurfaceY);
// Top left is (0, 0)
// TODO: Usage code
}
static void
WaylandCallbackPointerButton(void *Data, struct wl_pointer *WlPointer,
u32 Serial, u32 Time, u32 Button, u32 State)
{
b32 Pressed = (State == WL_POINTER_BUTTON_STATE_PRESSED);
b32 Released = (State == WL_POINTER_BUTTON_STATE_RELEASED);
b32 IsLeftButton = (Button == BTN_LEFT);
b32 IsRightButton = (Button == BTN_RIGHT);
// Also available: MIDDLE, SIDE, FORWARD, BACK, ...
// TODO: Usage code
}
static struct wl_pointer_listener GLOBALWlPointerListener = {
.enter = WaylandCallbackPointerEnter,
.leave = WaylandCallbackUnused,
.motion = WaylandCallbackPointerMotion,
.button = WaylandCallbackPointerButton,
.axis = WaylandCallbackUnused,
.frame = WaylandCallbackUnused,
.axis_source = WaylandCallbackUnused,
.axis_stop = WaylandCallbackUnused,
.axis_discrete = WaylandCallbackUnused,
.axis_value120 = WaylandCallbackUnused,
.axis_relative_direction = WaylandCallbackUnused,
};
static wayland_output *
WaylandFindMatchingOutput(wayland_context *Context, struct wl_output *ToFind)
{
wayland_output *Output = 0;
b32 FoundMatchingWlOutput = 0;
for (u32 Index = 0; Index < ARRAYCOUNT(Context->WaylandOutputs); ++Index)
{
Output = &(Context->WaylandOutputs[Index]);
if (Output->IsInitialized && Output->WlOutput == ToFind)
{
FoundMatchingWlOutput = 1;
break;
}
}
assert(FoundMatchingWlOutput);
return Output;
}
static void
WaylandUpdateUsedRefreshRate(wayland_context *Context)
{
f64 MaxUsedRefreshRateHz = 0;
for (u32 Index = 0; Index < ARRAYCOUNT(Context->WaylandOutputs); ++Index)
{
wayland_output *Output = &(Context->WaylandOutputs[Index]);
if (Output->IsInitialized && Output->DisplaysWindow)
{
MaxUsedRefreshRateHz = MAX(MaxUsedRefreshRateHz, Output->RefreshRateHz);
}
}
Context->WindowCurrentRefreshRate = MaxUsedRefreshRateHz;
}
static void
WaylandCallbackOutputMode(void *Data, struct wl_output *WlOutput,
u32 Flags, int Width, int Height, int Refresh)
{
wayland_context *Context = Data;
wayland_output *Output = WaylandFindMatchingOutput(Context, WlOutput);
Output->RefreshRateHz = Refresh / 1000.0; // Refresh is in mHz
}
static struct wl_output_listener GLOBALWlOutputListener = {
.geometry = WaylandCallbackUnused,
.mode = WaylandCallbackOutputMode,
.done = WaylandCallbackUnused,
.scale = WaylandCallbackUnused,
.name = WaylandCallbackUnused,
.description = WaylandCallbackUnused,
};
static void
WaylandCallbackSurfaceEnter(void *Data, struct wl_surface *WlSurface, struct wl_output *WlOutput)
{
wayland_context *Context = Data;
wayland_output *Output = WaylandFindMatchingOutput(Context, WlOutput);
Output->DisplaysWindow = 1;
WaylandUpdateUsedRefreshRate(Context);
}
static void
WaylandCallbackSurfaceLeave(void *Data, struct wl_surface *WlSurface, struct wl_output *WlOutput)
{
wayland_context *Context = Data;
wayland_output *Output = WaylandFindMatchingOutput(Context, WlOutput);
Output->DisplaysWindow = 0;
WaylandUpdateUsedRefreshRate(Context);
}
static struct wl_surface_listener GLOBALWlSurfaceListener = {
.enter = WaylandCallbackSurfaceEnter,
.leave = WaylandCallbackSurfaceLeave,
.preferred_buffer_scale = WaylandCallbackUnused,
.preferred_buffer_transform = WaylandCallbackUnused,
};
static void
WaylandCallbackXdgToplevelConfigure(void *Data, struct xdg_toplevel *XdgToplevel, int32_t Width, int32_t Height, struct wl_array *States)
{
wayland_context *Context = Data;
s32 WidthToUse = Width;
s32 HeightToUse = Height;
if (Width == 0)
{
WidthToUse = Context->WindowWantsWidth;
}
if (Height == 0)
{
HeightToUse = Context->WindowWantsHeight;
}
if (WidthToUse != Context->WindowCurrentWidth ||
HeightToUse != Context->WindowCurrentHeight)
{
Context->WindowCurrentWidth = WidthToUse;
Context->WindowCurrentHeight = HeightToUse;
Context->OpenGLNeedsResize = 1;
}
uint32_t *CurrentState;
wl_array_for_each(CurrentState, States)
{
switch (*CurrentState)
{
case XDG_TOPLEVEL_STATE_FULLSCREEN:
case XDG_TOPLEVEL_STATE_MAXIMIZED:
{
Context->WindowFullscreen = 1;
} break;
}
}
}
static void
WaylandCallbackXdgToplevelClose(void *Data, struct xdg_toplevel *XdgToplevel)
{
wayland_context *Context = Data;
Context->WantsToExit = 1;
}
static struct xdg_toplevel_listener GLOBALXdgToplevelListener = {
WaylandCallbackXdgToplevelConfigure,
WaylandCallbackXdgToplevelClose
};
static void
WaylandCallbackXdgSurfaceConfigure(void *Data, struct xdg_surface *XdgSurface, uint32_t Serial)
{
wayland_context *Context = Data;
if (Context->OpenGLNeedsResize)
{
// Info on the last two parameters dx,dy
// https://lists.freedesktop.org/archives/wayland-devel/2014-May/014847.html
// For a floating, top-level window, dx/dy should cause the window to move.
// (Programmatically, not by the suer / server)
int DeltaX = 0;
int DeltaY = 0;
wl_egl_window_resize(Context->WlEglWindow, Context->WindowCurrentWidth, Context->WindowCurrentHeight, DeltaX, DeltaY);
// Consider performing
// Render(); eglSwapBuffers(Context->EglDisplay, Context->EglSurface);
// here
}
xdg_surface_ack_configure(XdgSurface, Serial);
}
static struct xdg_surface_listener GLOBALXdgSurfaceListener = {
WaylandCallbackXdgSurfaceConfigure
};
static void
WaylandCallbackXdgBasePing(void *Data, struct xdg_wm_base *XdgBase, uint32_t Serial)
{
xdg_wm_base_pong(XdgBase, Serial);
}
static struct xdg_wm_base_listener GLOBALXdgBaseListener = {
WaylandCallbackXdgBasePing
};
static void
WaylandCallbackSeatCapabilities(void *Data, struct wl_seat *WlSeat, u32 Capabilities)
{
// Assure that we can get input.
// This will fail for pointer if you e.g. temporarily have no monitors attached
//assert(Capabilities & WL_SEAT_CAPABILITY_KEYBOARD);
//assert(Capabilities & WL_SEAT_CAPABILITY_POINTER);
}
static struct wl_seat_listener GLOBALWlSeatListener = {
.capabilities = WaylandCallbackSeatCapabilities,
.name = WaylandCallbackUnused,
};
static void
WaylandCallbackRegistryGlobalReceive(void *Data, struct wl_registry *WlRegistry, u32 Name, const char *Interface, u32 Version)
{
wayland_context *Context = Data;
if ( ! strcmp(Interface, wl_compositor_interface.name))
{
Context->WlCompositor = wl_registry_bind(WlRegistry, Name, &wl_compositor_interface, Version);
}
else if ( ! strcmp(Interface, xdg_wm_base_interface.name))
{
Context->XdgBase = wl_registry_bind(WlRegistry, Name, &xdg_wm_base_interface, Version);
}
else if ( ! strcmp(Interface, wl_seat_interface.name))
{
Context->WlSeat = wl_registry_bind(WlRegistry, Name, &wl_seat_interface, Version);
wl_seat_add_listener(Context->WlSeat, &GLOBALWlSeatListener, Context);
}
else if ( ! strcmp(Interface, wl_shm_interface.name))
{
Context->WlShm = wl_registry_bind(WlRegistry, Name, &wl_shm_interface, Version);
}
else if ( ! strcmp(Interface, wl_output_interface.name))
{
b32 FoundEmptyOutputSlot = 0;
wayland_output *Output = 0;
for (u32 Index = 0; Index < ARRAYCOUNT(Context->WaylandOutputs); ++Index)
{
Output = &(Context->WaylandOutputs[Index]);
if ( ! Output->IsInitialized)
{
FoundEmptyOutputSlot = 1;
break;
}
}
assert(FoundEmptyOutputSlot);
Output->WlOutput = wl_registry_bind(WlRegistry, Name, &wl_output_interface, Version);
Output->IsInitialized = 1;
Output->NameID = Name;
wl_output_add_listener(Output->WlOutput, &GLOBALWlOutputListener, Context);
}
}
static void
WaylandCallbackRegistryGlobalRemove(void *Data, struct wl_registry *WlRegistry, u32 Name)
{
wayland_context *Context = Data;
for (u32 Index = 0; Index < ARRAYCOUNT(Context->WaylandOutputs); ++Index)
{
wayland_output *Output = &(Context->WaylandOutputs[Index]);
if (Output->NameID == Name)
{
Output->IsInitialized = 0;
}
}
}
static struct wl_registry_listener GLOBALRegistryListener = {
.global = WaylandCallbackRegistryGlobalReceive,
.global_remove = WaylandCallbackRegistryGlobalRemove,
};
static u64
DeltaNs(struct timespec Start, struct timespec End)
{
enum { NS_PER_SECOND = 1000000000ull };
s64 NSec = End.tv_nsec - Start.tv_nsec;
s64 Sec = End.tv_sec - Start.tv_sec;
u64 Result = 0;
if (Sec > 0 && NSec < 0)
{
Result = (u64)(NSec + NS_PER_SECOND);
}
else
{
Result = (u64)NSec;
}
return Result;
}
int
main(int ArgumentCount, char **ArgumentStrings)
{
// Wayland & Egl Window initialization
wayland_context Context = { 0 };
{
char *WindowTitle = "Wayland OpenGL Application";
s32 DefaultWindowWidth = 1280;
s32 DefaultWindowHeight = 720;
// Important: Both "wants" and "Current" need to be set to the same value.
// Otherwise, the callbacks will call wl_egl_window_resize too early
// and cause a segmentation fault.
Context.WindowWantsWidth = DefaultWindowWidth;
Context.WindowWantsHeight = DefaultWindowHeight;
Context.WindowCurrentWidth = DefaultWindowWidth;
Context.WindowCurrentHeight = DefaultWindowHeight;
Context.WlDisplay = wl_display_connect(0);
Context.EglDisplay = eglGetDisplay(Context.WlDisplay);
eglInitialize(Context.EglDisplay, 0, 0);
EGLint ConfigAttributes[] = {
EGL_SURFACE_TYPE, EGL_WINDOW_BIT,
EGL_RENDERABLE_TYPE, EGL_OPENGL_BIT,
EGL_RED_SIZE, 8,
EGL_GREEN_SIZE, 8,
EGL_BLUE_SIZE, 8,
EGL_ALPHA_SIZE, 8,
EGL_STENCIL_SIZE, 8,
EGL_NONE
};
EGLConfig EglConfig;
EGLint ConfigNumberOfFrameBufferConfigurations;
EGLBoolean EglChooseConfigResult = eglChooseConfig(Context.EglDisplay, ConfigAttributes, &EglConfig, 1, &ConfigNumberOfFrameBufferConfigurations);
assert(EglChooseConfigResult == EGL_TRUE);
Context.WlRegistry = wl_display_get_registry(Context.WlDisplay);
wl_registry_add_listener(Context.WlRegistry, &GLOBALRegistryListener, &Context);
wl_display_dispatch(Context.WlDisplay);
wl_display_roundtrip(Context.WlDisplay); // I'm not sure this roundtrip is necessary
EGLint ContextAttributes[] = {
EGL_CONTEXT_CLIENT_VERSION, 3,
EGL_NONE
};
eglBindAPI(EGL_OPENGL_API);
Context.EglContext = eglCreateContext(Context.EglDisplay, EglConfig, EGL_NO_CONTEXT, ContextAttributes);
Context.WlSurface = wl_compositor_create_surface(Context.WlCompositor);
wl_surface_add_listener(Context.WlSurface, &GLOBALWlSurfaceListener, &Context);
struct xdg_surface *XdgSurface = xdg_wm_base_get_xdg_surface(Context.XdgBase, Context.WlSurface);
xdg_surface_add_listener(XdgSurface, &GLOBALXdgSurfaceListener, &Context);
struct xdg_toplevel *XdgToplevel = xdg_surface_get_toplevel(XdgSurface);
xdg_toplevel_add_listener(XdgToplevel, &GLOBALXdgToplevelListener, &Context);
xdg_toplevel_set_title(XdgToplevel, WindowTitle);
wl_surface_commit(Context.WlSurface);
wl_display_dispatch(Context.WlDisplay);
wl_display_roundtrip(Context.WlDisplay);
Context.WlEglWindow = wl_egl_window_create(Context.WlSurface, DefaultWindowWidth, DefaultWindowHeight);
Context.EglSurface = eglCreateWindowSurface(Context.EglDisplay, EglConfig, Context.WlEglWindow, 0);
enum { VSYNC_DISABLE = 0, VSYNC_ENABLE = 1, };
eglMakeCurrent(Context.EglDisplay, Context.EglSurface, Context.EglSurface, Context.EglContext);
eglSwapInterval(Context.EglDisplay, VSYNC_ENABLE);
eglSwapBuffers(Context.EglDisplay, Context.EglSurface);
wl_display_dispatch(Context.WlDisplay);
wl_display_roundtrip(Context.WlDisplay);
// Taken from https://bugaevc.gitbooks.io/writing-wayland-clients/content/beyond-the-black-square/cursors.html
char *CursorThemeName = 0; // same as "default"
char *CursorType = "left_ptr"; // Default cursor. Check wl_cursor[].name for possible entries
Context.WlCursorTheme = wl_cursor_theme_load(CursorThemeName, 24, Context.WlShm); // I have no idea where that magic 24 for the size is coming from.
Context.WlCursor = wl_cursor_theme_get_cursor(Context.WlCursorTheme, CursorType);
Context.WlCursorImage = Context.WlCursor->images[0];
Context.WlCursorBuffer = wl_cursor_image_get_buffer(Context.WlCursorImage);
Context.WlCursorSurface = wl_compositor_create_surface(Context.WlCompositor);
wl_surface_attach(Context.WlCursorSurface, Context.WlCursorBuffer, 0, 0);
wl_surface_commit(Context.WlCursorSurface);
xdg_wm_base_add_listener(Context.XdgBase, &GLOBALXdgBaseListener, 0);
Context.XkbContext = xkb_context_new(XKB_CONTEXT_NO_FLAGS);
Context.WlKeyboard = wl_seat_get_keyboard(Context.WlSeat);
wl_keyboard_add_listener(Context.WlKeyboard, &GLOBALWlKeyboardListener, &Context);
Context.WlPointer = wl_seat_get_pointer(Context.WlSeat);
wl_pointer_add_listener(Context.WlPointer, &GLOBALWlPointerListener, &Context);
} // End of window initialization
// TODO: OpenGL usage code (shaders, textures, vertex buffer, uniforms, etc)
glClearColor(1.0, 0.0, 1.0, 1.0);
// Setup file descriptors for polling
Context.TimerFd = timerfd_create(CLOCK_MONOTONIC, 0);
assert(Context.TimerFd > 0);
struct pollfd FdToPoll = { .fd = Context.TimerFd, .events = POLLIN, };
// NOTE: Main loop
while ( ! Context.WantsToExit)
{
// Frame start
struct timespec FrameTimeStart;
clock_gettime(CLOCK_MONOTONIC_RAW, &FrameTimeStart);
// Event based loop
wl_display_dispatch_pending(Context.WlDisplay);
// Keyboard times
{
FdToPoll.revents = 0;
poll(&FdToPoll, 1, 0);
b32 TimerFired = (FdToPoll.revents == POLLIN);
if (TimerFired)
{
u64 TimersElapsed = 0;
read(Context.TimerFd, &TimersElapsed, sizeof(TimersElapsed));
for (u64 Index = 0; Index < TimersElapsed; ++Index)
{
ProcessKeyUpdate(&Context, Context.KeyCode, Context.KeyState);
}
}
}
// Render
f64 FrameTimeDelta = 1.0 / Context.WindowCurrentRefreshRate; // Assume constant frame delta
glClear(GL_COLOR_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
// Frame End
struct timespec FrameTimeEnd;
clock_gettime(CLOCK_MONOTONIC_RAW, &FrameTimeEnd);
// Debug performance output, push stats into next frame
{
u64 DeltaInNs = DeltaNs(FrameTimeStart, FrameTimeEnd);
f64 DeltaInMs = (f64)DeltaInNs / 1000000.0;
u32 FPS = (u32)(1000.0 / DeltaInMs);
// TODO: Usage code here
}
// If vsync: Block until frame is drawn
eglSwapBuffers(Context.EglDisplay, Context.EglSurface);
}
// NOTE: Automatic cleanup on process exit
return 0;
}
#!/bin/sh
PROJECT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
cd $PROJECT_DIR
mkdir -p ./build
mkdir -p ./generated
if ! [ -f ./xdg_shell.c ]; then
wayland-scanner private-code < /usr/share/wayland-protocols/stable/xdg-shell/xdg-shell.xml > ./generated/xdg_shell.c
fi
if ! [ -f ./xdg_shell.h ]; then
wayland-scanner client-header < /usr/share/wayland-protocols/stable/xdg-shell/xdg-shell.xml > ./generated/xdg_shell.h
fi
WARNINGS="-Wextra -Wall -Werror -pedantic -std=c17 -Wformat=2 -Wformat-truncation -Wundef -Wdouble-promotion -Wshadow -Wpointer-arith -Wcast-align -Wfloat-conversion -Wsign-conversion"
DISABLEDWARNINGS="-Wno-unused-variable -Wno-unused-function -Wno-unused-parameter -Wno-missing-field-initializers"
LIBRARIES="-lwayland-egl -lwayland-client -lwayland-cursor -lEGL -lGL -lxkbcommon -lrt -lm"
DEBUG="-O0 -g"
RELEASE="-O3"
gcc 00_wayland_opengl_main.c -o ./build/template_opengl_wayland_debug $DEBUG $WARNINGS $DISABLEDWARNINGS $LIBRARIES &
gcc 00_wayland_opengl_main.c -o ./build/template_opengl_wayland $RELEASE $WARNINGS $DISABLEDWARNINGS $LIBRARIES &
wait
Resources:
- SDL
- uwindow (https://github.com/lennyerik/uwindow) (this helped tremendiously find an OpenGL init-bug of mine, which lengthened init time)
- https://wayland.app/
- Focus editor (https://github.com/focus-editor/focus)
- https://lists.freedesktop.org/archives/wayland-devel/2014-May/014847.html (wl_egl_window_resize Info)
- https://bugaevc.gitbooks.io/writing-wayland-clients/content/beyond-the-black-square/cursors.html (Wayland Cursor Display)
- Weston Client example
- man pages
- https://wayland-book.com/
- probably more which I can't recall right now
There's probably a lot of room for improvement / room for error checking in the example code, but this should be sufficient to get up and running.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment