diff options
author | Vito Caputo <vcaputo@pengaru.com> | 2020-07-31 16:54:31 -0700 |
---|---|---|
committer | Vito Caputo <vcaputo@pengaru.com> | 2020-07-31 16:54:31 -0700 |
commit | 94de2291e005f1c646fd779e8d32b2820e22e071 (patch) | |
tree | 988bb9fde53a73edf948dd14fec07802a2f42406 /src/pig.c | |
parent | cd7e9ebd9675461f59d5f7a9c7c0a9a1a0536d12 (diff) |
*: initial implementation of pig
This is nothing to write home about, but it provides a little sandbox for
developing shader-generated textures in the spirit of shadertoy or the
demoscene tool bonzomatic.
It's more oriented towards developing shaders for use with libstage in
the small games I've been hacking on.
Diffstat (limited to 'src/pig.c')
-rw-r--r-- | src/pig.c | 375 |
1 files changed, 375 insertions, 0 deletions
diff --git a/src/pig.c b/src/pig.c new file mode 100644 index 0000000..fa6c706 --- /dev/null +++ b/src/pig.c @@ -0,0 +1,375 @@ +/* + * Copyright (C) 2020 - Vito Caputo - <vcaputo@pengaru.com> + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 3 as published + * by the Free Software Foundation. + * + * This program 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 this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <assert.h> +#include <SDL.h> +#include <stdlib.h> // for rand() + +#include <play.h> +#include <stage.h> + +#include "checker-node.h" +#include "clear-node.h" +#include "glad.h" +#include "m4f.h" +#include "macros.h" +#include "pig.h" +#include "shader-node.h" +#include "shader.h" + +#define PIG_DEFAULT_WIDTH 640 +#define PIG_DEFAULT_HEIGHT 480 +//#define PIG_WINDOW_FLAGS (SDL_WINDOW_FULLSCREEN | SDL_WINDOW_RESIZABLE | SDL_WINDOW_OPENGL) +#define PIG_WINDOW_FLAGS (SDL_WINDOW_RESIZABLE | SDL_WINDOW_OPENGL | SDL_WINDOW_ALLOW_HIGHDPI) + +#if 0 +#define PIG_ASPECT_RATIO .7 +#endif + +static inline float randf(void) +{ + return rand() * (1.f / (float)RAND_MAX); +} + + +void pig_canvas_size(pig_t *pig, int *res_width, int *res_height) +{ + assert(pig); + assert(res_width); + assert(res_height); + + SDL_GL_GetDrawableSize(pig->window, res_width, res_height); +} + + +void pig_canvas_to_ndc(pig_t *pig, int x, int y, float *res_x, float *res_y) +{ + int w, h; + + assert(pig); + assert(res_x); + assert(res_y); + + pig_canvas_size(pig, &w, &h); + + *res_x = (float)x / (float)w * 2.f - 1.f; + *res_y = (1.f - (float)y / (float)h) * 2.f - 1.f; +} + + +void pig_canvas_from_ndc(pig_t *pig, float x, float y, int *res_x, int *res_y) +{ + int w, h; + + assert(pig); + assert(res_x); + assert(res_y); + + pig_canvas_size(pig, &w, &h); + + *res_x = roundf(x * (float)w * .5f + .5f * (float)w); + *res_y = roundf(y * (float)h * .5f + .5f * (float)h); +} + + +void pig_viewport_size(pig_t *pig, int *res_width, int *res_height) +{ + assert(pig); + assert(res_width); + assert(res_height); + + SDL_GetWindowSize(pig->window, res_width, res_height); +} + + +void pig_viewport_to_ndc(pig_t *pig, int x, int y, float *res_x, float *res_y) +{ + int w, h; + + assert(pig); + assert(res_x); + assert(res_y); + + pig_viewport_size(pig, &w, &h); + + *res_x = (float)x / (float)w * 2.f - 1.f; + *res_y = (1.f - (float)y / (float)h) * 2.f - 1.f; +} + + +void pig_viewport_from_ndc(pig_t *pig, float x, float y, int *res_x, int *res_y) +{ + int w, h; + + assert(pig); + assert(res_x); + assert(res_y); + + pig_viewport_size(pig, &w, &h); + + *res_x = roundf(x * (float)w * .5f + .5f * (float)w); + *res_y = roundf(y * (float)h * .5f + .5f * (float)h); +} + + +uint32_t pig_viewport_id(pig_t *pig) +{ + return SDL_GetWindowID(pig->window); +} + + +void pig_toggle_fullscreen(pig_t *pig) +{ + if (pig->windowed) { + if (!SDL_SetWindowFullscreen(pig->window, SDL_WINDOW_FULLSCREEN_DESKTOP)) + pig->windowed = 0; + } else { + if (!SDL_SetWindowFullscreen(pig->window, 0)) + pig->windowed = 1; + } +} + + +static void randomize_color(v3f_t *color) +{ + color->x = randf(); + color->y = randf(); + color->z = randf(); + + if (v3f_length(color) < .8f) + *color = v3f_normalize(color); +} + + +static void pig_uniforms_func(shader_t *shader, void *uniforms_ctxt, void *render_ctxt, unsigned n_uniforms, const int *uniforms, const m4f_t *model_x, float alpha) +{ + play_t *play = render_ctxt; + pig_t *pig = uniforms_ctxt; + unsigned t0, t1; + float r = randf(); + + if (play_ticks_elapsed(play, PLAY_TICKS_TIMER2, 100)) + shader_reload_files(shader); + + /* this one just keeps increasing */ + t0 = play_ticks(play, PLAY_TICKS_TIMER0); + + /* this one resets every second and gets constrained to 0.f-1.f */ + t1 = play_ticks(play, PLAY_TICKS_TIMER1); + if (t1 >= 1000) { + randomize_color(&pig->color); + + pig->seed = randf(); + play_ticks_reset(play, PLAY_TICKS_TIMER1); + } + + glUniform1f(uniforms[0], alpha); + glUniformMatrix4fv(uniforms[1], 1, GL_FALSE, &model_x->m[0][0]); // TODO: make transform manipulatable + glUniform3f(uniforms[2], pig->color.x, pig->color.y, pig->color.z); // TODO: make color configurable + glUniform1f(uniforms[3], (float)t0 * .001f); + glUniform1f(uniforms[4], (float)t1 * .001f); + glUniform1f(uniforms[5], pig->seed); + glUniform1f(uniforms[6], r); +} + +static const char *pig_uniforms[] = { + "alpha", + "model_x", + "color", + "time", + "T", + "seed", + "rand" +}; + + + +static void * pig_init(play_t *play, int argc, char *argv[], unsigned flags) +{ + pig_t *pig; + + fatal_if(argc != 3, "Usage: %s vertex.shader fragment.shader", argv[0]); + + pig = calloc(1, sizeof(pig_t)); + fatal_if(!pig, "Unable to allocate pig_t"); + + pig->window_width = PIG_DEFAULT_WIDTH; + pig->window_height = PIG_DEFAULT_HEIGHT; + pig->windowed = 1; + + fatal_if(SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_COMPATIBILITY) < 0, + "Unable to set GL core profile attribute"); + fatal_if(SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 2) < 0, + "Unable to set GL major version attribute"); + fatal_if(SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 1) < 0, + "Unable to set GL minor version attribute"); + fatal_if(SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1) < 0, + "Unable to set GL doublebuffer attribute"); + + fatal_if(SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 8) < 0, + "Unable to set GL red size attribute"); + fatal_if(SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 8) < 0, + "Unable to set GL green size attribute"); + fatal_if(SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 8) < 0, + "Unable to set GL blue size attribute"); + fatal_if(SDL_GL_SetAttribute(SDL_GL_ALPHA_SIZE, 8) < 0, /* this in particular is required for cache-node.c to work w/alpha */ + "Unable to set GL alpha size attribute"); + +//#define MSAA_RENDER_TARGET +#ifdef MSAA_RENDER_TARGET + fatal_if(SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, 4) < 0, + "Unable to et GL multisample samples attribute"); + fatal_if(SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS, 1) < 0, + "Unable to set GL multisample buffers attribute"); +#endif + +/* On older SDL versions, this define is missing at compile-time, + * but we can just provide it here and still try the sethint call, + * just in case the runtime SDL version knows how to handle it. + */ +#ifndef SDL_HINT_TOUCH_MOUSE_EVENTS +#define SDL_HINT_TOUCH_MOUSE_EVENTS "SDL_TOUCH_MOUSE_EVENTS" +#endif + + warn_if(!SDL_SetHint(SDL_HINT_TOUCH_MOUSE_EVENTS, "0"), + "Unable to suppress synthetic mouse events on touch"); + + pig->window = SDL_CreateWindow("pig", + SDL_WINDOWPOS_CENTERED, + SDL_WINDOWPOS_CENTERED, + pig->window_width, pig->window_height, + PIG_WINDOW_FLAGS | (pig->windowed ? 0 : SDL_WINDOW_FULLSCREEN_DESKTOP)); + + if (!pig->window) { + fatal_if(SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, 0) < 0, + "Unable to clear GL multisample samples attribute"); + fatal_if(SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS, 0) < 0, + "Unable to clear GL multisample buffers attribute"); + + pig->window = SDL_CreateWindow("pig", + SDL_WINDOWPOS_CENTERED, + SDL_WINDOWPOS_CENTERED, + pig->window_width, pig->window_height, + PIG_WINDOW_FLAGS | (pig->windowed ? 0 : SDL_WINDOW_FULLSCREEN_DESKTOP)); + + fatal_if(!pig->window, + "Unable to create SDL window"); + } + + pig->gl = SDL_GL_CreateContext(pig->window); + fatal_if(!pig->gl, + "Unable to create GL context"); + + fatal_if(SDL_GL_SetSwapInterval(1) < 0, + "Unable to enable vsync"); + + fatal_if(!gladLoadGL(), + "Failed to initialize GLAD OpenGL extension loader"); + + //SDL_ShowCursor(SDL_DISABLE); + +#ifdef MSAA_RENDER_TARGET + glEnable(GL_MULTISAMPLE); +#endif + + pig->transform = m4f_identity(); + pig->seed = randf(); + randomize_color(&pig->color); + + pig->stage = stage_new(&(stage_conf_t){.name = "pig", .active = 1, .alpha = 1.f}, NULL, NULL); + fatal_if(!pig->stage, "Unable to create new stage"); + + (void) clear_node_new(&(stage_conf_t){ + .parent = pig->stage, + .name = "gl-clear-pig", + .active = 1 + } + ); + + (void) checker_node_new(&(stage_conf_t){ + .parent = pig->stage, + .name = "checkers", + .layer = 1, + .active = 1, + .alpha = 1.f, + }, + &pig->transform, + &pig->color + ); + + (void) shader_node_new_files(&(stage_conf_t){ + .parent = pig->stage, + .name = "shader", + .layer = 2, + .active = 1, + .alpha = 1.f, + }, + argv[1], argv[2], + &pig->transform, + pig_uniforms_func, + pig, + NELEMS(pig_uniforms), + pig_uniforms + ); + + return pig; +} + + +static void pig_update(play_t *play, void *context) +{ + pig_t *pig = context; + + stage_dirty(pig->stage); +} + + +static void pig_render(play_t *play, void *context) +{ + pig_t *pig = context; + + if (stage_render(pig->stage, play)) { + SDL_GL_SwapWindow(pig->window); + } else { + SDL_Delay(100); // FIXME: this should be computed + } +} + + +static void pig_dispatch(play_t *play, void *context, SDL_Event *event) +{ + pig_t *pig = context; + + if (event->type == SDL_WINDOWEVENT && event->window.event == SDL_WINDOWEVENT_RESIZED) { + int w, h; + + /* on highdpi the window size and pixels are decoupled, so ignore what + * the event contains and query the canvas size. + */ + pig_canvas_size(pig, &w, &h); + glViewport(0, 0, w, h); + stage_dirty(pig->stage); + } +} + + +const play_ops_t pig_ops = { + .init = pig_init, + .update = pig_update, + .render = pig_render, + .dispatch = pig_dispatch, +// .shutdown = pig_shutdown, +}; |