diff options
Diffstat (limited to 'src/demoscene-eo.c')
-rw-r--r-- | src/demoscene-eo.c | 456 |
1 files changed, 456 insertions, 0 deletions
diff --git a/src/demoscene-eo.c b/src/demoscene-eo.c new file mode 100644 index 0000000..94db4e5 --- /dev/null +++ b/src/demoscene-eo.c @@ -0,0 +1,456 @@ +/* + * demoscene-eo, an ASCII art demoscene written as a gift for Emmanuel Otton retirement + * Copyright (C) 2019 Ludovic Pouzenc <ludovic@pouzenc.fr> + * + * 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 <http://www.gnu.org/licenses/> + */ +#include "main.h" +#include "scene00.h" +#include "scene01.h" +#include "scene02.h" + +#include <stdlib.h> // calloc() +#include <strings.h> // bzero() +#include <sys/mman.h> // mmap() +#include <semaphore.h> // sem_t, sem_init(), Link with -pthread +#include <unistd.h> // fork() +#include <sys/wait.h> // wait() +#include <errno.h> // errno +#include <stdint.h> // uint32_t +#include <stdio.h> // printf() +#include <signal.h> // sigaction() + +#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 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(); + +static int skip = 0; +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"); + fflush(stdout); + switch (fork()) { + case -1: return 4; break; + case 0: return worker_sdl(); break; + default: return do_fork2(); break; + } +} + +int do_fork2() { + TRACE("call"); + fflush(stdout); + 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, 41666); // 1e6µs/24 = 41666.66... It is ~24 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; +} + +static void worker_sdl_sighandler(int sig) { + TRACE("call"); + if ( sig == SIGALRM ) { + skip = 1; + sem_post(&shm->worker_sdl_can_render); + } +} + +int worker_sdl() { + int lastscene=-1, res; + struct sigaction sa; +#ifdef DEBUG + Uint32 sdl_win_flags = 0; //XXX for final version, consider adding SDL_WINDOW_HIDDEN + SDL_Event sdl_ev; +#else + Uint32 sdl_win_flags = SDL_WINDOW_HIDDEN; +#endif + SDL_RendererInfo renderer_info; + + TRACE("call"); + + sa.sa_handler = worker_sdl_sighandler; + sigemptyset(&sa.sa_mask); + sa.sa_flags = 0; + if (sigaction(SIGALRM, &sa, NULL) == -1) return 2; + + // 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) { + alarm(1); + SEM_WAIT(&shm->worker_sdl_can_render,20); + alarm(0); + + if (!shm->paused && !skip) { + // 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; + } + if (skip) skip = 0; + +#ifdef DEBUG + // 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; + } + } +#endif + 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; +} + +static void worker_gl_sighandler(int sig) { + TRACE("call"); + if ( sig == SIGALRM ) { + skip = 1; + sem_post(&shm->worker_gl_can_render); + } +} + +int worker_gl() { + int lastscene=-1, res; + struct sigaction sa; +#ifdef DEBUG + Uint32 sdl_win_flags = SDL_WINDOW_OPENGL; + SDL_Event gl_ev; +#else + Uint32 sdl_win_flags = SDL_WINDOW_OPENGL | SDL_WINDOW_HIDDEN; +#endif + SDL_RendererInfo renderer_info; + + TRACE("call"); + + sa.sa_handler = worker_gl_sighandler; + sigemptyset(&sa.sa_mask); + sa.sa_flags = 0; + if (sigaction(SIGALRM, &sa, NULL) == -1) return 2; + + // Initialize SDL for GL. 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; +/* + glBindFramebuffer(GL_FRAMEBUFFER,0); + glClearColor(0,0,0,1); + glClear(GL_COLOR_BUFFER_BIT); + SDL_GL_SwapWindow(shm->ge.gl_win); +*/ + while (!shm->done) { + alarm(1); + SEM_WAIT(&shm->worker_gl_can_render,10); + alarm(0); + + if (!shm->paused && !skip) { + // 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; + } + if (skip) skip = 0; + +#ifdef DEBUG + // 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; + } + } +#endif + 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; +} |