rgame 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 +7 -0
- data/LICENSE +26 -0
- data/README.md +406 -0
- data/docs/api/README.md +167 -0
- data/docs/api/app.md +192 -0
- data/docs/api/assets.md +426 -0
- data/docs/api/audio.md +208 -0
- data/docs/api/components.md +321 -0
- data/docs/api/drawing.md +330 -0
- data/docs/api/game.md +99 -0
- data/docs/api/images.md +118 -0
- data/docs/api/input.md +179 -0
- data/docs/api/internals.md +110 -0
- data/docs/api/scene_graph.md +159 -0
- data/docs/api/signals.md +142 -0
- data/docs/api/systems.md +98 -0
- data/docs/api/text.md +116 -0
- data/docs/api/toolbox.md +240 -0
- data/docs/api/values.md +101 -0
- data/ext/README.md +225 -0
- data/ext/rgame_core/app/app.c +721 -0
- data/ext/rgame_core/app/app_gl.h +64 -0
- data/ext/rgame_core/app/frame_loop.c +42 -0
- data/ext/rgame_core/app/frame_loop.h +54 -0
- data/ext/rgame_core/audio/audio.c +466 -0
- data/ext/rgame_core/audio/audio_internal.h +45 -0
- data/ext/rgame_core/audio/vorbis_decoder.c +282 -0
- data/ext/rgame_core/audio/vorbis_decoder.h +45 -0
- data/ext/rgame_core/example.rb +188 -0
- data/ext/rgame_core/extconf.rb +167 -0
- data/ext/rgame_core/graphics/backend.c +52 -0
- data/ext/rgame_core/graphics/backend.h +64 -0
- data/ext/rgame_core/graphics/canvas.c +247 -0
- data/ext/rgame_core/graphics/canvas.h +143 -0
- data/ext/rgame_core/graphics/clip.c +87 -0
- data/ext/rgame_core/graphics/clip.h +89 -0
- data/ext/rgame_core/graphics/draw_queue.c +216 -0
- data/ext/rgame_core/graphics/draw_queue.h +174 -0
- data/ext/rgame_core/graphics/gl_backend.c +122 -0
- data/ext/rgame_core/graphics/gl_backend.h +43 -0
- data/ext/rgame_core/graphics/image.c +304 -0
- data/ext/rgame_core/graphics/image_internal.h +30 -0
- data/ext/rgame_core/graphics/primitives.c +189 -0
- data/ext/rgame_core/graphics/primitives.h +111 -0
- data/ext/rgame_core/graphics/recording.c +119 -0
- data/ext/rgame_core/graphics/recording.h +88 -0
- data/ext/rgame_core/graphics/texture.c +181 -0
- data/ext/rgame_core/graphics/texture.h +165 -0
- data/ext/rgame_core/graphics/transform.c +128 -0
- data/ext/rgame_core/graphics/transform.h +106 -0
- data/ext/rgame_core/include/rgame/core.h +577 -0
- data/ext/rgame_core/input/device_slots.c +103 -0
- data/ext/rgame_core/input/device_slots.h +93 -0
- data/ext/rgame_core/input/gamepad.c +145 -0
- data/ext/rgame_core/input/gamepad.h +63 -0
- data/ext/rgame_core/input/input.c +109 -0
- data/ext/rgame_core/input/input.h +99 -0
- data/ext/rgame_core/ruby/audio_ext.c +321 -0
- data/ext/rgame_core/ruby/core_ext.c +513 -0
- data/ext/rgame_core/ruby/core_ext.h +51 -0
- data/ext/rgame_core/ruby/font_ext.c +168 -0
- data/ext/rgame_core/ruby/image_ext.c +230 -0
- data/ext/rgame_core/ruby/recording_ext.c +186 -0
- data/ext/rgame_core/ruby/renderer_ext.c +376 -0
- data/ext/rgame_core/text/atlas.c +59 -0
- data/ext/rgame_core/text/atlas.h +85 -0
- data/ext/rgame_core/text/font.c +281 -0
- data/ext/rgame_core/text/font.h +139 -0
- data/ext/rgame_core/text/font_atlas.c +385 -0
- data/ext/rgame_core/text/font_internal.h +47 -0
- data/ext/rgame_core/text/glyph_cache.c +142 -0
- data/ext/rgame_core/text/glyph_cache.h +89 -0
- data/ext/rgame_core/vendor/README.md +159 -0
- data/ext/rgame_core/vendor/miniaudio.h +95864 -0
- data/ext/rgame_core/vendor/miniaudio_impl.c +62 -0
- data/ext/rgame_core/vendor/stb_image.h +7988 -0
- data/ext/rgame_core/vendor/stb_image_impl.c +31 -0
- data/ext/rgame_core/vendor/stb_truetype.h +5079 -0
- data/ext/rgame_core/vendor/stb_truetype_impl.c +23 -0
- data/ext/rgame_core/vendor/stb_vorbis.c +5584 -0
- data/ext/rgame_core/vendor/stb_vorbis_impl.c +29 -0
- data/ext/rgame_util/color.c +19 -0
- data/ext/rgame_util/color.h +60 -0
- data/ext/rgame_util/color_ext.c +156 -0
- data/ext/rgame_util/extconf.rb +27 -0
- data/ext/rgame_util/tensor.c +186 -0
- data/ext/rgame_util/util_ext.c +27 -0
- data/ext/rgame_util/util_ext.h +16 -0
- data/lib/rgame/boot.rb +13 -0
- data/lib/rgame/core/app.rb +82 -0
- data/lib/rgame/core/asset_manager.rb +224 -0
- data/lib/rgame/core/audio.rb +124 -0
- data/lib/rgame/core/font.rb +49 -0
- data/lib/rgame/core/gamepad.rb +55 -0
- data/lib/rgame/core/image.rb +55 -0
- data/lib/rgame/core/input.rb +77 -0
- data/lib/rgame/core/nine_slice.rb +163 -0
- data/lib/rgame/core/recording.rb +52 -0
- data/lib/rgame/core/renderer.rb +363 -0
- data/lib/rgame/core/sprite_sheet.rb +108 -0
- data/lib/rgame/core/tile_map_renderer.rb +160 -0
- data/lib/rgame/core/ui_atlas.rb +86 -0
- data/lib/rgame/core.rb +24 -0
- data/lib/rgame/engine/actor.rb +53 -0
- data/lib/rgame/engine/animation_set.rb +49 -0
- data/lib/rgame/engine/animator.rb +44 -0
- data/lib/rgame/engine/audio_bus.rb +24 -0
- data/lib/rgame/engine/audio_director.rb +29 -0
- data/lib/rgame/engine/body.rb +49 -0
- data/lib/rgame/engine/cached_label.rb +33 -0
- data/lib/rgame/engine/camera.rb +33 -0
- data/lib/rgame/engine/camera_view.rb +28 -0
- data/lib/rgame/engine/circle_collider.rb +32 -0
- data/lib/rgame/engine/collision_box.rb +34 -0
- data/lib/rgame/engine/collision_system.rb +44 -0
- data/lib/rgame/engine/component.rb +30 -0
- data/lib/rgame/engine/components/action_trigger.rb +41 -0
- data/lib/rgame/engine/components/animated_sprite.rb +63 -0
- data/lib/rgame/engine/components/character_body.rb +70 -0
- data/lib/rgame/engine/components/circle_collider.rb +44 -0
- data/lib/rgame/engine/components/collision_world.rb +103 -0
- data/lib/rgame/engine/components/despawn_offscreen.rb +26 -0
- data/lib/rgame/engine/components/path_follow.rb +84 -0
- data/lib/rgame/engine/components/player_controller.rb +24 -0
- data/lib/rgame/engine/components/pool.rb +53 -0
- data/lib/rgame/engine/components/screen_wrap.rb +27 -0
- data/lib/rgame/engine/components/sprite.rb +31 -0
- data/lib/rgame/engine/components/targeting.rb +54 -0
- data/lib/rgame/engine/components/thrust_controller.rb +65 -0
- data/lib/rgame/engine/components/tile_world.rb +68 -0
- data/lib/rgame/engine/components/timer.rb +75 -0
- data/lib/rgame/engine/components/velocity.rb +27 -0
- data/lib/rgame/engine/components/wander_controller.rb +60 -0
- data/lib/rgame/engine/debug_overlay.rb +106 -0
- data/lib/rgame/engine/i18n.rb +97 -0
- data/lib/rgame/engine/input/action_mapper.rb +46 -0
- data/lib/rgame/engine/input/actions.rb +41 -0
- data/lib/rgame/engine/input/player_controller.rb +14 -0
- data/lib/rgame/engine/matrix.rb +32 -0
- data/lib/rgame/engine/node2d.rb +271 -0
- data/lib/rgame/engine/path.rb +78 -0
- data/lib/rgame/engine/pool.rb +51 -0
- data/lib/rgame/engine/resettable.rb +67 -0
- data/lib/rgame/engine/scene/scene_stack.rb +65 -0
- data/lib/rgame/engine/signal.rb +75 -0
- data/lib/rgame/engine/spatial_hash.rb +71 -0
- data/lib/rgame/engine/tile_collision.rb +78 -0
- data/lib/rgame/engine/tile_map.rb +149 -0
- data/lib/rgame/engine/tileset.rb +101 -0
- data/lib/rgame/engine/timer.rb +51 -0
- data/lib/rgame/engine.rb +68 -0
- data/lib/rgame/fonts/LiberationSans-Regular.ttf +0 -0
- data/lib/rgame/fonts/OFL.txt +102 -0
- data/lib/rgame/game.rb +129 -0
- data/lib/rgame/util/color.rb +27 -0
- data/lib/rgame/util/controls.rb +107 -0
- data/lib/rgame/util/tensor.rb +12 -0
- data/lib/rgame/util.rb +8 -0
- data/lib/rgame/version.rb +12 -0
- data/lib/rgame.rb +20 -0
- metadata +215 -0
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
#include "graphics/backend.h"
|
|
2
|
+
|
|
3
|
+
void rgame_draw_submit(const rgame_draw_queue *queue, const rgame_draw_backend *backend,
|
|
4
|
+
int width, int height) {
|
|
5
|
+
if (!queue || !backend) {
|
|
6
|
+
return;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
if (backend->begin_frame) {
|
|
10
|
+
backend->begin_frame(backend->ctx, width, height);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/*
|
|
14
|
+
* Track the clip actually in force rather than assuming each batch needs a
|
|
15
|
+
* fresh one. `have_clip` distinguishes "no clip set yet" from "the current
|
|
16
|
+
* clip happens to equal the zero rect", which a plain comparison against a
|
|
17
|
+
* zeroed variable could not.
|
|
18
|
+
*
|
|
19
|
+
* No batch can currently carry the zero rect — an empty clip makes the
|
|
20
|
+
* queue drop the command before it ever becomes a batch — so the flag is
|
|
21
|
+
* unreachable today, and a test cannot distinguish it. It is kept because
|
|
22
|
+
* the invariant that saves it lives in *another* module: if the queue ever
|
|
23
|
+
* stopped dropping empty-clipped commands, the first batch of a frame would
|
|
24
|
+
* silently go unscissored.
|
|
25
|
+
*/
|
|
26
|
+
rgame_rect current_clip = { 0, 0, 0, 0 };
|
|
27
|
+
int have_clip = 0;
|
|
28
|
+
|
|
29
|
+
const rgame_vertex *vertices = rgame_draw_queue_vertices(queue);
|
|
30
|
+
unsigned int batches = rgame_draw_queue_batch_count(queue);
|
|
31
|
+
|
|
32
|
+
for (unsigned int i = 0; i < batches; i++) {
|
|
33
|
+
const rgame_draw_batch *batch = rgame_draw_queue_batch(queue, i);
|
|
34
|
+
|
|
35
|
+
if (!have_clip || !rgame_rect_equals(current_clip, batch->clip)) {
|
|
36
|
+
if (backend->set_clip) {
|
|
37
|
+
backend->set_clip(backend->ctx, batch->clip);
|
|
38
|
+
}
|
|
39
|
+
current_clip = batch->clip;
|
|
40
|
+
have_clip = 1;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (backend->draw_batch) {
|
|
44
|
+
backend->draw_batch(backend->ctx, batch->texture, &vertices[batch->first_vertex],
|
|
45
|
+
batch->vertex_count);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (backend->end_frame) {
|
|
50
|
+
backend->end_frame(backend->ctx);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
#ifndef RGAME_BACKEND_H
|
|
2
|
+
#define RGAME_BACKEND_H
|
|
3
|
+
|
|
4
|
+
#include "graphics/clip.h"
|
|
5
|
+
#include "graphics/draw_queue.h"
|
|
6
|
+
|
|
7
|
+
/*
|
|
8
|
+
* The drawing backend seam — "layer 2" in CLAUDE.md's abstraction strategy.
|
|
9
|
+
*
|
|
10
|
+
* Everything up to here is arithmetic: the canvas positions vertices, the queue
|
|
11
|
+
* sorts and batches them. This is where that stops and real GL calls begin, so
|
|
12
|
+
* this is where the function-pointer table goes — added at the point real calls
|
|
13
|
+
* appear rather than speculatively ahead of it.
|
|
14
|
+
*
|
|
15
|
+
* Two implementations exist by design: the real one that talks to OpenGL, and a
|
|
16
|
+
* recording one the Check suite substitutes to assert that the right primitive
|
|
17
|
+
* calls happened in the right order, with no display anywhere in sight.
|
|
18
|
+
*
|
|
19
|
+
* ---------------------------------------------------------------------------
|
|
20
|
+
* What the seam is *not* for
|
|
21
|
+
* ---------------------------------------------------------------------------
|
|
22
|
+
*
|
|
23
|
+
* It is not how the queue and canvas get tested. Those hand back their prepared
|
|
24
|
+
* batches directly and are exercised without any backend at all, which keeps
|
|
25
|
+
* the dependency one-directional: draw_queue and canvas do not include this
|
|
26
|
+
* header. What is genuinely worth a fake is the submit loop below — the small
|
|
27
|
+
* amount of logic that sits between a prepared frame and the GPU.
|
|
28
|
+
*/
|
|
29
|
+
|
|
30
|
+
/*
|
|
31
|
+
* Every member may be NULL, in which case that call is skipped. `ctx` is the
|
|
32
|
+
* implementation's own state, passed back to each function.
|
|
33
|
+
*
|
|
34
|
+
* Fixed arity throughout, like the app callbacks: a variadic convention on a
|
|
35
|
+
* per-frame path marshals arguments for no benefit.
|
|
36
|
+
*/
|
|
37
|
+
typedef struct {
|
|
38
|
+
void (*begin_frame)(void *ctx, int width, int height);
|
|
39
|
+
void (*set_clip)(void *ctx, rgame_rect clip);
|
|
40
|
+
void (*draw_batch)(void *ctx, unsigned int texture, const rgame_vertex *vertices,
|
|
41
|
+
unsigned int count);
|
|
42
|
+
void (*end_frame)(void *ctx);
|
|
43
|
+
void *ctx;
|
|
44
|
+
} rgame_draw_backend;
|
|
45
|
+
|
|
46
|
+
/*
|
|
47
|
+
* Walks a *prepared* queue and drives the backend:
|
|
48
|
+
*
|
|
49
|
+
* begin_frame
|
|
50
|
+
* set_clip / draw_batch ... (per batch, clip only when it changes)
|
|
51
|
+
* end_frame
|
|
52
|
+
*
|
|
53
|
+
* `set_clip` is issued only when the rectangle actually differs from the last
|
|
54
|
+
* one set. Batches split on either texture or clip, so two adjacent batches
|
|
55
|
+
* often share a clip and differ only in texture — re-issuing an identical
|
|
56
|
+
* scissor for each would be a wasted state change every frame.
|
|
57
|
+
*
|
|
58
|
+
* begin_frame and end_frame are called even for an empty queue: the real
|
|
59
|
+
* backend still has to clear and present the frame.
|
|
60
|
+
*/
|
|
61
|
+
void rgame_draw_submit(const rgame_draw_queue *queue, const rgame_draw_backend *backend,
|
|
62
|
+
int width, int height);
|
|
63
|
+
|
|
64
|
+
#endif /* RGAME_BACKEND_H */
|
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
#include "graphics/canvas.h"
|
|
2
|
+
|
|
3
|
+
#include <math.h>
|
|
4
|
+
|
|
5
|
+
/* What a recorded push has to undo. */
|
|
6
|
+
enum {
|
|
7
|
+
RGAME_PUSH_TRANSFORM,
|
|
8
|
+
RGAME_PUSH_CLIP,
|
|
9
|
+
/* The underlying stack was full. Nothing to undo, but it still occupies a
|
|
10
|
+
* slot so that the caller's matching pop stays paired with this push. */
|
|
11
|
+
RGAME_PUSH_NOTHING
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
void rgame_canvas_init(rgame_canvas *canvas) {
|
|
15
|
+
rgame_draw_queue_init(&canvas->queue);
|
|
16
|
+
rgame_transform_stack_init(&canvas->transforms);
|
|
17
|
+
rgame_clip_stack_init(&canvas->clips, 0, 0);
|
|
18
|
+
canvas->push_depth = 0;
|
|
19
|
+
canvas->unrecorded_pushes = 0;
|
|
20
|
+
canvas->width = 0;
|
|
21
|
+
canvas->height = 0;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
void rgame_canvas_destroy(rgame_canvas *canvas) {
|
|
25
|
+
rgame_draw_queue_destroy(&canvas->queue);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
void rgame_canvas_begin_frame(rgame_canvas *canvas, int width, int height) {
|
|
29
|
+
rgame_draw_queue_reset(&canvas->queue);
|
|
30
|
+
rgame_transform_stack_init(&canvas->transforms);
|
|
31
|
+
rgame_clip_stack_init(&canvas->clips, width, height);
|
|
32
|
+
canvas->push_depth = 0;
|
|
33
|
+
canvas->unrecorded_pushes = 0;
|
|
34
|
+
canvas->width = width;
|
|
35
|
+
canvas->height = height;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
void rgame_canvas_end_frame(rgame_canvas *canvas) {
|
|
39
|
+
rgame_draw_queue_prepare(&canvas->queue);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/* Records what a successful push has to undo. Returns 0 when there is no room
|
|
43
|
+
* to record it, in which case the caller counts it as unrecorded instead. */
|
|
44
|
+
static int record_push(rgame_canvas *canvas, unsigned char kind) {
|
|
45
|
+
if (canvas->push_depth >= RGAME_CANVAS_STACK_DEPTH) {
|
|
46
|
+
return 0;
|
|
47
|
+
}
|
|
48
|
+
canvas->pushes[canvas->push_depth++] = kind;
|
|
49
|
+
return 1;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/*
|
|
53
|
+
* Shared tail of every push: if the underlying stack accepted it, remember
|
|
54
|
+
* which one to undo; otherwise remember that there is nothing to undo. Either
|
|
55
|
+
* way a push is accounted for, so pop always has something to consume.
|
|
56
|
+
*/
|
|
57
|
+
static void account_push(rgame_canvas *canvas, int accepted, unsigned char kind) {
|
|
58
|
+
if (!record_push(canvas, accepted ? kind : RGAME_PUSH_NOTHING)) {
|
|
59
|
+
canvas->unrecorded_pushes++;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
void rgame_canvas_push_translate(rgame_canvas *canvas, float dx, float dy) {
|
|
64
|
+
int ok = rgame_transform_push_translate(&canvas->transforms, dx, dy);
|
|
65
|
+
account_push(canvas, ok, RGAME_PUSH_TRANSFORM);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
void rgame_canvas_push_rotate(rgame_canvas *canvas, float degrees, float pivot_x,
|
|
69
|
+
float pivot_y) {
|
|
70
|
+
int ok = rgame_transform_push_rotate(&canvas->transforms, degrees, pivot_x, pivot_y);
|
|
71
|
+
account_push(canvas, ok, RGAME_PUSH_TRANSFORM);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
void rgame_canvas_push_scale(rgame_canvas *canvas, float sx, float sy) {
|
|
75
|
+
int ok = rgame_transform_push_scale(&canvas->transforms, sx, sy);
|
|
76
|
+
account_push(canvas, ok, RGAME_PUSH_TRANSFORM);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/*
|
|
80
|
+
* Maps a rect through the current transform and takes the axis-aligned bounding
|
|
81
|
+
* box of the result.
|
|
82
|
+
*
|
|
83
|
+
* All four corners are mapped, not just two: under rotation the box is decided
|
|
84
|
+
* by whichever corners happen to be extreme, and under a mirroring scale the
|
|
85
|
+
* "top-left" corner is no longer top-left. Bounds are rounded outwards so a
|
|
86
|
+
* partially covered pixel is kept rather than clipped away.
|
|
87
|
+
*/
|
|
88
|
+
static rgame_rect transformed_bounds(const rgame_canvas *canvas, rgame_rect rect) {
|
|
89
|
+
float corners[4][2] = { { (float)rect.x, (float)rect.y },
|
|
90
|
+
{ (float)(rect.x + rect.w), (float)rect.y },
|
|
91
|
+
{ (float)(rect.x + rect.w), (float)(rect.y + rect.h) },
|
|
92
|
+
{ (float)rect.x, (float)(rect.y + rect.h) } };
|
|
93
|
+
|
|
94
|
+
float min_x = 0.0f, min_y = 0.0f, max_x = 0.0f, max_y = 0.0f;
|
|
95
|
+
for (int i = 0; i < 4; i++) {
|
|
96
|
+
float x, y;
|
|
97
|
+
rgame_transform_apply(&canvas->transforms, corners[i][0], corners[i][1], &x, &y);
|
|
98
|
+
if (i == 0) {
|
|
99
|
+
min_x = max_x = x;
|
|
100
|
+
min_y = max_y = y;
|
|
101
|
+
continue;
|
|
102
|
+
}
|
|
103
|
+
if (x < min_x) {
|
|
104
|
+
min_x = x;
|
|
105
|
+
}
|
|
106
|
+
if (x > max_x) {
|
|
107
|
+
max_x = x;
|
|
108
|
+
}
|
|
109
|
+
if (y < min_y) {
|
|
110
|
+
min_y = y;
|
|
111
|
+
}
|
|
112
|
+
if (y > max_y) {
|
|
113
|
+
max_y = y;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
int left = (int)floorf(min_x);
|
|
118
|
+
int top = (int)floorf(min_y);
|
|
119
|
+
int right = (int)ceilf(max_x);
|
|
120
|
+
int bottom = (int)ceilf(max_y);
|
|
121
|
+
return rgame_rect_make(left, top, right - left, bottom - top);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
void rgame_canvas_push_clip(rgame_canvas *canvas, rgame_rect rect) {
|
|
125
|
+
int ok = rgame_clip_push(&canvas->clips, transformed_bounds(canvas, rect));
|
|
126
|
+
account_push(canvas, ok, RGAME_PUSH_CLIP);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
void rgame_canvas_pop(rgame_canvas *canvas) {
|
|
130
|
+
/* Unrecorded pushes unwind first: they are the most recent ones, since a
|
|
131
|
+
* push is only left unrecorded once the record stack is already full. */
|
|
132
|
+
if (canvas->unrecorded_pushes > 0) {
|
|
133
|
+
canvas->unrecorded_pushes--;
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
if (canvas->push_depth == 0) {
|
|
137
|
+
return; /* unbalanced pop; harmless */
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
switch (canvas->pushes[--canvas->push_depth]) {
|
|
141
|
+
case RGAME_PUSH_TRANSFORM:
|
|
142
|
+
rgame_transform_pop(&canvas->transforms);
|
|
143
|
+
break;
|
|
144
|
+
case RGAME_PUSH_CLIP:
|
|
145
|
+
rgame_clip_pop(&canvas->clips);
|
|
146
|
+
break;
|
|
147
|
+
default:
|
|
148
|
+
break; /* RGAME_PUSH_NOTHING: the push never took effect */
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/* Fills one vertex: map the point into screen space, copy through the texture
|
|
153
|
+
* coordinate, and write the colour as the four bytes GL reads. */
|
|
154
|
+
static void write_vertex(const rgame_canvas *canvas, rgame_vertex *vertex, float x, float y,
|
|
155
|
+
float u, float v, rgame_color color) {
|
|
156
|
+
rgame_transform_apply(&canvas->transforms, x, y, &vertex->x, &vertex->y);
|
|
157
|
+
vertex->u = u;
|
|
158
|
+
vertex->v = v;
|
|
159
|
+
rgame_color_bytes(color, vertex->rgba);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
void rgame_canvas_triangle(rgame_canvas *canvas, const float *xy6, rgame_color color,
|
|
163
|
+
double z) {
|
|
164
|
+
rgame_vertex *out = rgame_draw_queue_alloc(&canvas->queue, 3, z, 0,
|
|
165
|
+
rgame_clip_current(&canvas->clips));
|
|
166
|
+
for (int i = 0; i < 3; i++) {
|
|
167
|
+
write_vertex(canvas, &out[i], xy6[i * 2], xy6[(i * 2) + 1], 0.0f, 0.0f, color);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/* Corner indices for the two triangles a quad becomes. */
|
|
172
|
+
static const int RGAME_QUAD_TRIANGLES[6] = { 0, 1, 2, 0, 2, 3 };
|
|
173
|
+
|
|
174
|
+
void rgame_canvas_quad(rgame_canvas *canvas, const float *xy8, rgame_color color, double z) {
|
|
175
|
+
rgame_vertex *out = rgame_draw_queue_alloc(&canvas->queue, 6, z, 0,
|
|
176
|
+
rgame_clip_current(&canvas->clips));
|
|
177
|
+
for (int i = 0; i < 6; i++) {
|
|
178
|
+
int corner = RGAME_QUAD_TRIANGLES[i];
|
|
179
|
+
write_vertex(canvas, &out[i], xy8[corner * 2], xy8[(corner * 2) + 1], 0.0f, 0.0f,
|
|
180
|
+
color);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
void rgame_canvas_textured_quad(rgame_canvas *canvas, unsigned int texture, const float *xy8,
|
|
185
|
+
const float *uv8, rgame_color color, double z) {
|
|
186
|
+
rgame_vertex *out = rgame_draw_queue_alloc(&canvas->queue, 6, z, texture,
|
|
187
|
+
rgame_clip_current(&canvas->clips));
|
|
188
|
+
for (int i = 0; i < 6; i++) {
|
|
189
|
+
int corner = RGAME_QUAD_TRIANGLES[i];
|
|
190
|
+
write_vertex(canvas, &out[i], xy8[corner * 2], xy8[(corner * 2) + 1], uv8[corner * 2],
|
|
191
|
+
uv8[(corner * 2) + 1], color);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/* Multiplies two colour components, 0..255 in and out: 255 leaves the other
|
|
196
|
+
* untouched, which is what makes WHITE the "no tint" value. */
|
|
197
|
+
static unsigned char modulate(unsigned char value, int tint) {
|
|
198
|
+
return (unsigned char)((value * tint) / 255);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
void rgame_canvas_replay(rgame_canvas *canvas, const rgame_recording *recording, float dx,
|
|
202
|
+
float dy, rgame_color color, double z) {
|
|
203
|
+
if (!recording) {
|
|
204
|
+
return;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
int tint[4] = { rgame_color_r(color), rgame_color_g(color), rgame_color_b(color),
|
|
208
|
+
rgame_color_a(color) };
|
|
209
|
+
|
|
210
|
+
for (unsigned int b = 0; b < recording->batch_count; b++) {
|
|
211
|
+
const rgame_recording_batch *batch = &recording->batches[b];
|
|
212
|
+
|
|
213
|
+
/* One command per baked batch, rather than one per original draw call:
|
|
214
|
+
* the whole point of a recording is that the per-tile work happened
|
|
215
|
+
* once, at bake time. */
|
|
216
|
+
rgame_vertex *out = rgame_draw_queue_alloc(&canvas->queue, batch->vertex_count, z,
|
|
217
|
+
batch->texture,
|
|
218
|
+
rgame_clip_current(&canvas->clips));
|
|
219
|
+
|
|
220
|
+
for (unsigned int i = 0; i < batch->vertex_count; i++) {
|
|
221
|
+
const rgame_vertex *baked = &recording->vertices[batch->first_vertex + i];
|
|
222
|
+
|
|
223
|
+
/* Offset first, then the current transform — so the offset is in
|
|
224
|
+
* the recording's own coordinates and the transform is the world's,
|
|
225
|
+
* which is the order a camera needs. */
|
|
226
|
+
rgame_transform_apply(&canvas->transforms, baked->x + dx, baked->y + dy, &out[i].x,
|
|
227
|
+
&out[i].y);
|
|
228
|
+
out[i].u = baked->u;
|
|
229
|
+
out[i].v = baked->v;
|
|
230
|
+
for (int c = 0; c < 4; c++) {
|
|
231
|
+
out[i].rgba[c] = modulate(baked->rgba[c], tint[c]);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
void rgame_canvas_submit(const rgame_canvas *canvas, const rgame_draw_backend *backend) {
|
|
238
|
+
rgame_draw_submit(&canvas->queue, backend, canvas->width, canvas->height);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
const rgame_draw_queue *rgame_canvas_queue(const rgame_canvas *canvas) {
|
|
242
|
+
return &canvas->queue;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
int rgame_canvas_depth(const rgame_canvas *canvas) {
|
|
246
|
+
return canvas->push_depth + canvas->unrecorded_pushes;
|
|
247
|
+
}
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
#ifndef RGAME_CANVAS_H
|
|
2
|
+
#define RGAME_CANVAS_H
|
|
3
|
+
|
|
4
|
+
#include "graphics/backend.h"
|
|
5
|
+
#include "graphics/clip.h"
|
|
6
|
+
#include "color.h"
|
|
7
|
+
#include "graphics/draw_queue.h"
|
|
8
|
+
#include "graphics/recording.h"
|
|
9
|
+
#include "graphics/transform.h"
|
|
10
|
+
|
|
11
|
+
/*
|
|
12
|
+
* The canvas: where the transform stack, the clip stack and the draw queue meet
|
|
13
|
+
* — and still pure arithmetic, with no SDL and no GL.
|
|
14
|
+
*
|
|
15
|
+
* Each of the three pieces below it does one thing and knows nothing of the
|
|
16
|
+
* others. This is the layer that composes them, and it is the seam the drawing
|
|
17
|
+
* API is actually written against: everything above (the Ruby renderer, and
|
|
18
|
+
* eventually the scene graph) speaks to a canvas, and everything below is
|
|
19
|
+
* arithmetic that can be tested without a window.
|
|
20
|
+
*
|
|
21
|
+
* ---------------------------------------------------------------------------
|
|
22
|
+
* Vertices are transformed on the way in
|
|
23
|
+
* ---------------------------------------------------------------------------
|
|
24
|
+
*
|
|
25
|
+
* A primitive's points are mapped through the current transform *before* they
|
|
26
|
+
* reach the queue, so the queue stores screen-space coordinates. That is what
|
|
27
|
+
* lets it sort freely: once a vertex is positioned, reordering commands cannot
|
|
28
|
+
* change where anything lands. Storing untransformed points and applying the
|
|
29
|
+
* transform at flush time would mean re-associating each command with the
|
|
30
|
+
* transform that was current when it was issued — which is exactly the
|
|
31
|
+
* bookkeeping this design exists to avoid.
|
|
32
|
+
*
|
|
33
|
+
* ---------------------------------------------------------------------------
|
|
34
|
+
* Clips are transformed too
|
|
35
|
+
* ---------------------------------------------------------------------------
|
|
36
|
+
*
|
|
37
|
+
* A clip pushed inside a translate moves with it — measured against the layer
|
|
38
|
+
* being replaced, where a clip at x 0..20 inside a translate of 50 clips
|
|
39
|
+
* x 50..70. So `push_clip` maps the caller's rect through the current transform
|
|
40
|
+
* before handing it to the clip stack, which works purely in screen space.
|
|
41
|
+
*
|
|
42
|
+
* Under translation and scaling that mapping is exact. Under rotation it cannot
|
|
43
|
+
* be: a scissor rectangle is axis-aligned and a rotated rectangle is not, so
|
|
44
|
+
* the axis-aligned bounding box is used instead. That errs towards clipping
|
|
45
|
+
* *less* than asked, which is the safe direction, and nothing in the engine
|
|
46
|
+
* rotates a clip today.
|
|
47
|
+
*
|
|
48
|
+
* ---------------------------------------------------------------------------
|
|
49
|
+
* One pop for any push
|
|
50
|
+
* ---------------------------------------------------------------------------
|
|
51
|
+
*
|
|
52
|
+
* `push_translate`, `push_rotate`, `push_scale` and `push_clip` are all undone
|
|
53
|
+
* by the same `pop`. The canvas remembers which stack each push went to, so a
|
|
54
|
+
* caller never has to — and cannot pop the wrong one. Pushes that could not be
|
|
55
|
+
* honoured (a full stack) are still counted, so pop stays balanced and the
|
|
56
|
+
* drawing comes out untransformed rather than desynchronised.
|
|
57
|
+
*/
|
|
58
|
+
|
|
59
|
+
/* Deep enough that it cannot fill before both underlying stacks have; pushes
|
|
60
|
+
* beyond that are counted rather than recorded, so balance always holds. */
|
|
61
|
+
#define RGAME_CANVAS_STACK_DEPTH (RGAME_TRANSFORM_STACK_DEPTH + RGAME_CLIP_STACK_DEPTH)
|
|
62
|
+
|
|
63
|
+
typedef struct {
|
|
64
|
+
rgame_transform_stack transforms;
|
|
65
|
+
rgame_clip_stack clips;
|
|
66
|
+
rgame_draw_queue queue;
|
|
67
|
+
|
|
68
|
+
/* Which stack each outstanding push went to, so pop can undo the right one. */
|
|
69
|
+
unsigned char pushes[RGAME_CANVAS_STACK_DEPTH];
|
|
70
|
+
int push_depth;
|
|
71
|
+
/* Pushes with nowhere to be recorded. Counted so pop still balances. */
|
|
72
|
+
int unrecorded_pushes;
|
|
73
|
+
|
|
74
|
+
/* Remembered from begin_frame, so submit needs no repeat of the size. */
|
|
75
|
+
int width, height;
|
|
76
|
+
} rgame_canvas;
|
|
77
|
+
|
|
78
|
+
void rgame_canvas_init(rgame_canvas *canvas);
|
|
79
|
+
void rgame_canvas_destroy(rgame_canvas *canvas);
|
|
80
|
+
|
|
81
|
+
/*
|
|
82
|
+
* Starts a frame: empties the queue (keeping its buffers), resets both stacks,
|
|
83
|
+
* and sets the clip base to the window. Re-stating the size every frame is also
|
|
84
|
+
* how a resize takes effect, so there is no separate path to forget.
|
|
85
|
+
*/
|
|
86
|
+
void rgame_canvas_begin_frame(rgame_canvas *canvas, int width, int height);
|
|
87
|
+
|
|
88
|
+
/* Sorts and batches what was drawn. Read the result through rgame_canvas_queue. */
|
|
89
|
+
void rgame_canvas_end_frame(rgame_canvas *canvas);
|
|
90
|
+
|
|
91
|
+
void rgame_canvas_push_translate(rgame_canvas *canvas, float dx, float dy);
|
|
92
|
+
void rgame_canvas_push_rotate(rgame_canvas *canvas, float degrees, float pivot_x,
|
|
93
|
+
float pivot_y);
|
|
94
|
+
void rgame_canvas_push_scale(rgame_canvas *canvas, float sx, float sy);
|
|
95
|
+
|
|
96
|
+
/* `rect` is in the caller's local coordinates and is mapped through the current
|
|
97
|
+
* transform before narrowing the clip. */
|
|
98
|
+
void rgame_canvas_push_clip(rgame_canvas *canvas, rgame_rect rect);
|
|
99
|
+
|
|
100
|
+
void rgame_canvas_pop(rgame_canvas *canvas);
|
|
101
|
+
|
|
102
|
+
/*
|
|
103
|
+
* Primitives. Points arrive as flat coordinate pairs in the caller's local
|
|
104
|
+
* space: `xy6` is three points, `xy8` four. A quad's points are taken in loop
|
|
105
|
+
* order (for a rectangle: top-left, top-right, bottom-right, bottom-left) and
|
|
106
|
+
* split into triangles 0-1-2 and 0-2-3.
|
|
107
|
+
*
|
|
108
|
+
* `uv8` is the matching four texture coordinates, in the same corner order.
|
|
109
|
+
*/
|
|
110
|
+
void rgame_canvas_triangle(rgame_canvas *canvas, const float *xy6, rgame_color color,
|
|
111
|
+
double z);
|
|
112
|
+
void rgame_canvas_quad(rgame_canvas *canvas, const float *xy8, rgame_color color, double z);
|
|
113
|
+
void rgame_canvas_textured_quad(rgame_canvas *canvas, unsigned int texture, const float *xy8,
|
|
114
|
+
const float *uv8, rgame_color color, double z);
|
|
115
|
+
|
|
116
|
+
/*
|
|
117
|
+
* Replays a baked recording (see recording.h) offset by (dx, dy), at `z`, tinted
|
|
118
|
+
* by `color` — RGAME_COLOR_WHITE leaves the recorded colours alone.
|
|
119
|
+
*
|
|
120
|
+
* The offset is applied *before* the current transform, so a baked layer moves
|
|
121
|
+
* with the camera it is drawn under and can be placed anywhere without being
|
|
122
|
+
* re-baked. Every batch becomes one command at the same z, and because equal z
|
|
123
|
+
* keeps insertion order, the painter order baked into the recording survives.
|
|
124
|
+
*
|
|
125
|
+
* The clip is the one current at replay time; recordings do not carry their own
|
|
126
|
+
* (recording.h says why).
|
|
127
|
+
*/
|
|
128
|
+
void rgame_canvas_replay(rgame_canvas *canvas, const rgame_recording *recording, float dx,
|
|
129
|
+
float dy, rgame_color color, double z);
|
|
130
|
+
|
|
131
|
+
/*
|
|
132
|
+
* Hands the finished frame to a backend. Call after end_frame; equivalent to
|
|
133
|
+
* rgame_draw_submit with the size this frame was begun at.
|
|
134
|
+
*/
|
|
135
|
+
void rgame_canvas_submit(const rgame_canvas *canvas, const rgame_draw_backend *backend);
|
|
136
|
+
|
|
137
|
+
const rgame_draw_queue *rgame_canvas_queue(const rgame_canvas *canvas);
|
|
138
|
+
|
|
139
|
+
/* How many pushes are outstanding, counting ones that could not be recorded.
|
|
140
|
+
* A balanced frame ends at zero. */
|
|
141
|
+
int rgame_canvas_depth(const rgame_canvas *canvas);
|
|
142
|
+
|
|
143
|
+
#endif /* RGAME_CANVAS_H */
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
#include "graphics/clip.h"
|
|
2
|
+
|
|
3
|
+
/* The canonical empty rect. Every path that produces "nothing" produces this
|
|
4
|
+
* exact value, so two empties compare equal and cannot split a batch. */
|
|
5
|
+
static const rgame_rect RGAME_EMPTY_RECT = { 0, 0, 0, 0 };
|
|
6
|
+
|
|
7
|
+
rgame_rect rgame_rect_make(int x, int y, int w, int h) {
|
|
8
|
+
if (w <= 0 || h <= 0) {
|
|
9
|
+
return RGAME_EMPTY_RECT;
|
|
10
|
+
}
|
|
11
|
+
rgame_rect r = { x, y, w, h };
|
|
12
|
+
return r;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
int rgame_rect_is_empty(rgame_rect r) {
|
|
16
|
+
return r.w <= 0 || r.h <= 0;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
int rgame_rect_equals(rgame_rect a, rgame_rect b) {
|
|
20
|
+
return a.x == b.x && a.y == b.y && a.w == b.w && a.h == b.h;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
int rgame_rect_contains_point(rgame_rect r, int x, int y) {
|
|
24
|
+
return !rgame_rect_is_empty(r) && x >= r.x && y >= r.y && x < r.x + r.w && y < r.y + r.h;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
rgame_rect rgame_rect_intersect(rgame_rect a, rgame_rect b) {
|
|
28
|
+
if (rgame_rect_is_empty(a) || rgame_rect_is_empty(b)) {
|
|
29
|
+
return RGAME_EMPTY_RECT;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/*
|
|
33
|
+
* Edges are computed in `long` rather than `int`. A caller can legitimately
|
|
34
|
+
* pass a very large width to mean "as wide as possible", and x + w would
|
|
35
|
+
* then overflow — which is undefined behaviour, not merely a wrong answer.
|
|
36
|
+
* The result always fits back in an int because it is bounded by the
|
|
37
|
+
* smaller of the two inputs.
|
|
38
|
+
*/
|
|
39
|
+
long left = a.x > b.x ? a.x : b.x;
|
|
40
|
+
long top = a.y > b.y ? a.y : b.y;
|
|
41
|
+
long a_right = (long)a.x + a.w;
|
|
42
|
+
long b_right = (long)b.x + b.w;
|
|
43
|
+
long a_bottom = (long)a.y + a.h;
|
|
44
|
+
long b_bottom = (long)b.y + b.h;
|
|
45
|
+
long right = a_right < b_right ? a_right : b_right;
|
|
46
|
+
long bottom = a_bottom < b_bottom ? a_bottom : b_bottom;
|
|
47
|
+
|
|
48
|
+
if (right <= left || bottom <= top) {
|
|
49
|
+
return RGAME_EMPTY_RECT;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
rgame_rect result = { (int)left, (int)top, (int)(right - left), (int)(bottom - top) };
|
|
53
|
+
return result;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
void rgame_clip_stack_init(rgame_clip_stack *stack, int width, int height) {
|
|
57
|
+
stack->depth = 0;
|
|
58
|
+
stack->entries[0] = rgame_rect_make(0, 0, width, height);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
int rgame_clip_push(rgame_clip_stack *stack, rgame_rect rect) {
|
|
62
|
+
if (stack->depth + 1 >= RGAME_CLIP_STACK_DEPTH) {
|
|
63
|
+
return 0;
|
|
64
|
+
}
|
|
65
|
+
stack->entries[stack->depth + 1] =
|
|
66
|
+
rgame_rect_intersect(stack->entries[stack->depth], rect);
|
|
67
|
+
stack->depth++;
|
|
68
|
+
return 1;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
void rgame_clip_pop(rgame_clip_stack *stack) {
|
|
72
|
+
if (stack->depth > 0) {
|
|
73
|
+
stack->depth--;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
rgame_rect rgame_clip_current(const rgame_clip_stack *stack) {
|
|
78
|
+
return stack->entries[stack->depth];
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
int rgame_clip_is_empty(const rgame_clip_stack *stack) {
|
|
82
|
+
return rgame_rect_is_empty(stack->entries[stack->depth]);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
int rgame_clip_depth(const rgame_clip_stack *stack) {
|
|
86
|
+
return stack->depth;
|
|
87
|
+
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
#ifndef RGAME_CLIP_H
|
|
2
|
+
#define RGAME_CLIP_H
|
|
3
|
+
|
|
4
|
+
/*
|
|
5
|
+
* Rectangles and the clip stack — pure arithmetic, no SDL, no GL, no I/O.
|
|
6
|
+
*
|
|
7
|
+
* Clipping restricts drawing to a rectangle. The engine needs it in two very
|
|
8
|
+
* different places: tiling a 9-slice texture's edges without overdraw, and
|
|
9
|
+
* giving each player their own screen region in split-screen. Both want the
|
|
10
|
+
* same thing — a stack where pushing *narrows* the visible area and popping
|
|
11
|
+
* restores it.
|
|
12
|
+
*
|
|
13
|
+
* ---------------------------------------------------------------------------
|
|
14
|
+
* Screen space, and whose job the transform is
|
|
15
|
+
* ---------------------------------------------------------------------------
|
|
16
|
+
*
|
|
17
|
+
* Everything here is in screen pixels. That is deliberate, and it is *not* the
|
|
18
|
+
* whole story for callers: in the layer being replaced, a clip pushed inside a
|
|
19
|
+
* translate moves with it (measured — a clip at x 0..20 inside translate(50,0)
|
|
20
|
+
* clips x 50..70). Reproducing that is the canvas's job: it maps the caller's
|
|
21
|
+
* rect through the current transform and pushes the screen-space result here.
|
|
22
|
+
*
|
|
23
|
+
* Keeping the two apart is what lets this module stay pure integer arithmetic
|
|
24
|
+
* with no idea that transforms exist.
|
|
25
|
+
*
|
|
26
|
+
* ---------------------------------------------------------------------------
|
|
27
|
+
* Empty is a real answer
|
|
28
|
+
* ---------------------------------------------------------------------------
|
|
29
|
+
*
|
|
30
|
+
* Two disjoint rects intersect to nothing, and "nothing" has to be
|
|
31
|
+
* representable — the draw queue uses it to drop commands before they ever
|
|
32
|
+
* reach the GPU. An empty result is canonicalised to {0,0,0,0} so that two
|
|
33
|
+
* different ways of ending up with nothing compare equal, which matters
|
|
34
|
+
* because the clip rect is part of the queue's batch key.
|
|
35
|
+
*/
|
|
36
|
+
|
|
37
|
+
typedef struct {
|
|
38
|
+
int x, y, w, h;
|
|
39
|
+
} rgame_rect;
|
|
40
|
+
|
|
41
|
+
/* Deep enough for any sane scene; a bounded stack cannot run away. */
|
|
42
|
+
#define RGAME_CLIP_STACK_DEPTH 32
|
|
43
|
+
|
|
44
|
+
typedef struct {
|
|
45
|
+
/* entries[0] is the window bounds — the widest anything may draw — so
|
|
46
|
+
* every command has a meaningful clip and there is no "no clip" case. */
|
|
47
|
+
rgame_rect entries[RGAME_CLIP_STACK_DEPTH];
|
|
48
|
+
int depth;
|
|
49
|
+
} rgame_clip_stack;
|
|
50
|
+
|
|
51
|
+
rgame_rect rgame_rect_make(int x, int y, int w, int h);
|
|
52
|
+
|
|
53
|
+
/* The overlap of two rects, or {0,0,0,0} if they do not overlap. */
|
|
54
|
+
rgame_rect rgame_rect_intersect(rgame_rect a, rgame_rect b);
|
|
55
|
+
|
|
56
|
+
int rgame_rect_is_empty(rgame_rect r);
|
|
57
|
+
|
|
58
|
+
/* Exact equality. The draw queue compares clips to decide whether two commands
|
|
59
|
+
* can share a batch, so this needs to be cheap and total. */
|
|
60
|
+
int rgame_rect_equals(rgame_rect a, rgame_rect b);
|
|
61
|
+
|
|
62
|
+
int rgame_rect_contains_point(rgame_rect r, int x, int y);
|
|
63
|
+
|
|
64
|
+
/*
|
|
65
|
+
* Resets the stack with the window bounds as its base. Called at the start of
|
|
66
|
+
* every frame, which is also how a window resize takes effect — there is no
|
|
67
|
+
* separate "the window changed size" path to forget.
|
|
68
|
+
*/
|
|
69
|
+
void rgame_clip_stack_init(rgame_clip_stack *stack, int width, int height);
|
|
70
|
+
|
|
71
|
+
/*
|
|
72
|
+
* Narrows the clip to `rect` intersected with the current one, and pushes the
|
|
73
|
+
* result. Returns 1, or 0 if the stack is full — in which case nothing is
|
|
74
|
+
* pushed and no matching pop must be issued.
|
|
75
|
+
*
|
|
76
|
+
* Note this always *narrows*: a pushed rect larger than the current clip
|
|
77
|
+
* cannot widen it. That is what makes nesting safe — a child can never draw
|
|
78
|
+
* outside the region its parent allowed.
|
|
79
|
+
*/
|
|
80
|
+
int rgame_clip_push(rgame_clip_stack *stack, rgame_rect rect);
|
|
81
|
+
|
|
82
|
+
/* Pops the current clip. Popping the base is a no-op rather than an underflow. */
|
|
83
|
+
void rgame_clip_pop(rgame_clip_stack *stack);
|
|
84
|
+
|
|
85
|
+
rgame_rect rgame_clip_current(const rgame_clip_stack *stack);
|
|
86
|
+
int rgame_clip_is_empty(const rgame_clip_stack *stack);
|
|
87
|
+
int rgame_clip_depth(const rgame_clip_stack *stack);
|
|
88
|
+
|
|
89
|
+
#endif /* RGAME_CLIP_H */
|