diff options
| author | Roberto Esteves <contact@robertoesteves.dev> | 2025-11-22 16:35:06 +0000 |
|---|---|---|
| committer | Roberto Esteves <contact@robertoesteves.dev> | 2025-11-22 16:35:06 +0000 |
| commit | e7c2ec9719349f7a20dd1d4854869c10d8836a11 (patch) | |
| tree | af5b34d8d21cabd4ec6d4206ce5a6fd4651de56c /core | |
| parent | d84537c52f537920369618028f84b48296811fd0 (diff) | |
Diffstat (limited to 'core')
| -rw-r--r-- | core/event.c | 2 | ||||
| -rw-r--r-- | core/meson.build | 16 | ||||
| -rw-r--r-- | core/renderer/gles3.c | 92 | ||||
| -rw-r--r-- | core/surface/egl.c | 147 | ||||
| -rw-r--r-- | core/window.c | 141 | ||||
| -rw-r--r-- | core/window/xcb.c | 232 |
6 files changed, 630 insertions, 0 deletions
diff --git a/core/event.c b/core/event.c new file mode 100644 index 0000000..e559e8d --- /dev/null +++ b/core/event.c @@ -0,0 +1,2 @@ +#include "core/event.h" +#include "core/window.h" diff --git a/core/meson.build b/core/meson.build new file mode 100644 index 0000000..b58e1ef --- /dev/null +++ b/core/meson.build @@ -0,0 +1,16 @@ +core_sources = files('window.c', 'event.c') +core_deps = [] + +if os == 'linux' + core_sources += ['window/xcb.c', 'surface/egl.c', 'renderer/gles3.c'] + core_deps += [ dependency('egl'), dependency('glesv2'), dependency('xcb') ] +endif + +core_library = library( + 'RuimCore', + core_sources, + include_directories: ruim_include, + dependencies: core_deps, + link_with: internal_lib, + install: true, +) diff --git a/core/renderer/gles3.c b/core/renderer/gles3.c new file mode 100644 index 0000000..5dd1538 --- /dev/null +++ b/core/renderer/gles3.c @@ -0,0 +1,92 @@ +#include "core/renderer.h" +#include "core/window.h" +#include "internal/utils.h" + +#include <GLES2/gl2.h> +#include <GLES3/gl3.h> +#include <stddef.h> +#include <stdio.h> +#include <stdlib.h> + +GLuint load_shader(const char *code, uint type) { + int success; + char infoLog[512]; + GLuint shader = glCreateShader(type); + + glShaderSource(shader, 1, &code, NULL); + glCompileShader(shader); + glGetShaderiv(shader, GL_COMPILE_STATUS, &success); + + if (!success) { + glGetShaderInfoLog(shader, 512, NULL, infoLog); + printf("Shader compilation failed: %s\n", infoLog); + } + + return shader; +} + +struct ruim_rendererGLES RuimSetupGLES(void) { + int success; + char infoLog[512]; + struct ruim_rendererGLES renderer; + float vertices[] = {-0.5f, -0.5f, 0.0f, 0.5f, -0.5f, 0.0f, 0.0f, 0.5f, 0.0f}; + + GLuint vertexShdr = + load_shader("#version 300 es\n" + "layout (location = 0) in vec3 aPos;\n" + "void main()\n" + "{\n" + "gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0);\n" + "}\0", + GL_VERTEX_SHADER); + + GLuint fragmentShdr = load_shader("#version 300 es\n" + "precision mediump float;\n" + "out vec4 FragColor;\n" + "void main()\n" + "{\n" + "FragColor = vec4(1.0, 0.5, 0.2, 1.0);\n" + "}\0", + GL_FRAGMENT_SHADER); + + glGenBuffers(1, &renderer.VBO); + glGenVertexArrays(1, &renderer.VAO); + + renderer.shaderProgram = glCreateProgram(); + glAttachShader(renderer.shaderProgram, vertexShdr); + glAttachShader(renderer.shaderProgram, fragmentShdr); + glLinkProgram(renderer.shaderProgram); + glGetProgramiv(renderer.shaderProgram, GL_LINK_STATUS, &success); + if (!success) { + glGetProgramInfoLog(renderer.shaderProgram, 512, NULL, infoLog); + printf("Program linking failed: %s\n", infoLog); + } + + glDeleteShader(vertexShdr); + glDeleteShader(fragmentShdr); + + glBindVertexArray(renderer.VAO); + + glBindBuffer(GL_ARRAY_BUFFER, renderer.VBO); + glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW); + + glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void *)0); + glEnableVertexAttribArray(0); + + glBindVertexArray(0); + + return renderer; +} + +void RuimResizeGLES(struct ruim_rendererGLES *renderer, int width, int height) { + glViewport(0, 0, width, height); +} + +void RuimRenderGLES(struct ruim_rendererGLES *renderer) { + glClearColor(0.2f, 0.3f, 0.3f, 1.0f); + glClear(GL_COLOR_BUFFER_BIT); + + glUseProgram(renderer->shaderProgram); + glBindVertexArray(renderer->VAO); + glDrawArrays(GL_TRIANGLES, 0, 3); +} diff --git a/core/surface/egl.c b/core/surface/egl.c new file mode 100644 index 0000000..f0370ce --- /dev/null +++ b/core/surface/egl.c @@ -0,0 +1,147 @@ +#include "core/event.h" +#include "core/renderer.h" +#include "core/window.h" +#include "internal/utils.h" + +#include <stddef.h> +#include <stdio.h> +#include <stdlib.h> + +/* Get human-readable EGL error string */ +const char *egl_error_string(EGLint error) { + switch (error) { + case EGL_SUCCESS: + return "EGL_SUCCESS"; + case EGL_NOT_INITIALIZED: + return "EGL_NOT_INITIALIZED"; + case EGL_BAD_ACCESS: + return "EGL_BAD_ACCESS"; + case EGL_BAD_ALLOC: + return "EGL_BAD_ALLOC"; + case EGL_BAD_ATTRIBUTE: + return "EGL_BAD_ATTRIBUTE"; + case EGL_BAD_CONFIG: + return "EGL_BAD_CONFIG"; + case EGL_BAD_CONTEXT: + return "EGL_BAD_CONTEXT"; + case EGL_BAD_CURRENT_SURFACE: + return "EGL_BAD_CURRENT_SURFACE"; + case EGL_BAD_DISPLAY: + return "EGL_BAD_DISPLAY"; + case EGL_BAD_MATCH: + return "EGL_BAD_MATCH"; + case EGL_BAD_NATIVE_PIXMAP: + return "EGL_BAD_NATIVE_PIXMAP"; + case EGL_BAD_NATIVE_WINDOW: + return "EGL_BAD_NATIVE_WINDOW"; + case EGL_BAD_PARAMETER: + return "EGL_BAD_PARAMETER"; + case EGL_BAD_SURFACE: + return "EGL_BAD_SURFACE"; + case EGL_CONTEXT_LOST: + return "EGL_CONTEXT_LOST"; + default: + return "UNKNOWN_EGL_ERROR"; + } +} + +/* Crash with EGL error information + * */ +void crash_egl(const char *expr, const char *file, int line) { + EGLint error = eglGetError(); + fprintf(stderr, "EGL Error at %s:%d\n", file, line); + fprintf(stderr, "Expression: %s\n", expr); + fprintf(stderr, "Error code: 0x%x (%s)\n", error, egl_error_string(error)); + abort(); +} + +/* Macro for checking EGL boolean returns (EGL_TRUE/EGL_FALSE) */ +#define assert_egl(expr) \ + do { \ + if (!(expr)) \ + crash_egl(#expr, __FILE__, __LINE__); \ + } while (0) + +void RuimInitEGL(RuimToplevel *toplevel) { + RuimSurfaceEGL ruimSurface; + EGLint version_major, version_minor; + EGLConfig config; + EGLint num_config; + struct toplevel_x11 *x11_toplevel = (struct toplevel_x11 *)toplevel->data; + struct backend_x11 *x11_backend = + (struct backend_x11 *)toplevel->backend->data; + EGLint constraints[] = { + EGL_SURFACE_TYPE, + EGL_WINDOW_BIT, + EGL_RENDERABLE_TYPE, + EGL_OPENGL_ES3_BIT, + EGL_RED_SIZE, + 8, + EGL_GREEN_SIZE, + 8, + EGL_BLUE_SIZE, + 8, + EGL_ALPHA_SIZE, + 8, + EGL_DEPTH_SIZE, + 24, + EGL_NONE, + }; + + /* egl-contexts collect all state descriptions needed required for operation + */ + EGLint ctxattr[] = {EGL_CONTEXT_MAJOR_VERSION, 3, EGL_CONTEXT_MINOR_VERSION, + 0, EGL_NONE}; + + const char *client_extensions = + eglQueryString(EGL_NO_DISPLAY, EGL_EXTENSIONS); + + if (!client_extensions) { + abort(); + } + if (!strstr(client_extensions, "EGL_EXT_platform_xcb")) { + abort(); + } + + ruimSurface.display = eglGetPlatformDisplay(EGL_PLATFORM_XCB_EXT, + x11_backend->connection, NULL); + + assert_egl(ruimSurface.display); + + assert_egl( + eglInitialize(ruimSurface.display, &version_major, &version_minor)); + ; + assert_egl(eglChooseConfig(ruimSurface.display, constraints, &config, 1, + &num_config)); + assert_egl(eglBindAPI(EGL_OPENGL_ES_API)); + + ruimSurface.surface = eglCreatePlatformWindowSurface( + ruimSurface.display, config, &x11_toplevel->window, NULL); + assert_egl(ruimSurface.surface); + + ruimSurface.context = + eglCreateContext(ruimSurface.display, config, EGL_NO_CONTEXT, ctxattr); + assert_egl(ruimSurface.context); + + assert_egl(eglMakeCurrent(ruimSurface.display, ruimSurface.surface, + ruimSurface.surface, ruimSurface.context)); + assert_egl(eglSwapInterval(ruimSurface.display, 1)); + + x11_backend->surface = ruimSurface; +} + +EGLBoolean RuimSwapEGL(RuimToplevel *toplevel) { + struct backend_x11 *x11_backend = + (struct backend_x11 *)toplevel->backend->data; + + return eglSwapBuffers(x11_backend->surface.display, + x11_backend->surface.surface); +} + +void RuimDeinitEGL(RuimSurfaceEGL *eglCtx) { + assert_egl(eglMakeCurrent(eglCtx->display, EGL_NO_SURFACE, EGL_NO_SURFACE, + EGL_NO_CONTEXT)); + assert_egl(eglDestroyContext(eglCtx->display, eglCtx->context)); + assert_egl(eglDestroySurface(eglCtx->display, eglCtx->surface)); + assert_egl(eglTerminate(eglCtx->display)); +} diff --git a/core/window.c b/core/window.c new file mode 100644 index 0000000..6db3f5b --- /dev/null +++ b/core/window.c @@ -0,0 +1,141 @@ +#include "core/window.h" +#include "core/event.h" +#include "core/renderer.h" +#include "core/types.h" +#include "internal/utils.h" + +/* other stuff */ + +int RuimToplevelBackendInit(RuimToplevelBackend *backend) { + if (backend == NULL) { + crash_error("backend must be non-null"); + } + + if (backend->type == 0) { + /* TODO: select default one based on platform */ + backend->type = RUIM_TOPLEVEL_X11; + } + + switch (backend->type) { +#ifdef RUIM_PLATFORM_LINUX + case RUIM_TOPLEVEL_X11: { + return _init_xcb_backend(backend); + } break; +#endif + default: + printf("%d\n", backend->type); + crash_error("unrecognized backend type"); + } + + crash_error("unreachable"); + return -1; +} + +int RuimToplevelBackendDeinit(RuimToplevelBackend *backend) { + if (backend == NULL || backend->type == 0) { + crash_error("some crash"); + } + + switch (backend->type) { +#ifdef RUIM_PLATFORM_LINUX + case RUIM_TOPLEVEL_X11: { + return _deinit_xcb_backend(backend); + } break; +#endif + default: + crash_error("some crash"); + } + + return 0; +} + +int RuimToplevelCreate(RuimToplevel *toplevel, RuimToplevelType type) { + if (type && type != RUIM_TOPLEVEL_WINDOW) { + crash_error("TODO: toplevel must be a window"); + } + + if (toplevel->backend == NULL) { + crash_error("TODO: backend must be set"); + } + + switch (toplevel->backend->type) { +#ifdef RUIM_PLATFORM_LINUX + case RUIM_TOPLEVEL_X11: { + return _create_toplevel_xcb(toplevel); + } break; +#endif + default: + crash_error("unknown backend type"); + } + + return 0; +} + +int RuimToplevelDisplay(RuimToplevel *toplevel) { + if (toplevel == NULL) { + crash_error("toplevel must be non-null"); + } + + switch (toplevel->backend->type) { +#ifdef RUIM_PLATFORM_LINUX + case RUIM_TOPLEVEL_X11: { + return _display_toplevel_xcb(toplevel); + } break; +#endif + default: + crash_error("some crash"); + } + + return 0; +} + +int RuimToplevelBackendPoll(RuimToplevelBackend *backend, RuimEvent *event) { + switch (backend->type) { +#ifdef RUIM_PLATFORM_LINUX + case RUIM_TOPLEVEL_X11: { + if (_poll_event_xcb(backend, event) != 0) { + crash_error("xcb connection error"); + } + } break; +#endif + default: + crash_error("unknown backend type"); + } + + if (event->type == RUIM_EVENT_NOTHING) { + return 0; + } else { + return 1; + } +} + +int RuimToplevelWaitForEvent(RuimToplevel *toplevel) { + switch (toplevel->backend->type) { +#ifdef RUIM_PLATFORM_LINUX + case RUIM_TOPLEVEL_X11: { + return _wait_event_xcb(toplevel); + } break; +#endif + default: + crash_error("unknown backend type"); + } + + crash_error("unreachable"); + return -1; +} + +int RuimToplevelDestroy(RuimToplevel *toplevel) { + + switch (toplevel->backend->type) { +#ifdef RUIM_PLATFORM_LINUX + case RUIM_TOPLEVEL_X11: { + return _destroy_toplevel_xcb(toplevel); + } break; +#endif + default: + crash_error("unknown backend type"); + } + + crash_error("unreachable"); + return -1; +} diff --git a/core/window/xcb.c b/core/window/xcb.c new file mode 100644 index 0000000..94196ec --- /dev/null +++ b/core/window/xcb.c @@ -0,0 +1,232 @@ +#include "core/types.h" +#include "core/window.h" +#include "internal/utils.h" + +#include "core/event.h" + +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <xcb/xcb.h> +#include <xcb/xproto.h> + +/* TODO(roberto): put the correct error values */ +static void _xcb_handle_error(int error) { + switch (error) { + case XCB_WINDOW: + crash_error("Invalid window error\n"); + case XCB_PIXMAP: + crash_error("Invalid pixmap error\n"); + case XCB_ATOM: + crash_error("Invalid atom error\n"); + case XCB_VALUE: + crash_error("Invalid value error\n"); + case XCB_MATCH: + crash_error("Match error (incompatible parameters)\n"); + case XCB_DRAWABLE: + crash_error("Invalid drawable error\n"); + case XCB_ACCESS: + crash_error("Access denied error\n"); + case XCB_ALLOC: + crash_error("Resource allocation error\n"); + default: + crash_error("Unknown error\n"); + } +} + +static xcb_atom_t _get_atom_xcb(xcb_connection_t *conn, const char *name) { + xcb_intern_atom_cookie_t cookie; + xcb_intern_atom_reply_t *reply; + xcb_atom_t atom; + + cookie = xcb_intern_atom(conn, 0, strlen(name), name); + reply = xcb_intern_atom_reply(conn, cookie, NULL); + + if (reply) { + atom = reply->atom; + free(reply); + return atom; + } + + return XCB_ATOM_NONE; +} + +int _init_xcb_backend(RuimToplevelBackend *backend) { + struct backend_x11 *x11; + xcb_screen_iterator_t iter; + int error = 0; + + x11 = (struct backend_x11 *)malloc(sizeof(struct backend_x11)); + x11->connection = xcb_connect(NULL, &x11->screen_num); + if ((error = xcb_connection_has_error(x11->connection)) != 0) { + return error; + } + + x11->poll.fd = xcb_get_file_descriptor(x11->connection); + x11->poll.events = POLLIN; + + x11->setup = xcb_get_setup(x11->connection); + iter = xcb_setup_roots_iterator(x11->setup); + x11->screen = iter.data; + + x11->wm_protocols = _get_atom_xcb(x11->connection, "WM_PROTOCOLS"); + x11->wm_delete_window = _get_atom_xcb(x11->connection, "WM_DELETE_WINDOW"); + + backend->data = (void *)x11; + + return 0; +} + +int _deinit_xcb_backend(RuimToplevelBackend *backend) { + struct backend_x11 *x11 = (struct backend_x11 *)backend->data; + xcb_disconnect(x11->connection); + + free(backend->data); + return 0; +} + +/* TODO: support more toplevel types */ +int _create_toplevel_xcb(RuimToplevel *toplevel) { + int error; + struct backend_x11 *x11_backend; + struct toplevel_x11 *x11_toplevel; + + x11_toplevel = (struct toplevel_x11 *)malloc(sizeof(struct toplevel_x11)); + x11_toplevel->event_values = (uint32_t *)malloc(sizeof(uint32_t) * 2); + x11_backend = (struct backend_x11 *)toplevel->backend->data; + if ((error = xcb_connection_has_error(x11_backend->connection)) != 0) { + return error; + } + + x11_toplevel->window = xcb_generate_id(x11_backend->connection); + x11_toplevel->event_mask = XCB_CW_EVENT_MASK; + x11_toplevel->event_values[0] = + XCB_EVENT_MASK_EXPOSURE | XCB_EVENT_MASK_BUTTON_PRESS | + XCB_EVENT_MASK_KEY_PRESS | XCB_EVENT_MASK_STRUCTURE_NOTIFY; + + xcb_create_window(x11_backend->connection, XCB_COPY_FROM_PARENT, + x11_toplevel->window, x11_backend->screen->root, 0, 0, 150, + 150, 10, XCB_WINDOW_CLASS_INPUT_OUTPUT, + x11_backend->screen->root_visual, x11_toplevel->event_mask, + x11_toplevel->event_values); + + xcb_change_property(x11_backend->connection, XCB_PROP_MODE_REPLACE, + x11_toplevel->window, x11_backend->wm_protocols, + XCB_ATOM_ATOM, 32, 1, &x11_backend->wm_delete_window); + + xcb_flush(x11_backend->connection); + + toplevel->data = (void *)x11_toplevel; + + return 0; +} + +int _display_toplevel_xcb(RuimToplevel *toplevel) { + struct backend_x11 *x11_backend = + (struct backend_x11 *)toplevel->backend->data; + struct toplevel_x11 *x11_toplevel = (struct toplevel_x11 *)toplevel->data; + + xcb_map_window(x11_backend->connection, x11_toplevel->window); + /* FIXME(roberto): decide where to flush the connection */ + return xcb_flush(x11_backend->connection) > 0; +} + +int _poll_event_xcb(RuimToplevelBackend *backend, RuimEvent *event) { + struct backend_x11 *x11_backend = (struct backend_x11 *)backend->data; + xcb_generic_event_t *x11_event; + int error = 0; + /* FIXME(roberto): make it actually poll */ + /* FIXME(roberto): too laggy */ + x11_event = xcb_poll_for_event(x11_backend->connection); + + if (x11_event != NULL) { + switch (x11_event->response_type & ~0x80) { + case XCB_EXPOSE: { + RuimEventRedraw redraw; + xcb_configure_notify_event_t *cfg = + (xcb_configure_notify_event_t *)x11_event; + + event->type = RUIM_EVENT_REDRAW; + redraw.width = cfg->width; + redraw.height = cfg->height; + redraw.x = cfg->x; + redraw.y = cfg->y; + + event->data.redraw = redraw; + } break; + case XCB_CLIENT_MESSAGE: { + xcb_client_message_event_t *cm = (xcb_client_message_event_t *)x11_event; + + if (cm->data.data32[0] == x11_backend->wm_delete_window) { + event->type = RUIM_EVENT_QUIT; + } + } break; + case XCB_KEY_PRESS: { + xcb_key_press_event_t *key = (xcb_key_press_event_t *)x11_event; + + event->type = RUIM_EVENT_KEYDOWN; + event->data.mouse.keycode = key->state; + event->data.mouse.x = key->event_x; + event->data.mouse.y = key->event_y; + } break; + case XCB_KEY_RELEASE: { + xcb_key_release_event_t *key = (xcb_key_press_event_t *)x11_event; + + event->type = RUIM_EVENT_KEYUP; + event->data.mouse.keycode = key->state; + event->data.mouse.x = key->event_x; + event->data.mouse.y = key->event_y; + } break; + case XCB_BUTTON_PRESS: { + xcb_button_press_event_t *button = (xcb_button_press_event_t *)x11_event; + + event->type = RUIM_EVENT_MOUSEDOWN; + event->data.mouse.keycode = button->state; + event->data.mouse.x = button->event_x; + event->data.mouse.y = button->event_y; + } break; + case XCB_BUTTON_RELEASE: { + xcb_button_release_event_t *button = + (xcb_button_press_event_t *)x11_event; + + event->type = RUIM_EVENT_MOUSEUP; + event->data.mouse.keycode = button->state; + event->data.mouse.x = button->event_x; + event->data.mouse.y = button->event_y; + } break; + case XCB_CONFIGURE_NOTIFY: { + xcb_configure_notify_event_t *cfg = + (xcb_configure_notify_event_t *)x11_event; + + event->type = RUIM_EVENT_WINDOW; + event->data.window.width = cfg->width; + event->data.window.height = cfg->height; + event->data.window.x = cfg->x; + event->data.window.y = cfg->y; + } break; + default: + break; + } + } else if ((error = xcb_connection_has_error(x11_backend->connection)) != 0) { + _xcb_handle_error(error); + return error; + } else { + event->type = RUIM_EVENT_NOTHING; + } + + free(x11_event); + return 0; +} + +int _wait_event_xcb(RuimToplevel *toplevel) { + struct backend_x11 *backend = (struct backend_x11 *)toplevel->backend->data; + poll(&backend->poll, 1, 1); + + return 0; +} + +int _destroy_toplevel_xcb(RuimToplevel *toplevel) { + free(toplevel->data); + + return 0; +} |