Skip to content

Instantly share code, notes, and snippets.

@poppyfanboy
Created January 18, 2025 22:34
Diamond-square Algorithm
// gui.h
//
// Linux:
// Dependencies (Ubuntu package names): libx11-dev, libxext-dev
// Compile: cc main.c -lX11 -Xext
//
// Windows:
// Dependencies: user32.lib, gdi32.lib
// Compile: cc main.c -lgdi32 -luser32
#ifndef GUI_H
#define GUI_H
#ifdef __cplusplus
extern "C" {
#endif
#ifdef GUI_STATIC_IMPLEMENTATION
#define GUI_API static
#else
#define GUI_API
#endif
#ifndef GUI_PRINT_ERROR
#define GUI_PRINT_ERROR(format, ...)
#endif
#ifndef GUI_U8_T
#define GUI_U8_T unsigned char
#define GUI_I8_T char
#define GUI_U16_T unsigned short
#define GUI_I16_T short
#define GUI_U32_T unsigned int
#define GUI_I32_T int
#define GUI_U64_T unsigned long long
#define GUI_I64_T long long
#endif
#ifndef GUI_USIZE_T
#define GUI_USIZE_T unsigned long
#define GUI_ISIZE_T long
#endif
#if !defined(__cplusplus) && \
!defined(bool) && \
(!defined(__STDC_VERSION__) || __STDC_VERSION__ < 202300L)
#define true 1
#define false 0
#define bool _Bool
#endif
#if defined(__clang__)
#define GUI_COMPILER_CLANG
#elif defined(__GNUC__)
#define GUI_COMPILER_GCC
#elif defined(_MSC_VER)
#define GUI_COMPILER_MSVC
#else
#error "Compiler is not supported."
#endif
#if defined(_WIN32) || defined(_WIN64)
#define GUI_PLATFORM_WINDOWS
#elif defined(__linux__) || defined(__gnu_linux__)
#define GUI_PLATFORM_LINUX
#else
#error "Platform is not supported."
#endif
#ifndef GUI_ALLOCATOR_T
#define GUI_USE_DEFAULT_ALLOCATOR
#if defined(GUI_PLATFORM_WINDOWS)
#define GUI_ALLOCATOR_T void *
#elif defined(GUI_PLATFORM_LINUX)
#define GUI_ALLOCATOR_T void *
#else
#error "No default allocator is provided for the current platform."
#endif
#endif
typedef GUI_ALLOCATOR_T GuiAllocator;
typedef GUI_U8_T u8;
typedef GUI_I8_T i8;
typedef GUI_U16_T u16;
typedef GUI_I16_T i16;
typedef GUI_U32_T u32;
typedef GUI_I32_T i32;
typedef GUI_U64_T u64;
typedef GUI_I64_T i64;
typedef GUI_USIZE_T usize;
typedef GUI_ISIZE_T isize;
typedef float f32;
typedef double f64;
// =================================================================================================
// Library interface
// =================================================================================================
#define GUI_WINDOW_MAX_WIDTH ((isize) 16384)
#define GUI_WINDOW_MAX_HEIGHT ((isize) 16384)
typedef struct GuiWindow GuiWindow;
GUI_API GuiWindow *gui_window_create(
GuiAllocator allocator,
isize width,
isize height,
char const *title
);
GUI_API bool gui_window_should_close(GuiWindow *window);
GUI_API bool gui_window_resized(GuiWindow *window);
GUI_API void gui_window_size(GuiWindow *window, isize *width, isize *height);
// VK_OEM_CLEAR
#define GUI_MAX_KEY 0xFE
// Same values as in Windows virtual key codes:
// https://learn.microsoft.com/en-us/windows/win32/inputdev/virtual-key-codes
typedef enum {
GUI_KEY_NONE = 0x00,
GUI_KEY_ENTER = 0x0d,
GUI_KEY_ESCAPE = 0x1b,
GUI_KEY_SPACE = 0x20,
GUI_KEY_LEFT = 0x25,
GUI_KEY_RIGHT = 0x26,
GUI_KEY_UP = 0x27,
GUI_KEY_DOWN = 0x28,
GUI_KEY_A = 0x41,
GUI_KEY_B = 0x42,
GUI_KEY_C = 0x43,
GUI_KEY_D = 0x44,
GUI_KEY_E = 0x45,
GUI_KEY_F = 0x46,
GUI_KEY_G = 0x47,
GUI_KEY_H = 0x48,
GUI_KEY_I = 0x49,
GUI_KEY_J = 0x4a,
GUI_KEY_K = 0x4b,
GUI_KEY_L = 0x4c,
GUI_KEY_M = 0x4d,
GUI_KEY_N = 0x4e,
GUI_KEY_O = 0x4f,
GUI_KEY_P = 0x50,
GUI_KEY_Q = 0x51,
GUI_KEY_R = 0x52,
GUI_KEY_S = 0x53,
GUI_KEY_T = 0x54,
GUI_KEY_U = 0x55,
GUI_KEY_V = 0x56,
GUI_KEY_W = 0x57,
GUI_KEY_X = 0x58,
GUI_KEY_Y = 0x59,
GUI_KEY_Z = 0x5a,
} GuiKeyboardKey;
GUI_API bool gui_key_down(GuiWindow *window, GuiKeyboardKey key);
GUI_API bool gui_key_up(GuiWindow *window, GuiKeyboardKey key);
typedef struct GuiBitmap GuiBitmap;
GUI_API GuiBitmap *gui_window_bitmap(GuiWindow *window);
GUI_API void gui_bitmap_size(GuiBitmap *bitmap, isize *width, isize *height);
GUI_API u32 *gui_bitmap_data(GuiBitmap *bitmap);
GUI_API void gui_bitmap_render(GuiBitmap *bitmap);
GUI_API f64 gui_window_time(GuiWindow *window);
GUI_API f64 gui_window_frame_time(GuiWindow *window);
GUI_API void gui_window_destroy(GuiWindow *window);
#ifdef __cplusplus
}
#endif
#endif // GUI_H
// =================================================================================================
// Implementation section
// =================================================================================================
#if defined(GUI_IMPLEMENTATION) && !defined(GUI_IMPLEMENTED)
#define GUI_IMPLEMENTED
#ifndef NULL
#define NULL ((void *) 0)
#endif
#ifndef ARRAY_SIZE
#define ARRAY_SIZE(array) ((isize) sizeof(array) / (isize) sizeof((array)[0]))
#endif
static bool gui__window_size_out_of_bounds(isize width, isize height) {
return
width < 0 || width > GUI_WINDOW_MAX_WIDTH ||
height < 0 || height > GUI_WINDOW_MAX_HEIGHT;
}
// =================================================================================================
// Linux (libX11) platform code
// =================================================================================================
#ifdef GUI_PLATFORM_LINUX
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/extensions/XShm.h>
#include <sys/shm.h>
#include <sys/ipc.h>
#include <time.h>
#ifdef GUI_USE_DEFAULT_ALLOCATOR
#include <stdlib.h>
#define GUI_ALLOC(allocator, size) ((void) (allocator), malloc(size))
#define GUI_DEALLOC(allocator, ptr) ((void) (allocator), free(ptr))
#endif
struct GuiBitmap {
GuiWindow *window;
XImage *image;
XShmSegmentInfo shared_segment_info;
bool available;
isize shared_buffer_size;
isize width;
isize height;
};
struct GuiWindow {
Display *x11_display;
Window x11_window;
XVisualInfo x11_visual_info;
Atom delete_window_atom;
GuiKeyboardKey keycode_to_key[256];
struct {
struct timespec created_time;
struct timespec last_update_time;
f64 last_frame_delta;
} timer;
GuiAllocator allocator;
bool should_close;
bool keys[GUI_MAX_KEY + 1];
isize width;
isize height;
bool resized;
GuiBitmap bitmaps[2];
isize active_bitmap_index;
};
static bool gui__bitmap_create(GuiWindow *window, GuiBitmap *bitmap) {
isize bitmap_width = window->width;
isize bitmap_height = window->height;
XImage *image = XShmCreateImage(
window->x11_display,
window->x11_visual_info.visual,
(unsigned int) window->x11_visual_info.depth,
ZPixmap,
NULL,
&bitmap->shared_segment_info,
(unsigned int) bitmap_width,
(unsigned int) bitmap_height
);
if (image == NULL) {
GUI_PRINT_ERROR("Failed to create an X11 image for the bitmap.\n");
return false;
}
isize shared_buffer_size = image->bytes_per_line * image->height;
int shared_memory_id = shmget(
IPC_PRIVATE,
(size_t) shared_buffer_size,
IPC_CREAT | 0600
);
if (shared_memory_id == -1) {
GUI_PRINT_ERROR("Failed to allocate shared memory for the bitmap image.\n");
return false;
}
char *image_memory = shmat(shared_memory_id, 0, 0);
image->data = image_memory;
bitmap->shared_segment_info.shmid = shared_memory_id;
bitmap->shared_segment_info.shmaddr = image_memory;
bitmap->shared_segment_info.readOnly = false;
XShmAttach(window->x11_display, &bitmap->shared_segment_info);
bitmap->window = window;
bitmap->image = image;
bitmap->available = true;
bitmap->width = bitmap_width;
bitmap->height = bitmap_height;
bitmap->shared_buffer_size = shared_buffer_size;
return true;
}
static void gui__bitmap_destroy(GuiBitmap *bitmap) {
if (bitmap->image != NULL) {
XShmDetach(bitmap->window->x11_display, &bitmap->shared_segment_info);
XDestroyImage(bitmap->image);
shmdt(bitmap->shared_segment_info.shmaddr);
shmctl(bitmap->shared_segment_info.shmid, IPC_RMID, 0);
for (isize i = 0; i < sizeof(GuiBitmap); i += 1) {
((u8 *) bitmap)[i] = 0;
}
}
}
static bool gui__bitmap_fit_to_window(GuiWindow *window, GuiBitmap *bitmap) {
isize bitmap_required_size = window->width * window->height * (isize) sizeof(u32);
if (bitmap->shared_buffer_size >= bitmap_required_size) {
int shared_memory_id = bitmap->shared_segment_info.shmid;
XShmDetach(window->x11_display, &bitmap->shared_segment_info);
XDestroyImage(bitmap->image);
XImage *image = XShmCreateImage(
window->x11_display,
window->x11_visual_info.visual,
(unsigned int) window->x11_visual_info.depth,
ZPixmap,
NULL,
&bitmap->shared_segment_info,
(unsigned int) window->width,
(unsigned int) window->height
);
if (image == NULL) {
GUI_PRINT_ERROR("Failed to create an X11 image for the bitmap.\n");
return false;
}
char *image_memory = shmat(shared_memory_id, 0, 0);
image->data = image_memory;
bitmap->shared_segment_info.shmid = shared_memory_id;
bitmap->shared_segment_info.shmaddr = image_memory;
bitmap->shared_segment_info.readOnly = false;
XShmAttach(window->x11_display, &bitmap->shared_segment_info);
bitmap->image = image;
bitmap->width = window->width;
bitmap->height = window->height;
bitmap->shared_buffer_size = (isize) image->bytes_per_line * (isize) image->height;
} else {
gui__bitmap_destroy(bitmap);
if (!gui__bitmap_create(window, bitmap)) {
return false;
}
}
return true;
}
GUI_API GuiWindow *gui_window_create(
GuiAllocator allocator,
isize width,
isize height,
char const *title
) {
if (gui__window_size_out_of_bounds(width, height)) {
GUI_PRINT_ERROR("Requested window size is out of bounds.\n");
return NULL;
}
GuiWindow *window = GUI_ALLOC(allocator, sizeof(GuiWindow));
if (window == NULL) {
GUI_PRINT_ERROR("Failed to allocate memory for the window data.\n");
return NULL;
}
Display *x11_display = XOpenDisplay(NULL);
if (x11_display == NULL) {
GUI_PRINT_ERROR("Failed to open X11 display.\n");
return NULL;
}
Window x11_root_window = DefaultRootWindow(x11_display);
int x11_screen = DefaultScreen(x11_display);
XVisualInfo x11_visual_info;
if (
!XMatchVisualInfo(x11_display, x11_screen, 24, TrueColor, &x11_visual_info) ||
x11_visual_info.red_mask != 0x00ff0000 ||
x11_visual_info.green_mask != 0x0000ff00 ||
x11_visual_info.blue_mask != 0x000000ff
) {
GUI_PRINT_ERROR("TrueColor RGB-8-8-8 is not supported.\n");
XCloseDisplay(x11_display);
return NULL;
}
XSetWindowAttributes x11_window_attributes = {
.background_pixel = BlackPixel(x11_display, x11_screen),
.colormap = XCreateColormap(
x11_display,
x11_root_window,
x11_visual_info.visual,
AllocNone
),
.bit_gravity = StaticGravity,
.event_mask = StructureNotifyMask | KeyPress | KeyRelease,
};
Window x11_window = XCreateWindow(
x11_display,
x11_root_window,
0,
0,
(unsigned int) width,
(unsigned int) height,
0,
x11_visual_info.depth,
InputOutput,
x11_visual_info.visual,
CWBackPixel | CWColormap | CWBitGravity | CWEventMask,
&x11_window_attributes
);
if (x11_window == None) {
GUI_PRINT_ERROR("Failed to create an X11 window.\n");
XCloseDisplay(x11_display);
return NULL;
}
XStoreName(x11_display, x11_window, title);
XMapWindow(x11_display, x11_window);
XFlush(x11_display);
// https://handmade.network/forums/articles/t/2834-tutorial_a_tour_through_xlib_and_related_technologies#part_4_-_even_better_shutdown
//
// Ask for a ClientMessage event to get sent when the window is getting closed, so that we could
// handle destroying the window ourselves.
Atom delete_window_atom = XInternAtom(x11_display, "WM_DELETE_WINDOW", False);
XSetWMProtocols(x11_display, x11_window, &delete_window_atom, 1);
window->x11_display = x11_display;
window->x11_window = x11_window;
window->x11_visual_info = x11_visual_info;
window->delete_window_atom = delete_window_atom;
window->allocator = allocator;
window->should_close = false;
window->width = width;
window->height = height;
window->resized = false;
for (isize i = 0; i < ARRAY_SIZE(window->keys); i += 1) {
window->keys[i] = false;
}
for (isize i = 0; i < 256; i += 1) {
window->keycode_to_key[i] = GUI_KEY_NONE;
}
struct {
KeySym keysym;
GuiKeyboardKey key;
} keysym_to_key[] = {
{XK_space, GUI_KEY_SPACE },
{XK_a, GUI_KEY_A },
{XK_b, GUI_KEY_B },
{XK_c, GUI_KEY_C },
{XK_d, GUI_KEY_D },
{XK_e, GUI_KEY_E },
{XK_f, GUI_KEY_F },
{XK_g, GUI_KEY_G },
{XK_h, GUI_KEY_H },
{XK_i, GUI_KEY_I },
{XK_j, GUI_KEY_J },
{XK_k, GUI_KEY_K },
{XK_l, GUI_KEY_L },
{XK_m, GUI_KEY_M },
{XK_n, GUI_KEY_N },
{XK_o, GUI_KEY_O },
{XK_p, GUI_KEY_P },
{XK_q, GUI_KEY_Q },
{XK_r, GUI_KEY_R },
{XK_s, GUI_KEY_S },
{XK_t, GUI_KEY_T },
{XK_u, GUI_KEY_U },
{XK_v, GUI_KEY_V },
{XK_w, GUI_KEY_W },
{XK_x, GUI_KEY_X },
{XK_y, GUI_KEY_Y },
{XK_z, GUI_KEY_Z },
{XK_Escape, GUI_KEY_ESCAPE },
{XK_Return, GUI_KEY_ENTER },
{XK_Right, GUI_KEY_RIGHT },
{XK_Left, GUI_KEY_LEFT },
{XK_Down, GUI_KEY_DOWN },
{XK_Up, GUI_KEY_UP },
};
for (isize i = 0; i < ARRAY_SIZE(keysym_to_key); i += 1) {
KeySym keysym = keysym_to_key[i].keysym;
GuiKeyboardKey key = keysym_to_key[i].key;
KeyCode keycode = XKeysymToKeycode(x11_display, keysym);
window->keycode_to_key[keycode] = key;
}
for (isize i = 0; i < ARRAY_SIZE(window->bitmaps); i += 1) {
window->bitmaps[i].window = window;
window->bitmaps[i].image = NULL;
window->bitmaps[i].available = true;
window->bitmaps[i].shared_segment_info.readOnly = false;
window->bitmaps[i].shared_segment_info.shmaddr = NULL;
window->bitmaps[i].shared_segment_info.shmid = 0;
window->bitmaps[i].shared_segment_info.shmseg = 0;
window->bitmaps[i].shared_buffer_size = 0;
window->bitmaps[i].width = 0;
window->bitmaps[i].height = 0;
}
window->active_bitmap_index = 0;
struct timespec created_time;
clock_gettime(CLOCK_MONOTONIC, &created_time);
window->timer.created_time = created_time;
window->timer.last_update_time = created_time;
window->timer.last_frame_delta = 0.0;
return window;
}
static void gui__window_process_events(GuiWindow *window) {
while (XPending(window->x11_display) > 0) {
XEvent event;
XNextEvent(window->x11_display, &event);
if (event.type == ConfigureNotify) {
window->width = event.xconfigure.width;
window->height = event.xconfigure.height;
window->resized = true;
} else if (event.type == KeyPress) {
window->keys[window->keycode_to_key[event.xkey.keycode]] = true;
} else if (event.type == KeyRelease) {
window->keys[window->keycode_to_key[event.xkey.keycode]] = false;
} else if (event.type == DestroyNotify) {
if (event.xdestroywindow.window == window->x11_window) {
window->should_close = true;
}
} else if (event.type == ClientMessage) {
if (event.xclient.data.l[0] == window->delete_window_atom) {
XDestroyWindow(window->x11_display, window->x11_window);
window->should_close = true;
}
} else if (event.type == XShmGetEventBase(window->x11_display) + ShmCompletion) {
XShmCompletionEvent *completion_event = (XShmCompletionEvent *) &event;
for (isize i = 0; i < ARRAY_SIZE(window->bitmaps); i += 1) {
if (window->bitmaps[i].shared_segment_info.shmseg == completion_event->shmseg) {
window->bitmaps[i].available = true;
break;
}
}
}
}
}
GUI_API bool gui_window_should_close(GuiWindow *window) {
if (window == NULL) {
return true;
}
struct timespec current_time;
clock_gettime(CLOCK_MONOTONIC, &current_time);
window->timer.last_frame_delta =
(f64) (current_time.tv_sec - window->timer.created_time.tv_sec) +
(f64) (current_time.tv_nsec - window->timer.created_time.tv_nsec) / 1e9;
window->timer.last_update_time = current_time;
window->resized = false;
gui__window_process_events(window);
return window->should_close;
}
GUI_API bool gui_window_resized(GuiWindow *window) {
return window->resized;
}
GUI_API void gui_window_size(GuiWindow *window, isize *width, isize *height) {
*width = window->width;
*height = window->height;
}
GUI_API bool gui_key_down(GuiWindow *window, GuiKeyboardKey key) {
return window->keys[key];
}
GUI_API bool gui_key_up(GuiWindow *window, GuiKeyboardKey key) {
return !window->keys[key];
}
GUI_API f64 gui_window_time(GuiWindow *window) {
struct timespec current_time;
clock_gettime(CLOCK_MONOTONIC, &current_time);
return
(f64) (current_time.tv_sec - window->timer.created_time.tv_sec) +
(f64) (current_time.tv_nsec - window->timer.created_time.tv_nsec) / 1e9;
}
GUI_API f64 gui_window_frame_time(GuiWindow *window) {
return window->timer.last_frame_delta;
}
GUI_API void gui_window_destroy(GuiWindow *window) {
if (window != NULL) {
for (isize i = 0; i < ARRAY_SIZE(window->bitmaps); i += 1) {
gui__bitmap_destroy(&window->bitmaps[i]);
}
XCloseDisplay(window->x11_display);
GuiAllocator allocator = window->allocator;
GUI_DEALLOC(allocator, window);
}
}
GUI_API GuiBitmap *gui_window_bitmap(GuiWindow *window) {
while (!window->bitmaps[window->active_bitmap_index].available) {
gui__window_process_events(window);
}
GuiBitmap *bitmap = &window->bitmaps[window->active_bitmap_index];
window->active_bitmap_index = (window->active_bitmap_index + 1) % ARRAY_SIZE(window->bitmaps);
if (bitmap->width != window->width || bitmap->height != window->height) {
if (!gui__bitmap_fit_to_window(window, bitmap)) {
return NULL;
}
}
return bitmap;
}
GUI_API void gui_bitmap_size(GuiBitmap *bitmap, isize *width, isize *height) {
*width = bitmap->width;
*height = bitmap->height;
}
GUI_API void gui_bitmap_render(GuiBitmap *bitmap) {
bitmap->available = false;
XShmPutImage(
bitmap->window->x11_display,
bitmap->window->x11_window,
DefaultGC(bitmap->window->x11_display, bitmap->window->x11_visual_info.screen),
bitmap->image,
0,
0,
0,
0,
(unsigned int) bitmap->width,
(unsigned int) bitmap->height,
true
);
XFlush(bitmap->window->x11_display);
}
GUI_API u32 *gui_bitmap_data(GuiBitmap *bitmap) {
return (u32 *) bitmap->shared_segment_info.shmaddr;
}
#endif // GUI_PLATFORM_LINUX
#ifdef GUI_PLATFORM_WINDOWS
#define WIN32_LEAN_AND_MEAN
#define NOMINMAX
#include <Windows.h>
// =================================================================================================
// Atomic operations implementation
// =================================================================================================
// I took these from here:
// https://github.com/f1nalspace/final_game_tech/blob/master/final_platform_layer.h
#if defined(GUI_COMPILER_GCC) || defined(GUI_COMPILER_CLANG)
static u32 gui__u32_atomic_load(volatile u32 *source) {
u32 result = __sync_add_and_fetch(source, 0);
return result;
}
static void gui__u32_atomic_store(volatile u32 *dest, u32 value) {
__sync_synchronize();
__sync_lock_test_and_set(dest, value);
}
static u32 gui__u32_atomic_exchange(volatile u32 *target, u32 value) {
__sync_synchronize();
u32 result = __sync_lock_test_and_set(target, value);
return result;
}
#elif defined(GUI_COMPILER_MSVC)
static u32 gui__u32_atomic_load(volatile u32 *source) {
u32 result = (u32) InterlockedCompareExchange((volatile LONG *) source, 0, 0);
return result;
}
static void gui__u32_atomic_store(volatile u32 *dest, u32 value) {
InterlockedExchange((volatile LONG *) dest, value);
}
static u32 gui__u32_atomic_exchange(volatile u32 *target, u32 value) {
u32 result = (u32) InterlockedExchange((volatile LONG *) target, value);
return result;
}
#endif
// =================================================================================================
// Windows platform code
// =================================================================================================
#ifdef GUI_USE_DEFAULT_ALLOCATOR
#define GUI_ALLOC(allocator, size) \
((void) (allocator), HeapAlloc(GetProcessHeap(), 0, (SIZE_T) (size)))
#define GUI_DEALLOC(allocator, ptr) \
((void) (allocator), HeapFree(GetProcessHeap(), 0, ptr))
#endif
#define GUI_WINDOW_CREATE_MESSAGE WM_USER
#define GUI_WINDOW_CLASS_NAME L"GUI_WINDOW_CLASS_NAME"
struct GuiBitmap {
GuiWindow *window;
HBITMAP handle;
HDC device_context;
u32 *data;
isize width;
isize height;
};
struct GuiWindow {
HWND window_handle;
HDC window_device_context;
struct {
LARGE_INTEGER frequency;
LARGE_INTEGER created_time;
LARGE_INTEGER last_update_time;
f64 last_frame_delta;
} timer;
GuiAllocator allocator;
volatile u32 should_close;
volatile u32 keys[GUI_MAX_KEY + 1];
volatile u32 width;
volatile u32 height;
volatile u32 resized;
GuiBitmap bitmap;
};
static LRESULT CALLBACK gui__window_procedure(
HWND window_handle,
UINT message_type,
WPARAM w_param,
LPARAM l_param
) {
GuiWindow *window = NULL;
if (window_handle != NULL) {
window = (GuiWindow *) GetWindowLongPtrW(window_handle, GWLP_USERDATA);
}
if (window != NULL) {
switch (message_type) {
case WM_DESTROY: {
gui__u32_atomic_store(&window->should_close, true);
} break;
case WM_SIZE: {
u32 new_width = LOWORD(l_param);
gui__u32_atomic_store(&window->width, new_width);
u32 new_height = HIWORD(l_param);
gui__u32_atomic_store(&window->height, new_height);
gui__u32_atomic_store(&window->resized, true);
} break;
case WM_KEYDOWN: {
gui__u32_atomic_store(&window->keys[w_param], true);
} break;
case WM_KEYUP: {
gui__u32_atomic_store(&window->keys[w_param], false);
} break;
}
}
return DefWindowProcW(window_handle, message_type, w_param, l_param);
}
typedef struct {
GuiWindow *window;
WCHAR *title;
isize width;
isize height;
HANDLE created_event;
} GuiWindowCreateMessageData;
static DWORD gui__event_loop_procedure(LPVOID param) {
// Call a function from user32 (PeekMessageW in this case) to set up a message queue.
// https://stackoverflow.com/questions/22428066/postthreadmessage-doesnt-work#comment34127566_22433275
{
MSG message;
PeekMessageW(&message, NULL, 0, 0, PM_NOREMOVE);
HANDLE *event_loop_created_event = (HANDLE *) param;
SetEvent(*event_loop_created_event);
}
MSG message;
while (GetMessageW(&message, NULL, 0, 0) > 0) {
switch (message.message) {
case GUI_WINDOW_CREATE_MESSAGE: {
GuiWindowCreateMessageData *message_data =
(GuiWindowCreateMessageData *) message.lParam;
HWND window_handle = CreateWindowExW(
0,
GUI_WINDOW_CLASS_NAME,
message_data->title,
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT,
CW_USEDEFAULT,
message_data->width,
message_data->height,
NULL,
NULL,
GetModuleHandleW(NULL),
NULL
);
if (window_handle != NULL) {
SetWindowLongPtrW(window_handle, GWLP_USERDATA, (LONG_PTR) message_data->window);
ShowWindow(window_handle, SW_SHOWNORMAL);
}
message_data->window->window_handle = window_handle;
SetEvent(message_data->created_event);
} break;
default: {
TranslateMessage(&message);
DispatchMessageW(&message);
} break;
}
}
return 0;
}
GUI_API GuiWindow *gui_window_create(
GuiAllocator allocator,
isize width,
isize height,
char const *title
) {
if (gui__window_size_out_of_bounds(width, height)) {
GUI_PRINT_ERROR("Requested window size is out of bounds.\n");
return NULL;
}
WNDCLASSW window_class_data = {
.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC,
.lpfnWndProc = gui__window_procedure,
.hInstance = GetModuleHandleW(NULL),
.hCursor = LoadCursor(NULL, IDC_ARROW),
.lpszClassName = GUI_WINDOW_CLASS_NAME,
};
ATOM window_class = RegisterClassW(&window_class_data);
if (window_class == 0 && GetLastError() != ERROR_CLASS_ALREADY_EXISTS) {
GUI_PRINT_ERROR("Failed to create a window class.\n");
return NULL;
}
// Create a separate thread which would be responsible for creating windows and handling their
// events. If we were to handle messages on the same thread where we render frames, message
// processing during resizing would interrupt rendering new frames until resizing is finished.
//
// https://github.com/cmuratori/dtc
static HANDLE event_loop_thread_handle = NULL;
static DWORD event_loop_thread_id = 0;
if (event_loop_thread_handle == NULL) {
HANDLE event_loop_created_event = CreateEventW(NULL, true, false, NULL);
event_loop_thread_handle = CreateThread(
NULL,
0,
gui__event_loop_procedure,
&event_loop_created_event,
0,
&event_loop_thread_id
);
if (event_loop_thread_handle == NULL) {
GUI_PRINT_ERROR("Failed to create a thread for the window event loop.\n");
return NULL;
}
if (WaitForSingleObject(event_loop_created_event, 5000) != WAIT_OBJECT_0) {
GUI_PRINT_ERROR("Failed to create en event loop for the window.\n");
return NULL;
}
}
GuiWindow *window = (GuiWindow *) GUI_ALLOC(allocator, sizeof(GuiWindow));
if (window == NULL) {
GUI_PRINT_ERROR("Failed to allocate memory for the window data.\n");
return NULL;
}
window->allocator = allocator;
window->should_close = false;
window->width = (u32) width;
window->height = (u32) height;
window->resized = false;
for (isize i = 0; i < ARRAY_SIZE(window->keys); i += 1) {
window->keys[i] = false;
}
WCHAR wide_title_stack_buffer[256];
int wide_title_size = MultiByteToWideChar(CP_UTF8, 0, title, -1, NULL, 0);
WCHAR *wide_title = NULL;
if (wide_title_size <= ARRAY_SIZE(wide_title_stack_buffer)) {
wide_title = wide_title_stack_buffer;
} else {
wide_title = (WCHAR *) GUI_ALLOC(allocator, wide_title_size * (isize) sizeof(WCHAR));
}
if (wide_title == NULL) {
wide_title = wide_title_stack_buffer;
wide_title_size = ARRAY_SIZE(wide_title_stack_buffer);
}
if (MultiByteToWideChar(CP_UTF8, 0, title, -1, wide_title, wide_title_size) == 0) {
*wide_title = 0;
}
GuiWindowCreateMessageData window_create_message_data = {
.window = window,
.title = wide_title,
.width = width,
.height = height,
.created_event = CreateEventW(NULL, true, false, NULL),
};
if (!PostThreadMessageW(
event_loop_thread_id,
GUI_WINDOW_CREATE_MESSAGE,
0,
(LPARAM) &window_create_message_data
)) {
GUI_PRINT_ERROR("Failed to send a message to the event loop thread.\n");
return NULL;
}
if (WaitForSingleObject(window_create_message_data.created_event, 5000) != WAIT_OBJECT_0) {
GUI_PRINT_ERROR("Failed to create a window.\n");
return NULL;
}
if (wide_title != wide_title_stack_buffer) {
GUI_DEALLOC(allocator, wide_title);
}
if (window->window_handle == NULL) {
GUI_PRINT_ERROR("Failed to create a window.\n");
return NULL;
}
window->window_device_context = GetDC(window->window_handle);
BITMAPINFO bitmap_info = {
.bmiHeader = {
.biSize = sizeof(BITMAPINFOHEADER),
.biWidth = width,
.biHeight = -height,
.biPlanes = 1,
.biBitCount = 32,
.biCompression = BI_RGB,
},
};
window->bitmap.handle = CreateDIBSection(
window->window_device_context,
&bitmap_info,
DIB_RGB_COLORS,
(void **) &window->bitmap.data,
NULL,
0
);
window->bitmap.device_context = CreateCompatibleDC(window->window_device_context);
HBITMAP default_bitmap = (HBITMAP) SelectObject(
window->bitmap.device_context,
window->bitmap.handle
);
DeleteObject(default_bitmap);
window->bitmap.width = width;
window->bitmap.height = height;
window->bitmap.window = window;
QueryPerformanceFrequency(&window->timer.frequency);
LARGE_INTEGER created_time;
QueryPerformanceCounter(&created_time);
window->timer.created_time = created_time;
window->timer.last_update_time = created_time;
window->timer.last_frame_delta = 0.0;
return window;
}
GUI_API bool gui_window_should_close(GuiWindow *window) {
if (window == NULL) {
return true;
}
LARGE_INTEGER current_time;
QueryPerformanceCounter(&current_time);
LONGLONG elapsed_ticks = current_time.QuadPart - window->timer.last_update_time.QuadPart;
window->timer.last_frame_delta =
(f64) (elapsed_ticks * 1000000 / window->timer.frequency.QuadPart) / 1e6;
window->timer.last_update_time = current_time;
return gui__u32_atomic_load(&window->should_close) != false;
}
GUI_API bool gui_window_resized(GuiWindow *window) {
return gui__u32_atomic_exchange(&window->resized, false) != false;
}
GUI_API void gui_window_size(GuiWindow *window, isize *width, isize *height) {
*width = (isize) gui__u32_atomic_load(&window->width);
*height = (isize) gui__u32_atomic_load(&window->height);
}
GUI_API bool gui_key_down(GuiWindow *window, GuiKeyboardKey key) {
return gui__u32_atomic_load(&window->keys[key]) != false;
}
GUI_API bool gui_key_up(GuiWindow *window, GuiKeyboardKey key) {
return gui__u32_atomic_load(&window->keys[key]) == false;
}
GUI_API f64 gui_window_time(GuiWindow *window) {
LARGE_INTEGER current_time;
QueryPerformanceCounter(&current_time);
LONGLONG elapsed_ticks = current_time.QuadPart - window->timer.created_time.QuadPart;
return (f64) (elapsed_ticks * 1000000 / window->timer.frequency.QuadPart) / 1e6;
}
GUI_API f64 gui_window_frame_time(GuiWindow *window) {
return window->timer.last_frame_delta;
}
GUI_API void gui_window_destroy(GuiWindow *window) {
if (window != NULL) {
DeleteDC(window->bitmap.device_context);
DeleteObject(window->bitmap.data);
DeleteObject(window->bitmap.handle);
// https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-destroywindow
// > A thread cannot use DestroyWindow to destroy a window created by a different thread.
PostMessageW(window->window_handle, WM_CLOSE, 0, 0);
GuiAllocator allocator = window->allocator;
GUI_DEALLOC(allocator, window);
}
}
GUI_API GuiBitmap *gui_window_bitmap(GuiWindow *window) {
GuiBitmap *bitmap = &window->bitmap;
isize window_width, window_height;
gui_window_size(window, &window_width, &window_height);
if (bitmap->width != window_width || bitmap->height != window_height) {
BITMAPINFO bitmap_info = {
.bmiHeader = {
.biSize = sizeof(BITMAPINFOHEADER),
.biWidth = window_width,
.biHeight = -window_height,
.biPlanes = 1,
.biBitCount = 32,
.biCompression = BI_RGB,
},
};
HBITMAP new_bitmap_handle = CreateDIBSection(
window->window_device_context,
&bitmap_info,
DIB_RGB_COLORS,
(void **) &bitmap->data,
NULL,
0
);
HDC new_bitmap_device_context = CreateCompatibleDC(window->window_device_context);
HBITMAP default_bitmap = (HBITMAP) SelectObject(new_bitmap_device_context, new_bitmap_handle);
DeleteObject(default_bitmap);
DeleteDC(bitmap->device_context);
DeleteObject(bitmap->data);
bitmap->device_context = new_bitmap_device_context;
bitmap->handle = new_bitmap_handle;
bitmap->width = window_width;
bitmap->height = window_height;
}
return bitmap;
}
GUI_API void gui_bitmap_size(GuiBitmap *bitmap, isize *width, isize *height) {
*width = bitmap->width;
*height = bitmap->height;
}
GUI_API void gui_bitmap_render(GuiBitmap *bitmap) {
BitBlt(
bitmap->window->window_device_context,
0,
0,
bitmap->width,
bitmap->height,
bitmap->device_context,
0,
0,
SRCCOPY
);
}
GUI_API u32 *gui_bitmap_data(GuiBitmap *bitmap) {
return bitmap->data;
}
#endif // GUI_PLATFORM_WINDOWS
#endif // GUI_IMPLEMENTATION
// =================================================================================================
// PCG source code
// https://www.pcg-random.org/download.html#minimal-c-implementation
// https://github.com/imneme/pcg-c-basic/blob/master/pcg_basic.c
// =================================================================================================
#include <stdint.h>
// *Really* minimal PCG32 code / (c) 2014 M.E. O'Neill / pcg-random.org
// Licensed under Apache License 2.0 (NO WARRANTY, etc. see website)
typedef struct { uint64_t state; uint64_t inc; } pcg32_random_t;
uint32_t pcg32_random_r(pcg32_random_t* rng)
{
uint64_t oldstate = rng->state;
// Advance internal state
rng->state = oldstate * 6364136223846793005ULL + (rng->inc|1);
// Calculate output function (XSH RR), uses old state for max ILP
uint32_t xorshifted = ((oldstate >> 18u) ^ oldstate) >> 27u;
uint32_t rot = oldstate >> 59u;
return (xorshifted >> rot) | (xorshifted << ((-rot) & 31));
}
void pcg32_srandom_r(pcg32_random_t* rng, uint64_t initstate, uint64_t initseq)
{
rng->state = 0U;
rng->inc = (initseq << 1u) | 1u;
pcg32_random_r(rng);
rng->state += initstate;
pcg32_random_r(rng);
}
// =================================================================================================
// Diamond-square algorithm plasma visualization
//
// https://en.wikipedia.org/wiki/Diamond-square_algorithm
// https://learn.64bitdragon.com/articles/computer-science/procedural-generation/the-diamond-square-algorithm
// https://lodev.org/cgtutor/plasma.html
// =================================================================================================
#define GUI_IMPLEMENTATION
#include "gui.h"
#include <math.h>
#include <stdlib.h>
f64 f64_random(pcg32_random_t *rng, f64 from, f64 to) {
// https://www.pcg-random.org/using-pcg-c-basic.html#generating-doubles
// > If you are happy to have a floating point value in the range [0,1) that has been rounded
// > down to the nearest multiple of 1/2^32
f64 random_float = ldexp(pcg32_random_r(rng), -32);
return from + (to - from) * random_float;
}
f64 f64_min(f64 self, f64 other) {
return self < other ? self : other;
}
f64 f64_max(f64 self, f64 other) {
return self > other ? self : other;
}
// Based on this:
// https://www.rapidtables.com/convert/color/hsv-to-rgb.html
u32 hsv_to_rgb(u8 hue, u8 saturation, u8 value) {
i32 hue_degrees = (i32) (360.0F * ((f32) hue / 255.0F)) % 360;
f32 saturation_norm = (f32) saturation / 255.0F;
f32 value_norm = (f32) value / 255.0F;
f32 c = value_norm * saturation_norm;
f32 x = c * (1.0F - fabsf(fmodf((f32) hue_degrees / 60.0F, 2.0F) - 1.0F));
f32 red, green, blue;
switch (hue_degrees / 60) {
case 0: red = c; green = x; blue = 0.0F; break;
case 1: red = x; green = c; blue = 0.0F; break;
case 2: red = 0.0F; green = c; blue = x; break;
case 3: red = 0.0F; green = x; blue = c; break;
case 4: red = x; green = 0.0F; blue = c; break;
case 5: red = c; green = 0.0F; blue = x; break;
}
f32 m = value_norm - c;
return
((u32) (u8) ((red + m) * 255.0F) << 16) +
((u32) (u8) ((green + m) * 255.0F) << 8 ) +
((u32) (u8) ((blue + m) * 255.0F) << 0 );
}
int main(void) {
pcg32_random_t rng;
pcg32_srandom_r(&rng, 0xcafe, 0xbabe);
// Outline of the diamond-square algorithm:
// 1. Set the corner pixels of the bitmap (which is a square).
// 2. Set the pixel in the middle of the square ("diamond" step).
// 3. Set the pixels in the middle of each side of the square ("square" step).
// 4. Subdivide the square into 4 more squares, repeat the steps 2 and 3 for each of them.
// Bitmap size is set to a "power of two minus one", so that there is a pixel right in the
// middle.
isize noise_size = 1025;
f64 *noise = (f64 *) malloc((size_t) (noise_size * noise_size * (isize) sizeof(f64)));
for (isize i = 0; i < noise_size * noise_size; i += 1) {
noise[i] = 0.0;
}
f64 random_value_scale = 256.0;
f64 random_factor = 2.0;
f64 corner_value = f64_random(&rng, 0.0, random_value_scale * random_factor);
noise[0 * noise_size + 0] = corner_value;
noise[0 * noise_size + (noise_size - 1)] = corner_value;
noise[(noise_size - 1) * noise_size + 0] = corner_value;
noise[(noise_size - 1) * noise_size + (noise_size - 1)] = corner_value;
isize tile = noise_size - 1;
while (tile > 1) {
isize half_tile = tile / 2;
// The diamond step.
//
// x and y iterate over coordinates of the "squares" (their top-left corners):
// - [0,0]
// - [0,2]
// - [2,0]
// - [2,2]
//
// Unkown pixels are in the middle of each square and we calculate their values based on the
// known pixels in the corners of the square.
//
// For example:
// bitmap[1,1] = (bitmap[0,0] + bitmap[0,2] + bitmap[2,0] + bitmap[2,2]) / 4 + random
//
// 0 1 2 3 4
// +-----------+
// 0 | X X X X X |
// | \ / \ / |
// 1 | X ? X ? X |
// | / \ / \ |
// 2 | X X X X X |
// | \ / \ / |
// 3 | X ? X ? X |
// | / \ / \ |
// 4 | X X X X X |
// +-----------+
for (isize x = 0; x < noise_size - 1; x += tile) {
for (isize y = 0; y < noise_size - 1; y += tile) {
f64 average =
noise[y * noise_size + x] +
noise[y * noise_size + (x + tile)] +
noise[(y + tile) * noise_size + x] +
noise[(y + tile) * noise_size + (x + tile)];
average /= 4.0;
f64 noise_value =
average +
f64_random(&rng, -random_value_scale, random_value_scale);
noise[(y + half_tile) * noise_size + (x + half_tile)] = noise_value;
}
}
// The square step.
//
// Here x and y iterate over the unkown pixels themselves:
// - [0,1] and [4,1] is set to the same value
// - [0,3] and [4,3] is set to the same value
// - [1,0] and [1,4] is set to the same value
// - [1,2]
// - [2,1]
// - [2,3]
// - [3,0] and [3,4] is set to the same value
// - [3,2]
//
// We calculate the unknown pixels based on the adjacent pixels to the left/right and at the
// top/bottom relative to the unknown pixel.
//
// For example:
// bitmap[2,1] = (bitmap[1,1] + bitmap[3,1] + bitmap[2,0] + bitmap[2,2]) / 4 + random
//
// Another example (boundary pixel):
// bitmap[0,1] = (bitmap[3,1] + bitmap[1,1] + bitmap[0,0] + bitmap[0,2]) / 4 + random
//
// 0 1 2 3 4
// +-------------------+
// 0 | X - ? - X - ? - X |
// | | | | | | |
// 1 | ? - X - ? - X - ? |
// | | | | | | |
// 2 | X - ? - X - ? - X |
// | | | | | | |
// 3 | ? - X - ? - X - ? |
// | | | | | | |
// 4 | X - ? - X - ? - X |
// +-------------------+
for (isize x = 0; x < noise_size - 1; x += half_tile) {
for (isize y = (x + half_tile) % tile; y < noise_size - 1; y += tile) {
f64 average =
noise[y * noise_size + (x - half_tile + noise_size - 1) % (noise_size - 1)] +
noise[y * noise_size + (x + half_tile) % (noise_size - 1)] +
noise[((y + half_tile) % (noise_size - 1)) * noise_size + x] +
noise[((y - half_tile + noise_size - 1) % (noise_size - 1)) * noise_size + x];
average /= 4.0;
f64 noise_value =
average +
f64_random(&rng, -random_value_scale, random_value_scale);
noise[y * noise_size + x] = noise_value;
if (x == 0) {
noise[y * noise_size + (noise_size - 1)] = noise_value;
}
if (y == 0) {
noise[(noise_size - 1) * noise_size + x] = noise_value;
}
}
}
random_value_scale = f64_max(random_value_scale / random_factor, 1.0 / random_factor);
tile /= 2;
}
f64 noise_min = INFINITY;
f64 noise_max = -INFINITY;
for (isize i = 0; i < noise_size * noise_size; i += 1) {
noise_min = f64_min(noise[i], noise_min);
noise_max = f64_max(noise[i], noise_max);
}
for (isize i = 0; i < noise_size * noise_size; i += 1) {
noise[i] = (noise[i] - noise_min) / (noise_max - noise_min);
}
u32 palette[256];
for (isize hue = 0; hue < 256; hue += 1) {
palette[hue] = hsv_to_rgb((u8) hue, 175, 240);
}
GuiWindow *window = gui_window_create(NULL, 640, 480, "Diamond-square Algorithm");
while (!gui_window_should_close(window)) {
if (gui_key_down(window, GUI_KEY_Q)) {
break;
}
double time = gui_window_time(window);
GuiBitmap *bitmap = gui_window_bitmap(window);
isize bitmap_width, bitmap_height;
gui_bitmap_size(bitmap, &bitmap_width, &bitmap_height);
u32 *bitmap_iter = gui_bitmap_data(bitmap);;
for (isize y = 0; y < bitmap_height; y += 1) {
for (isize x = 0; x < bitmap_width; x += 1) {
f64 noise_value =
noise[(y % (noise_size - 1)) * noise_size + (x % (noise_size - 1))];
*bitmap_iter = palette[(isize) (255.0 * noise_value + 64.0 * time) % 256];
bitmap_iter += 1;
}
}
gui_bitmap_render(bitmap);
}
gui_window_destroy(window);
free(noise);
return 0;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment