268 lines
6.1 KiB
C
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(¤t->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(¤t->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,
|
|
},
|
|
};
|