/* * Copyright (C) 2018-2019 - Vito Caputo - * * 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 . */ /* 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 #include #include // FIXME testing #include #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++; }