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.
@@ -0,0 +1,534 @@
1
+ #include "teek_sdl2.h"
2
+
3
+ /* ---------------------------------------------------------
4
+ * Layer 1: Pure SDL2 surface management
5
+ *
6
+ * No Tk knowledge. Manages SDL2 windows, renderers, and
7
+ * textures. Can be tested standalone.
8
+ * --------------------------------------------------------- */
9
+
10
+ static VALUE cRenderer;
11
+ static VALUE cTexture;
12
+ static VALUE eSDL2Error;
13
+
14
+ /* Track whether SDL2 has been initialized */
15
+ static int sdl2_initialized = 0;
16
+
17
+ /* ---------------------------------------------------------
18
+ * SDL2 lazy initialization
19
+ * --------------------------------------------------------- */
20
+
21
+ void
22
+ ensure_sdl2_init(void)
23
+ {
24
+ if (sdl2_initialized) return;
25
+
26
+ if (SDL_Init(SDL_INIT_VIDEO) < 0) {
27
+ rb_raise(rb_eRuntimeError, "SDL_Init failed: %s", SDL_GetError());
28
+ }
29
+ sdl2_initialized = 1;
30
+ }
31
+
32
+ /* ---------------------------------------------------------
33
+ * Renderer (wraps SDL_Window + SDL_Renderer)
34
+ * --------------------------------------------------------- */
35
+
36
+ static void
37
+ renderer_mark(void *ptr)
38
+ {
39
+ (void)ptr;
40
+ }
41
+
42
+ static void
43
+ renderer_free(void *ptr)
44
+ {
45
+ struct sdl2_renderer *r = ptr;
46
+ if (!r->destroyed) {
47
+ if (r->renderer) {
48
+ SDL_DestroyRenderer(r->renderer);
49
+ r->renderer = NULL;
50
+ }
51
+ if (r->window && r->owned_window) {
52
+ SDL_DestroyWindow(r->window);
53
+ r->window = NULL;
54
+ }
55
+ r->destroyed = 1;
56
+ }
57
+ xfree(r);
58
+ }
59
+
60
+ static size_t
61
+ renderer_memsize(const void *ptr)
62
+ {
63
+ return sizeof(struct sdl2_renderer);
64
+ }
65
+
66
+ const rb_data_type_t renderer_type = {
67
+ .wrap_struct_name = "TeekSDL2::Renderer",
68
+ .function = {
69
+ .dmark = renderer_mark,
70
+ .dfree = renderer_free,
71
+ .dsize = renderer_memsize,
72
+ },
73
+ .flags = RUBY_TYPED_FREE_IMMEDIATELY,
74
+ };
75
+
76
+ static VALUE
77
+ renderer_alloc(VALUE klass)
78
+ {
79
+ struct sdl2_renderer *r;
80
+ VALUE obj = TypedData_Make_Struct(klass, struct sdl2_renderer, &renderer_type, r);
81
+ r->window = NULL;
82
+ r->renderer = NULL;
83
+ r->owned_window = 0;
84
+ r->destroyed = 0;
85
+ return obj;
86
+ }
87
+
88
+ struct sdl2_renderer *
89
+ get_renderer(VALUE self)
90
+ {
91
+ struct sdl2_renderer *r;
92
+ TypedData_Get_Struct(self, struct sdl2_renderer, &renderer_type, r);
93
+ if (r->destroyed || r->renderer == NULL) {
94
+ rb_raise(eSDL2Error, "renderer has been destroyed");
95
+ }
96
+ return r;
97
+ }
98
+
99
+ /*
100
+ * Teek::SDL2::Renderer#clear(r=0, g=0, b=0, a=255)
101
+ */
102
+ static VALUE
103
+ renderer_clear(int argc, VALUE *argv, VALUE self)
104
+ {
105
+ struct sdl2_renderer *ren = get_renderer(self);
106
+ Uint8 r = 0, g = 0, b = 0, a = 255;
107
+
108
+ if (argc > 0) r = (Uint8)NUM2INT(argv[0]);
109
+ if (argc > 1) g = (Uint8)NUM2INT(argv[1]);
110
+ if (argc > 2) b = (Uint8)NUM2INT(argv[2]);
111
+ if (argc > 3) a = (Uint8)NUM2INT(argv[3]);
112
+
113
+ SDL_SetRenderDrawColor(ren->renderer, r, g, b, a);
114
+ SDL_RenderClear(ren->renderer);
115
+ return self;
116
+ }
117
+
118
+ /*
119
+ * Teek::SDL2::Renderer#present
120
+ */
121
+ static VALUE
122
+ renderer_present(VALUE self)
123
+ {
124
+ struct sdl2_renderer *ren = get_renderer(self);
125
+ SDL_RenderPresent(ren->renderer);
126
+ return self;
127
+ }
128
+
129
+ /*
130
+ * Teek::SDL2::Renderer#fill_rect(x, y, w, h, r, g, b, a=255)
131
+ */
132
+ static VALUE
133
+ renderer_fill_rect(int argc, VALUE *argv, VALUE self)
134
+ {
135
+ struct sdl2_renderer *ren = get_renderer(self);
136
+ SDL_Rect rect;
137
+ Uint8 r, g, b, a = 255;
138
+
139
+ rb_check_arity(argc, 7, 8);
140
+ rect.x = NUM2INT(argv[0]);
141
+ rect.y = NUM2INT(argv[1]);
142
+ rect.w = NUM2INT(argv[2]);
143
+ rect.h = NUM2INT(argv[3]);
144
+ r = (Uint8)NUM2INT(argv[4]);
145
+ g = (Uint8)NUM2INT(argv[5]);
146
+ b = (Uint8)NUM2INT(argv[6]);
147
+ if (argc > 7) a = (Uint8)NUM2INT(argv[7]);
148
+
149
+ SDL_SetRenderDrawColor(ren->renderer, r, g, b, a);
150
+ SDL_RenderFillRect(ren->renderer, &rect);
151
+ return self;
152
+ }
153
+
154
+ /*
155
+ * Teek::SDL2::Renderer#draw_rect(x, y, w, h, r, g, b, a=255)
156
+ */
157
+ static VALUE
158
+ renderer_draw_rect(int argc, VALUE *argv, VALUE self)
159
+ {
160
+ struct sdl2_renderer *ren = get_renderer(self);
161
+ SDL_Rect rect;
162
+ Uint8 r, g, b, a = 255;
163
+
164
+ rb_check_arity(argc, 7, 8);
165
+ rect.x = NUM2INT(argv[0]);
166
+ rect.y = NUM2INT(argv[1]);
167
+ rect.w = NUM2INT(argv[2]);
168
+ rect.h = NUM2INT(argv[3]);
169
+ r = (Uint8)NUM2INT(argv[4]);
170
+ g = (Uint8)NUM2INT(argv[5]);
171
+ b = (Uint8)NUM2INT(argv[6]);
172
+ if (argc > 7) a = (Uint8)NUM2INT(argv[7]);
173
+
174
+ SDL_SetRenderDrawColor(ren->renderer, r, g, b, a);
175
+ SDL_RenderDrawRect(ren->renderer, &rect);
176
+ return self;
177
+ }
178
+
179
+ /*
180
+ * Teek::SDL2::Renderer#draw_line(x1, y1, x2, y2, r, g, b, a=255)
181
+ */
182
+ static VALUE
183
+ renderer_draw_line(int argc, VALUE *argv, VALUE self)
184
+ {
185
+ struct sdl2_renderer *ren = get_renderer(self);
186
+ Uint8 r, g, b, a = 255;
187
+
188
+ rb_check_arity(argc, 7, 8);
189
+ r = (Uint8)NUM2INT(argv[4]);
190
+ g = (Uint8)NUM2INT(argv[5]);
191
+ b = (Uint8)NUM2INT(argv[6]);
192
+ if (argc > 7) a = (Uint8)NUM2INT(argv[7]);
193
+
194
+ SDL_SetRenderDrawColor(ren->renderer, r, g, b, a);
195
+ SDL_RenderDrawLine(ren->renderer,
196
+ NUM2INT(argv[0]), NUM2INT(argv[1]),
197
+ NUM2INT(argv[2]), NUM2INT(argv[3]));
198
+ return self;
199
+ }
200
+
201
+ /*
202
+ * Teek::SDL2::Renderer#output_size -> [w, h]
203
+ */
204
+ static VALUE
205
+ renderer_output_size(VALUE self)
206
+ {
207
+ struct sdl2_renderer *ren = get_renderer(self);
208
+ int w, h;
209
+
210
+ if (SDL_GetRendererOutputSize(ren->renderer, &w, &h) != 0) {
211
+ rb_raise(eSDL2Error, "SDL_GetRendererOutputSize: %s", SDL_GetError());
212
+ }
213
+ return rb_ary_new_from_args(2, INT2NUM(w), INT2NUM(h));
214
+ }
215
+
216
+ /*
217
+ * Teek::SDL2::Renderer#read_pixels -> String (RGBA bytes)
218
+ *
219
+ * Reads the current renderer contents as raw RGBA pixel data.
220
+ * Returns a binary String of width*height*4 bytes.
221
+ * Call after rendering but before present for consistent results.
222
+ */
223
+ static VALUE
224
+ renderer_read_pixels(VALUE self)
225
+ {
226
+ struct sdl2_renderer *ren = get_renderer(self);
227
+ int w, h;
228
+
229
+ if (SDL_GetRendererOutputSize(ren->renderer, &w, &h) != 0) {
230
+ rb_raise(eSDL2Error, "SDL_GetRendererOutputSize: %s", SDL_GetError());
231
+ }
232
+
233
+ long size = (long)w * h * 4;
234
+ VALUE buf = rb_str_buf_new(size);
235
+ rb_str_set_len(buf, size);
236
+
237
+ if (SDL_RenderReadPixels(ren->renderer, NULL, SDL_PIXELFORMAT_RGBA8888,
238
+ RSTRING_PTR(buf), w * 4) != 0) {
239
+ rb_raise(eSDL2Error, "SDL_RenderReadPixels: %s", SDL_GetError());
240
+ }
241
+
242
+ return buf;
243
+ }
244
+
245
+ /*
246
+ * Teek::SDL2::Renderer#destroy
247
+ */
248
+ static VALUE
249
+ renderer_destroy(VALUE self)
250
+ {
251
+ struct sdl2_renderer *r;
252
+ TypedData_Get_Struct(self, struct sdl2_renderer, &renderer_type, r);
253
+ if (!r->destroyed) {
254
+ if (r->renderer) {
255
+ SDL_DestroyRenderer(r->renderer);
256
+ r->renderer = NULL;
257
+ }
258
+ if (r->window && r->owned_window) {
259
+ SDL_DestroyWindow(r->window);
260
+ r->window = NULL;
261
+ }
262
+ r->destroyed = 1;
263
+ }
264
+ return Qnil;
265
+ }
266
+
267
+ /*
268
+ * Teek::SDL2::Renderer#destroyed? -> true/false
269
+ */
270
+ static VALUE
271
+ renderer_destroyed_p(VALUE self)
272
+ {
273
+ struct sdl2_renderer *r;
274
+ TypedData_Get_Struct(self, struct sdl2_renderer, &renderer_type, r);
275
+ return r->destroyed ? Qtrue : Qfalse;
276
+ }
277
+
278
+ /* ---------------------------------------------------------
279
+ * Texture (wraps SDL_Texture)
280
+ *
281
+ * struct sdl2_texture is defined in teek_sdl2.h so sdl2text.c
282
+ * can create Texture objects from TTF-rendered surfaces.
283
+ * --------------------------------------------------------- */
284
+
285
+ static void
286
+ texture_mark(void *ptr)
287
+ {
288
+ struct sdl2_texture *t = ptr;
289
+ rb_gc_mark(t->renderer_obj);
290
+ }
291
+
292
+ static void
293
+ texture_free(void *ptr)
294
+ {
295
+ struct sdl2_texture *t = ptr;
296
+ if (!t->destroyed && t->texture) {
297
+ SDL_DestroyTexture(t->texture);
298
+ t->texture = NULL;
299
+ t->destroyed = 1;
300
+ }
301
+ xfree(t);
302
+ }
303
+
304
+ static size_t
305
+ texture_memsize(const void *ptr)
306
+ {
307
+ return sizeof(struct sdl2_texture);
308
+ }
309
+
310
+ const rb_data_type_t texture_type = {
311
+ .wrap_struct_name = "TeekSDL2::Texture",
312
+ .function = {
313
+ .dmark = texture_mark,
314
+ .dfree = texture_free,
315
+ .dsize = texture_memsize,
316
+ },
317
+ .flags = RUBY_TYPED_FREE_IMMEDIATELY,
318
+ };
319
+
320
+ static VALUE
321
+ texture_alloc(VALUE klass)
322
+ {
323
+ struct sdl2_texture *t;
324
+ VALUE obj = TypedData_Make_Struct(klass, struct sdl2_texture, &texture_type, t);
325
+ t->texture = NULL;
326
+ t->w = 0;
327
+ t->h = 0;
328
+ t->destroyed = 0;
329
+ t->renderer_obj = Qnil;
330
+ return obj;
331
+ }
332
+
333
+ static struct sdl2_texture *
334
+ get_texture(VALUE self)
335
+ {
336
+ struct sdl2_texture *t;
337
+ TypedData_Get_Struct(self, struct sdl2_texture, &texture_type, t);
338
+ if (t->destroyed || t->texture == NULL) {
339
+ rb_raise(eSDL2Error, "texture has been destroyed");
340
+ }
341
+ return t;
342
+ }
343
+
344
+ /*
345
+ * Teek::SDL2::Renderer#create_texture(w, h, access=:streaming)
346
+ *
347
+ * Creates an ARGB8888 texture. Access modes:
348
+ * :static - rarely updated
349
+ * :streaming - frequently updated (lock/unlock)
350
+ * :target - can be used as render target
351
+ */
352
+ static VALUE
353
+ renderer_create_texture(int argc, VALUE *argv, VALUE self)
354
+ {
355
+ struct sdl2_renderer *ren = get_renderer(self);
356
+ int w, h;
357
+ int access = SDL_TEXTUREACCESS_STREAMING;
358
+
359
+ rb_check_arity(argc, 2, 3);
360
+ w = NUM2INT(argv[0]);
361
+ h = NUM2INT(argv[1]);
362
+
363
+ if (argc > 2 && !NIL_P(argv[2])) {
364
+ ID sym = SYM2ID(argv[2]);
365
+ if (sym == rb_intern("static"))
366
+ access = SDL_TEXTUREACCESS_STATIC;
367
+ else if (sym == rb_intern("streaming"))
368
+ access = SDL_TEXTUREACCESS_STREAMING;
369
+ else if (sym == rb_intern("target"))
370
+ access = SDL_TEXTUREACCESS_TARGET;
371
+ else
372
+ rb_raise(rb_eArgError, "unknown access mode (use :static, :streaming, or :target)");
373
+ }
374
+
375
+ SDL_Texture *tex = SDL_CreateTexture(ren->renderer,
376
+ SDL_PIXELFORMAT_ARGB8888,
377
+ access, w, h);
378
+ if (!tex) {
379
+ rb_raise(eSDL2Error, "SDL_CreateTexture: %s", SDL_GetError());
380
+ }
381
+
382
+ VALUE obj = texture_alloc(cTexture);
383
+ struct sdl2_texture *t;
384
+ TypedData_Get_Struct(obj, struct sdl2_texture, &texture_type, t);
385
+ t->texture = tex;
386
+ t->w = w;
387
+ t->h = h;
388
+ t->renderer_obj = self;
389
+ return obj;
390
+ }
391
+
392
+ /*
393
+ * Teek::SDL2::Texture#update(pixels)
394
+ *
395
+ * Updates the entire texture with pixel data.
396
+ * pixels must be a String of w*h*4 bytes (ARGB8888).
397
+ */
398
+ static VALUE
399
+ texture_update(VALUE self, VALUE pixels)
400
+ {
401
+ struct sdl2_texture *t = get_texture(self);
402
+ Check_Type(pixels, T_STRING);
403
+
404
+ long expected = (long)t->w * t->h * 4;
405
+ if (RSTRING_LEN(pixels) != expected) {
406
+ rb_raise(rb_eArgError, "pixel data must be %ld bytes (got %ld)",
407
+ expected, RSTRING_LEN(pixels));
408
+ }
409
+
410
+ int pitch = t->w * 4;
411
+ if (SDL_UpdateTexture(t->texture, NULL, RSTRING_PTR(pixels), pitch) != 0) {
412
+ rb_raise(eSDL2Error, "SDL_UpdateTexture: %s", SDL_GetError());
413
+ }
414
+ return self;
415
+ }
416
+
417
+ /*
418
+ * Teek::SDL2::Renderer#copy(texture, src_rect=nil, dst_rect=nil)
419
+ *
420
+ * Copies texture to the renderer. Rects are [x, y, w, h] arrays or nil for full area.
421
+ */
422
+ static VALUE
423
+ renderer_copy(int argc, VALUE *argv, VALUE self)
424
+ {
425
+ struct sdl2_renderer *ren = get_renderer(self);
426
+ VALUE tex_obj, src_rect_obj, dst_rect_obj;
427
+ SDL_Rect src, dst, *srcp = NULL, *dstp = NULL;
428
+
429
+ rb_scan_args(argc, argv, "12", &tex_obj, &src_rect_obj, &dst_rect_obj);
430
+
431
+ struct sdl2_texture *t = get_texture(tex_obj);
432
+
433
+ if (!NIL_P(src_rect_obj)) {
434
+ Check_Type(src_rect_obj, T_ARRAY);
435
+ src.x = NUM2INT(rb_ary_entry(src_rect_obj, 0));
436
+ src.y = NUM2INT(rb_ary_entry(src_rect_obj, 1));
437
+ src.w = NUM2INT(rb_ary_entry(src_rect_obj, 2));
438
+ src.h = NUM2INT(rb_ary_entry(src_rect_obj, 3));
439
+ srcp = &src;
440
+ }
441
+
442
+ if (!NIL_P(dst_rect_obj)) {
443
+ Check_Type(dst_rect_obj, T_ARRAY);
444
+ dst.x = NUM2INT(rb_ary_entry(dst_rect_obj, 0));
445
+ dst.y = NUM2INT(rb_ary_entry(dst_rect_obj, 1));
446
+ dst.w = NUM2INT(rb_ary_entry(dst_rect_obj, 2));
447
+ dst.h = NUM2INT(rb_ary_entry(dst_rect_obj, 3));
448
+ dstp = &dst;
449
+ }
450
+
451
+ if (SDL_RenderCopy(ren->renderer, t->texture, srcp, dstp) != 0) {
452
+ rb_raise(eSDL2Error, "SDL_RenderCopy: %s", SDL_GetError());
453
+ }
454
+ return self;
455
+ }
456
+
457
+ /*
458
+ * Teek::SDL2::Texture#width -> Integer
459
+ */
460
+ static VALUE
461
+ texture_width(VALUE self)
462
+ {
463
+ return INT2NUM(get_texture(self)->w);
464
+ }
465
+
466
+ /*
467
+ * Teek::SDL2::Texture#height -> Integer
468
+ */
469
+ static VALUE
470
+ texture_height(VALUE self)
471
+ {
472
+ return INT2NUM(get_texture(self)->h);
473
+ }
474
+
475
+ /*
476
+ * Teek::SDL2::Texture#destroy
477
+ */
478
+ static VALUE
479
+ texture_destroy(VALUE self)
480
+ {
481
+ struct sdl2_texture *t;
482
+ TypedData_Get_Struct(self, struct sdl2_texture, &texture_type, t);
483
+ if (!t->destroyed && t->texture) {
484
+ SDL_DestroyTexture(t->texture);
485
+ t->texture = NULL;
486
+ t->destroyed = 1;
487
+ }
488
+ return Qnil;
489
+ }
490
+
491
+ /*
492
+ * Teek::SDL2::Texture#destroyed? -> true/false
493
+ */
494
+ static VALUE
495
+ texture_destroyed_p(VALUE self)
496
+ {
497
+ struct sdl2_texture *t;
498
+ TypedData_Get_Struct(self, struct sdl2_texture, &texture_type, t);
499
+ return t->destroyed ? Qtrue : Qfalse;
500
+ }
501
+
502
+ /* ---------------------------------------------------------
503
+ * Init
504
+ * --------------------------------------------------------- */
505
+
506
+ void
507
+ Init_sdl2surface(VALUE mTeekSDL2)
508
+ {
509
+ eSDL2Error = rb_define_class_under(mTeekSDL2, "Error", rb_eRuntimeError);
510
+
511
+ /* Renderer */
512
+ cRenderer = rb_define_class_under(mTeekSDL2, "Renderer", rb_cObject);
513
+ rb_define_alloc_func(cRenderer, renderer_alloc);
514
+ rb_define_method(cRenderer, "clear", renderer_clear, -1);
515
+ rb_define_method(cRenderer, "present", renderer_present, 0);
516
+ rb_define_method(cRenderer, "fill_rect", renderer_fill_rect, -1);
517
+ rb_define_method(cRenderer, "draw_rect", renderer_draw_rect, -1);
518
+ rb_define_method(cRenderer, "draw_line", renderer_draw_line, -1);
519
+ rb_define_method(cRenderer, "output_size", renderer_output_size, 0);
520
+ rb_define_method(cRenderer, "read_pixels", renderer_read_pixels, 0);
521
+ rb_define_method(cRenderer, "create_texture", renderer_create_texture, -1);
522
+ rb_define_method(cRenderer, "copy", renderer_copy, -1);
523
+ rb_define_method(cRenderer, "destroy", renderer_destroy, 0);
524
+ rb_define_method(cRenderer, "destroyed?", renderer_destroyed_p, 0);
525
+
526
+ /* Texture */
527
+ cTexture = rb_define_class_under(mTeekSDL2, "Texture", rb_cObject);
528
+ rb_define_alloc_func(cTexture, texture_alloc);
529
+ rb_define_method(cTexture, "update", texture_update, 1);
530
+ rb_define_method(cTexture, "width", texture_width, 0);
531
+ rb_define_method(cTexture, "height", texture_height, 0);
532
+ rb_define_method(cTexture, "destroy", texture_destroy, 0);
533
+ rb_define_method(cTexture, "destroyed?", texture_destroyed_p, 0);
534
+ }