ref: b559b93eeb0aa4fb01713b5af89e567096a1c43b
dir: /glsl_shader.c/
// This file is heavily influenced by Snes9x
#include "third_party/gl_core/gl_core_3_1.h"
#include "glsl_shader.h"
#include "util.h"
#include "config.h"
#include <stdio.h>
#include <assert.h>
#include <string.h>
#define STB_IMAGE_IMPLEMENTATION
#define STBI_NO_THREAD_LOCALS
#define STBI_ONLY_PNG
#define STBI_MAX_DIMENSIONS 4096
#define STBI_NO_FAILURE_STRINGS
#include "third_party/stb/stb_image.h"
static GlslPass *ParseConfigKeyPass(GlslShader *gs, const char *key, const char *match) {
char *endp;
for (; *match; key++, match++) {
if (*key != *match)
return NULL;
}
if ((uint8)(*key - '0') >= 10)
return NULL;
uint pass = strtoul(key, &endp, 10);
if (pass >= gs->n_pass || *endp != 0)
return NULL;
return gs->pass + pass + 1;
}
static uint8 ParseScaleType(const char *s) {
return StringEqualsNoCase(s, "source") ? GLSL_SOURCE :
StringEqualsNoCase(s, "viewport") ? GLSL_VIEWPORT :
StringEqualsNoCase(s, "absolute") ? GLSL_ABSOLUTE : GLSL_NONE;
}
static uint ParseWrapMode(const char *s) {
return StringEqualsNoCase(s, "repeat") ? GL_REPEAT :
StringEqualsNoCase(s, "clamp_to_edge") ? GL_CLAMP_TO_EDGE :
StringEqualsNoCase(s, "clamp") ? GL_CLAMP : GL_CLAMP_TO_BORDER;
}
static void GlslPass_Initialize(GlslPass *pass) {
pass->scale_x = 1.0f;
pass->scale_y = 1.0f;
pass->wrap_mode = GL_CLAMP_TO_BORDER;
}
static void ParseTextures(GlslShader *gs, char *value) {
char *id;
GlslTexture **nextp = &gs->first_texture;
for (int num = 0; (id = NextDelim(&value, ';')) != NULL && num < kGlslMaxTextures; num++) {
GlslTexture *t = calloc(sizeof(GlslTexture), 1);
t->id = strdup(id);
t->wrap_mode = GL_CLAMP_TO_BORDER;
t->filter = GL_NEAREST;
*nextp = t;
nextp = &t->next;
}
}
static bool ParseTextureKeyValue(GlslShader *gs, const char *key, const char *value) {
for (GlslTexture *t = gs->first_texture; t != NULL; t = t->next) {
const char *key2 = SkipPrefix(key, t->id);
if (!key2) continue;
if (*key2 == 0) {
StrSet(&t->filename, value);
return true;
} else if (!strcmp(key2, "_wrap_mode")) {
t->wrap_mode = ParseWrapMode(value);
return true;
} else if (!strcmp(key2, "_mipmap")) {
t->mipmap = ParseBool(value, NULL);
return true;
} else if (!strcmp(key2, "_linear")) {
t->filter = ParseBool(value, NULL) ? GL_LINEAR : GL_NEAREST;
return true;
}
}
return false;
}
static GlslParam *GlslShader_GetParam(GlslShader *gs, const char *id) {
GlslParam **pp = &gs->first_param;
for (; (*pp) != NULL; pp = &(*pp)->next)
if (!strcmp((*pp)->id, id))
return *pp;
GlslParam *p = (GlslParam *)calloc(1, sizeof(GlslParam));
*pp = p;
p->id = strdup(id);
return p;
}
static void ParseParameters(GlslShader *gs, char *value) {
char *id;
while ((id = NextDelim(&value, ';')) != NULL)
GlslShader_GetParam(gs, id);
}
static bool ParseParameterKeyValue(GlslShader *gs, const char *key, const char *value) {
for (GlslParam *p = gs->first_param; p != NULL; p = p->next) {
if (strcmp(p->id, key) == 0) {
p->value = atof(value);
p->has_value = true;
return true;
}
}
return false;
}
static void GlslShader_InitializePasses(GlslShader *gs, int passes) {
gs->n_pass = passes;
gs->pass = (GlslPass *)calloc(gs->n_pass + 1, sizeof(GlslPass));
for (int i = 0; i < gs->n_pass; i++)
GlslPass_Initialize(gs->pass + i + 1);
}
static bool GlslShader_ReadPresetFile(GlslShader *gs, const char *filename) {
char *data = (char *)ReadWholeFile(filename, NULL), *data_org = data, *line;
GlslPass *pass;
if (data == NULL)
return false;
for (int lineno = 1; (line = NextLineStripComments(&data)) != NULL; lineno++) {
char *value = SplitKeyValue(line), *t;
if (value == NULL) {
if (*line)
fprintf(stderr, "%s:%d: Expecting key=value\n", filename, lineno);
continue;
}
if (*value == '"') {
for (t = ++value; *t && *t != '"'; t++);
if (*t) *t = 0;
}
if (gs->n_pass == 0) {
if (strcmp(line, "shaders") != 0) {
fprintf(stderr, "%s:%d: Expecting 'shaders'\n", filename, lineno);
break;
}
int passes = strtoul(value, NULL, 10);
if (passes < 1 || passes > kGlslMaxPasses)
break;
GlslShader_InitializePasses(gs, passes);
continue;
}
if ((pass = ParseConfigKeyPass(gs, line, "filter_linear")) != NULL)
pass->filter = ParseBool(value, NULL) ? GL_LINEAR : GL_NEAREST;
else if ((pass = ParseConfigKeyPass(gs, line, "scale_type")) != NULL)
pass->scale_type_x = pass->scale_type_y = ParseScaleType(value);
else if ((pass = ParseConfigKeyPass(gs, line, "scale_type_x")) != NULL)
pass->scale_type_x = ParseScaleType(value);
else if ((pass = ParseConfigKeyPass(gs, line, "scale_type_y")) != NULL)
pass->scale_type_y = ParseScaleType(value);
else if ((pass = ParseConfigKeyPass(gs, line, "scale")) != NULL)
pass->scale_x = pass->scale_y = atof(value);
else if ((pass = ParseConfigKeyPass(gs, line, "scale_x")) != NULL)
pass->scale_x = atof(value);
else if ((pass = ParseConfigKeyPass(gs, line, "scale_y")) != NULL)
pass->scale_y = atof(value);
else if ((pass = ParseConfigKeyPass(gs, line, "shader")) != NULL)
StrSet(&pass->filename, value);
else if ((pass = ParseConfigKeyPass(gs, line, "wrap_mode")) != NULL)
pass->wrap_mode = ParseWrapMode(value);
else if ((pass = ParseConfigKeyPass(gs, line, "mipmap_input")) != NULL)
pass->mipmap_input = ParseBool(value, NULL);
else if ((pass = ParseConfigKeyPass(gs, line, "frame_count_mod")) != NULL)
pass->frame_count_mod = atoi(value);
else if ((pass = ParseConfigKeyPass(gs, line, "float_framebuffer")) != NULL)
pass->float_framebuffer = ParseBool(value, NULL);
else if ((pass = ParseConfigKeyPass(gs, line, "srgb_framebuffer")) != NULL)
pass->srgb_framebuffer = ParseBool(value, NULL);
else if ((pass = ParseConfigKeyPass(gs, line, "alias")) != NULL)
;
else if (strcmp(line, "textures") == 0 && gs->first_texture == NULL)
ParseTextures(gs, value);
else if (strcmp(line, "parameters") == 0)
ParseParameters(gs, value);
else if (!ParseTextureKeyValue(gs, line, value) && !ParseParameterKeyValue(gs, line, value))
fprintf(stderr, "%s:%d: Unknown key '%s'\n", filename, lineno, line);
}
free(data_org);
return gs->n_pass != 0;
}
void GlslShader_ReadShaderFile(GlslShader *gs, const char *filename, ByteArray *result) {
char *data = (char *)ReadWholeFile(filename, NULL), *data_org = data, *line;
if (data == NULL) {
fprintf(stderr, "Unable to read file '%s'\n", filename);
return;
}
while ((line = NextDelim(&data, '\n')) != NULL) {
size_t linelen = strlen(line);
if (linelen >= 8 && memcmp(line, "#include", 8) == 0) {
char *tt = line + 8;
char *new_filename = ReplaceFilenameWithNewPath(filename, NextPossiblyQuotedString(&tt));
GlslShader_ReadShaderFile(gs, new_filename, result);
free(new_filename);
} else if (linelen >= 17 && memcmp(line, "#pragma parameter", 17) == 0) {
char *tt = line + 17;
GlslParam *param = GlslShader_GetParam(gs, NextPossiblyQuotedString(&tt));
NextPossiblyQuotedString(&tt); // skip name
float value = atof(NextPossiblyQuotedString(&tt));
if (!param->has_value)
param->value = value;
param->min = atof(NextPossiblyQuotedString(&tt));
param->max = atof(NextPossiblyQuotedString(&tt));
// skip step
} else {
line[linelen] = '\n';
ByteArray_AppendData(result, (uint8 *)line, linelen + 1);
}
}
free(data_org);
}
size_t LengthOfInitialComments(const uint8 *data, size_t size) {
const uint8 *data_org = data, *data_end = data + size;
while (data != data_end) {
uint8 c = *data++;
if (c == ' ' || c == '\t' || c == '\r' || c == '\n')
continue;
if (c != '/' || data == data_end) {
data--;
break;
}
c = *data++;
if (c == '/') {
while (data != data_end && *data++ != '\n') {}
} else if (c == '*') {
do {
if (data_end - data < 2)
return 0;
} while (*data++ != '*' || data[0] != '/');
data++;
} else {
data--;
break;
}
}
return data - data_org;
}
static bool GlslPass_Compile(GlslPass *p, uint type, const uint8 *data, size_t size, bool use_opengl_es) {
static const char kVertexPrefix[] = "#define VERTEX\n#define PARAMETER_UNIFORM\n";
static const char kFragmentPrefixCore[] = "#define FRAGMENT\n#define PARAMETER_UNIFORM\n";
static const char kFragmentPrefixEs[] = "#define FRAGMENT\n#define PARAMETER_UNIFORM\n" \
"precision mediump float;";
const GLchar *strings[3];
GLint lengths[3];
char buffer[256];
GLint compile_status = 0;
size_t skip = 0;
size_t commsize = LengthOfInitialComments(data, size);
data += commsize, size -= commsize;
if (size < 8 || memcmp(data, "#version", 8) != 0) {
if (!use_opengl_es) {
strings[0] = "#version 330\n";
lengths[0] = sizeof("#version 330\n") - 1;
} else {
strings[0] = "#version 300 es\n";
lengths[0] = sizeof("#version 300 es\n") - 1;
}
} else {
while (skip < size && data[skip++] != '\n') {}
strings[0] = (char*)data;
lengths[0] = (int)skip;
}
if (type == GL_VERTEX_SHADER) {
strings[1] = (char *)kVertexPrefix;
lengths[1] = sizeof(kVertexPrefix) - 1;
} else {
strings[1] = (use_opengl_es) ? (char *)kFragmentPrefixEs : kFragmentPrefixCore;
lengths[1] = (use_opengl_es) ? sizeof(kFragmentPrefixEs) - 1 : sizeof(kFragmentPrefixCore) - 1;
}
strings[2] = (GLchar *)data + skip;
lengths[2] = (int)(size - skip);
uint shader = glCreateShader(type);
glShaderSource(shader, 3, strings, lengths);
glCompileShader(shader);
glGetShaderiv(shader, GL_COMPILE_STATUS, &compile_status);
buffer[0] = 0;
glGetShaderInfoLog(shader, sizeof(buffer), NULL, buffer);
if (compile_status != GL_TRUE || buffer[0]) {
fprintf(stderr, "%s compiling %s shader in file '%s':\n%s\n",
compile_status != GL_TRUE ? "Error" : "While",
type == GL_VERTEX_SHADER ? "vertex" : "fragment", p->filename, buffer);
}
if (compile_status == GL_TRUE)
glAttachShader(p->gl_program, shader);
glDeleteShader(shader);
return (compile_status == GL_TRUE);
}
static void GlslTextureUniform_Read(uint program, const char *prefix, int i, GlslTextureUniform *result) {
char buf[40];
char *e = &buf[snprintf(buf, sizeof(buf), i >= 0 ? "%s%u" : "%s", prefix, i)];
memcpy(e, "Texture", 8);
result->Texture = glGetUniformLocation(program, buf);
memcpy(e, "InputSize", 10);
result->InputSize = glGetUniformLocation(program, buf);
memcpy(e, "TextureSize", 12);
result->TextureSize = glGetUniformLocation(program, buf);
memcpy(e, "TexCoord", 9);
result->TexCoord = glGetAttribLocation(program, buf);
}
static const float kMvpMatrixOrtho[16] = {
2.0f, 0.0f, 0.0f, 0.0f,
0.0f, 2.0f, 0.0f, 0.0f,
0.0f, 0.0f, -1.0f, 0.0f,
-1.0f, -1.0f, 0.0f, 1.0f
};
static void GlslShader_GetUniforms(GlslShader *gs) {
int pass_idx = 1;
gs->max_prev_frame = 0;
for (GlslPass *p = gs->pass + 1, *p_end = p + gs->n_pass; p < p_end; p++, pass_idx++) {
uint program = p->gl_program;
glUseProgram(program);
GLint MVPMatrix = glGetUniformLocation(program, "MVPMatrix");
if (MVPMatrix >= 0)
glUniformMatrix4fv(MVPMatrix, 1, GL_FALSE, kMvpMatrixOrtho);
GlslTextureUniform_Read(program, "", -1, &p->unif.Top);
p->unif.OutputSize = glGetUniformLocation(program, "OutputSize");
p->unif.FrameCount = glGetUniformLocation(program, "FrameCount");
p->unif.FrameDirection = glGetUniformLocation(program, "FrameDirection");
p->unif.LUTTexCoord = glGetAttribLocation(program, "LUTTexCoord");
p->unif.VertexCoord = glGetAttribLocation(program, "VertexCoord");
GlslTextureUniform_Read(program, "Orig", -1, &p->unif.Orig);
for (int j = 0; j < 7; j++) {
GlslTextureUniform_Read(program, "Prev", j ? j : -1, &p->unif.Prev[j]);
if (p->unif.Prev[j].Texture >= 0)
gs->max_prev_frame = j + 1;
}
for (int j = 0; j < gs->n_pass; j++) {
GlslTextureUniform_Read(program, "Pass", j, &p->unif.Pass[j]);
GlslTextureUniform_Read(program, "PassPrev", j, &p->unif.PassPrev[j]);
}
GlslTexture *t = gs->first_texture;
for (int j = 0; t != NULL; t = t->next, j++)
p->unif.Texture[j] = glGetUniformLocation(program, t->id);
for (GlslParam *pa = gs->first_param; pa != NULL; pa = pa->next)
pa->uniform[pass_idx] = glGetUniformLocation(program, pa->id);
}
glUseProgram(0);
}
static bool IsGlslFilename(const char *filename) {
size_t len = strlen(filename);
return len >= 5 && memcmp(filename + len - 5, ".glsl", 5) == 0;
}
GlslShader *GlslShader_CreateFromFile(const char *filename, bool opengl_es) {
char buffer[256];
GLint link_status;
ByteArray shader_code = { 0 };
bool success = false;
GlslShader *gs = (GlslShader *)calloc(sizeof(GlslShader), 1);
if (!gs)
return gs;
if (IsGlslFilename(filename)) {
GlslShader_InitializePasses(gs, 1);
gs->pass[1].filename = strdup(filename);
filename = "";
} else {
if (!GlslShader_ReadPresetFile(gs, filename)) {
fprintf(stderr, "Unable to read file '%s'\n", filename);
goto FAIL;
}
}
for (int i = 1; i <= gs->n_pass; i++) {
GlslPass *p = gs->pass + i;
shader_code.size = 0;
if (p->filename == NULL) {
fprintf(stderr, "shader%d attribute missing\n", i - 1);
goto FAIL;
}
char *new_filename = ReplaceFilenameWithNewPath(filename, p->filename);
GlslShader_ReadShaderFile(gs, new_filename, &shader_code);
free(new_filename);
if (shader_code.size == 0) {
fprintf(stderr, "Couldn't read shader in file '%s'\n", p->filename);
goto FAIL;
}
p->gl_program = glCreateProgram();
if (!GlslPass_Compile(p, GL_VERTEX_SHADER, shader_code.data, shader_code.size, opengl_es) ||
!GlslPass_Compile(p, GL_FRAGMENT_SHADER, shader_code.data, shader_code.size, opengl_es)) {
goto FAIL;
}
glLinkProgram(p->gl_program);
glGetProgramiv(p->gl_program, GL_LINK_STATUS, &link_status);
buffer[0] = 0;
glGetProgramInfoLog(p->gl_program, sizeof(buffer), NULL, buffer);
if (link_status != GL_TRUE || buffer[0])
fprintf(stderr, "%s linking shader in file '%s':\n%s\n",
link_status != GL_TRUE ? "Error" : "While", p->filename, buffer);
if (link_status != GL_TRUE)
goto FAIL;
glGenFramebuffers(1, &p->gl_fbo);
glGenTextures(1, &p->gl_texture);
}
for (GlslTexture *t = gs->first_texture; t; t = t->next) {
glGenTextures(1, &t->gl_texture);
glBindTexture(GL_TEXTURE_2D, t->gl_texture);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, t->wrap_mode);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, t->wrap_mode);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, t->filter);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, t->mipmap ?
(t->filter == GL_LINEAR ? GL_LINEAR_MIPMAP_LINEAR : GL_NEAREST_MIPMAP_NEAREST) : t->filter);
if (t->filename) {
char *new_filename = ReplaceFilenameWithNewPath(filename, t->filename);
int imw, imh, imn;
unsigned char *data = stbi_load(new_filename, &imw, &imh, &imn, 0);
if (!data) {
fprintf(stderr, "Unable to read PNG '%s'\n", new_filename);
} else {
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, imw, imh, 0,
(imn == 4) ? GL_RGBA : (imn == 3) ? GL_RGB : GL_LUMINANCE, GL_UNSIGNED_BYTE, data);
}
free(data);
free(new_filename);
}
if (t->mipmap)
glGenerateMipmap(GL_TEXTURE_2D);
}
for (GlslParam *p = gs->first_param; p; p = p->next)
p->value = (p->value < p->min) ? p->min : (p->value > p->max) ? p->max : p->value;
GlslShader_GetUniforms(gs);
for (int i = 0; i < gs->max_prev_frame; i++)
glGenTextures(1, &gs->prev_frame[-i - 1 & 7].gl_texture);
static const GLfloat kTexCoords[16] = {
0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f,
0.0f, 1.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f
};
glGenBuffers(1, &gs->gl_vbo);
glBindBuffer(GL_ARRAY_BUFFER, gs->gl_vbo);
glBufferData(GL_ARRAY_BUFFER, sizeof(kTexCoords), kTexCoords, GL_STATIC_DRAW);
glBindBuffer(GL_ARRAY_BUFFER, 0);
success = true;
FAIL:
if (!success) {
GlslShader_Destroy(gs);
gs = NULL;
}
ByteArray_Destroy(&shader_code);
return gs;
}
void GlslShader_Destroy(GlslShader *gs) {
for (GlslPass *p = gs->pass + 1, *p_end = p + gs->n_pass; p < p_end; p++) {
glDeleteProgram(p->gl_program);
glDeleteTextures(1, &p->gl_texture);
glDeleteFramebuffers(1, &p->gl_fbo);
free(p->filename);
}
free(gs->pass);
GlslTexture *t;
while ((t = gs->first_texture) != NULL) {
gs->first_texture = t->next;
glDeleteTextures(1, &t->gl_texture);
free(t->id);
free(t->filename);
free(t);
}
GlslParam *pp;
while ((pp = gs->first_param) != NULL) {
gs->first_param = pp->next;
free(pp->id);
free(pp);
}
for (int i = 0; i < 8; i++)
glDeleteTextures(1, &gs->prev_frame[i].gl_texture);
glDeleteBuffers(1, &gs->gl_vbo);
free(gs);
}
enum {
kMaxVaosInRenderCtx = 11 + kGlslMaxPasses * 2
};
typedef struct RenderCtx {
uint texture_unit;
uint offset;
uint num_vaos;
uint vaos[kMaxVaosInRenderCtx];
} RenderCtx;
static void RenderCtx_SetTexture(RenderCtx *ctx, int textureu, uint texture_id) {
if (textureu >= 0) {
glActiveTexture(GL_TEXTURE0 + ctx->texture_unit);
glBindTexture(GL_TEXTURE_2D, texture_id);
glUniform1i(textureu, ctx->texture_unit++);
}
}
static void RenderCtx_SetTexCoords(RenderCtx *ctx, int tex_coord, uintptr_t offset) {
if (tex_coord >= 0) {
assert(ctx->num_vaos < kMaxVaosInRenderCtx);
ctx->vaos[ctx->num_vaos++] = tex_coord;
glVertexAttribPointer(tex_coord, 2, GL_FLOAT, GL_FALSE, 0, (void *)offset);
glEnableVertexAttribArray(tex_coord);
}
}
static void RenderCtx_SetGlslTextureUniform(RenderCtx *ctx, GlslTextureUniform *u,
int width, int height, uint texture) {
float size[2] = { width, height };
RenderCtx_SetTexture(ctx, u->Texture, texture);
if (u->InputSize >= 0)
glUniform2fv(u->InputSize, 1, size);
if (u->TextureSize >= 0)
glUniform2fv(u->TextureSize, 1, size);
RenderCtx_SetTexCoords(ctx, u->TexCoord, ctx->offset);
}
static void GlslShader_SetShaderVars(GlslShader *gs, RenderCtx *ctx, int pass) {
GlslPass *p = &gs->pass[pass];
RenderCtx_SetGlslTextureUniform(ctx, &p->unif.Top, p[-1].width, p[-1].height, p[-1].gl_texture);
if (p->unif.OutputSize >= 0) {
float output_size[2] = { (float)p[0].width, (float)p[0].height };
glUniform2fv(p->unif.OutputSize, 1, output_size);
}
if (p->unif.FrameCount >= 0)
glUniform1i(p->unif.FrameCount, p->frame_count_mod ?
gs->frame_count % p->frame_count_mod : gs->frame_count);
if (p->unif.FrameDirection >= 0)
glUniform1i(p->unif.FrameDirection, 1);
RenderCtx_SetTexCoords(ctx, p->unif.LUTTexCoord, ctx->offset);
RenderCtx_SetTexCoords(ctx, p->unif.VertexCoord, 0);
RenderCtx_SetGlslTextureUniform(ctx, &p->unif.Orig, gs->pass[0].width, gs->pass[0].height, gs->pass[0].gl_texture);
// Prev, Prev1-Prev6 uniforms
for (int i = 0; i < gs->max_prev_frame; i++) {
GlTextureWithSize *t = &gs->prev_frame[(gs->frame_count - 1 - i) & 7];
assert(t->gl_texture != 0);
if (t->width)
RenderCtx_SetGlslTextureUniform(ctx, &p->unif.Prev[i], t->width, t->height, t->gl_texture);
}
// Texture uniforms
int tctr = 0;
for (GlslTexture *t = gs->first_texture; t; t = t->next, tctr++)
RenderCtx_SetTexture(ctx, p->unif.Texture[tctr], t->gl_texture);
// PassX uniforms
for (int i = 1; i < pass; i++)
RenderCtx_SetGlslTextureUniform(ctx, &p->unif.Pass[i], gs->pass[i].width, gs->pass[i].height, gs->pass[i].gl_texture);
// PassPrevX uniforms
for (int i = 1; i < pass; i++)
RenderCtx_SetGlslTextureUniform(ctx, &p->unif.PassPrev[pass - i], gs->pass[i].width, gs->pass[i].height, gs->pass[i].gl_texture);
// #parameter uniforms
for (GlslParam *pa = gs->first_param; pa != NULL; pa = pa->next)
if (pa->uniform[pass] >= 0)
glUniform1f(pa->uniform[pass], pa->value);
glActiveTexture(GL_TEXTURE0);
}
void GlslShader_Render(GlslShader *gs, GlTextureWithSize *tex, int viewport_x, int viewport_y, int viewport_width, int viewport_height) {
gs->pass[0].gl_texture = tex->gl_texture;
gs->pass[0].width = tex->width;
gs->pass[0].height = tex->height;
GLint previous_framebuffer;
glGetIntegerv(GL_FRAMEBUFFER_BINDING, &previous_framebuffer);
glBindBuffer(GL_ARRAY_BUFFER, gs->gl_vbo);
for (int pass = 1; pass <= gs->n_pass; pass++) {
bool last_pass = pass == gs->n_pass;
GlslPass *p = gs->pass + pass;
switch (p->scale_type_x) {
case GLSL_ABSOLUTE: p->width = (uint16)p->scale_x; break;
case GLSL_SOURCE: p->width = (uint16)(p[-1].width * p->scale_x); break;
case GLSL_VIEWPORT: p->width = (uint16)(viewport_width * p->scale_x); break;
default: p->width = (uint16)(last_pass ? viewport_width : (p[-1].width * p->scale_x)); break;
}
switch (p->scale_type_y) {
case GLSL_ABSOLUTE: p->height = (uint16)p->scale_y; break;
case GLSL_SOURCE: p->height = (uint16)(p[-1].height * p->scale_y); break;
case GLSL_VIEWPORT: p->height = (uint16)(viewport_height * p->scale_y); break;
default: p->height = (uint16)(last_pass ? viewport_height : (p[-1].height * p->scale_y)); break;
}
if (!last_pass) {
// output to a texture
glBindTexture(GL_TEXTURE_2D, p->gl_texture);
if (p->srgb_framebuffer) {
glEnable(GL_FRAMEBUFFER_SRGB);
glTexImage2D(GL_TEXTURE_2D, 0, GL_SRGB8_ALPHA8, p->width, p->height, 0, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8, NULL);
} else {
glTexImage2D(GL_TEXTURE_2D, 0, p->float_framebuffer ? GL_RGBA32F : GL_RGBA,
p->width, p->height, 0, GL_RGBA,
p->float_framebuffer ? GL_FLOAT : GL_UNSIGNED_INT_8_8_8_8, NULL);
}
glViewport(0, 0, p->width, p->height);
glBindFramebuffer(GL_FRAMEBUFFER, p->gl_fbo);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, p->gl_texture, 0);
} else {
// output to screen
glBindFramebuffer(GL_FRAMEBUFFER, previous_framebuffer);
glViewport(viewport_x, viewport_y, viewport_width, viewport_height);
}
glBindTexture(GL_TEXTURE_2D, p[-1].gl_texture);
uint filter = p->filter ? p->filter : (last_pass && g_config.linear_filtering) ? GL_LINEAR : GL_NEAREST;
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, filter);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, p->mipmap_input ?
(filter == GL_LINEAR ? GL_LINEAR_MIPMAP_LINEAR : GL_NEAREST_MIPMAP_NEAREST) : filter);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, p->wrap_mode);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, p->wrap_mode);
if (p->mipmap_input)
glGenerateMipmap(GL_TEXTURE_2D);
glUseProgram(p->gl_program);
RenderCtx ctx;
ctx.texture_unit = ctx.num_vaos = 0;
ctx.offset = last_pass ? sizeof(float) * 8 : 0;
GlslShader_SetShaderVars(gs, &ctx, pass);
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
for(int i = 0; i < ctx.num_vaos; i++)
glDisableVertexAttribArray(ctx.vaos[i]);
if (p->srgb_framebuffer)
glDisable(GL_FRAMEBUFFER_SRGB);
}
glBindFramebuffer(GL_FRAMEBUFFER, previous_framebuffer);
glUseProgram(0);
glBindTexture(GL_TEXTURE_2D, 0);
glBindBuffer(GL_ARRAY_BUFFER, 0);
// Store the input frame in the prev array, and extract the next one.
if (gs->max_prev_frame != 0) {
// 01234567
// 43210
// ^-- store pos
// ^-- load pos
GlTextureWithSize *store_pos = &gs->prev_frame[gs->frame_count & 7];
GlTextureWithSize *load_pos = &gs->prev_frame[gs->frame_count - gs->max_prev_frame & 7];
assert(store_pos->gl_texture == 0);
*store_pos = *tex;
*tex = *load_pos;
memset(load_pos, 0, sizeof(GlTextureWithSize));
}
gs->frame_count++;
}