summaryrefslogtreecommitdiff
path: root/src/demoscene-eo.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/demoscene-eo.c')
-rw-r--r--src/demoscene-eo.c456
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;
+}