teek-sdl2 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 3d084a6f681a0b04a3b68ad4bc0b905ff12a50e8cf08060982752601f1d33cad
4
+ data.tar.gz: 1ecc0e2befda331f9f427e9885d86f2a0261d609ca2cb6600c7a8f6ab3d92add
5
+ SHA512:
6
+ metadata.gz: 7f847401b7b73c85de8d153ca1275d5756b6d5fa258b1dfd7f7a3d4ff451775fa2d47fc541e0234e405c1b01cf49ee49f7cfa7bb32c042b7a1934aad8fcab246
7
+ data.tar.gz: 819be6909a74692e4de3a6e0c00b79305c7f899ba7175e0fe01ae815b9c549dfe5360a827af04988317e3a77806e953021cffa452285f506664b3d298167dd1c
@@ -0,0 +1,112 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'mkmf'
4
+
5
+ def find_sdl2
6
+ # 1. Try SDL2_DIR env var first (explicit override)
7
+ if ENV['SDL2_DIR']
8
+ dir = ENV['SDL2_DIR']
9
+ $INCFLAGS << " -I#{dir}/include"
10
+ $LDFLAGS << " -L#{dir}/lib"
11
+ if have_header('SDL2/SDL.h') && have_library('SDL2')
12
+ return true
13
+ end
14
+ # Also try flat include layout (SDL.h directly in include/)
15
+ if have_header('SDL.h') && have_library('SDL2')
16
+ return true
17
+ end
18
+ abort "SDL2_DIR=#{dir} set but SDL2 not found there"
19
+ end
20
+
21
+ # 2. Try pkg-config
22
+ if pkg_config('sdl2')
23
+ return true
24
+ end
25
+
26
+ # 3. Try Homebrew paths (macOS)
27
+ homebrew_dirs = [
28
+ '/opt/homebrew/opt/sdl2', # Apple Silicon
29
+ '/usr/local/opt/sdl2' # Intel
30
+ ]
31
+
32
+ homebrew_dirs.each do |dir|
33
+ next unless File.directory?(dir)
34
+ inc = "#{dir}/include"
35
+ lib = "#{dir}/lib"
36
+
37
+ # Homebrew SDL2 puts headers in include/SDL2/
38
+ if File.exist?("#{inc}/SDL2/SDL.h")
39
+ $INCFLAGS << " -I#{inc}"
40
+ $LDFLAGS << " -L#{lib}"
41
+ if have_header('SDL2/SDL.h') && have_library('SDL2')
42
+ return true
43
+ end
44
+ end
45
+ end
46
+
47
+ # 4. Try standard system paths
48
+ system_dirs = ['/usr/local', '/usr']
49
+ system_dirs.each do |dir|
50
+ inc = "#{dir}/include"
51
+ lib = "#{dir}/lib"
52
+ if File.exist?("#{inc}/SDL2/SDL.h")
53
+ $INCFLAGS << " -I#{inc}"
54
+ $LDFLAGS << " -L#{lib}"
55
+ if have_header('SDL2/SDL.h') && have_library('SDL2')
56
+ return true
57
+ end
58
+ end
59
+ end
60
+
61
+ # 5. MSYS2/MinGW (Windows)
62
+ if RbConfig::CONFIG['host_os'] =~ /mingw|mswin/
63
+ # MSYS2 installs to the mingw prefix
64
+ mingw_prefix = RbConfig::CONFIG['prefix'] # e.g. C:/msys64/mingw64
65
+ inc = "#{mingw_prefix}/include"
66
+ lib = "#{mingw_prefix}/lib"
67
+ if File.exist?("#{inc}/SDL2/SDL.h")
68
+ $INCFLAGS << " -I#{inc}"
69
+ $LDFLAGS << " -L#{lib}"
70
+ # MinGW uses -lSDL2 (same name)
71
+ if have_header('SDL2/SDL.h') && have_library('SDL2')
72
+ return true
73
+ end
74
+ end
75
+ end
76
+
77
+ false
78
+ end
79
+
80
+ unless find_sdl2
81
+ abort <<~MSG
82
+ SDL2 not found. Install it:
83
+ macOS: brew install sdl2
84
+ Debian: sudo apt-get install libsdl2-dev
85
+ Windows: pacman -S mingw-w64-x86_64-SDL2 (MSYS2)
86
+ Or set SDL2_DIR=/path/to/sdl2
87
+ MSG
88
+ end
89
+
90
+ # SDL2_ttf for text rendering
91
+ unless pkg_config('SDL2_ttf') || have_library('SDL2_ttf', 'TTF_Init', 'SDL2/SDL_ttf.h')
92
+ abort <<~MSG
93
+ SDL2_ttf not found. Install it:
94
+ macOS: brew install sdl2_ttf
95
+ Debian: sudo apt-get install libsdl2-ttf-dev
96
+ Windows: pacman -S mingw-w64-x86_64-SDL2_ttf (MSYS2)
97
+ MSG
98
+ end
99
+
100
+ # SDL2_image for loading PNG, JPG, WebP, etc.
101
+ unless pkg_config('SDL2_image') || have_library('SDL2_image', 'IMG_Init', 'SDL2/SDL_image.h')
102
+ abort <<~MSG
103
+ SDL2_image not found. Install it:
104
+ macOS: brew install sdl2_image
105
+ Debian: sudo apt-get install libsdl2-image-dev
106
+ Windows: pacman -S mingw-w64-x86_64-SDL2_image (MSYS2)
107
+ MSG
108
+ end
109
+
110
+ $srcs = ['teek_sdl2.c', 'sdl2surface.c', 'sdl2bridge.c', 'sdl2text.c', 'sdl2pixels.c', 'sdl2image.c']
111
+
112
+ create_makefile('teek_sdl2')
@@ -0,0 +1,142 @@
1
+ #include "teek_sdl2.h"
2
+
3
+ /* ---------------------------------------------------------
4
+ * Layer 2: Tk bridge
5
+ *
6
+ * Embeds an SDL2 window into a Tk frame using
7
+ * SDL_CreateWindowFrom(). This is the only file that knows
8
+ * about Tk's winfo id. It produces a Layer 1 Renderer.
9
+ * --------------------------------------------------------- */
10
+
11
+ /*
12
+ * Teek::SDL2.create_renderer_from_handle(native_handle) -> Renderer
13
+ *
14
+ * Creates an SDL2 window embedded in the native window identified by
15
+ * native_handle (from Tk's 'winfo id'), then creates a GPU-accelerated
16
+ * renderer on it.
17
+ *
18
+ * The Ruby Viewport class is responsible for getting the handle and
19
+ * calling this. This C function just does the SDL2 work.
20
+ */
21
+ static VALUE
22
+ bridge_create_renderer_from_handle(VALUE self, VALUE handle_val)
23
+ {
24
+ ensure_sdl2_init();
25
+
26
+ /*
27
+ * Handle comes from Teek::Interp#native_window_handle as an Integer:
28
+ * macOS: NSWindow* pointer
29
+ * X11: X Window ID
30
+ * Win: HWND
31
+ */
32
+ void *native_handle = (void *)(uintptr_t)NUM2ULL(handle_val);
33
+
34
+ if (!native_handle) {
35
+ rb_raise(rb_eArgError, "invalid native handle (NULL)");
36
+ }
37
+
38
+ SDL_Window *window = SDL_CreateWindowFrom(native_handle);
39
+ if (!window) {
40
+ rb_raise(rb_eRuntimeError, "SDL_CreateWindowFrom failed: %s", SDL_GetError());
41
+ }
42
+
43
+ SDL_Renderer *sdl_ren = SDL_CreateRenderer(window, -1,
44
+ SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC);
45
+ if (!sdl_ren) {
46
+ /* Fall back to software if GPU not available */
47
+ sdl_ren = SDL_CreateRenderer(window, -1, SDL_RENDERER_SOFTWARE);
48
+ }
49
+ if (!sdl_ren) {
50
+ SDL_DestroyWindow(window);
51
+ rb_raise(rb_eRuntimeError, "SDL_CreateRenderer failed: %s", SDL_GetError());
52
+ }
53
+
54
+ /* Wrap in a Renderer object (Layer 1) */
55
+ VALUE klass = rb_const_get(mTeekSDL2, rb_intern("Renderer"));
56
+ VALUE obj = rb_obj_alloc(klass);
57
+
58
+ struct sdl2_renderer *r;
59
+ TypedData_Get_Struct(obj, struct sdl2_renderer, &renderer_type, r);
60
+ r->window = window;
61
+ r->renderer = sdl_ren;
62
+ r->owned_window = 0; /* Tk owns the parent window */
63
+ r->destroyed = 0;
64
+
65
+ return obj;
66
+ }
67
+
68
+ /*
69
+ * C-level poll function — called directly from teek's event source
70
+ * check proc via function pointer. No Ruby overhead.
71
+ *
72
+ * Signature: void (*)(void *client_data)
73
+ */
74
+ static void
75
+ sdl2_event_check(void *client_data)
76
+ {
77
+ (void)client_data;
78
+
79
+ /*
80
+ * Intentionally a no-op for now. SDL_PollEvent() on macOS pumps
81
+ * the Cocoa run loop, which steals events from Tk and can freeze
82
+ * other windows (e.g. the debug inspector).
83
+ *
84
+ * When we need SDL events (mouse/kb in the viewport), this will
85
+ * be wired up carefully to avoid Cocoa event conflicts.
86
+ */
87
+ }
88
+
89
+ /*
90
+ * Teek::SDL2.poll_events -> Integer
91
+ *
92
+ * Manual pump for use outside the event source (e.g. testing).
93
+ */
94
+ static VALUE
95
+ bridge_poll_events(VALUE self)
96
+ {
97
+ SDL_Event event;
98
+ int count = 0;
99
+
100
+ while (SDL_PollEvent(&event)) {
101
+ count++;
102
+ }
103
+ return INT2NUM(count);
104
+ }
105
+
106
+ /*
107
+ * Teek::SDL2._event_check_fn_ptr -> Integer
108
+ *
109
+ * Returns the address of the C-level SDL2 event check function.
110
+ * Passed to Teek._register_event_source for hot-path polling.
111
+ */
112
+ static VALUE
113
+ bridge_event_check_fn_ptr(VALUE self)
114
+ {
115
+ return ULL2NUM((uintptr_t)sdl2_event_check);
116
+ }
117
+
118
+ /*
119
+ * Teek::SDL2.sdl_quit
120
+ *
121
+ * Shuts down SDL2 subsystems. Called at process exit.
122
+ */
123
+ static VALUE
124
+ bridge_sdl_quit(VALUE self)
125
+ {
126
+ SDL_Quit();
127
+ return Qnil;
128
+ }
129
+
130
+ /* ---------------------------------------------------------
131
+ * Init
132
+ * --------------------------------------------------------- */
133
+
134
+ void
135
+ Init_sdl2bridge(VALUE mTeekSDL2)
136
+ {
137
+ rb_define_module_function(mTeekSDL2, "create_renderer_from_handle",
138
+ bridge_create_renderer_from_handle, 1);
139
+ rb_define_module_function(mTeekSDL2, "poll_events", bridge_poll_events, 0);
140
+ rb_define_module_function(mTeekSDL2, "_event_check_fn_ptr", bridge_event_check_fn_ptr, 0);
141
+ rb_define_module_function(mTeekSDL2, "sdl_quit", bridge_sdl_quit, 0);
142
+ }
@@ -0,0 +1,83 @@
1
+ #include "teek_sdl2.h"
2
+ #include <SDL2/SDL_image.h>
3
+
4
+ /* ---------------------------------------------------------
5
+ * SDL2_image wrapper
6
+ *
7
+ * Loads image files (PNG, JPG, WebP, BMP, etc.) directly
8
+ * into SDL2 textures via IMG_LoadTexture.
9
+ * --------------------------------------------------------- */
10
+
11
+ static int img_initialized = 0;
12
+
13
+ static void
14
+ ensure_img_init(void)
15
+ {
16
+ if (img_initialized) return;
17
+
18
+ int flags = IMG_INIT_PNG | IMG_INIT_JPG;
19
+ int initted = IMG_Init(flags);
20
+
21
+ /* Not fatal if a specific codec is missing — IMG_LoadTexture
22
+ * will fail at load time with a clear error message. We just
23
+ * want to preload the common ones. */
24
+ (void)initted;
25
+ img_initialized = 1;
26
+ }
27
+
28
+ /*
29
+ * Teek::SDL2::Renderer#load_image(path) -> Texture
30
+ *
31
+ * Load an image file into a GPU texture. Supports PNG, JPG, BMP,
32
+ * GIF, WebP, TGA, and other formats via SDL2_image.
33
+ *
34
+ * The returned texture has alpha blending enabled and its width/height
35
+ * set from the image dimensions.
36
+ */
37
+ static VALUE
38
+ renderer_load_image(VALUE self, VALUE path)
39
+ {
40
+ struct sdl2_renderer *ren = get_renderer(self);
41
+
42
+ ensure_sdl2_init();
43
+ ensure_img_init();
44
+
45
+ StringValue(path);
46
+ const char *cpath = StringValueCStr(path);
47
+
48
+ SDL_Texture *texture = IMG_LoadTexture(ren->renderer, cpath);
49
+ if (!texture) {
50
+ rb_raise(rb_eRuntimeError, "IMG_LoadTexture failed: %s", IMG_GetError());
51
+ }
52
+
53
+ /* Query dimensions */
54
+ int w, h;
55
+ SDL_QueryTexture(texture, NULL, NULL, &w, &h);
56
+
57
+ /* Enable alpha blending (common for PNGs with transparency) */
58
+ SDL_SetTextureBlendMode(texture, SDL_BLENDMODE_BLEND);
59
+
60
+ /* Wrap as a Texture object */
61
+ VALUE klass = rb_const_get(mTeekSDL2, rb_intern("Texture"));
62
+ VALUE obj = rb_obj_alloc(klass);
63
+
64
+ struct sdl2_texture *t;
65
+ TypedData_Get_Struct(obj, struct sdl2_texture, &texture_type, t);
66
+ t->texture = texture;
67
+ t->w = w;
68
+ t->h = h;
69
+ t->renderer_obj = self;
70
+
71
+ return obj;
72
+ }
73
+
74
+ /* ---------------------------------------------------------
75
+ * Init
76
+ * --------------------------------------------------------- */
77
+
78
+ void
79
+ Init_sdl2image(VALUE mTeekSDL2)
80
+ {
81
+ VALUE cRenderer = rb_const_get(mTeekSDL2, rb_intern("Renderer"));
82
+ rb_define_method(cRenderer, "load_image", renderer_load_image, 1);
83
+ }
@@ -0,0 +1,166 @@
1
+ #include "teek_sdl2.h"
2
+
3
+ /* ---------------------------------------------------------
4
+ * Pixel format conversion helpers
5
+ *
6
+ * Fast C-level conversion from various pixel formats to
7
+ * ARGB8888 (our native SDL2 texture format). Designed for
8
+ * emulators and games that output pixels in different formats.
9
+ * --------------------------------------------------------- */
10
+
11
+ /*
12
+ * Teek::SDL2::Pixels.pack_uint32(array, width, height) -> String
13
+ *
14
+ * Packs an Array of uint32 integers into an ARGB8888 byte string
15
+ * suitable for Texture#update. Each integer is treated as a
16
+ * native-endian 32-bit pixel value.
17
+ *
18
+ * This is the fast path for optcarrot and similar emulators that
19
+ * output pre-palette-mapped uint32 pixel arrays.
20
+ */
21
+ static VALUE
22
+ pixels_pack_uint32(VALUE self, VALUE ary, VALUE vw, VALUE vh)
23
+ {
24
+ int w = NUM2INT(vw);
25
+ int h = NUM2INT(vh);
26
+ long expected = (long)w * h;
27
+ long len;
28
+ VALUE result;
29
+ uint32_t *dst;
30
+ long i;
31
+
32
+ Check_Type(ary, T_ARRAY);
33
+ len = RARRAY_LEN(ary);
34
+
35
+ if (len < expected) {
36
+ rb_raise(rb_eArgError,
37
+ "array too short: need %ld pixels, got %ld", expected, len);
38
+ }
39
+
40
+ result = rb_str_new(NULL, expected * 4);
41
+ dst = (uint32_t *)RSTRING_PTR(result);
42
+
43
+ for (i = 0; i < expected; i++) {
44
+ dst[i] = (uint32_t)NUM2UINT(rb_ary_entry(ary, i));
45
+ }
46
+
47
+ return result;
48
+ }
49
+
50
+ /*
51
+ * Teek::SDL2::Pixels.convert(source, width, height, from_format) -> String
52
+ *
53
+ * Converts a pixel byte string from one format to ARGB8888.
54
+ *
55
+ * Supported from_format values:
56
+ * :argb8888 - passthrough (no conversion)
57
+ * :rgba8888 - RGBA -> ARGB byte shuffle
58
+ * :bgra8888 - BGRA -> ARGB byte shuffle
59
+ * :abgr8888 - ABGR -> ARGB byte shuffle
60
+ * :rgb888 - 3-byte RGB -> 4-byte ARGB (adds 0xFF alpha)
61
+ */
62
+ static VALUE
63
+ pixels_convert(VALUE self, VALUE source, VALUE vw, VALUE vh, VALUE format)
64
+ {
65
+ int w = NUM2INT(vw);
66
+ int h = NUM2INT(vh);
67
+ long npixels = (long)w * h;
68
+ const uint8_t *src;
69
+ uint8_t *dst;
70
+ VALUE result;
71
+ ID fmt;
72
+ long i;
73
+
74
+ Check_Type(source, T_STRING);
75
+ fmt = SYM2ID(format);
76
+ src = (const uint8_t *)RSTRING_PTR(source);
77
+
78
+ /* :argb8888 — passthrough */
79
+ if (fmt == rb_intern("argb8888")) {
80
+ if (RSTRING_LEN(source) < npixels * 4) {
81
+ rb_raise(rb_eArgError, "source too short for %ldx%d ARGB8888", (long)w, h);
82
+ }
83
+ return rb_str_dup(source);
84
+ }
85
+
86
+ /* :rgba8888 — RGBA -> ARGB */
87
+ if (fmt == rb_intern("rgba8888")) {
88
+ if (RSTRING_LEN(source) < npixels * 4) {
89
+ rb_raise(rb_eArgError, "source too short for %ldx%d RGBA8888", (long)w, h);
90
+ }
91
+ result = rb_str_new(NULL, npixels * 4);
92
+ dst = (uint8_t *)RSTRING_PTR(result);
93
+ for (i = 0; i < npixels; i++) {
94
+ long off = i * 4;
95
+ dst[off + 0] = src[off + 3]; /* A */
96
+ dst[off + 1] = src[off + 0]; /* R */
97
+ dst[off + 2] = src[off + 1]; /* G */
98
+ dst[off + 3] = src[off + 2]; /* B */
99
+ }
100
+ return result;
101
+ }
102
+
103
+ /* :bgra8888 — BGRA -> ARGB */
104
+ if (fmt == rb_intern("bgra8888")) {
105
+ if (RSTRING_LEN(source) < npixels * 4) {
106
+ rb_raise(rb_eArgError, "source too short for %ldx%d BGRA8888", (long)w, h);
107
+ }
108
+ result = rb_str_new(NULL, npixels * 4);
109
+ dst = (uint8_t *)RSTRING_PTR(result);
110
+ for (i = 0; i < npixels; i++) {
111
+ long off = i * 4;
112
+ dst[off + 0] = src[off + 3]; /* A */
113
+ dst[off + 1] = src[off + 2]; /* R */
114
+ dst[off + 2] = src[off + 1]; /* G */
115
+ dst[off + 3] = src[off + 0]; /* B */
116
+ }
117
+ return result;
118
+ }
119
+
120
+ /* :abgr8888 — ABGR -> ARGB */
121
+ if (fmt == rb_intern("abgr8888")) {
122
+ if (RSTRING_LEN(source) < npixels * 4) {
123
+ rb_raise(rb_eArgError, "source too short for %ldx%d ABGR8888", (long)w, h);
124
+ }
125
+ result = rb_str_new(NULL, npixels * 4);
126
+ dst = (uint8_t *)RSTRING_PTR(result);
127
+ for (i = 0; i < npixels; i++) {
128
+ long off = i * 4;
129
+ dst[off + 0] = src[off + 0]; /* A */
130
+ dst[off + 1] = src[off + 3]; /* R */
131
+ dst[off + 2] = src[off + 2]; /* G */
132
+ dst[off + 3] = src[off + 1]; /* B */
133
+ }
134
+ return result;
135
+ }
136
+
137
+ /* :rgb888 — 3-byte RGB -> 4-byte ARGB */
138
+ if (fmt == rb_intern("rgb888")) {
139
+ if (RSTRING_LEN(source) < npixels * 3) {
140
+ rb_raise(rb_eArgError, "source too short for %ldx%d RGB888", (long)w, h);
141
+ }
142
+ result = rb_str_new(NULL, npixels * 4);
143
+ dst = (uint8_t *)RSTRING_PTR(result);
144
+ for (i = 0; i < npixels; i++) {
145
+ long src_off = i * 3;
146
+ long dst_off = i * 4;
147
+ dst[dst_off + 0] = 0xFF; /* A */
148
+ dst[dst_off + 1] = src[src_off + 0]; /* R */
149
+ dst[dst_off + 2] = src[src_off + 1]; /* G */
150
+ dst[dst_off + 3] = src[src_off + 2]; /* B */
151
+ }
152
+ return result;
153
+ }
154
+
155
+ rb_raise(rb_eArgError, "unknown pixel format: %"PRIsVALUE, format);
156
+ return Qnil; /* unreachable */
157
+ }
158
+
159
+ void
160
+ Init_sdl2pixels(VALUE mTeekSDL2)
161
+ {
162
+ VALUE cPixels = rb_define_module_under(mTeekSDL2, "Pixels");
163
+
164
+ rb_define_module_function(cPixels, "pack_uint32", pixels_pack_uint32, 3);
165
+ rb_define_module_function(cPixels, "convert", pixels_convert, 4);
166
+ }