/* * demoscene-eo, an ASCII art demoscene written as a gift for Emmanuel Otton retirement * Copyright (C) 2019 Ludovic Pouzenc * * This file is part of demoscene-eo. * * demoscene-eo is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * demoscene-eo is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with demoscene-eo. If not, see */ #include "main.h" #include "scene00.h" #include "scene01.h" #include "scene02.h" #include // calloc() #include // bzero() #include // mmap() #include // sem_t, sem_init(), Link with -pthread #include // fork() #include // wait() #include // errno #include // uint32_t #include // printf() #ifdef DEBUG #define TRACE(hint) printf("%s(): %s\n", __func__, hint) #else #define TRACE(hint) #endif #ifdef DEBUG_SEM #define TRACE_SEM(sem,op,hint) printf("%s(): %s(%s) %s\n", __func__, op, #sem, hint) #else #define TRACE_SEM(sem,op,hint) #endif #define SCENE_COUNT 3 #define SCENE_NEXT do { shm->scene = (shm->scene+1)%SCENE_COUNT; } while(0) #define EXPR_MIN_SIZE (shm->ge.w<80 || shm->ge.h<25) #define TEXT_MIN_SIZE "80x25" #define SEM_POST(sem,errcode) \ do { \ TRACE_SEM(sem,"sem_post",""); \ if (sem_post(sem) == -1) { return errcode; } \ } while(0) #define SEM_WAIT(sem,errcode) \ do { \ TRACE_SEM(sem,"sem_wait","call"); \ while (sem_wait(sem) == -1) { \ switch(errno) { \ case EINTR: \ case EAGAIN: \ break; \ default: \ return errcode; \ } \ } \ TRACE_SEM(sem,"sem_wait","done"); \ } while(0) typedef struct { sem_t worker_gl_can_render, worker_sdl_can_render, parent_can_read_result; int scene, paused, done; graphical_env_t ge; scene00_env_t s00e; scene01_env_t s01e; scene02_env_t s02e; } shm_t; int do_fork1(); int do_fork2(); int parent(); int worker_sdl(); int worker_gl(); shm_t *shm; int main() { TRACE("call"); shm = mmap(NULL, sizeof(shm_t), PROT_READ|PROT_WRITE, MAP_SHARED|MAP_ANONYMOUS, 0, 0); if (shm == NULL) return 1; bzero(shm, sizeof(shm_t)); if (sem_init(&shm->worker_gl_can_render, 1, 0) < 0) return 2; if (sem_init(&shm->worker_sdl_can_render, 1, 0) < 0) return 3; if (sem_init(&shm->parent_can_read_result, 1, 0) < 0) return 4; return do_fork1(); } int do_fork1() { TRACE("call"); switch (fork()) { case -1: return 4; break; case 0: return worker_sdl(); break; default: return do_fork2(); break; } } int do_fork2() { TRACE("call"); switch (fork()) { case -1: return 5; break; case 0: return worker_gl(); break; default: return parent(); break; } } int parent() { int lastscene=-1, res, child_status; caca_event_t caca_ev; TRACE("call"); // Initialize libcaca (http://caca.zoy.org/doxygen/libcaca/caca_8h.html) shm->ge.dp = caca_create_display(NULL); if(!shm->ge.dp) return 1; caca_set_display_title(shm->ge.dp, "demoscene-eo"); caca_set_display_time(shm->ge.dp, 16666); // 1e6µs/60 = 16666.66... It is ~60 fps shm->ge.cv = caca_get_canvas(shm->ge.dp); shm->ge.d = caca_create_dither(32, FBUF_W, FBUF_H, FBUF_W*4, 0x00ff0000, 0x0000ff00, 0x000000ff, 0); if (!shm->ge.d) return 2; caca_set_dither_color(shm->ge.d, "16"); // Main libcaca loop for caca window shm->ge.framecount=0; do { // Check canvas size at every frame because of unreliable CACA_EVENT_RESIZE shm->ge.w = caca_get_canvas_width(shm->ge.cv); shm->ge.h = caca_get_canvas_height(shm->ge.cv); if ( EXPR_MIN_SIZE ) { caca_set_color_ansi(shm->ge.cv, CACA_BLACK, CACA_WHITE); caca_put_str(shm->ge.cv, 0, 0, "Need a minimum of " TEXT_MIN_SIZE " terminal"); } else if (!shm->paused) { // init / free if scene transition if (lastscene != shm->scene) { switch(lastscene) { case 0: scene00_free_caca(&shm->ge, &shm->s00e); break; case 1: scene01_free_caca(&shm->ge, &shm->s01e); break; case 2: scene02_free_caca(&shm->ge, &shm->s02e); break; } } while (lastscene != shm->scene) { shm->ge.sdl_ticks = SDL_GetTicks(); shm->ge.sc_framecount = 0; switch(shm->scene) { case 0: res = scene00_init_caca(&shm->ge, &shm->s00e); break; case 1: res = scene01_init_caca(&shm->ge, &shm->s01e); break; case 2: res = scene02_init_caca(&shm->ge, &shm->s02e); break; } // If scene init fail, skip to the next one if (res) SCENE_NEXT; else lastscene = shm->scene; } shm->ge.sdl_ticks = SDL_GetTicks(); // This value wraps if the program runs for more than ~49 days SEM_POST(&shm->worker_gl_can_render,30); SEM_WAIT(&shm->parent_can_read_result,31); SEM_POST(&shm->worker_sdl_can_render,32); SEM_WAIT(&shm->parent_can_read_result,33); // Compute current scene frame (caca part) switch(shm->scene) { case 0: res = scene00_next_caca(&shm->ge, &shm->s00e); break; case 1: res = scene01_next_caca(&shm->ge, &shm->s01e); break; case 2: res = scene02_next_caca(&shm->ge, &shm->s02e); break; } if (res) SCENE_NEXT; shm->ge.framecount++; shm->ge.sc_framecount++; } // Display it caca_refresh_display(shm->ge.dp); // Auto framerate limiting (see caca_set_display_time()) // Event handling for the libcaca "window" (depending on CACA_DRIVER env variable) if (caca_get_event(shm->ge.dp, CACA_EVENT_KEY_PRESS|CACA_EVENT_RESIZE|CACA_EVENT_QUIT, &caca_ev, 0)) { switch(caca_get_event_type(&caca_ev)) { case CACA_EVENT_QUIT: shm->done = 1; break; case CACA_EVENT_KEY_PRESS: switch(caca_get_event_key_ch(&caca_ev)) { case 'q': shm->done = 1; break; case 'p': shm->paused = !shm->paused; break; case CACA_KEY_ESCAPE: SCENE_NEXT; break; } break; /* On Debian 10, no CACA_EVENT_RESIZE fired when using x11 CACA_DRIVER case CACA_EVENT_RESIZE: w = caca_get_event_resize_width(&caca_ev); h = caca_get_event_resize_height(&caca_ev); small = EXPR_MIN_SIZE; break; */ default: break; } } } while(!shm->done); caca_free_dither(shm->ge.d); caca_free_display(shm->ge.dp); shm->ge.cv=NULL; // reap forked processes (order doesn't matter) if (wait(&child_status) != -1) { if (child_status > 0) return child_status; } if (wait(&child_status) != -1) { if (child_status > 0) return child_status; } return 0; } int worker_sdl() { int lastscene=-1, res; Uint32 sdl_win_flags = 0; //XXX for final version, consider adding SDL_WINDOW_HIDDEN SDL_RendererInfo renderer_info; SDL_Event sdl_ev; TRACE("call"); // Initialize SDL (http://wiki.libsdl.org/SDL_CreateWindowAndRenderer) // Useful snippet : https://gist.github.com/koute/7391344 res = SDL_Init(SDL_INIT_VIDEO); if (res == -1) return 3; atexit(SDL_Quit); res = SDL_CreateWindowAndRenderer(FBUF_W, FBUF_H, sdl_win_flags, &shm->ge.sdl_win, &shm->ge.sdl_rndr); if (res == -1) return 4; SDL_SetWindowTitle(shm->ge.sdl_win, "SDL debug"); res = SDL_GetRendererInfo(shm->ge.sdl_rndr, &renderer_info); if (res < 0) return 5; if (!(renderer_info.flags & SDL_RENDERER_ACCELERATED)) return 6; if (!(renderer_info.flags & SDL_RENDERER_TARGETTEXTURE)) return 7; shm->ge.sdl_target = SDL_CreateTexture(shm->ge.sdl_rndr, SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_TARGET, FBUF_W, FBUF_H); if (shm->ge.sdl_target == NULL) return 9; while (!shm->done) { //FIXME change for TRYWAIT for event handling (+delay) SEM_WAIT(&shm->worker_sdl_can_render,20); if (!shm->paused) { // init / free if scene transition if (lastscene != shm->scene) { switch(lastscene) { case 0: scene00_free_sdl(&shm->ge, &shm->s00e); break; case 1: scene01_free_sdl(&shm->ge, &shm->s01e); break; case 2: scene02_free_sdl(&shm->ge, &shm->s02e); break; } } while (lastscene != shm->scene ) { shm->ge.sdl_ticks = SDL_GetTicks(); shm->ge.sc_framecount = 0; switch(shm->scene) { case 0: res = scene00_init_sdl(&shm->ge, &shm->s00e); break; case 1: res = scene01_init_sdl(&shm->ge, &shm->s01e); break; case 2: res = scene02_init_sdl(&shm->ge, &shm->s02e); break; } // If scene init fail, skip to the next one if (res) SCENE_NEXT; else lastscene = shm->scene; } } // Compute current scene frame (sdl part) switch(shm->scene) { case 0: res = scene00_next_sdl(&shm->ge, &shm->s00e); break; case 1: res = scene01_next_sdl(&shm->ge, &shm->s01e); break; case 2: res = scene02_next_sdl(&shm->ge, &shm->s02e); break; } if (res) SCENE_NEXT; // Event handling for the SDL window (debug purposes) while(SDL_PollEvent(&sdl_ev)) { switch(sdl_ev.type) { case SDL_QUIT: shm->done = 1; break; case SDL_KEYDOWN: switch(sdl_ev.key.keysym.sym) { case SDLK_q: shm->done = 1; break; case SDLK_p: shm->paused = !shm->paused; break; case SDLK_ESCAPE: SCENE_NEXT; break; } break; default: break; } } SEM_POST(&shm->parent_can_read_result, 21); } SDL_DestroyRenderer(shm->ge.sdl_rndr); SDL_DestroyTexture(shm->ge.sdl_target); SDL_DestroyWindow(shm->ge.sdl_win); return 0; } int worker_gl() { int lastscene=-1, res; Uint32 sdl_win_flags = SDL_WINDOW_OPENGL; //XXX for final version, consider adding SDL_WINDOW_HIDDEN SDL_RendererInfo renderer_info; SDL_Event gl_ev; TRACE("call"); // Initialize SDL (http://wiki.libsdl.org/SDL_CreateWindowAndRenderer) // Useful snippet : https://gist.github.com/koute/7391344 res = SDL_Init(SDL_INIT_VIDEO); if (res == -1) return 3; atexit(SDL_Quit); SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1); SDL_GL_SetAttribute(SDL_GL_ACCELERATED_VISUAL, 1); SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 8); SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 8); SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 8); SDL_GL_SetAttribute(SDL_GL_ALPHA_SIZE, 8); SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3); SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 2); SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE); // Initialize OpenGL //shm->ge.gl_win = SDL_CreateWindow("GL Debug", SDL_WINDOWPOS_CENTERED, 0, FBUF_W, FBUF_H, sdl_win_flags); res = SDL_CreateWindowAndRenderer(FBUF_W, FBUF_H, sdl_win_flags, &shm->ge.gl_win, &shm->ge.gl_rndr); if (res == -1) return 4; SDL_SetWindowTitle(shm->ge.gl_win, "GL debug"); res = SDL_GetRendererInfo(shm->ge.gl_rndr, &renderer_info); if (res < 0) return 5; if (!(renderer_info.flags & SDL_RENDERER_ACCELERATED)) return 6; if (!(renderer_info.flags & SDL_RENDERER_TARGETTEXTURE)) return 7; shm->ge.gl_target = SDL_CreateTexture(shm->ge.gl_rndr, SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_TARGET, FBUF_W, FBUF_H); if (shm->ge.gl_target == NULL) return 9; shm->ge.gl_ctx = SDL_GL_CreateContext(shm->ge.gl_win); if (shm->ge.gl_ctx == NULL) return 11; while (!shm->done) { //FIXME change for TRYWAIT for event handling (+delay) SEM_WAIT(&shm->worker_gl_can_render,10); if (!shm->paused) { // init / free if scene transition if (lastscene != shm->scene) { switch(lastscene) { case 0: scene00_free_gl(&shm->ge, &shm->s00e); break; case 1: scene01_free_gl(&shm->ge, &shm->s01e); break; case 2: scene02_free_gl(&shm->ge, &shm->s02e); break; } } while (lastscene != shm->scene) { shm->ge.sdl_ticks = SDL_GetTicks(); shm->ge.sc_framecount = 0; switch(shm->scene) { case 0: res = scene00_init_gl(&shm->ge, &shm->s00e); break; case 1: res = scene01_init_gl(&shm->ge, &shm->s01e); break; case 2: res = scene02_init_gl(&shm->ge, &shm->s02e); break; } // If scene init fail, skip to the next one if (res) SCENE_NEXT; else lastscene = shm->scene; } } // Compute current scene frame (gl part) switch(shm->scene) { case 0: res = scene00_next_gl(&shm->ge, &shm->s00e); break; case 1: res = scene01_next_gl(&shm->ge, &shm->s01e); break; case 2: res = scene02_next_gl(&shm->ge, &shm->s02e); break; } if (res) SCENE_NEXT; // Event handling for the GL window (debug purposes) while(SDL_PollEvent(&gl_ev)) { switch(gl_ev.type) { case SDL_QUIT: shm->done = 1; break; case SDL_KEYDOWN: switch(gl_ev.key.keysym.sym) { case SDLK_q: shm->done = 1; break; case SDLK_p: shm->paused = !shm->paused; break; case SDLK_ESCAPE: SCENE_NEXT; break; } break; default: break; } } SEM_POST(&shm->parent_can_read_result, 11); } //SDL_DestroyTexture(shm->ge.gl_target); SDL_GL_DeleteContext(shm->ge.gl_ctx); SDL_DestroyWindow(shm->ge.gl_win); return 0; }