Spout 1.4 for fredygames.com: SDL 1.2 -> SDL2 and a browser main loop. Same licence as Spout (MIT, see COPYING). Apply with: patch -p1 < spout-fredygames.patch - Video: an SDL2 window, renderer and a 128x88 streaming texture replace the 8-bit SDL 1.2 surfaces; the four grey levels are mapped to RGB when the frame is copied out. - Keys: read by scancode with SDL_GetKeyboardState (same keys as before). - The body of the main loop moved to frame(). Natively it still runs in a loop with SDL_Delay; in the browser (__EMSCRIPTEN__) emscripten_set_main_loop calls it once per display refresh and it runs as many 50 Hz game steps as are due, so the game speed does not depend on the screen rate. - In the browser the high score goes to /home/web_user/spout/spout.sco (the page keeps that folder in IndexedDB) and Shift+Esc on the title screen does not quit. - open() of the score file gets the mode argument that O_CREAT requires. - The globals "time, score, height, dispscore" are static: a global named "time" clashes with libc's time() when linking for WebAssembly. - config.mk asks pkg-config for sdl2. --- a/config.mk +++ b/config.mk @@ -7,8 +7,8 @@ MANPREFIX = ${PREFIX}/share/man # includes and libs -SDLINC = $(shell pkg-config --cflags sdl) -SDLLIB = $(shell pkg-config --libs sdl) +SDLINC = $(shell pkg-config --cflags sdl2) +SDLLIB = $(shell pkg-config --libs sdl2) INCS = -I. -I/usr/include ${SDLINC} LIBS = -L/usr/lib -lc ${SDLLIB} --- a/spout.c +++ b/spout.c @@ -9,6 +9,9 @@ #include #include #include +#ifdef __EMSCRIPTEN__ +#include +#endif #include "spout.h" #include "sintable.h" #include "font.h" @@ -64,16 +67,19 @@ int nGrain; -int time = FRAMERATE * 60, score = 0, height = 0, dispscore = 0; +/* static: a global named "time" clashes with libc time() when linking for WebAssembly. */ +static int time = FRAMERATE * 60, score = 0, height = 0, dispscore = 0; int hiScore[2] = {0, 0}; int dispPos, upperLine, rollCount; char score_path[512]; -SDL_Surface *video, *layer; -SDL_Rect layerRect; +SDL_Window *window; +SDL_Renderer *renderer; +SDL_Texture *texture; +Uint32 pixels[128 * 88]; unsigned char *vBuffer = NULL; -unsigned char *keys; +const Uint8 *keys; int exec = 1; int interval = 0; int font_posX = 0, font_posY = 0, font_width = 4, font_height = 6; @@ -102,7 +108,12 @@ memset(vbuff, 0, 128 * 88); +#ifdef __EMSCRIPTEN__ + /* In the browser the page mounts this folder on IndexedDB. */ + snprintf(score_path, 512, "%s", "/home/web_user/spout/spout.sco"); +#else snprintf(score_path, 512, "%s/%s", getenv("HOME"), ".spout.sco"); +#endif { int fa; @@ -126,7 +137,9 @@ if(gamePhase >= 2) { gamePhase = 0; } else { +#ifndef __EMSCRIPTEN__ exec = 0; +#endif } pad = 0; } @@ -144,7 +157,7 @@ int fa; hiScore[0] = score; hiScore[1] = height; - if((fa = open(score_path, O_CREAT | O_WRONLY | O_TRUNC)) != -1) { + if((fa = open(score_path, O_CREAT | O_WRONLY | O_TRUNC, S_IRUSR | S_IWUSR)) != -1) { write(fa, (void *) hiScore, 8); close(fa); } @@ -832,62 +845,48 @@ } void initSDL() { - SDL_PixelFormat *pfrm; - if(SDL_Init(SDL_INIT_VIDEO) < 0) { fprintf(stderr, "Couldn't initialize SDL: %s\n",SDL_GetError()); exit(1); } atexit(SDL_Quit); - if(fullscreen) - video = SDL_SetVideoMode(SDL_WIDTH, SDL_HEIGHT, 8, SDL_DOUBLEBUF | SDL_HWSURFACE | SDL_HWPALETTE | SDL_FULLSCREEN); - else - video = SDL_SetVideoMode(SDL_WIDTH, SDL_HEIGHT, 8, SDL_DOUBLEBUF | SDL_HWSURFACE | SDL_HWPALETTE); - if(video == NULL) { + window = SDL_CreateWindow("spout", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, + SDL_WIDTH, SDL_HEIGHT, fullscreen ? SDL_WINDOW_FULLSCREEN_DESKTOP : 0); + if(window == NULL) { fprintf(stderr, "Couldn't set video mode: %s\n", SDL_GetError()); exit(1); } - pfrm = video->format; - layer = SDL_CreateRGBSurface(SDL_SWSURFACE, SDL_WIDTH, SDL_HEIGHT, 8, pfrm->Rmask, pfrm->Gmask, pfrm->Bmask, pfrm->Amask); - if(layer == NULL) { - fprintf(stderr, "Couldn't create surface: %s\n", SDL_GetError()); + SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "0"); + renderer = SDL_CreateRenderer(window, -1, 0); + if(renderer == NULL) { + fprintf(stderr, "Couldn't create renderer: %s\n", SDL_GetError()); exit(1); } + SDL_RenderSetLogicalSize(renderer, SDL_WIDTH, SDL_HEIGHT); - layerRect.x = 0; - layerRect.y = 0; - layerRect.w = SDL_WIDTH; - layerRect.h = SDL_HEIGHT; - - { - static SDL_Color pltTbl[4] = { - {255, 255, 255}, - {170, 170, 170}, - {85, 85, 85}, - {0, 0, 0} - }; - SDL_SetColors(video, pltTbl, 0, 4); - SDL_SetColors(layer, pltTbl, 0, 4); + texture = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_STREAMING, 128, 88); + if(texture == NULL) { + fprintf(stderr, "Couldn't create surface: %s\n", SDL_GetError()); + exit(1); } } void pceLCDTrans() { - int x, y; - unsigned char *vbi, *bi; + static const Uint32 pltTbl[4] = { + 0xffffffff, 0xffaaaaaa, 0xff555555, 0xff000000 + }; + int i; - bi = layer->pixels; - for(y = 0; y < SDL_HEIGHT; y ++) { - vbi = vBuffer + (y / zoom) * 128; - for(x = 0; x < SDL_WIDTH; x ++) { - *bi ++ = *(vbi + x / zoom); - } - bi += layer->pitch - SDL_WIDTH; + for(i = 0; i < 128 * 88; i ++) { + pixels[i] = pltTbl[vBuffer[i] & 3]; } - SDL_BlitSurface(layer, NULL, video, &layerRect); - SDL_Flip(video); + SDL_UpdateTexture(texture, NULL, pixels, 128 * sizeof(Uint32)); + SDL_RenderClear(renderer); + SDL_RenderCopy(renderer, texture, NULL, NULL); + SDL_RenderPresent(renderer); } int pcePadGet() { @@ -895,10 +894,10 @@ int i = 0, op = pad & 0x00ff; int k[] = { - SDLK_UP, SDLK_DOWN, SDLK_LEFT, SDLK_RIGHT, - SDLK_KP8, SDLK_KP2, SDLK_KP4, SDLK_KP6, - SDLK_x, SDLK_z, SDLK_SPACE, SDLK_RETURN, - SDLK_ESCAPE, SDLK_LSHIFT, SDLK_RSHIFT + SDL_SCANCODE_UP, SDL_SCANCODE_DOWN, SDL_SCANCODE_LEFT, SDL_SCANCODE_RIGHT, + SDL_SCANCODE_KP_8, SDL_SCANCODE_KP_2, SDL_SCANCODE_KP_4, SDL_SCANCODE_KP_6, + SDL_SCANCODE_X, SDL_SCANCODE_Z, SDL_SCANCODE_SPACE, SDL_SCANCODE_RETURN, + SDL_SCANCODE_ESCAPE, SDL_SCANCODE_LSHIFT, SDL_SCANCODE_RSHIFT }; int p[] = { @@ -912,7 +911,7 @@ pad = 0; do { - if(keys[k[i]] == SDL_PRESSED) { + if(keys[k[i]]) { pad |= p[i]; } i ++; @@ -997,11 +996,56 @@ return 0; } -int main(int argc, char *argv[]) +long nextTick; +int cnt = 0; + +/* One pass of the original main loop: events, keys, then as many 50 Hz game steps as are due. */ +void frame(void) { SDL_Event event; - long nextTick, wait; - int cnt = 0; + int steps = 0; + + while(SDL_PollEvent(&event)) { +#ifndef __EMSCRIPTEN__ + if(event.type == SDL_QUIT) { + exec = 0; + } +#endif + } + keys = SDL_GetKeyboardState(NULL); + +#ifdef __EMSCRIPTEN__ + /* The browser calls this once per display refresh; the game keeps its own 50 Hz clock. */ + if((long)SDL_GetTicks() - nextTick > 250) { + nextTick = SDL_GetTicks(); + } + while((long)SDL_GetTicks() >= nextTick && steps < 5) { + pceAppProc(cnt); + nextTick += interval; + cnt ++; + steps ++; + } +#else + { + long wait = nextTick - SDL_GetTicks(); + if(wait > 0) { + SDL_Delay(wait); + } + } + + pceAppProc(cnt); + + nextTick += interval; + cnt ++; + + if(keys[SDL_SCANCODE_ESCAPE] && (keys[SDL_SCANCODE_LSHIFT] || keys[SDL_SCANCODE_RSHIFT])) { + exec = 0; + } +#endif +} + +int main(int argc, char *argv[]) +{ int i; for(i = 1; i < argc; i++) { switch(argv[i][1]) { @@ -1021,28 +1065,14 @@ initSDL(); pceAppInit(); - SDL_WM_SetCaption("spout", NULL); - nextTick = SDL_GetTicks() + interval; +#ifdef __EMSCRIPTEN__ + emscripten_set_main_loop(frame, 0, 1); +#else while(exec) { - SDL_PollEvent(&event); - keys = SDL_GetKeyState(NULL); - - wait = nextTick - SDL_GetTicks(); - if(wait > 0) { - SDL_Delay(wait); - } - - pceAppProc(cnt); - // SDL_Flip(video); - - nextTick += interval; - cnt ++; - - if((keys[SDLK_ESCAPE] == SDL_PRESSED && (keys[SDLK_LSHIFT] == SDL_PRESSED || keys[SDLK_RSHIFT] == SDL_PRESSED)) || event.type == SDL_QUIT) { - exec = 0; - } + frame(); } +#endif return 0; }