diff options
Diffstat (limited to 'contrib/SDL-3.2.8/examples/demo/04-bytepusher/bytepusher.c')
| -rw-r--r-- | contrib/SDL-3.2.8/examples/demo/04-bytepusher/bytepusher.c | 416 |
1 files changed, 416 insertions, 0 deletions
diff --git a/contrib/SDL-3.2.8/examples/demo/04-bytepusher/bytepusher.c b/contrib/SDL-3.2.8/examples/demo/04-bytepusher/bytepusher.c new file mode 100644 index 0000000..acb2ea4 --- /dev/null +++ b/contrib/SDL-3.2.8/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 | |||
| 28 | typedef 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 | |||
| 46 | static 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 | |||
| 56 | static 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 | |||
| 61 | static 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 | |||
| 66 | static 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 | |||
| 75 | static 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 | |||
| 103 | static 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 | |||
| 114 | static 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 | |||
| 124 | static 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 | |||
| 132 | SDL_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 | |||
| 242 | SDL_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 | |||
| 321 | static 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 | |||
| 333 | static 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 | |||
| 357 | SDL_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 | |||
| 402 | void 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 | } | ||
