summaryrefslogtreecommitdiff
path: root/src/main/c/simple_graphics.c
blob: 40d66ba923f50145cc4444e679159b6cf0a6045f (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
#define _POSIX_C_SOURCE 200809L
#include "simple_graphics.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <dlfcn.h>
#include <unistd.h>

#ifdef __CYGWIN__
#define BACKEND_NAME "simple_minifb.dll"
#define BACKEND_ABI "x86_64_v2_win64"
#else
#define BACKEND_NAME "libsimple_minifb.so"
#define BACKEND_ABI "x86_64_v2_SystemV"
#endif

// Executable-relative paths work even when launched outside the checkout.
// Working-directory paths also cover test executables and chapter/root shells.
static void *find_backend(void) {
    char executable[4096], path[8192];
    ssize_t n=readlink("/proc/self/exe",executable,sizeof(executable)-1);
    if (n>0 && n<(ssize_t)sizeof(executable)-1) {
        executable[n]=0;
        char *slash=strrchr(executable,'/');
        if (slash) {
            *slash=0;
            const char *relative[]={"/" BACKEND_NAME,
                "/../../release/" BACKEND_ABI "/" BACKEND_NAME,
                "/../release/" BACKEND_ABI "/" BACKEND_NAME};
            for (unsigned i=0;i<sizeof(relative)/sizeof(*relative);++i) {
                snprintf(path,sizeof(path),"%s%s",executable,relative[i]);
                void *handle=dlopen(path,RTLD_NOW | RTLD_LOCAL);
                if (handle) return handle;
            }
        }
    }
    const char *relative[]={"./" BACKEND_NAME,
        "./build/release/" BACKEND_ABI "/" BACKEND_NAME,
        "./chapter31/build/release/" BACKEND_ABI "/" BACKEND_NAME};
    for (unsigned i=0;i<sizeof(relative)/sizeof(*relative);++i) {
        void *handle=dlopen(relative[i],RTLD_NOW | RTLD_LOCAL);
        if (handle) return handle;
    }
    return NULL;
}

// No MiniFB headers or link dependency in the default runtime. Cygwin's
// dlopen also loads native Windows DLLs; both use the x64 Windows C ABI.
static const struct simple_graphics_api *api;
static void *library;

static int load_graphics(void) {
    // Explicitly disabling graphics also makes headless tests deterministic.
    const char *path = getenv("SIMPLE_MINIFB");
    if (path && strcmp(path, "off") == 0) {
        fprintf(stderr, "Simple graphics: disabled (SIMPLE_MINIFB=off).\n");
        return -1;
    }
    if (api) return 0;
    int explicit_path=path && *path;
    library = explicit_path ? dlopen(path, RTLD_NOW | RTLD_LOCAL) : find_backend();
    if (!library) {
        if (explicit_path)
            fprintf(stderr, "Simple graphics: cannot load MiniFB backend '%s': %s\n", path, dlerror());
        else
            fprintf(stderr, "Simple graphics: MiniFB backend not found beside the executable or in build/release/"
                    BACKEND_ABI ". Run make graphics, or set SIMPLE_MINIFB to its full path.\n");
        return -1;
    }
    if (!explicit_path) path="automatically discovered backend";
    const struct simple_graphics_api *(*get_api)(void) =
        (const struct simple_graphics_api *(*)(void))dlsym(library, "simple_minifb_api");
    const struct simple_graphics_api *candidate = get_api ? get_api() : NULL;
    if (!candidate || candidate->version != SIMPLE_GRAPHICS_VERSION ||
        !candidate->open || !candidate->present || !candidate->poll ||
        !candidate->mouse_x || !candidate->mouse_y || !candidate->button ||
        !candidate->key || !candidate->close || !candidate->wait) {
        fprintf(stderr, "Simple graphics: incompatible MiniFB backend '%s'. Rebuild with make graphics.\n", path);
        dlclose(library);
        library = NULL;
        return -1;
    }
    api = candidate;
    // Keep the library loaded until process exit; its window callbacks may
    // still be registered. Closing a window releases its native resources.
    atexit(api->close);
    return 0;
}

int32_t simple_gfx_open(const char *title, uint32_t width, uint32_t height) {
    return load_graphics() ? -1 : api->open(title, width, height);
}
int32_t simple_gfx_present(const uint32_t *pixels, uint32_t count) {
    return api ? api->present(pixels, count) : -1;
}
int32_t simple_gfx_poll(int32_t wait) { return api ? api->poll(wait) : -1; }
int32_t simple_gfx_mouse_x(void) { return api ? api->mouse_x() : 0; }
int32_t simple_gfx_mouse_y(void) { return api ? api->mouse_y() : 0; }
int32_t simple_gfx_button(int32_t button) { return api ? api->button(button) : 0; }
int32_t simple_gfx_key(int32_t key) { return api ? api->key(key) : 0; }
int32_t simple_gfx_close(void) { if (api) api->close(); return 0; }
int32_t simple_gfx_wait(int64_t timeout) { return api ? api->wait(timeout) : -1; }