diff options
| author | 2023-11-10 14:45:42 -0500 | |
|---|---|---|
| committer | 2023-11-10 14:45:42 -0500 | |
| commit | 08b14e82c9dbc8a7f46480ca585b972549a2063c (patch) | |
| tree | 41030a91ad2f9aeb7539c50ded0d97b79e591ed8 | |
| download | stela-08b14e82c9dbc8a7f46480ca585b972549a2063c.tar.gz stela-08b14e82c9dbc8a7f46480ca585b972549a2063c.tar.bz2 stela-08b14e82c9dbc8a7f46480ca585b972549a2063c.zip | |
Add sekihi
- Subprojects temporarily omitted
Signed-off-by: Andrew Opalach <andrew@akon.city>
| -rw-r--r-- | .gitignore | 5 | ||||
| -rw-r--r-- | README.md | 1 | ||||
| -rw-r--r-- | include/skh/egl.h | 3 | ||||
| -rw-r--r-- | include/skh/window.h | 3 | ||||
| -rw-r--r-- | meson.build | 80 | ||||
| -rw-r--r-- | meson_options.txt | 0 | ||||
| -rwxr-xr-x | scripts/line_count.sh | 2 | ||||
| -rw-r--r-- | src/egl_common.c | 195 | ||||
| -rw-r--r-- | src/egl_common.h | 38 | ||||
| -rw-r--r-- | src/gl_versions.h | 62 | ||||
| -rw-r--r-- | src/sekihi.c | 22 | ||||
| -rw-r--r-- | src/window.c | 178 | ||||
| -rw-r--r-- | src/window.h | 117 | ||||
| -rw-r--r-- | src/window_glfw.c | 362 | ||||
| -rw-r--r-- | src/window_glfw.h | 25 | ||||
| -rw-r--r-- | src/window_wayland.c | 718 | ||||
| -rw-r--r-- | src/window_wayland.h | 51 |
17 files changed, 1862 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..081e483 --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +build*/ +compile_commands.json +.cache/ +.clangd/ +subprojects/ diff --git a/README.md b/README.md new file mode 100644 index 0000000..97fe6c3 --- /dev/null +++ b/README.md @@ -0,0 +1 @@ +## sekihi diff --git a/include/skh/egl.h b/include/skh/egl.h new file mode 100644 index 0000000..b6f5c04 --- /dev/null +++ b/include/skh/egl.h @@ -0,0 +1,3 @@ +#pragma once + +#include "../../src/egl_common.h" diff --git a/include/skh/window.h b/include/skh/window.h new file mode 100644 index 0000000..b0c0068 --- /dev/null +++ b/include/skh/window.h @@ -0,0 +1,3 @@ +#pragma once + +#include "../../src/window.h" diff --git a/meson.build b/meson.build new file mode 100644 index 0000000..a5f7212 --- /dev/null +++ b/meson.build @@ -0,0 +1,80 @@ +project('sekihi', 'c', version: '0.1', + default_options: ['warning_level=2', 'c_std=c99']) + +compiler = meson.get_compiler('c') + +alabaster = subproject('libalabaster').get_variable('alabaster') +akiyo = subproject('libakiyo').get_variable('akiyo') + +sekihi_src = ['src/sekihi.c', 'src/window.c'] +sekihi_deps = [alabaster, akiyo] + +glfw3 = dependency('glfw3', required: false, allow_fallback: true) +if glfw3.found() + sekihi_src += ['src/window_glfw.c'] + sekihi_deps += [glfw3] +endif + +glad = subproject('glad').get_variable('glad') +sekihi_deps += [glad] + +#brcmEGL = compiler.find_library('brcmEGL') +#brcmGLESv2 = compiler.find_library('brcmGLESv2') +#sekihi_deps += [brcmEGL, brcmGLESv2] + +egl = dependency('egl', required: false) +if egl.found() + sekihi_src += ['src/egl_common.c'] + sekihi_deps += [egl] +endif + +wl_client = dependency('wayland-client', required: false) +wl_egl = dependency('wayland-egl', required: false) +if wl_client.found() and wl_egl.found() + wl_protocols = dependency('wayland-protocols', required: false) + if not wl_protocols.found() + wl_protocols_datadir = subproject('wayland-protocols').get_variable('wayland_protocols_srcdir') + else + wl_protocols_datadir = wl_protocols.get_variable(pkgconfig: 'pkgdatadir') + endif + scanner = find_program('wayland-scanner') + scanner_private_code = generator(scanner, output: '@BASENAME@-protocol.c', + arguments: ['private-code','@INPUT@', '@OUTPUT@']) + scanner_client_header = generator(scanner, output: '@BASENAME@-client-protocol.h', + arguments: ['client-header', '@INPUT@','@OUTPUT@']) + protocols_src = [ + scanner_private_code.process( + wl_protocols_datadir + '/stable/xdg-shell/xdg-shell.xml'), + scanner_private_code.process( + wl_protocols_datadir + '/unstable/xdg-decoration/xdg-decoration-unstable-v1.xml'), +# scanner_private_code.process( +# wl_protocols_datadir + '/stable/presentation-time/presentation-time.xml') + ] + protocols_headers = [ + scanner_client_header.process( + wl_protocols_datadir + '/stable/xdg-shell/xdg-shell.xml'), + scanner_client_header.process( + wl_protocols_datadir + '/unstable/xdg-decoration/xdg-decoration-unstable-v1.xml'), +# scanner_client_header.process( +# wl_protocols_datadir + '/stable/presentation-time/presentation-time.xml') + ] + lib_protocols = static_library( + 'protocols', protocols_src + protocols_headers, dependencies: wl_client) + protocols = declare_dependency(link_with: lib_protocols, sources: protocols_headers) + sekihi_src += ['src/window_wayland.c'] + sekihi_deps += [wl_egl, protocols] +endif + +vulkan = dependency('vulkan') +vulkan_headers = vulkan.partial_dependency(includes: true, compile_args: true) +sekihi_deps += [vulkan_headers] + +#sekihi_lib = shared_library('sekihi', sekihi_src, +# install: false, +# c_args: ['-DSEKIHI_WINDOW_NULL', '-DSEKIHI_API_NULL', '-DSEKIHI_POLL_INLINE'], +# gnu_symbol_visibility: 'hidden', +# dependencies: sekihi_deps) + +sekihi = declare_dependency(sources: sekihi_src, + include_directories: include_directories('./include'), + dependencies: [sekihi_deps]) diff --git a/meson_options.txt b/meson_options.txt new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/meson_options.txt diff --git a/scripts/line_count.sh b/scripts/line_count.sh new file mode 100755 index 0000000..aadb675 --- /dev/null +++ b/scripts/line_count.sh @@ -0,0 +1,2 @@ +#! /usr/bin/env sh +find ./src ./include -type f | xargs wc -l 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, ®istry_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); |