summaryrefslogtreecommitdiff
path: root/src/cache.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/cache.c')
-rw-r--r--src/cache.c308
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++;
+}
© All Rights Reserved