summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorAndrew Opalach <andrew@akon.city> 2023-11-10 14:45:42 -0500
committerAndrew Opalach <andrew@akon.city> 2023-11-10 14:45:42 -0500
commit08b14e82c9dbc8a7f46480ca585b972549a2063c (patch)
tree41030a91ad2f9aeb7539c50ded0d97b79e591ed8 /src
downloadstela-08b14e82c9dbc8a7f46480ca585b972549a2063c.tar.gz
stela-08b14e82c9dbc8a7f46480ca585b972549a2063c.tar.bz2
stela-08b14e82c9dbc8a7f46480ca585b972549a2063c.zip
Add sekihi
- Subprojects temporarily omitted Signed-off-by: Andrew Opalach <andrew@akon.city>
Diffstat (limited to 'src')
-rw-r--r--src/egl_common.c195
-rw-r--r--src/egl_common.h38
-rw-r--r--src/gl_versions.h62
-rw-r--r--src/sekihi.c22
-rw-r--r--src/window.c178
-rw-r--r--src/window.h117
-rw-r--r--src/window_glfw.c362
-rw-r--r--src/window_glfw.h25
-rw-r--r--src/window_wayland.c718
-rw-r--r--src/window_wayland.h51
10 files changed, 1768 insertions, 0 deletions
diff --git a/src/egl_common.c b/src/egl_common.c
new file mode 100644
index 0000000..eeaf501
--- /dev/null
+++ b/src/egl_common.c
@@ -0,0 +1,195 @@
+#include <al/lib.h>
+#include <al/log.h>
+
+#include "egl_common.h"
+
+#define SEKIHI_GL_API EGL_OPENGL_API
+#define SEKIHI_GLES_API EGL_OPENGL_ES_API
+
+#define SEKIHI_GL_BIT EGL_OPENGL_BIT
+#define SEKIHI_GLES2_BIT EGL_OPENGL_ES2_BIT
+#define SEKIHI_GLES3_BIT EGL_OPENGL_ES3_BIT
+
+#define SEKIHI_GL_CORE_PROFILE_BIT EGL_CONTEXT_OPENGL_CORE_PROFILE_BIT
+
+#include "gl_versions.h"
+
+PASTE_GL_VERSIONS()
+
+// https://gitlab.freedesktop.org/mesa/kmscube/-/blob/master/common.c#L162
+static s32 match_config_to_visual(EGLDisplay display, EGLint visual_id, EGLConfig *configs, s32 count)
+{
+ EGLint id;
+ for (s32 i = 0; i < count; i++) {
+ if (!eglGetConfigAttrib(display, configs[i], EGL_NATIVE_VISUAL_ID, &id)) {
+ continue;
+ }
+ if (id == visual_id) return i;
+ }
+ return -1;
+}
+
+static bool egl_choose_config(EGLDisplay display, const EGLint *attribs, EGLint visual_id, EGLConfig *config)
+{
+ EGLint count = 0;
+ EGLint matched = 0;
+ EGLConfig *configs;
+ s32 index = -1;
+
+ if (!eglGetConfigs(display, NULL, 0, &count) || count <= 0) {
+ al_log_error("egl", "No configs to choose from.");
+ return false;
+ }
+
+ configs = (EGLConfig *)al_malloc(count * sizeof(*configs));
+
+ if (!eglChooseConfig(display, attribs, configs, count, &matched) || !matched) {
+ al_log_error("egl", "No configs with appropriate attributes.");
+ goto out;
+ }
+
+ if (!visual_id) index = 0;
+
+ if (index == -1) {
+ index = match_config_to_visual(display, visual_id, configs, matched);
+ }
+
+ if (index != -1) *config = configs[index];
+
+out:
+ al_free(configs);
+
+ return index != -1;
+}
+
+bool skh_load_egl(struct skh_egl *egl)
+{
+ (void)egl;
+#ifdef SEKIHI_USE_GLAD
+ s32 egl_version = gladLoaderLoadEGL(NULL);
+ if (!egl_version) {
+ al_log_error("egl", "Failed to pre-load EGL (glad).");
+ return false;
+ }
+#endif
+ return true;
+}
+
+bool skh_init_egl(struct skh_egl *egl, EGLint visual_id, EGLConfig *config)
+{
+ s32 minor, major;
+ if (!eglInitialize(egl->display, &major, &minor)) {
+ al_log_error("egl", "Failed to initialize.");
+ return false;
+ }
+
+#ifdef SEKIHI_USE_GLAD
+ s32 egl_version = gladLoaderLoadEGL(egl->display);
+ if (!egl_version) {
+ al_log_error("egl", "Failed to load EGL (glad).");
+ return false;
+ }
+#endif
+
+ al_log_info("egl", "Initialized version %i.%i.", major, minor);
+
+ egl->context = EGL_NO_CONTEXT;
+
+ for (u32 i = 0; i < AL_ARRAY_SIZE(skh_gl_vers); i++) {
+ const EGLint context_attr_gles[] = {
+ EGL_CONTEXT_CLIENT_VERSION, skh_gl_vers[i].major,
+ EGL_NONE
+ };
+
+ const EGLint context_attr_gl[] = {
+ EGL_CONTEXT_MAJOR_VERSION, skh_gl_vers[i].major,
+ EGL_CONTEXT_MINOR_VERSION, skh_gl_vers[i].minor,
+ EGL_CONTEXT_OPENGL_PROFILE_MASK, skh_gl_vers[i].profile,
+ EGL_NONE
+ };
+
+ const EGLint *context_attr = (skh_gl_vers[i].api == SEKIHI_GL_API) ?
+ context_attr_gl : context_attr_gles;
+
+ eglBindAPI(skh_gl_vers[i].api);
+
+ const EGLint window_attr[] = {
+ EGL_SURFACE_TYPE, EGL_WINDOW_BIT,
+ EGL_RED_SIZE, 8,
+ EGL_GREEN_SIZE, 8,
+ EGL_BLUE_SIZE, 8,
+ EGL_ALPHA_SIZE, 0,
+ EGL_RENDERABLE_TYPE, skh_gl_vers[i].type,
+ EGL_SAMPLES, 0,
+ EGL_NONE
+ };
+
+ if (!egl_choose_config(egl->display, window_attr, visual_id, config)) {
+ al_log_error("egl", "Failed to get config.");
+ return false;
+ }
+
+ al_log_info("egl", "Trying to make a %s context of version %i.%i (GLSL %i).",
+ skh_gl_vers[i].api == SEKIHI_GL_API ? "GL" : "GLES",
+ skh_gl_vers[i].major, skh_gl_vers[i].minor, skh_gl_vers[i].glsl_ver);
+
+ egl->context = eglCreateContext(egl->display, *config, EGL_NO_CONTEXT, context_attr);
+
+ if (egl->context == EGL_NO_CONTEXT) {
+ continue;
+ } else {
+ al_log_info("egl", "Context successfully created.");
+ break;
+ }
+ }
+
+ if (egl->context == EGL_NO_CONTEXT) {
+ al_log_info("egl", "Failed to create context.");
+ return false;
+ }
+
+ egl->surface = EGL_NO_SURFACE;
+
+ return true;
+}
+
+void skh_egl_make_current(struct skh_egl *egl)
+{
+ eglMakeCurrent(egl->display, egl->surface, egl->surface, egl->context);
+}
+
+void skh_egl_release_current(struct skh_egl *egl)
+{
+ eglMakeCurrent(egl->display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
+}
+
+void skh_egl_swap_interval(struct skh_egl *egl, s32 interval)
+{
+ eglSwapInterval(&egl->display, interval);
+}
+
+void skh_egl_swap_buffers(struct skh_egl *egl)
+{
+ eglSwapBuffers(egl->display, egl->surface);
+}
+
+bool skh_egl_is_ready(struct skh_egl *egl)
+{
+ return egl->context != EGL_NO_CONTEXT;
+}
+
+void skh_egl_destroy_surface(struct skh_egl *egl)
+{
+ skh_egl_release_current(egl);
+ eglDestroySurface(egl->display, egl->surface);
+}
+
+void skh_egl_destroy_context(struct skh_egl *egl)
+{
+ eglDestroyContext(egl->display, egl->context);
+}
+
+void skh_egl_terminate(struct skh_egl *egl)
+{
+ eglTerminate(egl->display);
+}
diff --git a/src/egl_common.h b/src/egl_common.h
new file mode 100644
index 0000000..4a30000
--- /dev/null
+++ b/src/egl_common.h
@@ -0,0 +1,38 @@
+#pragma once
+
+#include <al/types.h>
+
+#define SEKIHI_USE_GLAD
+#define SEKIHI_USE_GLES
+
+#ifdef SEKIHI_USE_GLAD
+#include <glad/egl.h>
+#ifdef SEKIHI_USE_GLES
+#include <glad/gles2.h>
+#else
+#include <glad/gl.h>
+#endif
+#else
+#ifdef SEKIHI_USE_GLES
+#error "Only GLES is supported in this configuration"
+#endif
+#include <EGL/egl.h>
+#include <GLES2/gl2.h>
+#endif
+
+struct skh_egl {
+ EGLDisplay *display;
+ EGLSurface *surface;
+ EGLContext context;
+};
+
+bool skh_load_egl(struct skh_egl *egl);
+bool skh_init_egl(struct skh_egl *egl, EGLint visual_id, EGLConfig *config);
+void skh_egl_make_current(struct skh_egl *egl);
+void skh_egl_release_current(struct skh_egl *egl);
+void skh_egl_swap_interval(struct skh_egl *egl, s32 interval);
+void skh_egl_swap_buffers(struct skh_egl *egl);
+bool skh_egl_is_ready(struct skh_egl *egl);
+void skh_egl_destroy_surface(struct skh_egl *egl);
+void skh_egl_destroy_context(struct skh_egl *egl);
+void skh_egl_terminate(struct skh_egl *egl);
diff --git a/src/gl_versions.h b/src/gl_versions.h
new file mode 100644
index 0000000..5b9bfc2
--- /dev/null
+++ b/src/gl_versions.h
@@ -0,0 +1,62 @@
+#pragma once
+
+#include "egl_common.h"
+
+// https://github.com/haasn/libplacebo/blob/master/demos/window_glfw.c#L178
+#ifdef SEKIHI_USE_GLES
+#define PASTE_GL_VERSIONS() static struct { \
+ s32 api; \
+ s32 type; \
+ s32 major, minor; \
+ s32 glsl_ver; \
+ s32 profile; \
+} skh_gl_vers[] = { \
+ { SEKIHI_GLES_API, SEKIHI_GLES3_BIT, 3, 2, 320, 0 }, \
+ { SEKIHI_GLES_API, SEKIHI_GLES3_BIT, 3, 1, 310, 0 }, \
+ { SEKIHI_GLES_API, SEKIHI_GLES3_BIT, 3, 0, 300, 0 }, \
+ { SEKIHI_GLES_API, SEKIHI_GLES2_BIT, 2, 0, 100, 0 }, \
+};
+#else
+#define PASTE_GL_VERSIONS() static struct { \
+ s32 api; \
+ s32 type; \
+ s32 major, minor; \
+ s32 glsl_ver; \
+ s32 profile; \
+} skh_gl_vers[] = { \
+ { SEKIHI_GL_API, SEKIHI_GL_BIT, 4, 6, 460, SEKIHI_GL_CORE_PROFILE_BIT }, \
+ { SEKIHI_GL_API, SEKIHI_GL_BIT, 4, 5, 450, SEKIHI_GL_CORE_PROFILE_BIT }, \
+ { SEKIHI_GL_API, SEKIHI_GL_BIT, 4, 4, 440, SEKIHI_GL_CORE_PROFILE_BIT }, \
+ { SEKIHI_GL_API, SEKIHI_GL_BIT, 4, 0, 400, SEKIHI_GL_CORE_PROFILE_BIT }, \
+ { SEKIHI_GL_API, SEKIHI_GL_BIT, 3, 3, 330, SEKIHI_GL_CORE_PROFILE_BIT }, \
+ { SEKIHI_GL_API, SEKIHI_GL_BIT, 3, 2, 150, SEKIHI_GL_CORE_PROFILE_BIT }, \
+ { SEKIHI_GL_API, SEKIHI_GL_BIT, 3, 1, 140, 0 }, \
+ { SEKIHI_GL_API, SEKIHI_GL_BIT, 3, 0, 130, 0 }, \
+ { SEKIHI_GL_API, SEKIHI_GL_BIT, 2, 1, 120, 0 }, \
+ { SEKIHI_GL_API, SEKIHI_GL_BIT, 2, 0, 110, 0 }, \
+};
+#endif
+/*
+#define PASTE_GL_VERSIONS() static struct { \
+ s32 api; \
+ s32 type; \
+ s32 major, minor; \
+ s32 glsl_ver; \
+ s32 profile; \
+} skh_gl_vers[] = { \
+ { SEKIHI_GL_API, SEKIHI_GL_BIT, 4, 6, 460, SEKIHI_GL_CORE_PROFILE_BIT }, \
+ { SEKIHI_GL_API, SEKIHI_GL_BIT, 4, 5, 450, SEKIHI_GL_CORE_PROFILE_BIT }, \
+ { SEKIHI_GL_API, SEKIHI_GL_BIT, 4, 4, 440, SEKIHI_GL_CORE_PROFILE_BIT }, \
+ { SEKIHI_GL_API, SEKIHI_GL_BIT, 4, 0, 400, SEKIHI_GL_CORE_PROFILE_BIT }, \
+ { SEKIHI_GL_API, SEKIHI_GL_BIT, 3, 3, 330, SEKIHI_GL_CORE_PROFILE_BIT }, \
+ { SEKIHI_GL_API, SEKIHI_GL_BIT, 3, 2, 150, SEKIHI_GL_CORE_PROFILE_BIT }, \
+ { SEKIHI_GLES_API, SEKIHI_GLES3_BIT, 3, 2, 320, 0 }, \
+ { SEKIHI_GL_API, SEKIHI_GL_BIT, 3, 1, 140, 0 }, \
+ { SEKIHI_GLES_API, SEKIHI_GLES3_BIT, 3, 1, 310, 0 }, \
+ { SEKIHI_GL_API, SEKIHI_GL_BIT, 3, 0, 130, 0 }, \
+ { SEKIHI_GLES_API, SEKIHI_GLES3_BIT, 3, 0, 300, 0 }, \
+ { SEKIHI_GLES_API, SEKIHI_GLES2_BIT, 2, 0, 100, 0 }, \
+ { SEKIHI_GL_API, SEKIHI_GL_BIT, 2, 1, 120, 0 }, \
+ { SEKIHI_GL_API, SEKIHI_GL_BIT, 2, 0, 110, 0 }, \
+};
+*/
diff --git a/src/sekihi.c b/src/sekihi.c
new file mode 100644
index 0000000..04e7a89
--- /dev/null
+++ b/src/sekihi.c
@@ -0,0 +1,22 @@
+#include <al/log.h>
+
+#include "window.h"
+
+#if defined SEKIHI_WINDOW_GLFW
+#include "window_glfw.h"
+#elif defined SEKIHI_WINDOW_WAYLAND
+#include "window_wayland.h"
+#endif
+
+#if defined SEKIHI_WINDOW_WAYLAND || defined SEKIHI_WINDOW_GLFW
+struct skh_window *skh_window_create(void)
+{
+#if defined SEKIHI_WINDOW_WAYLAND
+ al_log_info("sekihi", "Creating wayland (EGL) window.");
+ return skh_window_wayland_create();
+#elif defined SEKIHI_WINDOW_GLFW
+ al_log_info("sekihi", "Creating GLFW3 window.");
+ return skh_window_glfw_create();
+#endif
+}
+#endif
diff --git a/src/window.c b/src/window.c
new file mode 100644
index 0000000..6d164a3
--- /dev/null
+++ b/src/window.c
@@ -0,0 +1,178 @@
+#include "window.h"
+
+#define GET_EVENT_CHUNK(op) \
+ size_t size; \
+ u8 *ptr = al_ring_buffer_write_chunk(&window->events, &size); \
+ if (size < SEKIHI_EVENT_SIZE) { \
+ return; \
+ } \
+ ptr[0] = op
+
+#define APPEND_EVENT_CHUNK() \
+ al_ring_buffer_append(&window->events, ptr, SEKIHI_EVENT_SIZE)
+
+void skh_window_send_pointer_pos_event(struct skh_window *window, f64 x, f64 y)
+{
+#if defined SEKIHI_POLL_CONSERVATIVE
+ GET_EVENT_CHUNK(SEKIHI_EVENT_POINTER_POS);
+ ((f64 *)(&ptr[1]))[0] = x;
+ ((f64 *)(&ptr[9]))[0] = y;
+ APPEND_EVENT_CHUNK();
+#else
+ if (window->pointer_pos_callback) {
+ window->pointer_pos_callback(window->userdata, x, y);
+ window->pending_refresh = true;
+ }
+#endif
+}
+
+void skh_window_send_scroll_event(struct skh_window *window, f64 y)
+{
+#if defined SEKIHI_POLL_CONSERVATIVE
+ GET_EVENT_CHUNK(SEKIHI_EVENT_SCROLL);
+ ((f64 *)(&ptr[1]))[0] = y;
+ APPEND_EVENT_CHUNK();
+#else
+ if (window->scroll_callback) {
+ window->scroll_callback(window->userdata, y);
+ window->pending_refresh = true;
+ }
+#endif
+}
+
+void skh_window_send_mouse_button_event(struct skh_window *window, u8 state, u8 button)
+{
+#if defined SEKIHI_POLL_CONSERVATIVE
+ GET_EVENT_CHUNK(SEKIHI_EVENT_MOUSE_BUTTON);
+ ptr[1] = state;
+ ptr[2] = button;
+ APPEND_EVENT_CHUNK();
+#else
+ if (window->mouse_button_callback) {
+ window->mouse_button_callback(window->userdata, state, button);
+ window->pending_refresh = true;
+ }
+#endif
+}
+
+void skh_window_send_key_event(struct skh_window *window, u8 state, u8 button)
+{
+ if (window->key_immediate_callback) {
+ window->key_immediate_callback(window->userdata, state, button);
+ }
+#if defined SEKIHI_POLL_CONSERVATIVE
+ GET_EVENT_CHUNK(SEKIHI_EVENT_KEY);
+ ptr[1] = state;
+ ptr[2] = button;
+ APPEND_EVENT_CHUNK();
+#else
+ if (window->key_callback) {
+ window->key_callback(window->userdata, state, button);
+ window->pending_refresh = true;
+ }
+#endif
+}
+
+void skh_window_send_resize_event(struct skh_window *window, s32 width, s32 height)
+{
+#if defined SEKIHI_POLL_CONSERVATIVE
+ GET_EVENT_CHUNK(SEKIHI_EVENT_RESIZE);
+ ((s32 *)(&ptr[1]))[0] = width;
+ ((s32 *)(&ptr[5]))[0] = height;
+ APPEND_EVENT_CHUNK();
+#else
+ if (window->resize_callback) {
+ window->resize_callback(window->userdata, width, height);
+ window->pending_refresh = true;
+ }
+#endif
+}
+
+#if defined SEKIHI_POLL_INLINE
+void skh_window_send_refresh_event(struct skh_window *window)
+{
+ if (window->refresh_callback) {
+ window->refresh_callback(window->userdata);
+ window->pending_refresh = true;
+ }
+}
+#endif
+
+void skh_window_send_should_close_event(struct skh_window *window)
+{
+#if defined SEKIHI_POLL_CONSERVATIVE
+ GET_EVENT_CHUNK(SEKIHI_EVENT_SHOULD_CLOSE);
+ APPEND_EVENT_CHUNK();
+#else
+ if (window->should_close_callback) {
+ window->should_close_callback(window->userdata);
+ window->pending_refresh = true;
+ }
+#endif
+}
+
+#if defined SEKIHI_POLL_CONSERVATIVE
+bool skh_window_read_events(struct skh_window *window)
+{
+ u8 op = SEKIHI_EVENT_NONE;
+ size_t size;
+ u8 *ptr = al_ring_buffer_read_chunk(&window->events, &size);
+ u8 *bc = ptr;
+ while ((size_t)(bc - ptr) < size) {
+ op = *bc;
+ switch (op) {
+ case SEKIHI_EVENT_POINTER_POS: {
+ f64 x = ((f64 *)(&bc[1]))[0];
+ f64 y = ((f64 *)(&bc[9]))[0];
+ if (window->pointer_pos_callback) {
+ window->pointer_pos_callback(window->userdata, x, y);
+ }
+ break;
+ }
+ case SEKIHI_EVENT_SCROLL: {
+ f64 y = ((f64 *)(&bc[1]))[0];
+ if (window->scroll_callback) {
+ window->scroll_callback(window->userdata, y);
+ }
+ break;
+ }
+ case SEKIHI_EVENT_MOUSE_BUTTON: {
+ u8 state = bc[1];
+ u8 button = bc[2];
+ if (window->mouse_button_callback) {
+ window->mouse_button_callback(window->userdata, state, button);
+ }
+ break;
+ }
+ case SEKIHI_EVENT_KEY: {
+ u8 state = bc[1];
+ u8 button = bc[2];
+ if (window->key_callback) {
+ window->key_callback(window->userdata, state, button);
+ }
+ break;
+ }
+ case SEKIHI_EVENT_RESIZE: {
+ s32 width = ((s32 *)(&bc[1]))[0];
+ s32 height = ((s32 *)(&bc[5]))[0];
+ if (window->resize_callback) {
+ window->resize_callback(window->userdata, width, height);
+ }
+ break;
+ }
+ case SEKIHI_EVENT_SHOULD_CLOSE: {
+ if (window->should_close_callback) {
+ window->should_close_callback(window->userdata);
+ }
+ break;
+ }
+ default:
+ break;
+ }
+ bc += SEKIHI_EVENT_SIZE;
+ }
+ al_assert((size_t)(bc - ptr) == size);
+ al_ring_buffer_consume(&window->events, ptr, size);
+ return op != SEKIHI_EVENT_NONE;
+}
+#endif
diff --git a/src/window.h b/src/window.h
new file mode 100644
index 0000000..249c43e
--- /dev/null
+++ b/src/window.h
@@ -0,0 +1,117 @@
+#pragma once
+
+#if !(defined(SEKIHI_API_NULL) || defined(SEKIHI_API_OPENGL) || \
+ defined(SEKIHI_API_VULKAN) || defined(SEKIHI_API_DX11))
+#error "No graphics API defined."
+#endif
+
+#if defined(SEKIHI_API_NULL) + defined(SEKIHI_API_OPENGL) + \
+ defined(SEKIHI_API_VULKAN) + defined(SEKIHI_API_DX11) != 1
+#error "Conflicting graphics APIs."
+#endif
+
+#if !(defined(SEKIHI_POLL_CONSERVATIVE) || defined(SEKIHI_POLL_INLINE))
+#error "No poll method defined."
+#endif
+
+#include <al/types.h>
+#include <al/lib.h>
+#include <al/ring_buffer.h>
+
+#if defined SEKIHI_API_VULKAN
+#include <vulkan/vulkan.h>
+#endif
+
+#ifndef SEKIHI_MIN_WIDTH
+#define SEKIHI_MIN_WIDTH 16
+#endif
+
+#ifndef SEKIHI_MIN_HEIGHT
+#define SEKIHI_MIN_HEIGHT 16
+#endif
+
+struct skh_window {
+ s32 width;
+ s32 height;
+ bool fullscreen;
+#if defined SEKIHI_POLL_CONSERVATIVE
+ u8 *data;
+ // Render thread events.
+ struct al_ring_buffer events;
+#elif defined SEKIHI_POLL_INLINE
+ bool pending_refresh;
+#endif
+ // Methods.
+ bool (*create_window)(struct skh_window *, s32, s32, const char *);
+ void (*resize)(struct skh_window *, s32, s32);
+ void (*toggle_fullscreen)(struct skh_window *);
+ bool (*poll)(struct skh_window *, bool);
+#if defined SEKIHI_PAUSE
+ void (*wake)(struct skh_window *);
+#endif
+ void (*set_ext_data)(struct skh_window *, void *);
+ void (*free)(struct skh_window **);
+ // Context specific.
+#if defined SEKIHI_API_VULKAN
+ VKAPI_ATTR PFN_vkVoidFunction VKAPI_CALL (*get_vk_proc_address)(VkInstance, const char *);
+ VkResult (*vk_create_surface)(void *, VkInstance, VkSurfaceKHR *);
+ const char *const *(*vk_get_extensions)(u32 *);
+#elif defined SEKIHI_API_OPENGL
+ void (*(*get_gl_proc_address)(const char *))(void);
+ bool (*gl_load_loader)(void *);
+ bool (*gl_make_current)(void *);
+ void (*gl_release_current)(void *);
+ void (*gl_swap_buffers)(void *);
+#elif defined SEKIHI_API_DX11
+#endif
+ // Callbacks.
+ void (*pointer_pos_callback)(void *, f64, f64);
+ void (*scroll_callback)(void *, f64);
+ void (*mouse_button_callback)(void *, u8, u8);
+ void (*key_callback)(void *, u8, u8);
+ // Always runs on the main thread.
+ void (*key_immediate_callback)(void *, u8, u8);
+ void (*resize_callback)(void *, s32, s32);
+ void (*refresh_callback)(void *);
+ void (*should_close_callback)(void *);
+ void *userdata;
+};
+
+enum {
+ SEKIHI_EVENT_POINTER_POS = 0,
+ SEKIHI_EVENT_SCROLL,
+ SEKIHI_EVENT_MOUSE_BUTTON,
+ SEKIHI_EVENT_KEY,
+ SEKIHI_EVENT_RESIZE,
+ SEKIHI_EVENT_SHOULD_CLOSE,
+ SEKIHI_EVENT_NONE = 255
+};
+
+enum {
+ SEKIHI_BUTTON_PRESSED = 0,
+ SEKIHI_BUTTON_RELEASED
+};
+
+enum {
+ SEKIHI_MOUSE1 = 0,
+ SEKIHI_MOUSE2
+};
+
+#define SEKIHI_EVENT_SIZE 32
+#ifndef SEKIHI_EVENTS_SIZE
+#define SEKIHI_EVENTS_SIZE (SEKIHI_EVENT_SIZE * 288)
+#endif
+
+struct skh_window *skh_window_create(void);
+void skh_window_send_pointer_pos_event(struct skh_window *window, f64 x, f64 y);
+void skh_window_send_scroll_event(struct skh_window *window, f64 y);
+void skh_window_send_mouse_button_event(struct skh_window *window, u8 state, u8 button);
+void skh_window_send_key_event(struct skh_window *window, u8 state, u8 button);
+void skh_window_send_resize_event(struct skh_window *window, s32 width, s32 height);
+#if defined SEKIHI_POLL_INLINE
+void skh_window_send_refresh_event(struct skh_window *window);
+#endif
+void skh_window_send_should_close_event(struct skh_window *window);
+#if defined SEKIHI_POLL_CONSERVATIVE
+bool skh_window_read_events(struct skh_window *window);
+#endif
diff --git a/src/window_glfw.c b/src/window_glfw.c
new file mode 100644
index 0000000..874e08f
--- /dev/null
+++ b/src/window_glfw.c
@@ -0,0 +1,362 @@
+#include <al/lib.h>
+#include <al/log.h>
+
+#include "egl_common.h"
+#include "window_glfw.h"
+
+#if defined SEKIHI_API_OPENGL
+#define SEKIHI_GL_API GLFW_OPENGL_API
+#define SEKIHI_GLES_API GLFW_OPENGL_ES_API
+
+// Unused.
+#define SEKIHI_GL_BIT 0
+#define SEKIHI_GLES2_BIT 0
+#define SEKIHI_GLES3_BIT 0
+
+#define SEKIHI_GL_CORE_PROFILE_BIT GLFW_OPENGL_CORE_PROFILE
+
+#include "gl_versions.h"
+
+PASTE_GL_VERSIONS()
+#endif
+
+static void key_callback(GLFWwindow *window, s32 key, s32 scancode, s32 action, s32 mods)
+{
+ struct skh_window_glfw *glfw = glfwGetWindowUserPointer(window);
+ (void)key;
+ (void)mods;
+ if (action == GLFW_PRESS) {
+ skh_window_send_key_event(&glfw->w, SEKIHI_BUTTON_PRESSED, scancode);
+ } else if (action == GLFW_RELEASE) {
+ skh_window_send_key_event(&glfw->w, SEKIHI_BUTTON_RELEASED, scancode);
+ }
+}
+
+static void cursor_position_callback(GLFWwindow *window, f64 xpos, f64 ypos)
+{
+ struct skh_window_glfw *glfw = glfwGetWindowUserPointer(window);
+ skh_window_send_pointer_pos_event(&glfw->w, xpos, ypos);
+}
+
+static void mouse_button_callback(GLFWwindow *window, s32 button, s32 action, s32 mods)
+{
+ struct skh_window_glfw *glfw = glfwGetWindowUserPointer(window);
+ (void)mods;
+ if (button == GLFW_MOUSE_BUTTON_1) {
+ if (action == GLFW_PRESS) {
+ skh_window_send_mouse_button_event(&glfw->w, SEKIHI_BUTTON_PRESSED, SEKIHI_MOUSE1);
+ } else if (action == GLFW_RELEASE) {
+ skh_window_send_mouse_button_event(&glfw->w, SEKIHI_BUTTON_RELEASED, SEKIHI_MOUSE1);
+ }
+ }
+}
+
+static void scroll_callback(GLFWwindow *window, f64 xoffset, f64 yoffset)
+{
+ struct skh_window_glfw *glfw = glfwGetWindowUserPointer(window);
+ (void)xoffset;
+ skh_window_send_scroll_event(&glfw->w, -yoffset);
+}
+
+static void resize_callback(GLFWwindow *window, s32 width, s32 height)
+{
+ struct skh_window_glfw *glfw = glfwGetWindowUserPointer(window);
+ glfw->w.width = width;
+ glfw->w.height = height;
+ skh_window_send_resize_event(&glfw->w, width, height);
+}
+
+#if defined SEKIHI_POLL_INLINE
+static void refresh_callback(GLFWwindow *window)
+{
+ struct skh_window_glfw *glfw = glfwGetWindowUserPointer(window);
+ skh_window_send_refresh_event(&glfw->w);
+}
+#endif
+
+static void close_callback(GLFWwindow *window)
+{
+ struct skh_window_glfw *glfw = glfwGetWindowUserPointer(window);
+ skh_window_send_should_close_event(&glfw->w);
+}
+
+static bool window_glfw_create_window(struct skh_window *window, s32 width, s32 height, const char *name)
+{
+ struct skh_window_glfw *glfw = (struct skh_window_glfw *)window;
+
+ if (!glfwInit()) {
+ al_log_error("window_glfw", "Failed to initialize GLFW.");
+ return false;
+ }
+
+ //glfwWindowHint(GLFW_SRGB_CAPABLE, GLFW_TRUE);
+ //glfwWindowHint(GLFW_TRANSPARENT_FRAMEBUFFER, GLFW_TRUE);
+
+#if defined SEKIHI_API_VULKAN || defined SEKIHI_API_DX11
+ glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API);
+#elif defined SEKIHI_API_OPENGL
+ //glfwWindowHint(GLFW_CONTEXT_CREATION_API, GLFW_EGL_CONTEXT_API);
+ glfwWindowHint(GLFW_CONTEXT_CREATION_API, GLFW_NATIVE_CONTEXT_API);
+ for (u32 i = 0; i < AL_ARRAY_SIZE(skh_gl_vers); i++) {
+ glfwWindowHint(GLFW_CLIENT_API, skh_gl_vers[i].api);
+ glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, skh_gl_vers[i].major);
+ glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, skh_gl_vers[i].minor);
+ if (skh_gl_vers[i].api == SEKIHI_GL_API) {
+ glfwWindowHint(GLFW_OPENGL_PROFILE, skh_gl_vers[i].profile);
+ }
+#ifdef __APPLE__
+ if (gl_vers[i].profile == GLFW_OPENGL_CORE_PROFILE) {
+ glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);
+ }
+#endif
+ al_log_info("glfw", "Trying to make a %s context of version %i.%i (GLSL %i).",
+ skh_gl_vers[i].api == SEKIHI_GL_API ? "GL" : "GLES",
+ skh_gl_vers[i].major, skh_gl_vers[i].minor, skh_gl_vers[i].glsl_ver);
+#endif
+ glfw->window = glfwCreateWindow(width, height, name, NULL, NULL);
+#if defined SEKIHI_API_OPENGL
+ if (glfw->window) {
+ al_log_info("glfw", "Context successfully created.");
+ break;
+ }
+ }
+#endif
+
+ if (!glfw->window) {
+ al_log_info("glfw", "Failed to create window.");
+ glfwTerminate();
+ return false;
+ }
+
+ glfwSwapInterval(1);
+
+ glfwGetFramebufferSize(glfw->window, &glfw->w.width, &glfw->w.height);
+ glfwSetWindowSizeLimits(glfw->window, SEKIHI_MIN_WIDTH, SEKIHI_MIN_HEIGHT, GLFW_DONT_CARE, GLFW_DONT_CARE);
+
+ glfwSetWindowUserPointer(glfw->window, glfw);
+
+ glfwSetKeyCallback(glfw->window, key_callback);
+ glfwSetCursorPosCallback(glfw->window, cursor_position_callback);
+ glfwSetMouseButtonCallback(glfw->window, mouse_button_callback);
+ glfwSetScrollCallback(glfw->window, scroll_callback);
+ //glfwSetWindowSizeCallback(glfw->window, resize_callback);
+ glfwSetFramebufferSizeCallback(glfw->window, resize_callback);
+#if defined SEKIHI_POLL_INLINE
+ glfwSetWindowRefreshCallback(glfw->window, refresh_callback);
+#endif
+ glfwSetWindowCloseCallback(glfw->window, close_callback);
+
+ return true;
+}
+
+static void window_glfw_resize(struct skh_window *window, s32 width, s32 height)
+{
+ struct skh_window_glfw *glfw = (struct skh_window_glfw *)window;
+ glfwSetWindowAspectRatio(glfw->window, width, height);
+ glfwSetWindowSize(glfw->window, width, height);
+}
+
+// https://stackoverflow.com/questions/21421074/how-to-create-a-full-screen-window-on-the-current-monitor-with-glfw
+static GLFWmonitor *get_current_monitor(GLFWwindow *window)
+{
+ s32 best_overlap = 0;
+ GLFWmonitor *best_monitor = NULL;
+
+ s32 wx, wy;
+ s32 ww, wh;
+ glfwGetWindowPos(window, &wx, &wy);
+ glfwGetWindowSize(window, &ww, &wh);
+
+ s32 monitor_count;
+ GLFWmonitor **monitors = glfwGetMonitors(&monitor_count);
+
+ const GLFWvidmode *mode;
+
+ s32 mx, my;
+ s32 mw, mh;
+ s32 overlap;
+
+ for (s32 i = 0; i < monitor_count; i++) {
+ mode = glfwGetVideoMode(monitors[i]);
+
+ glfwGetMonitorPos(monitors[i], &mx, &my);
+ mw = mode->width;
+ mh = mode->height;
+
+ s32 dx = AL_MIN(wx + ww, mx + mw) - AL_MAX(wx, mx);
+ s32 dy = AL_MIN(wy + wh, my + mh) - AL_MAX(wy, my);
+ overlap = AL_MAX(0, dx) * AL_MAX(0, dy);
+
+ if (best_overlap < overlap) {
+ best_overlap = overlap;
+ best_monitor = monitors[i];
+ }
+ }
+
+ return best_monitor;
+}
+
+static void window_glfw_toggle_fullscreen(struct skh_window *window)
+{
+ struct skh_window_glfw *glfw = (struct skh_window_glfw *)window;
+ if (!glfw->w.fullscreen) {
+ glfwGetWindowPos(glfw->window, &glfw->prev_state.x, &glfw->prev_state.y);
+ glfw->prev_state.width = glfw->w.width;
+ glfw->prev_state.height = glfw->w.height;
+ GLFWmonitor *monitor = get_current_monitor(glfw->window);
+ const GLFWvidmode *mode = glfwGetVideoMode(monitor);
+ glfwSetWindowMonitor(glfw->window, monitor, 0, 0, mode->width, mode->height, mode->refreshRate);
+ glfwSetInputMode(glfw->window, GLFW_CURSOR, GLFW_CURSOR_HIDDEN);
+ } else {
+ glfwSetWindowMonitor(glfw->window, NULL, glfw->prev_state.x, glfw->prev_state.y,
+ glfw->prev_state.width, glfw->prev_state.height, GLFW_DONT_CARE);
+ glfwSetInputMode(glfw->window, GLFW_CURSOR, GLFW_CURSOR_NORMAL);
+ }
+ glfw->w.fullscreen = !glfw->w.fullscreen;
+}
+
+static bool window_glfw_poll(struct skh_window *window, bool block)
+{
+ struct skh_window_glfw *glfw = (struct skh_window_glfw *)window;
+#if defined SEKIHI_POLL_CONSERVATIVE
+ (void)block;
+ glfwWaitEvents();
+#elif defined SEKIHI_POLL_INLINE
+#if defined SEKIHI_PAUSE
+ if (block) {
+ glfwWaitEvents();
+ } else {
+ glfwPollEvents();
+ }
+#else
+ (void)block;
+ glfwPollEvents();
+#endif
+#endif
+#if defined SEKIHI_POLL_INLINE
+ if (glfw->w.pending_refresh) {
+ glfw->w.pending_refresh = false;
+ return true;
+ }
+#endif
+ return !block;
+}
+
+#if defined SEKIHI_PAUSE
+static void window_glfw_wake(struct skh_window *window)
+{
+ struct skh_window_glfw *glfw = (struct skh_window_glfw *)window;
+ glfw->w.pending_refresh = true;
+ glfwPostEmptyEvent();
+}
+#endif
+
+static void window_glfw_set_ext_data(struct skh_window *window, void *ext_data)
+{
+ (void)window;
+ (void)ext_data;
+}
+
+static void window_glfw_free(struct skh_window **window)
+{
+ struct skh_window_glfw *glfw = (struct skh_window_glfw *)*window;
+ if (glfw->window) {
+ glfwDestroyWindow(glfw->window);
+ glfwTerminate();
+ }
+#if defined SEKIHI_POLL_CONSERVATIVE
+ al_free(glfw->w.data);
+#endif
+ al_free(glfw);
+ *window = NULL;
+}
+
+#if defined SEKIHI_API_VULKAN
+static VKAPI_ATTR PFN_vkVoidFunction VKAPI_CALL window_glfw_get_vk_proc_address(VkInstance inst, const char *name)
+{
+ return (PFN_vkVoidFunction)glfwGetInstanceProcAddress(inst, name);
+}
+
+static VkResult window_glfw_vk_create_surface(void *data, VkInstance inst, VkSurfaceKHR *surface)
+{
+ struct skh_window_glfw *glfw = (struct skh_window_glfw *)data;
+ return glfwCreateWindowSurface(inst, glfw->window, NULL, surface);
+}
+
+static const char *const *window_glfw_vk_get_extensions(u32 *num)
+{
+ return glfwGetRequiredInstanceExtensions(num);
+}
+#elif defined SEKIHI_API_OPENGL
+static bool window_glfw_gl_load_loader(void *data)
+{
+ struct skh_window_glfw *glfw = (struct skh_window_glfw *)data;
+#ifdef SEKIHI_USE_GLAD
+ //if (!gladLoadEGL(glfwGetEGLDisplay(), (GLADloadfunc)glfw->w.get_gl_proc_address)) {
+ // return false;
+ //}
+#ifdef SEKIHI_USE_GLES
+ if (!gladLoadGLES2((GLADloadfunc)glfw->w.get_gl_proc_address)) {
+ return false;
+ }
+#else
+ if (!gladLoadGL((GLADloadfunc)glfw->w.get_gl_proc_address)) {
+ return false;
+ }
+#endif
+#endif
+ return true;
+}
+
+static bool window_glfw_gl_make_current(void *data)
+{
+ struct skh_window_glfw *glfw = (struct skh_window_glfw *)data;
+ glfwMakeContextCurrent(glfw->window);
+ return true;
+}
+
+static void window_glfw_gl_release_current(void *data)
+{
+ (void)data;
+ glfwMakeContextCurrent(NULL);
+}
+
+static void window_glfw_gl_swap_buffers(void *data)
+{
+ struct skh_window_glfw *glfw = (struct skh_window_glfw *)data;
+ glfwSwapBuffers(glfw->window);
+}
+#endif
+
+struct skh_window *skh_window_glfw_create(void)
+{
+ struct skh_window_glfw *glfw = (struct skh_window_glfw *)al_calloc(
+ 1, sizeof(struct skh_window_glfw));
+#if defined SEKIHI_POLL_CONSERVATIVE
+ glfw->w.data = al_malloc(SEKIHI_WINDOW_EVENTS_SIZE);
+ al_ring_buffer_init(&glfw->w.events, glfw->w.data, SEKIHI_WINDOW_EVENTS_SIZE);
+#endif
+ glfw->w.fullscreen = false;
+ glfw->w.create_window = window_glfw_create_window;
+ glfw->w.resize = window_glfw_resize;
+ glfw->w.toggle_fullscreen = window_glfw_toggle_fullscreen;
+ glfw->w.poll = window_glfw_poll;
+#if defined SEKIHI_PAUSE
+ glfw->w.wake = window_glfw_wake;
+#endif
+ glfw->w.set_ext_data = window_glfw_set_ext_data;
+ glfw->w.free = window_glfw_free;
+#if defined SEKIHI_API_VULKAN
+ glfw->w.get_vk_proc_address = window_glfw_get_vk_proc_address;
+ glfw->w.vk_create_surface = window_glfw_vk_create_surface;
+ glfw->w.vk_get_extensions = window_glfw_vk_get_extensions;
+#elif defined SEKIHI_API_OPENGL
+ glfw->w.get_gl_proc_address = glfwGetProcAddress;
+ glfw->w.gl_load_loader = window_glfw_gl_load_loader;
+ glfw->w.gl_make_current = window_glfw_gl_make_current;
+ glfw->w.gl_release_current = window_glfw_gl_release_current;
+ glfw->w.gl_swap_buffers = window_glfw_gl_swap_buffers;
+#elif defined SEKIHI_API_DX11
+#endif
+ return (struct skh_window *)glfw;
+}
diff --git a/src/window_glfw.h b/src/window_glfw.h
new file mode 100644
index 0000000..dc7c4a5
--- /dev/null
+++ b/src/window_glfw.h
@@ -0,0 +1,25 @@
+#pragma once
+
+#if defined SEKIHI_API_VULKAN
+#define GLFW_INCLUDE_VULKAN
+#endif
+#include <GLFW/glfw3.h>
+#ifndef _WIN32
+#define GLFW_EXPOSE_NATIVE_EGL
+#include <GLFW/glfw3native.h>
+#endif
+
+#include "window.h"
+
+struct skh_window_glfw {
+ struct skh_window w;
+ GLFWwindow *window;
+ struct {
+ s32 x;
+ s32 y;
+ s32 width;
+ s32 height;
+ } prev_state;
+};
+
+struct skh_window *skh_window_glfw_create(void);
diff --git a/src/window_wayland.c b/src/window_wayland.c
new file mode 100644
index 0000000..0421254
--- /dev/null
+++ b/src/window_wayland.c
@@ -0,0 +1,718 @@
+#include <aki/thread.h>
+#include <al/log.h>
+
+#include "window_wayland.h"
+
+static void xdg_wm_base_ping(void *data, struct xdg_wm_base *shell, u32 serial)
+{
+ struct skh_window_wayland *wl = (struct skh_window_wayland *)data;
+ (void)wl;
+ xdg_wm_base_pong(shell, serial);
+}
+
+static const struct xdg_wm_base_listener xdg_wm_base_listener = {
+ xdg_wm_base_ping,
+};
+
+static void pointer_enter(void *data, struct wl_pointer *pointer,
+ u32 serial, struct wl_surface *surface, wl_fixed_t surface_x, wl_fixed_t surface_y)
+{
+ struct skh_window_wayland *wl = (struct skh_window_wayland *)data;
+ (void)pointer;
+ (void)serial;
+ (void)surface;
+ wl->pointer_x = wl_fixed_to_double(surface_x);
+ wl->pointer_y = wl_fixed_to_double(surface_y);
+ wl->last_pointer_x = wl->pointer_x;
+ wl->last_pointer_y = wl->pointer_y;
+}
+
+static void pointer_leave(void *data, struct wl_pointer *pointer,
+ u32 serial, struct wl_surface *surface)
+{
+ (void)data;
+ (void)pointer;
+ (void)serial;
+ (void)surface;
+}
+
+static void pointer_motion(void *data, struct wl_pointer *pointer,
+ u32 time, wl_fixed_t surface_x, wl_fixed_t surface_y)
+{
+ struct skh_window_wayland *wl = (struct skh_window_wayland *)data;
+ (void)pointer;
+ (void)time;
+ wl->last_pointer_x = wl->pointer_x;
+ wl->last_pointer_y = wl->pointer_y;
+ wl->pointer_x = wl_fixed_to_double(surface_x);
+ wl->pointer_y = wl_fixed_to_double(surface_y);
+ skh_window_send_pointer_pos_event(&wl->w, wl->pointer_x, wl->pointer_y);
+}
+
+static void pointer_button(void *data, struct wl_pointer *pointer,
+ u32 serial, u32 time, u32 button, u32 state)
+{
+ struct skh_window_wayland *wl = (struct skh_window_wayland *)data;
+ (void)pointer;
+ (void)serial;
+ (void)time;
+#define MOUSE1 0x110
+ if (button == MOUSE1) {
+ if (state == WL_POINTER_BUTTON_STATE_PRESSED) {
+ skh_window_send_mouse_button_event(&wl->w, SEKIHI_BUTTON_PRESSED, SEKIHI_MOUSE1);
+ } else if (state == WL_POINTER_BUTTON_STATE_RELEASED) {
+ skh_window_send_mouse_button_event(&wl->w, SEKIHI_BUTTON_RELEASED, SEKIHI_MOUSE1);
+ }
+ }
+}
+
+static void pointer_axis(void *data, struct wl_pointer *pointer,
+ u32 time, u32 axis, wl_fixed_t value)
+{
+ struct skh_window_wayland *wl = (struct skh_window_wayland *)data;
+ (void)pointer;
+ (void)time;
+ if (axis == WL_POINTER_AXIS_VERTICAL_SCROLL) {
+ skh_window_send_scroll_event(&wl->w, wl_fixed_to_double(value));
+ }
+}
+
+static void pointer_frame(void *data, struct wl_pointer *pointer)
+{
+ (void)data;
+ (void)pointer;
+}
+
+static void pointer_axis_source(void *data, struct wl_pointer *pointer,
+ u32 axis_source)
+{
+ (void)data;
+ (void)pointer;
+ (void)axis_source;
+}
+
+static void pointer_axis_stop(void *data, struct wl_pointer *pointer,
+ u32 time, u32 axis)
+{
+ (void)data;
+ (void)pointer;
+ (void)time;
+ (void)axis;
+}
+
+static void pointer_axis_discrete(void *data, struct wl_pointer *pointer,
+ u32 axis, s32 discrete)
+{
+ (void)data;
+ (void)pointer;
+ (void)axis;
+ (void)discrete;
+}
+
+static const struct wl_pointer_listener pointer_listener = {
+ .enter = pointer_enter,
+ .leave = pointer_leave,
+ .motion = pointer_motion,
+ .button = pointer_button,
+ .axis = pointer_axis,
+ .frame = pointer_frame,
+ .axis_source = pointer_axis_source,
+ .axis_stop = pointer_axis_stop,
+ .axis_discrete = pointer_axis_discrete
+};
+
+static void keyboard_keymap(void *data, struct wl_keyboard *keyboard,
+ u32 format, s32 fd, u32 size)
+{
+ (void)data;
+ (void)keyboard;
+ (void)format;
+ (void)fd;
+ (void)size;
+}
+
+static void keyboard_enter(void *data, struct wl_keyboard *keyboard,
+ u32 serial, struct wl_surface *surface, struct wl_array *keys)
+{
+ (void)data;
+ (void)keyboard;
+ (void)serial;
+ (void)surface;
+ (void)keys;
+}
+
+static void keyboard_leave(void *data, struct wl_keyboard *keyboard,
+ u32 serial, struct wl_surface *surface)
+{
+ (void)data;
+ (void)keyboard;
+ (void)serial;
+ (void)surface;
+}
+
+static void keyboard_key(void *data, struct wl_keyboard *keyboard,
+ u32 serial, u32 time, u32 key, u32 state)
+{
+ struct skh_window_wayland *wl = (struct skh_window_wayland *)data;
+ (void)serial;
+ (void)keyboard;
+ (void)time;
+ if (state == WL_KEYBOARD_KEY_STATE_PRESSED) {
+ skh_window_send_key_event(&wl->w, SEKIHI_BUTTON_PRESSED, key);
+ } else if (state == WL_KEYBOARD_KEY_STATE_RELEASED) {
+ skh_window_send_key_event(&wl->w, SEKIHI_BUTTON_RELEASED, key);
+ }
+}
+
+static void keyboard_modifiers(void *data, struct wl_keyboard *keyboard,
+ u32 serial, u32 mods_depressed, u32 mods_latched, u32 mods_locked, u32 group)
+{
+ (void)data;
+ (void)keyboard;
+ (void)serial;
+ (void)mods_depressed;
+ (void)mods_latched;
+ (void)mods_locked;
+ (void)group;
+}
+
+static void keyboard_repeat(void *data, struct wl_keyboard *keyboard, s32 rate, s32 delay)
+{
+ (void)data;
+ (void)keyboard;
+ (void)rate;
+ (void)delay;
+}
+
+static const struct wl_keyboard_listener keyboard_listener = {
+ .keymap = keyboard_keymap,
+ .enter = keyboard_enter,
+ .leave = keyboard_leave,
+ .key = keyboard_key,
+ .modifiers = keyboard_modifiers,
+ .repeat_info = keyboard_repeat
+};
+
+static void seat_capabilities(void *data, struct wl_seat *seat, u32 capabilities)
+{
+ struct skh_window_wayland *wl = (struct skh_window_wayland *)data;
+ (void)seat;
+
+ if (capabilities & WL_SEAT_CAPABILITY_POINTER) {
+ if (!wl->pointer) {
+ wl->pointer = wl_seat_get_pointer(wl->seat);
+ wl_pointer_add_listener(wl->pointer, &pointer_listener, wl);
+ }
+ } else {
+ if (wl->pointer) {
+ wl_pointer_release(wl->pointer);
+ wl->pointer = NULL;
+ }
+ }
+
+ if (capabilities & WL_SEAT_CAPABILITY_KEYBOARD) {
+ if (!wl->keyboard) {
+ wl->keyboard = wl_seat_get_keyboard(wl->seat);
+ wl_keyboard_add_listener(wl->keyboard, &keyboard_listener, wl);
+ }
+ } else {
+ if (wl->keyboard) {
+ wl_keyboard_release(wl->keyboard);
+ wl->keyboard = NULL;
+ }
+ }
+}
+
+static void seat_name(void *data, struct wl_seat *seat, const char *name)
+{
+ (void)data;
+ (void)seat;
+ (void)name;
+}
+
+static const struct wl_seat_listener seat_listener = {
+ .capabilities = seat_capabilities,
+ .name = seat_name
+};
+
+/*
+static void presentation_set_clock_id(void *data, struct wp_presentation *presentation, u32 clock_id)
+{
+ struct skh_window_wayland *wl = (struct skh_window_wayland *)data;
+ if (clock_id == CLOCK_MONOTONIC)
+ {
+ wl->presentation = presentation;
+ }
+}
+
+static const struct wp_presentation_listener presentation_listener = {
+ presentation_set_clock_id
+};
+*/
+
+static void global_add(void *data, struct wl_registry *registry, u32 name,
+ const char *interface, u32 version)
+{
+ struct skh_window_wayland *wl = (struct skh_window_wayland *)data;
+ (void)version;
+ if (strcmp(interface, wl_compositor_interface.name) == 0) {
+ wl->compositor = wl_registry_bind(registry, name, &wl_compositor_interface, 2);
+ } else if (strcmp(interface, wl_seat_interface.name) == 0) {
+ wl->seat = wl_registry_bind(registry, name, &wl_seat_interface, 4);
+ wl_seat_add_listener(wl->seat, &seat_listener, wl);
+ } else if (strcmp(interface, xdg_wm_base_interface.name) == 0) {
+ wl->xdg_wm_base = wl_registry_bind(registry, name, &xdg_wm_base_interface, 2);
+ xdg_wm_base_add_listener(wl->xdg_wm_base, &xdg_wm_base_listener, wl);
+ } else if (strcmp(interface, zxdg_decoration_manager_v1_interface.name) == 0) {
+ wl->zxdg_decoration_manager = wl_registry_bind(registry, name, &zxdg_decoration_manager_v1_interface, 1);
+ }/* else if (strcmp(interface, wp_presentation_interface.name) == 0) {
+ wl->presentation = wl_registry_bind(registry, name, &wp_presentation_interface, 1);
+ wp_presentation_add_listener(wl->presentation, &presentation_listener, wl);
+ }*/
+}
+
+static void global_remove(void *data, struct wl_registry *registry, u32 name)
+{
+ (void)data;
+ (void)registry;
+ (void)name;
+}
+
+struct wl_registry_listener registry_listener = {
+ .global = global_add,
+ .global_remove = global_remove
+};
+
+static void handle_surface_enter(void *data, struct wl_surface *surface, struct wl_output *output)
+{
+ (void)data;
+ (void)output;
+ wl_surface_set_buffer_scale(surface, 1);
+}
+
+static void handle_surface_leave(void *data, struct wl_surface *surface, struct wl_output *output)
+{
+ (void)data;
+ (void)surface;
+ (void)output;
+}
+
+static void preferred_buffer_scale(void *data, struct wl_surface *surface, s32 factor)
+{
+ (void)data;
+ (void)surface;
+ (void)factor;
+}
+
+static void preferred_buffer_transform(void *data, struct wl_surface *surface, u32 transform)
+{
+ (void)data;
+ (void)surface;
+ (void)transform;
+}
+
+static const struct wl_surface_listener surface_listener = {
+ handle_surface_enter,
+ handle_surface_leave,
+ preferred_buffer_scale,
+ preferred_buffer_transform
+};
+
+static void handle_xdg_surface_configure(void *data, struct xdg_surface *surface, u32 serial)
+{
+ struct skh_window_wayland *wl = (struct skh_window_wayland *)data;
+ xdg_surface_ack_configure(surface, serial);
+ if (!wl->pending) return;
+ wl->pending = false;
+ s32 width = wl->w.width;
+ s32 height = wl->w.height;
+ xdg_surface_set_window_geometry(wl->xdg_surface, 0, 0, width, height);
+ if (skh_egl_is_ready(&wl->egl)) {
+ wl_egl_window_resize(wl->egl_window, width, height, 0, 0);
+ }
+ skh_window_send_resize_event(&wl->w, width, height);
+}
+
+static const struct xdg_surface_listener xdg_surface_listener = {
+ handle_xdg_surface_configure,
+};
+
+static void handle_xdg_toplevel_configure(void *data, struct xdg_toplevel *toplevel,
+ s32 width, s32 height, struct wl_array *states)
+{
+ struct skh_window_wayland *wl = (struct skh_window_wayland *)data;
+ (void)toplevel;
+ (void)states;
+ if ((!width && !height) || (width == wl->w.width && height == wl->w.height)) {
+ return;
+ }
+ wl->w.width = width;
+ wl->w.height = height;
+ wl->pending = true;
+}
+
+static void handle_xdg_toplevel_close(void *data, struct xdg_toplevel *toplevel)
+{
+ struct skh_window_wayland *wl = (struct skh_window_wayland *)data;
+ (void)toplevel;
+ skh_window_send_should_close_event(&wl->w);
+}
+
+static void handle_xdg_toplevel_configure_bounds(void *data, struct xdg_toplevel *toplevel, s32 width, s32 height)
+{
+ (void)data;
+ (void)toplevel;
+ (void)width;
+ (void)height;
+}
+
+static const struct xdg_toplevel_listener xdg_toplevel_listener = {
+ handle_xdg_toplevel_configure,
+ handle_xdg_toplevel_close,
+ handle_xdg_toplevel_configure_bounds,
+ NULL
+};
+
+static bool wl_egl_init(struct skh_window_wayland *wl)
+{
+ if (!skh_load_egl(&wl->egl)) return false;
+
+ wl->egl_window = wl_egl_window_create(wl->surface, wl->w.width, wl->w.height);
+ wl->egl.display = eglGetDisplay((EGLNativeDisplayType)wl->display);
+ if (!wl->egl.display) {
+ al_log_error("window_wayland", "Failed to get wayland-native EGL display.");
+ return false;
+ }
+
+ EGLConfig config;
+ if (!skh_init_egl(&wl->egl, 0, &config)) {
+ return false;
+ }
+
+#if defined SEKIHI_API_OPENGL
+ wl->w.get_gl_proc_address = eglGetProcAddress;
+#endif
+
+ //wl->egl.surface = eglCreatePlatformWindowSurface(wl->egl.display, config, wl->egl_window, NULL);
+ wl->egl.surface = eglCreateWindowSurface(wl->egl.display, config, (EGLNativeWindowType)wl->egl_window, NULL);
+
+ return true;
+}
+
+//static const struct wl_callback_listener frame_listener;
+
+static bool window_wayland_create_window(struct skh_window *window, s32 width, s32 height, const char *name)
+{
+ struct skh_window_wayland *wl = (struct skh_window_wayland *)window;
+
+ wl->w.width = width;
+ wl->w.height = height;
+ wl->pending = true;
+
+ wl->display = wl_display_connect(NULL);
+ if (!wl->display) { al_assert(false); }
+
+ wl->registry = wl_display_get_registry(wl->display);
+ wl_registry_add_listener(wl->registry, &registry_listener, window);
+ wl_display_roundtrip(wl->display);
+
+ wl->surface = wl_compositor_create_surface(wl->compositor);
+ if (!wl->surface) { al_assert(false); }
+ wl_surface_add_listener(wl->surface, &surface_listener, window);
+
+ wl->xdg_surface = xdg_wm_base_get_xdg_surface(wl->xdg_wm_base, wl->surface);
+ if (!wl->xdg_surface) { al_assert(false); }
+ xdg_surface_add_listener(wl->xdg_surface, &xdg_surface_listener, window);
+
+ wl->xdg_toplevel = xdg_surface_get_toplevel(wl->xdg_surface);
+ if (!wl->xdg_toplevel) { al_assert(false); }
+ xdg_toplevel_add_listener(wl->xdg_toplevel, &xdg_toplevel_listener, window);
+
+ xdg_toplevel_set_title(wl->xdg_toplevel, name);
+ xdg_toplevel_set_app_id(wl->xdg_toplevel, name);
+ xdg_toplevel_set_min_size(wl->xdg_toplevel, SEKIHI_MIN_WIDTH, SEKIHI_MIN_HEIGHT);
+
+ wl->zxdg_toplevel_decoration = zxdg_decoration_manager_v1_get_toplevel_decoration(
+ wl->zxdg_decoration_manager, wl->xdg_toplevel);
+ zxdg_toplevel_decoration_v1_set_mode(wl->zxdg_toplevel_decoration,
+ ZXDG_TOPLEVEL_DECORATION_V1_MODE_SERVER_SIDE);
+
+ wl->fds[0].fd = wl_display_get_fd(wl->display);
+ wl->fds[0].events = POLLIN;
+
+#if defined SEKIHI_PAUSE
+ wl->fds[1].fd = eventfd(0, 0);
+ wl->fds[1].events = POLLIN;
+#endif
+
+ //wl->frame_callback = wl_surface_frame(wl->surface);
+ //wl_callback_add_listener(wl->frame_callback, &frame_listener, wl);
+
+ wl_surface_commit(wl->surface);
+ wl_display_roundtrip(wl->display);
+
+ if (!wl_egl_init(wl)) {
+ return false;
+ }
+
+ skh_egl_swap_interval(&wl->egl, 1);
+
+ return true;
+}
+
+static void window_wayland_resize(struct skh_window *window, s32 width, s32 height)
+{
+ (void)window;
+ (void)width;
+ (void)height;
+}
+
+static void window_wayland_toggle_fullscreen(struct skh_window *window)
+{
+ struct skh_window_wayland *wl = (struct skh_window_wayland *)window;
+ if (!wl->w.fullscreen) {
+ xdg_toplevel_set_fullscreen(wl->xdg_toplevel, NULL);
+ } else {
+ xdg_toplevel_unset_fullscreen(wl->xdg_toplevel);
+ }
+ wl->w.fullscreen = !wl->w.fullscreen;
+}
+
+static bool window_wayland_poll(struct skh_window *window, bool block)
+{
+ struct skh_window_wayland *wl = (struct skh_window_wayland *)window;
+
+ wl->fds[0].revents = 0;
+#if defined SEKIHI_PAUSE
+ wl->fds[1].revents = 0;
+#endif
+
+ if (wl_display_prepare_read(wl->display) != 0) {
+ wl_display_dispatch_pending(wl->display);
+ return false;
+ }
+
+ wl_display_flush(wl->display);
+
+#if defined SEKIHI_POLL_CONSERVATIVE
+#if defined SEKIHI_PAUSE
+ (void)block;
+ if (poll(wl->fds, 2, -1) <= 0) {
+#else
+ (void)block;
+ if (poll(wl->fds, 1, -1) <= 0) {
+#endif
+#elif defined SEKIHI_POLL_INLINE
+#if defined SEKIHI_PAUSE
+ if (poll(wl->fds, 2, (block) ? -1 : 0) <= 0) {
+#else
+ (void)block;
+ if (poll(wl->fds, 1, 0) <= 0) {
+#endif
+#endif
+ wl_display_cancel_read(wl->display);
+ return false;
+ }
+
+ if (wl->fds[0].revents & POLLIN) {
+ wl_display_read_events(wl->display);
+ wl_display_dispatch_pending(wl->display);
+ } else {
+ wl_display_cancel_read(wl->display);
+ }
+
+#if defined SEKIHI_PAUSE
+ if (wl->fds[1].revents & POLLIN) {
+ s64 b;
+ s64 ret = read(wl->fds[1].fd, &b, sizeof(s64));
+ al_assert(ret == sizeof(s64));
+ return true;
+ }
+#endif
+#if defined SEKIHI_POLL_INLINE
+ if (wl->w.pending_refresh) {
+ wl->w.pending_refresh = false;
+ return true;
+ }
+#endif
+ return !block;
+}
+
+#if defined SEKIHI_PAUSE
+static void window_wayland_wake(struct skh_window *window)
+{
+ struct skh_window_wayland *wl = (struct skh_window_wayland *)window;
+ wl->w.pending_refresh = true;
+ s64 b = 1;
+ ssize_t ret = write(wl->fds[1].fd, &b, sizeof(s64));
+ al_assert(ret == sizeof(s64));
+}
+#endif
+
+static void window_wayland_set_ext_data(struct skh_window *window, void *ext_data)
+{
+ (void)window;
+ (void)ext_data;
+}
+
+static void window_wayland_free(struct skh_window **window)
+{
+ struct skh_window_wayland *wl = (struct skh_window_wayland *)*window;
+ if (!wl->display) return;
+ wl_registry_destroy(wl->registry);
+ wl_seat_destroy(wl->seat);
+ wl_pointer_destroy(wl->pointer);
+ wl_keyboard_destroy(wl->keyboard);
+ wl_compositor_destroy(wl->compositor);
+ xdg_wm_base_destroy(wl->xdg_wm_base);
+ xdg_surface_destroy(wl->xdg_surface);
+ zxdg_toplevel_decoration_v1_destroy(wl->zxdg_toplevel_decoration);
+ zxdg_decoration_manager_v1_destroy(wl->zxdg_decoration_manager);
+ xdg_toplevel_destroy(wl->xdg_toplevel);
+ skh_egl_destroy_surface(&wl->egl);
+ wl_egl_window_destroy(wl->egl_window);
+ wl_surface_destroy(wl->surface);
+ skh_egl_destroy_context(&wl->egl);
+ skh_egl_terminate(&wl->egl);
+ wl_display_disconnect(wl->display);
+#if defined SEKIHI_POLL_CONSERVATIVE
+ al_free(wl->w.data);
+#endif
+ al_free(wl);
+ *window = NULL;
+}
+
+/*
+static void feedback_sync_output(void *data, struct wp_presentation_feedback *feedback,
+ struct wl_output *output)
+{
+ (void)data;
+ (void)feedback;
+ (void)output;
+}
+
+static void feedback_presented(void *data, struct wp_presentation_feedback *feedback,
+ u32 tvSecHi, u32 tvSecLo, u32 tvNsec, u32 refreshNsec, u32 seqHi, u32 seqLo, u32 flags)
+{
+ struct skh_window_wayland *wl = (struct skh_window_wayland *)data;
+ (void)refreshNsec;
+ (void)seqHi;
+ (void)seqLo;
+ (void)flags;
+
+ u64 sec = (u64)tvSecLo + ((u64)tvSecHi << 32);
+ u64 nsec = sec * 1000000000 + (u64)tvNsec;
+ wl->presentation_time = ((f64)(nsec * 1e-9)) - wl->timer_offset;
+
+ printf("%f\n", wl->presentation_time);
+
+ wp_presentation_feedback_destroy(feedback);
+}
+
+static void feedback_discarded(void *data, struct wp_presentation_feedback *feedback)
+{
+ struct skh_window_wayland *wl = (struct skh_window_wayland *)data;
+
+ wl->presentation_time = 0.0;
+
+ wp_presentation_feedback_destroy(feedback);
+}
+
+static const struct wp_presentation_feedback_listener feedback_listener = {
+ feedback_sync_output,
+ feedback_presented,
+ feedback_discarded
+};
+
+static void frame_callback(void *data, struct wl_callback *callback, u32 time)
+{
+ struct skh_window_wayland *wl = (struct skh_window_wayland *)data;
+ (void)time;
+
+ if (callback) wl_callback_destroy(callback);
+
+ wl->frame_callback = wl_surface_frame(wl->surface);
+ wl_callback_add_listener(wl->frame_callback, &frame_listener, wl);
+
+ struct wp_presentation_feedback *feedback = wp_presentation_feedback(wl->presentation, wl->surface);
+ wp_presentation_feedback_add_listener(feedback, &feedback_listener, wl);
+}
+
+static const struct wl_callback_listener frame_listener = {
+ frame_callback,
+};
+*/
+
+#if defined SEKIHI_API_OPENGL
+static bool window_wayland_gl_load_loader(void *data)
+{
+ struct skh_window_wayland *wl = (struct skh_window_wayland *)data;
+#ifdef SEKIHI_USE_GLAD
+ al_assert(wl->w.get_gl_proc_address);
+ if (!gladLoadEGL(wl->egl.display, (GLADloadfunc)wl->w.get_gl_proc_address)) {
+ return false;
+ }
+#ifdef SEKIHI_USE_GLES
+ if (!gladLoadGLES2((GLADloadfunc)wl->w.get_gl_proc_address)) {
+ return false;
+ }
+#else
+ if (!gladLoadGL((GLADloadfunc)wl->w.get_gl_proc_address)) {
+ return false;
+ }
+#endif
+#endif
+ return true;
+}
+
+static bool window_wayland_gl_make_current(void *data)
+{
+ struct skh_window_wayland *wl = (struct skh_window_wayland *)data;
+ skh_egl_make_current(&wl->egl);
+ return true;
+}
+
+static void window_wayland_gl_release_current(void *data)
+{
+ struct skh_window_wayland *wl = (struct skh_window_wayland *)data;
+ skh_egl_release_current(&wl->egl);
+}
+
+static void window_wayland_gl_swap_buffers(void *data)
+{
+ struct skh_window_wayland *wl = (struct skh_window_wayland *)data;
+ skh_egl_swap_buffers(&wl->egl);
+}
+#endif
+
+struct skh_window *skh_window_wayland_create(void)
+{
+ struct skh_window_wayland *wl = (struct skh_window_wayland *)al_calloc(
+ 1, sizeof(struct skh_window_wayland));
+#if defined SEKIHI_POLL_CONSERVATIVE
+ wl->w.data = al_malloc(SEKIHI_WINDOW_EVENTS_SIZE);
+ al_ring_buffer_init(&wl->w.events, wl->w.data, SEKIHI_WINDOW_EVENTS_SIZE);
+#endif
+ wl->w.fullscreen = false;
+ wl->w.create_window = window_wayland_create_window;
+ wl->w.resize = window_wayland_resize;
+ wl->w.toggle_fullscreen = window_wayland_toggle_fullscreen;
+ wl->w.poll = window_wayland_poll;
+#if defined SEKIHI_POLL_INLINE
+ wl->w.pending_refresh = true;
+#endif
+#if defined SEKIHI_PAUSE
+ wl->w.wake = window_wayland_wake;
+#endif
+ wl->w.set_ext_data = window_wayland_set_ext_data;
+ wl->w.free = window_wayland_free;
+#if defined SEKIHI_API_VULKAN
+#elif defined SEKIHI_API_OPENGL
+ wl->w.get_gl_proc_address = NULL;
+ wl->w.gl_load_loader = window_wayland_gl_load_loader;
+ wl->w.gl_make_current = window_wayland_gl_make_current;
+ wl->w.gl_release_current = window_wayland_gl_release_current;
+ wl->w.gl_swap_buffers = window_wayland_gl_swap_buffers;
+#endif
+ return (struct skh_window *)wl;
+}
diff --git a/src/window_wayland.h b/src/window_wayland.h
new file mode 100644
index 0000000..cbd9659
--- /dev/null
+++ b/src/window_wayland.h
@@ -0,0 +1,51 @@
+#pragma once
+
+#include <al/lib.h>
+
+#include <poll.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/eventfd.h>
+#include <wayland-client-core.h>
+#include <wayland-client-protocol.h>
+#include <wayland-egl-core.h>
+#include <wayland-egl.h>
+#include <wayland-client.h>
+#include <xdg-shell-client-protocol.h>
+#include <xdg-decoration-unstable-v1-client-protocol.h>
+//#include <presentation-time-client-protocol.h>
+
+#include "window.h"
+#include "egl_common.h"
+
+struct skh_window_wayland {
+ struct skh_window w;
+ struct wl_display *display;
+ struct wl_registry *registry;
+ struct wl_seat *seat;
+ struct wl_pointer *pointer;
+ struct wl_keyboard *keyboard;
+ struct wl_compositor *compositor;
+ struct wl_surface *surface;
+ bool pending;
+ struct xdg_wm_base *xdg_wm_base;
+ struct xdg_surface *xdg_surface;
+ struct xdg_toplevel *xdg_toplevel;
+ struct zxdg_decoration_manager_v1 *zxdg_decoration_manager;
+ struct zxdg_toplevel_decoration_v1 *zxdg_toplevel_decoration;
+ //struct wl_callback *frame_callback;
+ //struct wp_presentation *presentation;
+ struct skh_egl egl;
+ struct wl_egl_window *egl_window;
+#if defined SEKIHI_PAUSE
+ struct pollfd fds[2];
+#else
+ struct pollfd fds[1];
+#endif
+ f64 pointer_x;
+ f64 pointer_y;
+ f64 last_pointer_x;
+ f64 last_pointer_y;
+};
+
+struct skh_window *skh_window_wayland_create(void);