wasmcart 0.2.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,275 @@
1
+ /*
2
+ * wc_fb.h - 2D framebuffer drawing primitives for wasmcart carts
3
+ *
4
+ * Software rendering functions that replace SDL_RenderCopy, SDL_FillRect,
5
+ * SDL_BlitSurface, etc. for 2D carts using the wasmcart XRGB8888 framebuffer.
6
+ *
7
+ * All functions take the framebuffer pointer, width, and height as parameters
8
+ * so they work with any cart's buffer layout. All are static inline.
9
+ *
10
+ * USAGE:
11
+ * #include "wc_fb.h"
12
+ *
13
+ * // Fill a rect:
14
+ * wc_fb_fill(framebuffer, WIDTH, HEIGHT, 10, 20, 100, 50, 0xFF0000);
15
+ *
16
+ * // Blit indexed sprite with palette:
17
+ * wc_fb_blit_indexed(framebuffer, WIDTH, HEIGHT,
18
+ * sprite_data, sprite_stride, palette,
19
+ * sx, sy, sw, sh, dx, dy);
20
+ *
21
+ * // Blit RGBA pixels with transparency:
22
+ * wc_fb_blit_rgba(framebuffer, WIDTH, HEIGHT,
23
+ * pixels, src_w, sx, sy, sw, sh, dx, dy);
24
+ *
25
+ * PIXEL FORMAT:
26
+ * Framebuffer is uint32_t XRGB8888: 0x00RRGGBB
27
+ * RGBA source data is uint32_t: 0xAARRGGBB (alpha in high byte)
28
+ */
29
+
30
+ #ifndef WC_FB_H
31
+ #define WC_FB_H
32
+
33
+ #include <stdint.h>
34
+
35
+ /* ── Color helpers ────────────────────────────────────────────────── */
36
+
37
+ #define WC_RGB(r, g, b) (((uint32_t)(r) << 16) | ((uint32_t)(g) << 8) | (uint32_t)(b))
38
+ #define WC_RGBA(r, g, b, a) (((uint32_t)(a) << 24) | ((uint32_t)(r) << 16) | ((uint32_t)(g) << 8) | (uint32_t)(b))
39
+
40
+ #define WC_R(c) (((c) >> 16) & 0xFF)
41
+ #define WC_G(c) (((c) >> 8) & 0xFF)
42
+ #define WC_B(c) ( (c) & 0xFF)
43
+ #define WC_A(c) (((c) >> 24) & 0xFF)
44
+
45
+ /* ── Single pixel ─────────────────────────────────────────────────── */
46
+
47
+ static inline void wc_fb_pixel(uint32_t *fb, int fb_w, int fb_h,
48
+ int x, int y, uint32_t color) {
49
+ if (x >= 0 && x < fb_w && y >= 0 && y < fb_h)
50
+ fb[y * fb_w + x] = color;
51
+ }
52
+
53
+ /* ── Fill rect (solid color, no blending) ─────────────────────────── */
54
+
55
+ static inline void wc_fb_fill(uint32_t *fb, int fb_w, int fb_h,
56
+ int x, int y, int w, int h, uint32_t color) {
57
+ int x0 = x < 0 ? 0 : x;
58
+ int y0 = y < 0 ? 0 : y;
59
+ int x1 = x + w > fb_w ? fb_w : x + w;
60
+ int y1 = y + h > fb_h ? fb_h : y + h;
61
+ for (int row = y0; row < y1; row++)
62
+ for (int col = x0; col < x1; col++)
63
+ fb[row * fb_w + col] = color;
64
+ }
65
+
66
+ /* ── Fill rect with alpha blending ────────────────────────────────── */
67
+
68
+ static inline void wc_fb_fill_alpha(uint32_t *fb, int fb_w, int fb_h,
69
+ int x, int y, int w, int h,
70
+ uint32_t color, int alpha) {
71
+ int x0 = x < 0 ? 0 : x;
72
+ int y0 = y < 0 ? 0 : y;
73
+ int x1 = x + w > fb_w ? fb_w : x + w;
74
+ int y1 = y + h > fb_h ? fb_h : y + h;
75
+ int sr = WC_R(color), sg = WC_G(color), sb = WC_B(color);
76
+ int inv = 255 - alpha;
77
+ for (int row = y0; row < y1; row++)
78
+ for (int col = x0; col < x1; col++) {
79
+ uint32_t dst = fb[row * fb_w + col];
80
+ int dr = WC_R(dst), dg = WC_G(dst), db = WC_B(dst);
81
+ int rr = (sr * alpha + dr * inv) / 255;
82
+ int gg = (sg * alpha + dg * inv) / 255;
83
+ int bb = (sb * alpha + db * inv) / 255;
84
+ fb[row * fb_w + col] = WC_RGB(rr, gg, bb);
85
+ }
86
+ }
87
+
88
+ /* ── Clear entire framebuffer ─────────────────────────────────────── */
89
+
90
+ static inline void wc_fb_clear(uint32_t *fb, int fb_w, int fb_h, uint32_t color) {
91
+ int total = fb_w * fb_h;
92
+ for (int i = 0; i < total; i++)
93
+ fb[i] = color;
94
+ }
95
+
96
+ /* ── Horizontal line (fast) ───────────────────────────────────────── */
97
+
98
+ static inline void wc_fb_hline(uint32_t *fb, int fb_w, int fb_h,
99
+ int x, int y, int w, uint32_t color) {
100
+ if (y < 0 || y >= fb_h) return;
101
+ int x0 = x < 0 ? 0 : x;
102
+ int x1 = x + w > fb_w ? fb_w : x + w;
103
+ for (int col = x0; col < x1; col++)
104
+ fb[y * fb_w + col] = color;
105
+ }
106
+
107
+ /* ── Vertical line ────────────────────────────────────────────────── */
108
+
109
+ static inline void wc_fb_vline(uint32_t *fb, int fb_w, int fb_h,
110
+ int x, int y, int h, uint32_t color) {
111
+ if (x < 0 || x >= fb_w) return;
112
+ int y0 = y < 0 ? 0 : y;
113
+ int y1 = y + h > fb_h ? fb_h : y + h;
114
+ for (int row = y0; row < y1; row++)
115
+ fb[row * fb_w + x] = color;
116
+ }
117
+
118
+ /* ── Rect outline ─────────────────────────────────────────────────── */
119
+
120
+ static inline void wc_fb_rect(uint32_t *fb, int fb_w, int fb_h,
121
+ int x, int y, int w, int h, uint32_t color) {
122
+ wc_fb_hline(fb, fb_w, fb_h, x, y, w, color);
123
+ wc_fb_hline(fb, fb_w, fb_h, x, y + h - 1, w, color);
124
+ wc_fb_vline(fb, fb_w, fb_h, x, y, h, color);
125
+ wc_fb_vline(fb, fb_w, fb_h, x + w - 1, y, h, color);
126
+ }
127
+
128
+ /* ── Blit indexed palette sprite ──────────────────────────────────── */
129
+ /*
130
+ * Blit a region from an indexed (8-bit) image using a palette LUT.
131
+ * Index 0 = transparent (skipped). No scaling.
132
+ *
133
+ * src - pointer to indexed pixel data
134
+ * src_stride - width of full source image in pixels
135
+ * palette - uint32_t[256] color lookup table (XRGB8888)
136
+ * sx,sy - source region origin
137
+ * sw,sh - source region size
138
+ * dx,dy - destination position in framebuffer
139
+ */
140
+ static inline void wc_fb_blit_indexed(uint32_t *fb, int fb_w, int fb_h,
141
+ const uint8_t *src, int src_stride,
142
+ const uint32_t *palette,
143
+ int sx, int sy, int sw, int sh,
144
+ int dx, int dy) {
145
+ for (int py = 0; py < sh; py++) {
146
+ int dst_y = dy + py;
147
+ if (dst_y < 0 || dst_y >= fb_h) continue;
148
+ int src_row = sy + py;
149
+ if (src_row < 0) continue;
150
+ for (int px = 0; px < sw; px++) {
151
+ int dst_x = dx + px;
152
+ if (dst_x < 0 || dst_x >= fb_w) continue;
153
+ int src_col = sx + px;
154
+ if (src_col < 0) continue;
155
+ uint8_t idx = src[src_row * src_stride + src_col];
156
+ if (idx == 0) continue; /* transparent */
157
+ fb[dst_y * fb_w + dst_x] = palette[idx];
158
+ }
159
+ }
160
+ }
161
+
162
+ /* ── Blit indexed with custom transparent index ───────────────────── */
163
+
164
+ static inline void wc_fb_blit_indexed_key(uint32_t *fb, int fb_w, int fb_h,
165
+ const uint8_t *src, int src_stride,
166
+ const uint32_t *palette,
167
+ int sx, int sy, int sw, int sh,
168
+ int dx, int dy, uint8_t trans_idx) {
169
+ for (int py = 0; py < sh; py++) {
170
+ int dst_y = dy + py;
171
+ if (dst_y < 0 || dst_y >= fb_h) continue;
172
+ int src_row = sy + py;
173
+ if (src_row < 0) continue;
174
+ for (int px = 0; px < sw; px++) {
175
+ int dst_x = dx + px;
176
+ if (dst_x < 0 || dst_x >= fb_w) continue;
177
+ int src_col = sx + px;
178
+ if (src_col < 0) continue;
179
+ uint8_t idx = src[src_row * src_stride + src_col];
180
+ if (idx == trans_idx) continue;
181
+ fb[dst_y * fb_w + dst_x] = palette[idx];
182
+ }
183
+ }
184
+ }
185
+
186
+ /* ── Blit XRGB pixels (color-key transparency) ────────────────────── */
187
+ /*
188
+ * Blit a region of uint32_t XRGB pixels. Pixels matching color_key are
189
+ * treated as transparent. Pass color_key=0xFFFFFFFF to disable keying
190
+ * (blit all pixels).
191
+ */
192
+ static inline void wc_fb_blit(uint32_t *fb, int fb_w, int fb_h,
193
+ const uint32_t *src, int src_w,
194
+ int sx, int sy, int sw, int sh,
195
+ int dx, int dy, uint32_t color_key) {
196
+ for (int py = 0; py < sh; py++) {
197
+ int dst_y = dy + py;
198
+ if (dst_y < 0 || dst_y >= fb_h) continue;
199
+ int src_row = sy + py;
200
+ for (int px = 0; px < sw; px++) {
201
+ int dst_x = dx + px;
202
+ if (dst_x < 0 || dst_x >= fb_w) continue;
203
+ int src_col = sx + px;
204
+ uint32_t pixel = src[src_row * src_w + src_col];
205
+ if (pixel == color_key) continue;
206
+ fb[dst_y * fb_w + dst_x] = pixel;
207
+ }
208
+ }
209
+ }
210
+
211
+ /* ── Blit RGBA pixels with per-pixel alpha blending ───────────────── */
212
+ /*
213
+ * Source pixels are uint32_t 0xAARRGGBB. Alpha 0 = transparent,
214
+ * alpha 255 = opaque. Intermediate values are blended.
215
+ */
216
+ static inline void wc_fb_blit_rgba(uint32_t *fb, int fb_w, int fb_h,
217
+ const uint32_t *src, int src_w,
218
+ int sx, int sy, int sw, int sh,
219
+ int dx, int dy) {
220
+ for (int py = 0; py < sh; py++) {
221
+ int dst_y = dy + py;
222
+ if (dst_y < 0 || dst_y >= fb_h) continue;
223
+ int src_row = sy + py;
224
+ for (int px = 0; px < sw; px++) {
225
+ int dst_x = dx + px;
226
+ if (dst_x < 0 || dst_x >= fb_w) continue;
227
+ int src_col = sx + px;
228
+ uint32_t spix = src[src_row * src_w + src_col];
229
+ uint8_t sa = WC_A(spix);
230
+ if (sa == 0) continue;
231
+ if (sa == 255) {
232
+ fb[dst_y * fb_w + dst_x] = spix & 0x00FFFFFF;
233
+ continue;
234
+ }
235
+ /* Alpha blend */
236
+ uint8_t inv = 255 - sa;
237
+ uint32_t dpix = fb[dst_y * fb_w + dst_x];
238
+ int rr = (WC_R(spix) * sa + WC_R(dpix) * inv) / 255;
239
+ int gg = (WC_G(spix) * sa + WC_G(dpix) * inv) / 255;
240
+ int bb = (WC_B(spix) * sa + WC_B(dpix) * inv) / 255;
241
+ fb[dst_y * fb_w + dst_x] = WC_RGB(rr, gg, bb);
242
+ }
243
+ }
244
+ }
245
+
246
+ /* ── Blit with additive blending ──────────────────────────────────── */
247
+
248
+ static inline void wc_fb_blit_add(uint32_t *fb, int fb_w, int fb_h,
249
+ const uint32_t *src, int src_w,
250
+ int sx, int sy, int sw, int sh,
251
+ int dx, int dy) {
252
+ for (int py = 0; py < sh; py++) {
253
+ int dst_y = dy + py;
254
+ if (dst_y < 0 || dst_y >= fb_h) continue;
255
+ int src_row = sy + py;
256
+ for (int px = 0; px < sw; px++) {
257
+ int dst_x = dx + px;
258
+ if (dst_x < 0 || dst_x >= fb_w) continue;
259
+ int src_col = sx + px;
260
+ uint32_t spix = src[src_row * src_w + src_col];
261
+ uint8_t sa = WC_A(spix);
262
+ if (sa == 0) continue;
263
+ uint32_t dpix = fb[dst_y * fb_w + dst_x];
264
+ int rr = WC_R(dpix) + (WC_R(spix) * sa / 255);
265
+ int gg = WC_G(dpix) + (WC_G(spix) * sa / 255);
266
+ int bb = WC_B(dpix) + (WC_B(spix) * sa / 255);
267
+ if (rr > 255) rr = 255;
268
+ if (gg > 255) gg = 255;
269
+ if (bb > 255) bb = 255;
270
+ fb[dst_y * fb_w + dst_x] = WC_RGB(rr, gg, bb);
271
+ }
272
+ }
273
+ }
274
+
275
+ #endif /* WC_FB_H */
@@ -0,0 +1,224 @@
1
+ /*
2
+ * wc_gl.h - Common OpenGL ES 3.0 utilities for wasmcart GL carts
3
+ *
4
+ * Single-header library providing shader compilation, program linking,
5
+ * and other GL boilerplate that's identical across all GL carts.
6
+ *
7
+ * USAGE:
8
+ * #include "wasmcart.h" // must define WC_USE_GL before including
9
+ * #include "wc_gl.h"
10
+ *
11
+ * All functions are static inline. No separate .c file needed.
12
+ */
13
+
14
+ #ifndef WC_GL_H
15
+ #define WC_GL_H
16
+
17
+ /* ── Shader compilation ───────────────────────────────────────────── */
18
+
19
+ /*
20
+ * Compile a single shader (vertex or fragment).
21
+ * Returns the shader handle. No error checking (WASM has no stderr).
22
+ */
23
+ static inline GLuint wc_compile_shader(GLenum type, const char *src) {
24
+ GLuint s = glCreateShader(type);
25
+ const char *strings[1] = { src };
26
+ glShaderSource(s, 1, strings, 0);
27
+ glCompileShader(s);
28
+ return s;
29
+ }
30
+
31
+ /*
32
+ * Compile vertex + fragment shaders and link into a program.
33
+ * Deletes the individual shaders after linking.
34
+ * Returns the program handle.
35
+ */
36
+ static inline GLuint wc_link_program(const char *vs_src, const char *fs_src) {
37
+ GLuint vs = wc_compile_shader(GL_VERTEX_SHADER, vs_src);
38
+ GLuint fs = wc_compile_shader(GL_FRAGMENT_SHADER, fs_src);
39
+ GLuint prog = glCreateProgram();
40
+ glAttachShader(prog, vs);
41
+ glAttachShader(prog, fs);
42
+ glLinkProgram(prog);
43
+ glDeleteShader(vs);
44
+ glDeleteShader(fs);
45
+ return prog;
46
+ }
47
+
48
+ /* ── Resolution negotiation ───────────────────────────────────────── */
49
+
50
+ /*
51
+ * Read host preferred resolution, clamp to max, update width/height.
52
+ * Call in wc_init() after host_info has been written.
53
+ *
54
+ * host - pointer to host_info struct (written by host before wc_init)
55
+ * width - pointer to current width (updated in place)
56
+ * height - pointer to current height (updated in place)
57
+ * max_w - maximum width the cart supports
58
+ * max_h - maximum height the cart supports
59
+ */
60
+ static inline void wc_negotiate_resolution(const wc_host_info_t *host,
61
+ uint32_t *width, uint32_t *height,
62
+ uint32_t max_w, uint32_t max_h) {
63
+ if (host->preferred_width > 0 && host->preferred_height > 0) {
64
+ *width = host->preferred_width;
65
+ *height = host->preferred_height;
66
+ if (*width > max_w) *width = max_w;
67
+ if (*height > max_h) *height = max_h;
68
+ }
69
+ }
70
+
71
+ /* ── Common GL state setup ────────────────────────────────────────── */
72
+
73
+ /*
74
+ * Set up typical 2D GL state: viewport, alpha blending, no depth test.
75
+ * Good default for 2D games, menus, HUD rendering.
76
+ */
77
+ static inline void wc_gl_setup_2d(uint32_t width, uint32_t height) {
78
+ glViewport(0, 0, width, height);
79
+ glEnable(GL_BLEND);
80
+ glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
81
+ glDisable(GL_DEPTH_TEST);
82
+ }
83
+
84
+ /*
85
+ * Set up typical 3D GL state: viewport, alpha blending, depth test enabled.
86
+ * Good default for 3D games.
87
+ */
88
+ static inline void wc_gl_setup_3d(uint32_t width, uint32_t height) {
89
+ glViewport(0, 0, width, height);
90
+ glEnable(GL_BLEND);
91
+ glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
92
+ glEnable(GL_DEPTH_TEST);
93
+ glDepthFunc(GL_LEQUAL);
94
+ }
95
+
96
+ /* ── VAO/VBO creation helpers ─────────────────────────────────────── */
97
+
98
+ /*
99
+ * Create a simple VAO + VBO pair for dynamic vertex data.
100
+ * Allocates max_bytes of GL_DYNAMIC_DRAW storage.
101
+ * Returns the VBO handle (for later glBufferSubData calls).
102
+ * The VAO is bound after this call.
103
+ */
104
+ static inline GLuint wc_create_dynamic_vbo(GLuint *vao, int max_bytes) {
105
+ GLuint vbo;
106
+ glGenVertexArrays(1, vao);
107
+ glBindVertexArray(*vao);
108
+ glGenBuffers(1, &vbo);
109
+ glBindBuffer(GL_ARRAY_BUFFER, vbo);
110
+ glBufferData(GL_ARRAY_BUFFER, max_bytes, 0, GL_DYNAMIC_DRAW);
111
+ return vbo;
112
+ }
113
+
114
+ /*
115
+ * Create a VAO + VBO pair with static vertex data.
116
+ * Uploads data immediately. Returns the VBO handle.
117
+ * The VAO is bound after this call.
118
+ */
119
+ static inline GLuint wc_create_static_vbo(GLuint *vao,
120
+ const void *data, int size_bytes) {
121
+ GLuint vbo;
122
+ glGenVertexArrays(1, vao);
123
+ glBindVertexArray(*vao);
124
+ glGenBuffers(1, &vbo);
125
+ glBindBuffer(GL_ARRAY_BUFFER, vbo);
126
+ glBufferData(GL_ARRAY_BUFFER, size_bytes, data, GL_STATIC_DRAW);
127
+ return vbo;
128
+ }
129
+
130
+ /* ── Texture loading from WASM memory ─────────────────────────────── */
131
+
132
+ /*
133
+ * IMPORTANT: Texture vertical flip convention
134
+ *
135
+ * stb_image loads images top-to-bottom (row 0 = top of image).
136
+ * Whether you need to flip before uploading depends on the original
137
+ * game's framework:
138
+ *
139
+ * SDL games: Flip vertically before glTexImage2D.
140
+ * SDL + standard GL convention: V=0 = bottom of image.
141
+ * The game's tex coords expect V=0 at the bottom.
142
+ *
143
+ * SFML games: Do NOT flip. SFML convention: V=0 = top of image.
144
+ * stb_image's top-first output already matches.
145
+ * Flipping will invert ALL textures (trees, sprites,
146
+ * skybox, HUD - everything).
147
+ *
148
+ * Raw GL: Check the game's tex coord usage to determine which
149
+ * convention it expects.
150
+ *
151
+ * The functions below upload pixels as-is (no flip). If you need to
152
+ * flip for an SDL-based port, do it before calling these functions.
153
+ */
154
+
155
+ /*
156
+ * Upload RGBA pixel data to a new GL texture with typical settings.
157
+ * Returns the texture handle.
158
+ *
159
+ * Use with stb_image: decode image → get RGBA pixels → call this.
160
+ */
161
+ static inline GLuint wc_create_texture_rgba(const unsigned char *pixels,
162
+ int width, int height) {
163
+ GLuint tex;
164
+ glGenTextures(1, &tex);
165
+ glBindTexture(GL_TEXTURE_2D, tex);
166
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
167
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
168
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
169
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
170
+ glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0,
171
+ GL_RGBA, GL_UNSIGNED_BYTE, pixels);
172
+ return tex;
173
+ }
174
+
175
+ /*
176
+ * Create a texture with nearest-neighbor filtering (pixel art).
177
+ */
178
+ static inline GLuint wc_create_texture_nearest(const unsigned char *pixels,
179
+ int width, int height) {
180
+ GLuint tex;
181
+ glGenTextures(1, &tex);
182
+ glBindTexture(GL_TEXTURE_2D, tex);
183
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
184
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
185
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
186
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
187
+ glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0,
188
+ GL_RGBA, GL_UNSIGNED_BYTE, pixels);
189
+ return tex;
190
+ }
191
+
192
+ /* ── Simple PRNG (xorshift32) ─────────────────────────────────────── */
193
+ /* Duplicated in 5+ carts. Included here since GL carts commonly need it
194
+ * for particle effects, procedural generation, etc. */
195
+
196
+ static unsigned int _wc_rng_state = 12345;
197
+
198
+ static inline void wc_srand(unsigned int seed) {
199
+ _wc_rng_state = seed ? seed : 1;
200
+ }
201
+
202
+ static inline unsigned int wc_rand(void) {
203
+ _wc_rng_state ^= _wc_rng_state << 13;
204
+ _wc_rng_state ^= _wc_rng_state >> 17;
205
+ _wc_rng_state ^= _wc_rng_state << 5;
206
+ return _wc_rng_state;
207
+ }
208
+
209
+ /* Random float in [0, 1) */
210
+ static inline float wc_randf(void) {
211
+ return (float)(wc_rand() % 10000) / 10000.0f;
212
+ }
213
+
214
+ /* Random float in [lo, hi) */
215
+ static inline float wc_randf_range(float lo, float hi) {
216
+ return lo + wc_randf() * (hi - lo);
217
+ }
218
+
219
+ /* Random int in [0, max) */
220
+ static inline int wc_rand_range(int max) {
221
+ return (int)(wc_rand() % (unsigned int)max);
222
+ }
223
+
224
+ #endif /* WC_GL_H */
@@ -0,0 +1,129 @@
1
+ /*
2
+ * wc_gl_blit.h - Upload CPU pixels to GPU via GL texture + fullscreen quad
3
+ *
4
+ * The standard display path for ALL wasmcart carts. Even 2D framebuffer
5
+ * carts upload their pixels as a GL texture and draw a fullscreen quad.
6
+ * This means every cart uses gpu_api=1 and the host always uses the GL
7
+ * display path (swapBuffers).
8
+ *
9
+ * USAGE:
10
+ *
11
+ * #define WC_USE_GL
12
+ * #include "wasmcart.h"
13
+ * #include "wc_gl_blit.h"
14
+ *
15
+ * // In wc_get_info():
16
+ * info.gpu_api = 1; // always GL
17
+ *
18
+ * // In wc_render(), after drawing to your pixel buffer:
19
+ * wc_gl_blit(pixels, width, height);
20
+ *
21
+ * The first call compiles a blit shader and creates a texture.
22
+ * Subsequent calls just upload pixels and draw the quad.
23
+ *
24
+ * Pixel format: RGBA8888 (4 bytes per pixel, R first).
25
+ * If your buffer is XRGB/BGRA, convert before calling.
26
+ *
27
+ * For carts that render via GL directly (WebGL, three.js, etc.),
28
+ * don't call wc_gl_blit - the GL output is already on screen.
29
+ *
30
+ * STB-STYLE SINGLE-HEADER LIBRARY
31
+ * Define WC_GL_BLIT_IMPLEMENTATION in exactly ONE .c file before including.
32
+ */
33
+
34
+ #ifndef WC_GL_BLIT_H
35
+ #define WC_GL_BLIT_H
36
+
37
+ #ifndef WASMCART_H
38
+ #error "Include wasmcart.h (with WC_USE_GL) before wc_gl_blit.h"
39
+ #endif
40
+
41
+ /* Upload RGBA pixels to a GL texture and draw a fullscreen quad.
42
+ * Call once per frame after all CPU rendering is done. */
43
+ static void wc_gl_blit(const void *pixels, int width, int height);
44
+
45
+ #ifdef WC_GL_BLIT_IMPLEMENTATION
46
+
47
+ static GLuint _wc_blit_tex = 0;
48
+ static GLuint _wc_blit_program = 0;
49
+ static GLuint _wc_blit_vao = 0;
50
+ static GLuint _wc_blit_vbo = 0;
51
+ static int _wc_blit_ready = 0;
52
+
53
+ static void _wc_blit_init(int width, int height) {
54
+ const char *vs_src =
55
+ "#version 300 es\n"
56
+ "in vec2 aPos;\n"
57
+ "out vec2 vUV;\n"
58
+ "void main() {\n"
59
+ " vUV = aPos * 0.5 + 0.5;\n"
60
+ " vUV.y = 1.0 - vUV.y;\n"
61
+ " gl_Position = vec4(aPos, 0.0, 1.0);\n"
62
+ "}\n";
63
+ const char *fs_src =
64
+ "#version 300 es\n"
65
+ "precision mediump float;\n"
66
+ "in vec2 vUV;\n"
67
+ "uniform sampler2D uTex;\n"
68
+ "out vec4 fragColor;\n"
69
+ "void main() {\n"
70
+ " fragColor = texture(uTex, vUV);\n"
71
+ "}\n";
72
+
73
+ GLuint vs = glCreateShader(GL_VERTEX_SHADER);
74
+ GLint vs_len = (GLint)__builtin_strlen(vs_src);
75
+ glShaderSource(vs, 1, &vs_src, &vs_len);
76
+ glCompileShader(vs);
77
+
78
+ GLuint fs = glCreateShader(GL_FRAGMENT_SHADER);
79
+ GLint fs_len = (GLint)__builtin_strlen(fs_src);
80
+ glShaderSource(fs, 1, &fs_src, &fs_len);
81
+ glCompileShader(fs);
82
+
83
+ _wc_blit_program = glCreateProgram();
84
+ glAttachShader(_wc_blit_program, vs);
85
+ glAttachShader(_wc_blit_program, fs);
86
+ glLinkProgram(_wc_blit_program);
87
+
88
+ float quad[] = { -1,-1, 1,-1, -1,1, 1,1 };
89
+ glGenVertexArrays(1, &_wc_blit_vao);
90
+ glBindVertexArray(_wc_blit_vao);
91
+ glGenBuffers(1, &_wc_blit_vbo);
92
+ glBindBuffer(GL_ARRAY_BUFFER, _wc_blit_vbo);
93
+ glBufferData(GL_ARRAY_BUFFER, sizeof(quad), quad, GL_STATIC_DRAW);
94
+
95
+ GLint aPos = glGetAttribLocation(_wc_blit_program, "aPos");
96
+ glEnableVertexAttribArray(aPos);
97
+ glVertexAttribPointer(aPos, 2, GL_FLOAT, GL_FALSE, 0, 0);
98
+
99
+ glGenTextures(1, &_wc_blit_tex);
100
+ glBindTexture(GL_TEXTURE_2D, _wc_blit_tex);
101
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
102
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
103
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
104
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
105
+
106
+ _wc_blit_ready = 1;
107
+ }
108
+
109
+ static void wc_gl_blit(const void *pixels, int width, int height) {
110
+ if (!_wc_blit_ready) _wc_blit_init(width, height);
111
+ if (!_wc_blit_ready) return;
112
+
113
+ glBindFramebuffer(GL_FRAMEBUFFER, 0);
114
+ glViewport(0, 0, width, height);
115
+ glDisable(GL_DEPTH_TEST);
116
+ glDisable(GL_BLEND);
117
+
118
+ glBindTexture(GL_TEXTURE_2D, _wc_blit_tex);
119
+ glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0,
120
+ GL_RGBA, GL_UNSIGNED_BYTE, pixels);
121
+
122
+ glUseProgram(_wc_blit_program);
123
+ glUniform1i(glGetUniformLocation(_wc_blit_program, "uTex"), 0);
124
+ glBindVertexArray(_wc_blit_vao);
125
+ glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
126
+ }
127
+
128
+ #endif /* WC_GL_BLIT_IMPLEMENTATION */
129
+ #endif /* WC_GL_BLIT_H */