diff options
Diffstat (limited to 'src/cache.c')
-rw-r--r-- | src/cache.c | 308 |
1 files changed, 308 insertions, 0 deletions
diff --git a/src/cache.c b/src/cache.c new file mode 100644 index 0000000..69c5504 --- /dev/null +++ b/src/cache.c @@ -0,0 +1,308 @@ +/* + * Copyright (C) 2018-2019 - 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/>. + */ + +/* This implements a simple texture-based 2D cache of a stage. So you can + * construct a stage of costly stuff to render that doesn't change every frame, + * give that to a cache, then stick the cache in the actual stage being + * rendered every frame. + * + * When the associated stage is dirty, the cache will render it into the cached + * texture in the prepare pass of the render. When it's not dirty, the cached + * texture will be used as-is. + */ + +#include <assert.h> +#include <stdlib.h> + +#include <stdio.h> // FIXME testing + +#include <stage.h> + +#include "bb2f.h" +#include "cache.h" +#include "pig.h" +#include "glad.h" +#include "m4f.h" +#include "shader.h" + +struct cache_t { + stage_t *stage; + const bb2f_t *region; + unsigned tex; + int x, y, w, h; + + unsigned refcnt; +}; + +static unsigned vbo, tcbo; +static shader_t *cache_shader; + +static const float vertices[] = { + +1.f, +1.f, 0.f, + +1.f, -1.f, 0.f, + -1.f, +1.f, 0.f, + +1.f, -1.f, 0.f, + -1.f, -1.f, 0.f, + -1.f, +1.f, 0.f, +}; + +static const float texcoords[] = { + 1.f, 1.f, + 1.f, 0.f, + 0.f, 1.f, + 1.f, 0.f, + 0.f, 0.f, + 0.f, 1.f, +}; + + +static const char *cache_vs = "" + "#version 120\n" + + "attribute vec3 vertex;" + "attribute vec2 texcoord;" + + "uniform mat4 model_x, view_x, projection_x;" + + "void main()" + "{" + " gl_TexCoord[0].xy = texcoord;" + " gl_Position = projection_x * view_x * model_x * vec4(vertex, 1.f);" + "}" +""; + + +static const char *cache_fs = "" + "#version 120\n" + + "uniform sampler2D tex0;" + "uniform float alpha;" + + "void main()" + "{" + " gl_FragColor = texture2D(tex0, gl_TexCoord[0].st);" + " gl_FragColor.a *= alpha;" + "}" +""; + + +/* update is where the cache maintains the cache. It calls a stage + * render on the attached stage as part of the outer stage's prepare phase. + * 1 is returned if the cache needed updating, 0 if not. + */ +int cache_update(cache_t *cache, float alpha, void *render_ctxt) +{ + pig_t *pig = render_ctxt; + int w, h; + + assert(pig); + assert(cache); + + /* On a more modern GL this is where an FBO would be setup for off-screen rendering + * of the cached stage, leaving the contents in a texture for cache_render to use. + * + * On old GL, which eon is targeting, all we can do here is render into the backbuffer, + * and read it back out into memory for a texture upload. Then just clear the backbuffer + * for the next user, the contents never get flipped to. + * + * I've read that this can be buggy to do, so this whole approach may be useless without FBOs. + */ + + /* FIXME TODO: this is lifted from eon, I've mechanically converted the eon API to pig API, + * but it feels like this should be in libplay. + */ + + /* determine the cache'd region dimensions in pixels */ + if (!cache->region) { + pig_canvas_size(pig, &w, &h); + cache->x = cache->y = 0; + } else { + int xmin, ymin, xmax, ymax; + + /* compute texture 1:1 dimensions in pixels for the region */ + pig_canvas_from_ndc(pig, cache->region->min.x, cache->region->min.y, &xmin, &ymin); + pig_canvas_from_ndc(pig, cache->region->max.x, cache->region->max.y, &xmax, &ymax); + + assert(ymax >= ymin); + assert(xmax >= xmin); + + w = xmax - xmin; + h = ymax - ymin; + + cache->x = xmin; + cache->y = ymin; + } + + /* if the dimensions changed, (re)create the texture, and ensure the cached stage is dirty */ + if (w != cache->w || h != cache->h) { + cache->w = w; + cache->h = h; + + glBindTexture(GL_TEXTURE_2D, cache->tex); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, w, h, 0, GL_BGRA, GL_UNSIGNED_BYTE, NULL); + + stage_dirty(cache->stage); + } + + if (stage_render(cache->stage, render_ctxt)) { + + glFinish(); /* XXX: ensures all outstanding rendering is done before reading into the cache */ + glBindTexture(GL_TEXTURE_2D, cache->tex); + glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, cache->x, cache->y, w, h); + glBindTexture(GL_TEXTURE_2D, 0); + + return 1; + } + + return 0; +} + + +/* Render simply renders a cached texture onto the screen */ +void cache_render(cache_t *cache, float alpha, const m4f_t *model_x, const m4f_t *view_x, const m4f_t *projection_x) +{ + m4f_t identity = m4f_identity(); + int *uniforms, *attributes; + + assert(cache); + + glEnable(GL_TEXTURE_2D); + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + + shader_use(cache_shader, NULL, &uniforms, NULL, &attributes); + + glBindBuffer(GL_ARRAY_BUFFER, vbo); + glVertexAttribPointer(attributes[0], 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0); + glEnableVertexAttribArray(attributes[0]); + + glBindBuffer(GL_ARRAY_BUFFER, tcbo); + glEnableVertexAttribArray(attributes[1]); + glVertexAttribPointer(attributes[1], 2, GL_FLOAT, GL_FALSE, 2 * sizeof(float), (void *)0); + + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_2D, cache->tex); + glUniform1i(uniforms[0], 0); + + glUniform1f(uniforms[1], alpha); + + if (!model_x) + model_x = &identity; + + if (!view_x) + view_x = &identity; + + if (!projection_x) + projection_x = &identity; + + glUniformMatrix4fv(uniforms[2], 1, GL_FALSE, &model_x->m[0][0]); + glUniformMatrix4fv(uniforms[3], 1, GL_FALSE, &view_x->m[0][0]); + glUniformMatrix4fv(uniforms[4], 1, GL_FALSE, &projection_x->m[0][0]); + + glDrawArrays(GL_TRIANGLES, 0, 6); + + glBindTexture(GL_TEXTURE_2D, 0); + glUseProgram(0); + glBindBuffer(GL_ARRAY_BUFFER, 0); +} + + +void cache_free(cache_t *cache) +{ + assert(cache); + + cache->refcnt--; + if (cache->refcnt > 0) + return; + + stage_free(cache->stage); + glDeleteTextures(1, &cache->tex); + free(cache); +} + + +/* return a cache for a stage + * The cache takes ownership of the supplied stage, and will + * free it when the cache is freed w/cache_free(). + * When the associated stage is dirty, the cache gets updated with its rendered + * output on a subsequent render, in the prepare hook. + * + * region specifies, in ndc coordinates, the region of the canvas as rendered by the associated stage to cache. + * transform specifies the transformation to apply to the two triangles used for mapping the cached texture. + * The triangles used for mapping the texture form a section of a unit cube @ coordinates -1,-1,0 .. +1,+1,0, so + * if you supply an identity matrix for transform, the cached texture is drawn fullscreen. + * + * Either region and/or transform may be NULL. When region is NULL, it's assumed to be -1,-1..+1,+1. When + * transform is NULL, it's assumed to be an identity matrix. So supplying NULL for both caches the full canvas, + * and draws it directly back into the full canvas. + */ +cache_t * cache_new(stage_t *stage, const bb2f_t *region) +{ + cache_t *cache; + + assert(stage); + + if (!vbo) { + /* common to all cache nodes */ + cache_shader = shader_pair_new(cache_vs, cache_fs, + 5, + (const char *[]) { + "tex0", + "alpha", + "model_x", + "view_x", + "projection_x", + }, + 2, + (const char *[]) { + "vertex", + "texcoord", + }); + + glGenBuffers(1, &vbo); + glBindBuffer(GL_ARRAY_BUFFER, vbo); + glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW); + + glGenBuffers(1, &tcbo); + glBindBuffer(GL_ARRAY_BUFFER, tcbo); + glBufferData(GL_ARRAY_BUFFER, sizeof(texcoords), texcoords, GL_STATIC_DRAW); + + glBindBuffer(GL_ARRAY_BUFFER, 0); + } + + cache = calloc(1, sizeof(cache_t)); + if (!cache) + return NULL; + + cache->stage = stage; + cache->region = region; + + glGenTextures(1, &cache->tex); + glBindTexture(GL_TEXTURE_2D, cache->tex); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glBindTexture(GL_TEXTURE_2D, 0); + + cache->refcnt++; + + return cache; +} + + +void cache_ref(cache_t *cache) +{ + cache->refcnt++; +} |