taisei/src/resource/postprocess.c
2023-04-29 20:01:50 +02:00

268 lines
6.1 KiB
C

/*
* This software is licensed under the terms of the MIT License.
* See COPYING for further information.
* ---
* Copyright (c) 2011-2019, Lukas Weber <laochailan@web.de>.
* Copyright (c) 2012-2019, Andrei Alexeyev <akari@taisei-project.org>.
*/
#include "taisei.h"
#include <SDL.h>
#include "util.h"
#include "postprocess.h"
#include "resource.h"
#include "renderer/api.h"
#define PP_PATH_PREFIX SHPROG_PATH_PREFIX
#define PP_EXTENSION ".pp"
typedef struct PostprocessLoadData {
PostprocessShader *list;
ResourceFlags resflags;
} PostprocessLoadData;
#define SAMPLER_TAG 0xf0f0f0f0
static bool postprocess_load_callback(const char *key, const char *value, void *data) {
PostprocessLoadData *ldata = data;
PostprocessShader **slist = &ldata->list;
PostprocessShader *current = *slist;
if(!strcmp(key, "@shader")) {
current = ALLOC(PostprocessShader);
current->uniforms = NULL;
// if loading this fails, get_resource will print a warning
current->shader = res_get_data(RES_SHADER_PROGRAM, value, ldata->resflags);
list_append(slist, current);
return true;
}
for(PostprocessShader *c = current; c; c = c->next) {
current = c;
}
if(!current) {
log_error("Uniform '%s' ignored: no active shader", key);
return true;
}
if(!current->shader) {
// If loading the shader failed, just discard the uniforms.
// We will get rid of empty shader definitions later.
return true;
}
const char *name = key;
Uniform *uni = r_shader_uniform(current->shader, name);
if(!uni) {
log_error("No active uniform '%s' in shader", name);
return true;
}
UniformType type = r_uniform_type(uni);
const UniformTypeInfo *type_info = r_uniform_type_info(type);
if(UNIFORM_TYPE_IS_SAMPLER(type)) {
Texture *tex = res_get_data(RES_TEXTURE, value, ldata->resflags);
if(tex) {
list_append(&current->uniforms, ALLOC(PostprocessShaderUniform, {
.uniform = uni,
.texture = tex,
.elements = SAMPLER_TAG,
}));
}
return true;
}
bool integer_type;
switch(type) {
case UNIFORM_INT:
case UNIFORM_IVEC2:
case UNIFORM_IVEC3:
case UNIFORM_IVEC4:
integer_type = true;
break;
default:
integer_type = false;
break;
}
uint array_size = 0;
PostprocessShaderUniformValue *values = NULL;
assert(sizeof(*values) == type_info->element_size);
uint val_idx = 0;
while(true) {
PostprocessShaderUniformValue v;
char *next = (char*)value;
if(integer_type) {
v.i = strtol(value, &next, 10);
} else {
v.f = strtof(value, &next);
}
if(value == next) {
break;
}
value = next;
values = mem_realloc(values, (++array_size) * type_info->elements * type_info->element_size);
values[val_idx] = v;
val_idx++;
for(int i = 1; i < type_info->elements; ++i, ++val_idx) {
PostprocessShaderUniformValue *v = values + val_idx;
if(integer_type) {
v->i = strtol(value, (char**)&value, 10);
} else {
v->f = strtof(value, (char**)&value);
}
}
}
if(val_idx == 0) {
log_error("No values defined for uniform '%s'", name);
return true;
}
assert(val_idx % type_info->elements == 0);
attr_unused auto psu = list_append(&current->uniforms, ALLOC(PostprocessShaderUniform, {
.uniform = uni,
.values = values,
.elements = val_idx / type_info->elements,
}));
for(int i = 0; i < val_idx; ++i) {
log_debug("u[%i] = (f: %f; i: %i)", i, psu->values[i].f, psu->values[i].i);
}
return true;
}
static void* delete_uniform(List **dest, List *data, void *arg) {
PostprocessShaderUniform *uni = (PostprocessShaderUniform*)data;
if(uni->elements != SAMPLER_TAG) {
mem_free(uni->values);
}
mem_free(list_unlink(dest, data));
return NULL;
}
static void* delete_shader(List **dest, List *data, void *arg) {
PostprocessShader *ps = (PostprocessShader*)data;
list_foreach(&ps->uniforms, delete_uniform, NULL);
mem_free(list_unlink(dest, data));
return NULL;
}
PostprocessShader* postprocess_load(const char *path, ResourceFlags flags) {
PostprocessLoadData ldata = { .resflags = flags };
parse_keyvalue_file_cb(path, postprocess_load_callback, &ldata);
PostprocessShader *list = ldata.list;
for(PostprocessShader *s = list, *next; s; s = next) {
next = s->next;
if(!s->shader) {
delete_shader((List**)&list, (List*)s, NULL);
}
}
return list;
}
void postprocess_unload(PostprocessShader **list) {
list_foreach(list, delete_shader, NULL);
}
void postprocess(PostprocessShader *ppshaders, FBPair *fbos, PostprocessPrepareFuncPtr prepare, PostprocessDrawFuncPtr draw, double width, double height, void *arg) {
if(!ppshaders) {
return;
}
ShaderProgram *shader_saved = r_shader_current();
BlendMode blend_saved = r_blend_current();
r_blend(BLEND_NONE);
for(PostprocessShader *pps = ppshaders; pps; pps = pps->next) {
ShaderProgram *s = pps->shader;
r_framebuffer(fbos->back);
r_shader_ptr(s);
if(prepare) {
prepare(fbos->back, s, arg);
}
for(PostprocessShaderUniform *u = pps->uniforms; u; u = u->next) {
if(u->elements == SAMPLER_TAG) {
r_uniform_sampler(u->uniform, u->texture);
} else {
r_uniform_ptr_unsafe(u->uniform, 0, u->elements, u->values);
}
}
draw(fbos->front, width, height);
fbpair_swap(fbos);
}
r_shader_ptr(shader_saved);
r_blend(blend_saved);
}
/*
* Glue for resources api
*/
static char *postprocess_path(const char *name) {
return strjoin(PP_PATH_PREFIX, name, PP_EXTENSION, NULL);
}
static bool check_postprocess_path(const char *path) {
return strendswith(path, PP_EXTENSION);
}
static void load_postprocess_stage2(ResourceLoadState *st) {
PostprocessShader *pp = postprocess_load(st->path, st->flags);
if(pp) {
res_load_finished(st, pp);
} else {
res_load_failed(st);
}
}
static void load_postprocess_stage1(ResourceLoadState *st) {
res_load_continue_on_main(st, load_postprocess_stage2, NULL);
}
static void unload_postprocess(void *vlist) {
postprocess_unload((PostprocessShader**)&vlist);
}
ResourceHandler postprocess_res_handler = {
.type = RES_POSTPROCESS,
.typename = "postprocessing pipeline",
.subdir = PP_PATH_PREFIX,
.procs = {
.find = postprocess_path,
.check = check_postprocess_path,
.load = load_postprocess_stage1,
.unload = unload_postprocess,
},
};