summaryrefslogtreecommitdiff
path: root/src/contrib/SDL-3.2.20/examples/demo/04-bytepusher/bytepusher.c
diff options
context:
space:
mode:
author3gg <3gg@shellblade.net>2025-08-30 16:53:58 -0700
committer3gg <3gg@shellblade.net>2025-08-30 16:53:58 -0700
commit6aaedb813fa11ba0679c3051bc2eb28646b9506c (patch)
tree34acbfc9840e02cb4753e6306ea7ce978bf8b58e /src/contrib/SDL-3.2.20/examples/demo/04-bytepusher/bytepusher.c
parent8f228ade99dd3d4c8da9b78ade1815c9adf85c8f (diff)
Update to SDL3
Diffstat (limited to 'src/contrib/SDL-3.2.20/examples/demo/04-bytepusher/bytepusher.c')
-rw-r--r--src/contrib/SDL-3.2.20/examples/demo/04-bytepusher/bytepusher.c416
1 files changed, 416 insertions, 0 deletions
diff --git a/src/contrib/SDL-3.2.20/examples/demo/04-bytepusher/bytepusher.c b/src/contrib/SDL-3.2.20/examples/demo/04-bytepusher/bytepusher.c
new file mode 100644
index 0000000..acb2ea4
--- /dev/null
+++ b/src/contrib/SDL-3.2.20/examples/demo/04-bytepusher/bytepusher.c
@@ -0,0 +1,416 @@
1/*
2 * An implementation of the BytePusher VM.
3 *
4 * For example programs and more information about BytePusher, see
5 * https://esolangs.org/wiki/BytePusher
6 *
7 * This code is public domain. Feel free to use it for any purpose!
8 */
9
10#define SDL_MAIN_USE_CALLBACKS
11#include <SDL3/SDL.h>
12#include <SDL3/SDL_main.h>
13#include <stdarg.h>
14
15#define SCREEN_W 256
16#define SCREEN_H 256
17#define RAM_SIZE 0x1000000
18#define FRAMES_PER_SECOND 60
19#define SAMPLES_PER_FRAME 256
20#define NS_PER_SECOND (Uint64)SDL_NS_PER_SECOND
21#define MAX_AUDIO_LATENCY_FRAMES 5
22
23#define IO_KEYBOARD 0
24#define IO_PC 2
25#define IO_SCREEN_PAGE 5
26#define IO_AUDIO_BANK 6
27
28typedef struct {
29 Uint8 ram[RAM_SIZE + 8];
30 Uint8 screenbuf[SCREEN_W * SCREEN_H];
31 Uint64 last_tick;
32 Uint64 tick_acc;
33 SDL_Window* window;
34 SDL_Renderer* renderer;
35 SDL_Surface* screen;
36 SDL_Texture* screentex;
37 SDL_Texture* rendertarget; /* we need this render target for text to look good */
38 SDL_AudioStream* audiostream;
39 char status[SCREEN_W / 8];
40 int status_ticks;
41 Uint16 keystate;
42 bool display_help;
43 bool positional_input;
44} BytePusher;
45
46static const struct {
47 const char *key;
48 const char *value;
49} extended_metadata[] = {
50 { SDL_PROP_APP_METADATA_URL_STRING, "https://examples.libsdl.org/SDL3/demo/04-bytepusher/" },
51 { SDL_PROP_APP_METADATA_CREATOR_STRING, "SDL team" },
52 { SDL_PROP_APP_METADATA_COPYRIGHT_STRING, "Placed in the public domain" },
53 { SDL_PROP_APP_METADATA_TYPE_STRING, "game" }
54};
55
56static inline Uint16 read_u16(const BytePusher* vm, Uint32 addr) {
57 const Uint8* ptr = &vm->ram[addr];
58 return ((Uint16)ptr[0] << 8) | ((Uint16)ptr[1]);
59}
60
61static inline Uint32 read_u24(const BytePusher* vm, Uint32 addr) {
62 const Uint8* ptr = &vm->ram[addr];
63 return ((Uint32)ptr[0] << 16) | ((Uint32)ptr[1] << 8) | ((Uint32)ptr[2]);
64}
65
66static void set_status(BytePusher* vm, const char* fmt, ...) {
67 va_list args;
68 va_start(args, fmt);
69 SDL_vsnprintf(vm->status, sizeof(vm->status), fmt, args);
70 va_end(args);
71 vm->status[sizeof(vm->status) - 1] = 0;
72 vm->status_ticks = FRAMES_PER_SECOND * 3;
73}
74
75static bool load(BytePusher* vm, SDL_IOStream* stream, bool closeio) {
76 size_t bytes_read = 0;
77 bool ok = true;
78
79 SDL_memset(vm->ram, 0, RAM_SIZE);
80
81 if (!stream) {
82 return false;
83 }
84
85 while (bytes_read < RAM_SIZE) {
86 size_t read = SDL_ReadIO(stream, &vm->ram[bytes_read], RAM_SIZE - bytes_read);
87 bytes_read += read;
88 if (read == 0) {
89 ok = SDL_GetIOStatus(stream) == SDL_IO_STATUS_EOF;
90 break;
91 }
92 }
93 if (closeio) {
94 SDL_CloseIO(stream);
95 }
96
97 SDL_ClearAudioStream(vm->audiostream);
98
99 vm->display_help = !ok;
100 return ok;
101}
102
103static const char* filename(const char* path) {
104 size_t i = SDL_strlen(path) + 1;
105 while (i > 0) {
106 i -= 1;
107 if (path[i] == '/' || path[i] == '\\') {
108 return path + i + 1;
109 }
110 }
111 return path;
112}
113
114static bool load_file(BytePusher* vm, const char* path) {
115 if (load(vm, SDL_IOFromFile(path, "rb"), true)) {
116 set_status(vm, "loaded %s", filename(path));
117 return true;
118 } else {
119 set_status(vm, "load failed: %s", filename(path));
120 return false;
121 }
122}
123
124static void print(BytePusher* vm, int x, int y, const char* str) {
125 SDL_SetRenderDrawColor(vm->renderer, 0, 0, 0, SDL_ALPHA_OPAQUE);
126 SDL_RenderDebugText(vm->renderer, (float)(x + 1), (float)(y + 1), str);
127 SDL_SetRenderDrawColor(vm->renderer, 0xff, 0xff, 0xff, SDL_ALPHA_OPAQUE);
128 SDL_RenderDebugText(vm->renderer, (float)x, (float)y, str);
129 SDL_SetRenderDrawColor(vm->renderer, 0, 0, 0, SDL_ALPHA_OPAQUE);
130}
131
132SDL_AppResult SDL_AppInit(void** appstate, int argc, char* argv[]) {
133 BytePusher* vm;
134 SDL_Palette* palette;
135 SDL_Rect usable_bounds;
136 SDL_AudioSpec audiospec = { SDL_AUDIO_S8, 1, SAMPLES_PER_FRAME * FRAMES_PER_SECOND };
137 SDL_DisplayID primary_display;
138 SDL_PropertiesID texprops;
139 int zoom = 2;
140 int i;
141 Uint8 r, g, b;
142 (void)argc;
143 (void)argv;
144
145 if (!SDL_SetAppMetadata("SDL 3 BytePusher", "1.0", "com.example.SDL3BytePusher")) {
146 return SDL_APP_FAILURE;
147 }
148
149 for (i = 0; i < (int)SDL_arraysize(extended_metadata); i++) {
150 if (!SDL_SetAppMetadataProperty(extended_metadata[i].key, extended_metadata[i].value)) {
151 return SDL_APP_FAILURE;
152 }
153 }
154
155 if (!SDL_Init(SDL_INIT_AUDIO | SDL_INIT_VIDEO)) {
156 return SDL_APP_FAILURE;
157 }
158
159 if (!(vm = (BytePusher *)SDL_calloc(1, sizeof(*vm)))) {
160 return SDL_APP_FAILURE;
161 }
162 *(BytePusher**)appstate = vm;
163
164 vm->display_help = true;
165
166 primary_display = SDL_GetPrimaryDisplay();
167 if (SDL_GetDisplayUsableBounds(primary_display, &usable_bounds)) {
168 int zoom_w = (usable_bounds.w - usable_bounds.x) * 2 / 3 / SCREEN_W;
169 int zoom_h = (usable_bounds.h - usable_bounds.y) * 2 / 3 / SCREEN_H;
170 zoom = zoom_w < zoom_h ? zoom_w : zoom_h;
171 if (zoom < 1) {
172 zoom = 1;
173 }
174 }
175
176 if (!SDL_CreateWindowAndRenderer("SDL 3 BytePusher",
177 SCREEN_W * zoom, SCREEN_H * zoom, SDL_WINDOW_RESIZABLE,
178 &vm->window, &vm->renderer
179 )) {
180 return SDL_APP_FAILURE;
181 }
182
183 if (!SDL_SetRenderLogicalPresentation(
184 vm->renderer, SCREEN_W, SCREEN_H, SDL_LOGICAL_PRESENTATION_INTEGER_SCALE
185 )) {
186 return SDL_APP_FAILURE;
187 }
188
189 if (!(vm->screen = SDL_CreateSurfaceFrom(
190 SCREEN_W, SCREEN_H, SDL_PIXELFORMAT_INDEX8, vm->screenbuf, SCREEN_W
191 ))) {
192 return SDL_APP_FAILURE;
193 }
194
195 if (!(palette = SDL_CreateSurfacePalette(vm->screen))) {
196 return SDL_APP_FAILURE;
197 }
198 i = 0;
199 for (r = 0; r < 6; ++r) {
200 for (g = 0; g < 6; ++g) {
201 for (b = 0; b < 6; ++b, ++i) {
202 SDL_Color color = { (Uint8)(r * 0x33), (Uint8)(g * 0x33), (Uint8)(b * 0x33), SDL_ALPHA_OPAQUE };
203 palette->colors[i] = color;
204 }
205 }
206 }
207 for (; i < 256; ++i) {
208 SDL_Color color = { 0, 0, 0, SDL_ALPHA_OPAQUE };
209 palette->colors[i] = color;
210 }
211
212 texprops = SDL_CreateProperties();
213 SDL_SetNumberProperty(texprops, SDL_PROP_TEXTURE_CREATE_ACCESS_NUMBER, SDL_TEXTUREACCESS_STREAMING);
214 SDL_SetNumberProperty(texprops, SDL_PROP_TEXTURE_CREATE_WIDTH_NUMBER, SCREEN_W);
215 SDL_SetNumberProperty(texprops, SDL_PROP_TEXTURE_CREATE_HEIGHT_NUMBER, SCREEN_H);
216 vm->screentex = SDL_CreateTextureWithProperties(vm->renderer, texprops);
217 SDL_SetNumberProperty(texprops, SDL_PROP_TEXTURE_CREATE_ACCESS_NUMBER, SDL_TEXTUREACCESS_TARGET);
218 vm->rendertarget = SDL_CreateTextureWithProperties(vm->renderer, texprops);
219 SDL_DestroyProperties(texprops);
220 if (!vm->screentex || !vm->rendertarget) {
221 return SDL_APP_FAILURE;
222 }
223 SDL_SetTextureScaleMode(vm->screentex, SDL_SCALEMODE_NEAREST);
224 SDL_SetTextureScaleMode(vm->rendertarget, SDL_SCALEMODE_NEAREST);
225
226 if (!(vm->audiostream = SDL_OpenAudioDeviceStream(
227 SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK, &audiospec, NULL, NULL
228 ))) {
229 return SDL_APP_FAILURE;
230 }
231 SDL_SetAudioStreamGain(vm->audiostream, 0.1f); /* examples are loud! */
232 SDL_ResumeAudioStreamDevice(vm->audiostream);
233
234 set_status(vm, "renderer: %s", SDL_GetRendererName(vm->renderer));
235
236 vm->last_tick = SDL_GetTicksNS();
237 vm->tick_acc = NS_PER_SECOND;
238
239 return SDL_APP_CONTINUE;
240}
241
242SDL_AppResult SDL_AppIterate(void* appstate) {
243 BytePusher* vm = (BytePusher*)appstate;
244
245 Uint64 tick = SDL_GetTicksNS();
246 Uint64 delta = tick - vm->last_tick;
247 bool updated, skip_audio;
248
249 vm->last_tick = tick;
250
251 vm->tick_acc += delta * FRAMES_PER_SECOND;
252 updated = vm->tick_acc >= NS_PER_SECOND;
253 skip_audio = vm->tick_acc >= MAX_AUDIO_LATENCY_FRAMES * NS_PER_SECOND;
254
255 if (skip_audio) {
256 // don't let audio fall too far behind
257 SDL_ClearAudioStream(vm->audiostream);
258 }
259
260 while (vm->tick_acc >= NS_PER_SECOND) {
261 Uint32 pc;
262 int i;
263
264 vm->tick_acc -= NS_PER_SECOND;
265
266 vm->ram[IO_KEYBOARD] = (Uint8)(vm->keystate >> 8);
267 vm->ram[IO_KEYBOARD + 1] = (Uint8)(vm->keystate);
268
269 pc = read_u24(vm, IO_PC);
270 for (i = 0; i < SCREEN_W * SCREEN_H; ++i) {
271 Uint32 src = read_u24(vm, pc);
272 Uint32 dst = read_u24(vm, pc + 3);
273 vm->ram[dst] = vm->ram[src];
274 pc = read_u24(vm, pc + 6);
275 }
276
277 if (!skip_audio || vm->tick_acc < NS_PER_SECOND) {
278 SDL_PutAudioStreamData(
279 vm->audiostream,
280 &vm->ram[(Uint32)read_u16(vm, IO_AUDIO_BANK) << 8],
281 SAMPLES_PER_FRAME
282 );
283 }
284 }
285
286 if (updated) {
287 SDL_Surface *tex;
288
289 SDL_SetRenderTarget(vm->renderer, vm->rendertarget);
290
291 if (!SDL_LockTextureToSurface(vm->screentex, NULL, &tex)) {
292 return SDL_APP_FAILURE;
293 }
294 vm->screen->pixels = &vm->ram[(Uint32)vm->ram[IO_SCREEN_PAGE] << 16];
295 SDL_BlitSurface(vm->screen, NULL, tex, NULL);
296 SDL_UnlockTexture(vm->screentex);
297
298 SDL_RenderTexture(vm->renderer, vm->screentex, NULL, NULL);
299 }
300
301 if (vm->display_help) {
302 print(vm, 4, 4, "Drop a BytePusher file in this");
303 print(vm, 8, 12, "window to load and run it!");
304 print(vm, 4, 28, "Press ENTER to switch between");
305 print(vm, 8, 36, "positional and symbolic input.");
306 }
307
308 if (vm->status_ticks > 0) {
309 vm->status_ticks -= 1;
310 print(vm, 4, SCREEN_H - 12, vm->status);
311 }
312
313 SDL_SetRenderTarget(vm->renderer, NULL);
314 SDL_RenderClear(vm->renderer);
315 SDL_RenderTexture(vm->renderer, vm->rendertarget, NULL, NULL);
316 SDL_RenderPresent(vm->renderer);
317
318 return SDL_APP_CONTINUE;
319}
320
321static Uint16 keycode_mask(SDL_Keycode key) {
322 int index;
323 if (key >= SDLK_0 && key <= SDLK_9) {
324 index = key - SDLK_0;
325 } else if (key >= SDLK_A && key <= SDLK_F) {
326 index = key - SDLK_A + 10;
327 } else {
328 return 0;
329 }
330 return (Uint16)1 << index;
331}
332
333static Uint16 scancode_mask(SDL_Scancode scancode) {
334 int index;
335 switch (scancode) {
336 case SDL_SCANCODE_1: index = 0x1; break;
337 case SDL_SCANCODE_2: index = 0x2; break;
338 case SDL_SCANCODE_3: index = 0x3; break;
339 case SDL_SCANCODE_4: index = 0xc; break;
340 case SDL_SCANCODE_Q: index = 0x4; break;
341 case SDL_SCANCODE_W: index = 0x5; break;
342 case SDL_SCANCODE_E: index = 0x6; break;
343 case SDL_SCANCODE_R: index = 0xd; break;
344 case SDL_SCANCODE_A: index = 0x7; break;
345 case SDL_SCANCODE_S: index = 0x8; break;
346 case SDL_SCANCODE_D: index = 0x9; break;
347 case SDL_SCANCODE_F: index = 0xe; break;
348 case SDL_SCANCODE_Z: index = 0xa; break;
349 case SDL_SCANCODE_X: index = 0x0; break;
350 case SDL_SCANCODE_C: index = 0xb; break;
351 case SDL_SCANCODE_V: index = 0xf; break;
352 default: return 0;
353 }
354 return (Uint16)1 << index;
355}
356
357SDL_AppResult SDL_AppEvent(void* appstate, SDL_Event* event) {
358 BytePusher* vm = (BytePusher*)appstate;
359
360 switch (event->type) {
361 case SDL_EVENT_QUIT:
362 return SDL_APP_SUCCESS;
363
364 case SDL_EVENT_DROP_FILE:
365 load_file(vm, event->drop.data);
366 break;
367
368 case SDL_EVENT_KEY_DOWN:
369#ifndef __EMSCRIPTEN__
370 if (event->key.key == SDLK_ESCAPE) {
371 return SDL_APP_SUCCESS;
372 }
373#endif
374 if (event->key.key == SDLK_RETURN) {
375 vm->positional_input = !vm->positional_input;
376 vm->keystate = 0;
377 if (vm->positional_input) {
378 set_status(vm, "switched to positional input");
379 } else {
380 set_status(vm, "switched to symbolic input");
381 }
382 }
383 if (vm->positional_input) {
384 vm->keystate |= scancode_mask(event->key.scancode);
385 } else {
386 vm->keystate |= keycode_mask(event->key.key);
387 }
388 break;
389
390 case SDL_EVENT_KEY_UP:
391 if (vm->positional_input) {
392 vm->keystate &= ~scancode_mask(event->key.scancode);
393 } else {
394 vm->keystate &= ~keycode_mask(event->key.key);
395 }
396 break;
397 }
398
399 return SDL_APP_CONTINUE;
400}
401
402void SDL_AppQuit(void* appstate, SDL_AppResult result) {
403 if (result == SDL_APP_FAILURE) {
404 SDL_Log("Error: %s", SDL_GetError());
405 }
406 if (appstate) {
407 BytePusher* vm = (BytePusher*)appstate;
408 SDL_DestroyAudioStream(vm->audiostream);
409 SDL_DestroyTexture(vm->rendertarget);
410 SDL_DestroyTexture(vm->screentex);
411 SDL_DestroySurface(vm->screen);
412 SDL_DestroyRenderer(vm->renderer);
413 SDL_DestroyWindow(vm->window);
414 SDL_free(vm);
415 }
416}