Files
perfect_dark/port/fast3d/gfx_opengl.cpp
T

1324 lines
50 KiB
C++

#include <stdint.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <map>
#include <unordered_map>
#include <vector>
#include <SDL.h>
#ifndef _LANGUAGE_C
#define _LANGUAGE_C
#endif
#include <PR/gbi.h>
#include "glad/glad.h"
#include "gfx_cc.h"
#include "gfx_rendering_api.h"
#include "gfx_pc.h"
using namespace std;
struct ShaderProgram {
GLuint opengl_program_id;
uint8_t num_inputs;
bool used_textures[SHADER_MAX_TEXTURES];
uint8_t num_floats;
GLint attrib_locations[16];
uint8_t attrib_sizes[16];
uint8_t num_attribs;
GLint frame_count_location;
GLint noise_scale_location;
GLint three_point_filter_locations[2];
};
struct Framebuffer {
uint32_t width, height;
bool has_depth_buffer;
uint32_t msaa_level;
bool invert_y;
GLuint fbo, clrbuf, clrbuf_msaa, rbo;
};
static std::map<pair<uint64_t, uint32_t>, struct ShaderProgram> shader_program_pool;
static GLuint opengl_vbo;
static GLuint opengl_vao;
static bool current_depth_mask;
static uint32_t frame_count;
static std::vector<Framebuffer> framebuffers;
static size_t current_framebuffer;
static float current_noise_scale;
static int current_anisotropy_level;
static FilteringMode current_filter_mode = FILTER_LINEAR;
static MipmapFilteringMode current_mipmap_filter_mode = MIPMAP_LINEAR;
static bool current_textures_linear_filter[2] = {false, false};
static int gl_glsl_version = 130;
static char gl_glsl_version_str[16] = "130";
static GLenum gl_mirror_clamp = GL_MIRROR_CLAMP_TO_EDGE;
static bool gl_es = false;
static bool gl_core_profile = false;
static int gfx_opengl_get_max_texture_size() {
GLint max_texture_size;
glGetIntegerv(GL_MAX_TEXTURE_SIZE, &max_texture_size);
return max_texture_size;
}
static const char* gfx_opengl_get_name() {
return "OpenGL";
}
static struct GfxClipParameters gfx_opengl_get_clip_parameters(void) {
return { false, framebuffers[current_framebuffer].invert_y };
}
static void gfx_opengl_vertex_array_set_attribs(struct ShaderProgram* prg) {
size_t num_floats = prg->num_floats;
size_t pos = 0;
for (int i = 0; i < prg->num_attribs; i++) {
if (prg->attrib_locations[i] >= 0) {
glEnableVertexAttribArray(prg->attrib_locations[i]);
glVertexAttribPointer(prg->attrib_locations[i], prg->attrib_sizes[i], GL_FLOAT, GL_FALSE,
num_floats * sizeof(float), (void*)(pos * sizeof(float)));
}
pos += prg->attrib_sizes[i];
}
}
static void gfx_opengl_set_uniforms(struct ShaderProgram* prg) {
if (prg->frame_count_location >= 0) {
glUniform1i(prg->frame_count_location, frame_count);
}
if (prg->noise_scale_location >= 0) {
glUniform1f(prg->noise_scale_location, current_noise_scale);
}
if (prg->three_point_filter_locations[0] >= 0) {
glUniform1i(prg->three_point_filter_locations[0], current_textures_linear_filter[0]);
}
if (prg->three_point_filter_locations[1] >= 0) {
glUniform1i(prg->three_point_filter_locations[1], current_textures_linear_filter[1]);
}
}
static void gfx_opengl_unload_shader(struct ShaderProgram* old_prg) {
if (old_prg != NULL) {
for (int i = 0; i < old_prg->num_attribs; i++) {
if (old_prg->attrib_locations[i] >= 0) {
glDisableVertexAttribArray(old_prg->attrib_locations[i]);
}
}
}
}
static void gfx_opengl_load_shader(struct ShaderProgram* new_prg) {
// if (!new_prg) return;
glUseProgram(new_prg->opengl_program_id);
gfx_opengl_vertex_array_set_attribs(new_prg);
gfx_opengl_set_uniforms(new_prg);
}
static void append_str(char* buf, size_t* len, const char* str) {
while (*str != '\0') {
buf[(*len)++] = *str++;
}
}
static void append_line(char* buf, size_t* len, const char* str) {
while (*str != '\0') {
buf[(*len)++] = *str++;
}
buf[(*len)++] = '\n';
}
#define RAND_NOISE "((random(vec3(floor(gl_FragCoord.xy * noise_scale), float(frame_count))) + 1.0) / 2.0)"
static const char* shader_item_to_str(uint32_t item, bool with_alpha, bool only_alpha, bool inputs_have_alpha,
bool hint_single_element) {
if (!only_alpha) {
switch (item) {
case SHADER_0:
return with_alpha ? "vec4(0.0, 0.0, 0.0, 0.0)" : "vec3(0.0, 0.0, 0.0)";
case SHADER_1:
return with_alpha ? "vec4(1.0, 1.0, 1.0, 1.0)" : "vec3(1.0, 1.0, 1.0)";
case SHADER_INPUT_1:
return with_alpha || !inputs_have_alpha ? "vInput1" : "vInput1.rgb";
case SHADER_INPUT_2:
return with_alpha || !inputs_have_alpha ? "vInput2" : "vInput2.rgb";
case SHADER_INPUT_3:
return with_alpha || !inputs_have_alpha ? "vInput3" : "vInput3.rgb";
case SHADER_INPUT_4:
return with_alpha || !inputs_have_alpha ? "vInput4" : "vInput4.rgb";
case SHADER_TEXEL0:
return with_alpha ? "texVal0" : "texVal0.rgb";
case SHADER_TEXEL0A:
return hint_single_element ? "texVal0.a"
: (with_alpha ? "vec4(texVal0.a, texVal0.a, texVal0.a, texVal0.a)"
: "vec3(texVal0.a, texVal0.a, texVal0.a)");
case SHADER_TEXEL1A:
return hint_single_element ? "texVal1.a"
: (with_alpha ? "vec4(texVal1.a, texVal1.a, texVal1.a, texVal1.a)"
: "vec3(texVal1.a, texVal1.a, texVal1.a)");
case SHADER_TEXEL1:
return with_alpha ? "texVal1" : "texVal1.rgb";
case SHADER_COMBINED:
return with_alpha ? "texel" : "texel.rgb";
case SHADER_NOISE:
return with_alpha ? "vec4(" RAND_NOISE ", " RAND_NOISE ", " RAND_NOISE ", " RAND_NOISE ")"
: "vec3(" RAND_NOISE ", " RAND_NOISE ", " RAND_NOISE ")";
}
} else {
switch (item) {
case SHADER_0:
return "0.0";
case SHADER_1:
return "1.0";
case SHADER_INPUT_1:
return "vInput1.a";
case SHADER_INPUT_2:
return "vInput2.a";
case SHADER_INPUT_3:
return "vInput3.a";
case SHADER_INPUT_4:
return "vInput4.a";
case SHADER_TEXEL0:
return "texVal0.a";
case SHADER_TEXEL0A:
return "texVal0.a";
case SHADER_TEXEL1A:
return "texVal1.a";
case SHADER_TEXEL1:
return "texVal1.a";
case SHADER_COMBINED:
return "texel.a";
case SHADER_NOISE:
return RAND_NOISE;
}
}
return "";
}
#undef RAND_NOISE
static void append_formula(char* buf, size_t* len, uint8_t c[2][4], bool do_single, bool do_multiply, bool do_mix,
bool with_alpha, bool only_alpha, bool opt_alpha) {
if (do_single) {
append_str(buf, len, shader_item_to_str(c[only_alpha][3], with_alpha, only_alpha, opt_alpha, false));
} else if (do_multiply) {
append_str(buf, len, shader_item_to_str(c[only_alpha][0], with_alpha, only_alpha, opt_alpha, false));
append_str(buf, len, " * ");
append_str(buf, len, shader_item_to_str(c[only_alpha][2], with_alpha, only_alpha, opt_alpha, true));
} else if (do_mix) {
append_str(buf, len, "mix(");
append_str(buf, len, shader_item_to_str(c[only_alpha][1], with_alpha, only_alpha, opt_alpha, false));
append_str(buf, len, ", ");
append_str(buf, len, shader_item_to_str(c[only_alpha][0], with_alpha, only_alpha, opt_alpha, false));
append_str(buf, len, ", ");
append_str(buf, len, shader_item_to_str(c[only_alpha][2], with_alpha, only_alpha, opt_alpha, true));
append_str(buf, len, ")");
} else {
append_str(buf, len, "(");
append_str(buf, len, shader_item_to_str(c[only_alpha][0], with_alpha, only_alpha, opt_alpha, false));
append_str(buf, len, " - ");
append_str(buf, len, shader_item_to_str(c[only_alpha][1], with_alpha, only_alpha, opt_alpha, false));
append_str(buf, len, ") * ");
append_str(buf, len, shader_item_to_str(c[only_alpha][2], with_alpha, only_alpha, opt_alpha, true));
append_str(buf, len, " + ");
append_str(buf, len, shader_item_to_str(c[only_alpha][3], with_alpha, only_alpha, opt_alpha, false));
}
}
static struct ShaderProgram* gfx_opengl_create_and_load_new_shader(uint64_t shader_id0, uint32_t shader_id1) {
struct CCFeatures cc_features = { 0 };
gfx_cc_get_features(shader_id0, shader_id1, &cc_features);
char vs_buf[2048];
char fs_buf[8192];
size_t vs_len = 0;
size_t fs_len = 0;
size_t num_floats = 4;
// Vertex shader
vs_len += sprintf(vs_buf + vs_len, "#version %s\n", gl_glsl_version_str);
if (gl_es) {
append_line(vs_buf, &vs_len, "precision mediump float;");
}
if (gl_glsl_version >= 130) {
append_line(vs_buf, &vs_len, "#define INPUT in");
append_line(vs_buf, &vs_len, "#define OUTPUT out");
} else {
append_line(vs_buf, &vs_len, "#define INPUT attribute");
append_line(vs_buf, &vs_len, "#define OUTPUT varying");
}
append_line(vs_buf, &vs_len, "INPUT vec4 aVtxPos;");
for (int i = 0; i < 2; i++) {
if (cc_features.used_textures[i]) {
vs_len += sprintf(vs_buf + vs_len, "INPUT vec2 aTexCoord%d;\n", i);
vs_len += sprintf(vs_buf + vs_len, "OUTPUT vec2 vTexCoord%d;\n", i);
num_floats += 2;
for (int j = 0; j < 2; j++) {
if (cc_features.clamp[i][j]) {
vs_len += sprintf(vs_buf + vs_len, "INPUT float aTexClamp%s%d;\n", j == 0 ? "S" : "T", i);
vs_len += sprintf(vs_buf + vs_len, "OUTPUT float vTexClamp%s%d;\n", j == 0 ? "S" : "T", i);
num_floats += 1;
}
}
}
}
if (cc_features.opt_fog) {
append_line(vs_buf, &vs_len, "INPUT vec4 aFog;");
append_line(vs_buf, &vs_len, "OUTPUT vec4 vFog;");
num_floats += 4;
}
if (cc_features.opt_grayscale) {
append_line(vs_buf, &vs_len, "INPUT vec4 aGrayscaleColor;");
append_line(vs_buf, &vs_len, "OUTPUT vec4 vGrayscaleColor;");
num_floats += 4;
}
for (int i = 0; i < cc_features.num_inputs; i++) {
vs_len += sprintf(vs_buf + vs_len, "INPUT vec%d aInput%d;\n", cc_features.opt_alpha ? 4 : 3, i + 1);
vs_len += sprintf(vs_buf + vs_len, "OUTPUT vec%d vInput%d;\n", cc_features.opt_alpha ? 4 : 3, i + 1);
num_floats += cc_features.opt_alpha ? 4 : 3;
}
append_line(vs_buf, &vs_len, "void main() {");
for (int i = 0; i < 2; i++) {
if (cc_features.used_textures[i]) {
vs_len += sprintf(vs_buf + vs_len, " vTexCoord%d = aTexCoord%d;\n", i, i);
for (int j = 0; j < 2; j++) {
if (cc_features.clamp[i][j]) {
vs_len += sprintf(vs_buf + vs_len, " vTexClamp%s%d = aTexClamp%s%d;\n", j == 0 ? "S" : "T", i,
j == 0 ? "S" : "T", i);
}
}
}
}
if (cc_features.opt_fog) {
append_line(vs_buf, &vs_len, " vFog = aFog;");
}
if (cc_features.opt_grayscale) {
append_line(vs_buf, &vs_len, " vGrayscaleColor = aGrayscaleColor;");
}
for (int i = 0; i < cc_features.num_inputs; i++) {
vs_len += sprintf(vs_buf + vs_len, " vInput%d = aInput%d;\n", i + 1, i + 1);
}
append_line(vs_buf, &vs_len, " gl_Position = aVtxPos;");
if (!GLAD_GL_ARB_depth_clamp) {
// HACK: workaround for no GL_DEPTH_CLAMP
append_line(vs_buf, &vs_len, " gl_Position.z *= 0.3f;");
}
append_line(vs_buf, &vs_len, "}");
// Fragment shader
fs_len += sprintf(fs_buf + fs_len, "#version %s\n", gl_glsl_version_str);
if (gl_es) {
append_line(fs_buf, &fs_len, "precision mediump float;");
}
if (gl_glsl_version >= 130) {
append_line(fs_buf, &fs_len, "#define INPUT in");
append_line(fs_buf, &fs_len, "#define OUTPUT_COLOR outColor");
append_line(fs_buf, &fs_len, "#define SAMPLE_TEX(tex, uv) texture(tex, uv)");
} else {
append_line(fs_buf, &fs_len, "#define INPUT varying");
append_line(fs_buf, &fs_len, "#define OUTPUT_COLOR gl_FragColor");
append_line(fs_buf, &fs_len, "#define SAMPLE_TEX(tex, uv) texture2D(tex, uv)");
}
// Reference approach to color wrapping as per GLideN64
// Return wrapped value of x in interval [low, high)
append_line(fs_buf, &fs_len, "#define WRAP(x, low, high) mod((x)-(low), (high)-(low)) + (low)");
append_line(fs_buf, &fs_len, "#define TEX_OFFSET(tex, uv, texSize, off) SAMPLE_TEX(tex, uv - (off)/texSize)");
// append_line(fs_buf, &fs_len, "precision mediump float;");
for (int i = 0; i < 2; i++) {
if (cc_features.used_textures[i]) {
fs_len += sprintf(fs_buf + fs_len, "INPUT vec2 vTexCoord%d;\n", i);
for (int j = 0; j < 2; j++) {
if (cc_features.clamp[i][j]) {
fs_len += sprintf(fs_buf + fs_len, "INPUT float vTexClamp%s%d;\n", j == 0 ? "S" : "T", i);
}
}
}
}
if (cc_features.opt_fog) {
append_line(fs_buf, &fs_len, "INPUT vec4 vFog;");
}
if (cc_features.opt_grayscale) {
append_line(fs_buf, &fs_len, "INPUT vec4 vGrayscaleColor;");
}
for (int i = 0; i < cc_features.num_inputs; i++) {
fs_len += sprintf(fs_buf + fs_len, "INPUT vec%d vInput%d;\n", cc_features.opt_alpha ? 4 : 3, i + 1);
}
if (cc_features.used_textures[0]) {
append_line(fs_buf, &fs_len, "uniform sampler2D uTex0;");
if (current_filter_mode == FILTER_THREE_POINT)
append_line(fs_buf, &fs_len, "uniform int three_point_filter0;");
}
if (cc_features.used_textures[1]) {
append_line(fs_buf, &fs_len, "uniform sampler2D uTex1;");
if (current_filter_mode == FILTER_THREE_POINT)
append_line(fs_buf, &fs_len, "uniform int three_point_filter1;");
}
append_line(fs_buf, &fs_len, "uniform int frame_count;");
append_line(fs_buf, &fs_len, "uniform float noise_scale;");
append_line(fs_buf, &fs_len, "float random(in vec3 value) {");
append_line(fs_buf, &fs_len, " float random = dot(sin(value), vec3(12.9898, 78.233, 37.719));");
append_line(fs_buf, &fs_len, " return fract(sin(random) * 143758.5453);");
append_line(fs_buf, &fs_len, "}");
if (current_filter_mode == FILTER_THREE_POINT) {
append_line(fs_buf, &fs_len, "vec4 filter3point(in sampler2D tex, in vec2 texCoord, in vec2 texSize) {");
append_line(fs_buf, &fs_len, " vec2 offset = fract(texCoord*texSize - vec2(0.5));");
append_line(fs_buf, &fs_len, " offset -= step(1.0, offset.x + offset.y);");
append_line(fs_buf, &fs_len, " vec4 c0 = TEX_OFFSET(tex, texCoord, texSize, offset);");
append_line(fs_buf, &fs_len, " vec4 c1 = TEX_OFFSET(tex, texCoord, texSize, vec2(offset.x - sign(offset.x), offset.y));");
append_line(fs_buf, &fs_len, " vec4 c2 = TEX_OFFSET(tex, texCoord, texSize, vec2(offset.x, offset.y - sign(offset.y)));");
append_line(fs_buf, &fs_len, " return c0 + abs(offset.x)*(c1-c0) + abs(offset.y)*(c2-c0);");
append_line(fs_buf, &fs_len, "}");
}
if (cc_features.opt_blur) {
// blur filter, used for menu backgrounds
// used to be two for loops from 0 to 4, but apparently intel drivers crashed trying to unroll it
// used to have a const weight array, but apparently drivers for the GT620 don't like const array initializers
if (current_filter_mode == FILTER_THREE_POINT)
append_line(fs_buf, &fs_len, "lowp vec4 hookTexture2D(in sampler2D t, in vec2 uv, in vec2 texSize, in int three_point_filter) {");
else
append_line(fs_buf, &fs_len, "lowp vec4 hookTexture2D(in sampler2D t, in vec2 uv, in vec2 texSize) {");
append_line(fs_buf, &fs_len, " lowp vec4 cw = vec4(0.0);");
append_line(fs_buf, &fs_len, " for (int i = 0; i < 16; ++i) {");
append_line(fs_buf, &fs_len, " vec2 xy = vec2(float(i & 3), float(i >> 2));");
append_line(fs_buf, &fs_len, " lowp float w = 0.009947 - length(xy) * 0.001;");
append_line(fs_buf, &fs_len, " vec2 scaled_uv = uv + (vec2(-1.5) + xy) / texSize;");
if (current_filter_mode == FILTER_THREE_POINT)
append_line(fs_buf, &fs_len, " lowp vec4 tex = mix(SAMPLE_TEX(t, scaled_uv), filter3point(t, scaled_uv, texSize), three_point_filter);");
else
append_line(fs_buf, &fs_len, " lowp vec4 tex = SAMPLE_TEX(t, scaled_uv);");
append_line(fs_buf, &fs_len, " cw += vec4(tex.rgb * w, w);");
append_line(fs_buf, &fs_len, " }");
append_line(fs_buf, &fs_len, " return vec4(cw.rgb / cw.a, 1.0);");
append_line(fs_buf, &fs_len, "}");
} else {
if (current_filter_mode == FILTER_THREE_POINT) {
append_line(fs_buf, &fs_len, "vec4 hookTexture2D(in sampler2D tex, in vec2 uv, in vec2 texSize, in int three_point_filter) {");
append_line(fs_buf, &fs_len, " return mix(SAMPLE_TEX(tex, uv), filter3point(tex, uv, texSize), three_point_filter);");
append_line(fs_buf, &fs_len, "}");
} else {
append_line(fs_buf, &fs_len, "vec4 hookTexture2D(in sampler2D tex, in vec2 uv, in vec2 texSize) {");
append_line(fs_buf, &fs_len, " return SAMPLE_TEX(tex, uv);");
append_line(fs_buf, &fs_len, "}");
}
}
if (gl_glsl_version >= 130) {
append_line(fs_buf, &fs_len, "out vec4 outColor;");
}
append_line(fs_buf, &fs_len, "void main() {");
for (int i = 0; i < 2; i++) {
if (cc_features.used_textures[i]) {
bool s = cc_features.clamp[i][0], t = cc_features.clamp[i][1];
fs_len += sprintf(fs_buf + fs_len, " vec2 texSize%d = vec2(textureSize(uTex%d, 0));\n", i, i);
if (!s && !t) {
fs_len += sprintf(fs_buf + fs_len, " vec2 vTexCoordAdj%d = vTexCoord%d;\n", i, i);
} else {
if (s && t) {
fs_len += sprintf(fs_buf + fs_len,
" vec2 vTexCoordAdj%d = clamp(vTexCoord%d, 0.5 / texSize%d, "
"vec2(vTexClampS%d, vTexClampT%d));\n",
i, i, i, i, i);
} else if (s) {
fs_len += sprintf(fs_buf + fs_len,
" vec2 vTexCoordAdj%d = vec2(clamp(vTexCoord%d.s, 0.5 / "
"texSize%d.s, vTexClampS%d), vTexCoord%d.t);\n",
i, i, i, i, i);
} else {
fs_len += sprintf(fs_buf + fs_len,
" vec2 vTexCoordAdj%d = vec2(vTexCoord%d.s, clamp(vTexCoord%d.t, "
"0.5 / texSize%d.t, vTexClampT%d));\n",
i, i, i, i, i);
}
}
if (current_filter_mode == FILTER_THREE_POINT)
fs_len += sprintf(fs_buf + fs_len, " vec4 texVal%d = hookTexture2D(uTex%d, vTexCoordAdj%d, texSize%d, three_point_filter%d);\n", i, i, i, i, i);
else
fs_len += sprintf(fs_buf + fs_len, " vec4 texVal%d = hookTexture2D(uTex%d, vTexCoordAdj%d, texSize%d);\n", i, i, i, i);
}
}
append_line(fs_buf, &fs_len, cc_features.opt_alpha ? " vec4 texel;" : " vec3 texel;");
for (int c = 0; c < (cc_features.opt_2cyc ? 2 : 1); c++) {
append_str(fs_buf, &fs_len, " texel = ");
if (!cc_features.color_alpha_same[c] && cc_features.opt_alpha) {
append_str(fs_buf, &fs_len, "vec4(");
append_formula(fs_buf, &fs_len, cc_features.c[c], cc_features.do_single[c][0],
cc_features.do_multiply[c][0], cc_features.do_mix[c][0], false, false, true);
append_str(fs_buf, &fs_len, ", ");
append_formula(fs_buf, &fs_len, cc_features.c[c], cc_features.do_single[c][1],
cc_features.do_multiply[c][1], cc_features.do_mix[c][1], true, true, true);
append_str(fs_buf, &fs_len, ")");
} else {
append_formula(fs_buf, &fs_len, cc_features.c[c], cc_features.do_single[c][0],
cc_features.do_multiply[c][0], cc_features.do_mix[c][0], cc_features.opt_alpha, false,
cc_features.opt_alpha);
}
append_line(fs_buf, &fs_len, ";");
if (c == 0) {
append_line(fs_buf, &fs_len, " texel = WRAP(texel, -1.01, 1.01);");
}
}
append_line(fs_buf, &fs_len, " texel = WRAP(texel, -0.51, 1.51);");
append_line(fs_buf, &fs_len, " texel = clamp(texel, 0.0, 1.0);");
// TODO discard if alpha is 0?
if (cc_features.opt_fog) {
if (cc_features.opt_alpha) {
append_line(fs_buf, &fs_len, " texel = vec4(mix(texel.rgb, vFog.rgb, vFog.a), texel.a);");
} else {
append_line(fs_buf, &fs_len, " texel = mix(texel, vFog.rgb, vFog.a);");
}
}
if (cc_features.opt_texture_edge && cc_features.opt_alpha) {
append_line(fs_buf, &fs_len, " if (texel.a > 0.19) texel.a = 1.0; else discard;");
}
if (cc_features.opt_alpha && cc_features.opt_noise) {
append_line(fs_buf, &fs_len,
" texel.a *= floor(clamp(random(vec3(floor(gl_FragCoord.xy * noise_scale), float(frame_count))) + "
"texel.a, 0.0, 1.0));");
}
if (cc_features.opt_grayscale) {
append_line(fs_buf, &fs_len, " float intensity = (texel.r + texel.g + texel.b) / 3.0;");
append_line(fs_buf, &fs_len, " vec3 new_texel = vGrayscaleColor.rgb * intensity;");
append_line(fs_buf, &fs_len, " texel.rgb = mix(texel.rgb, new_texel, vGrayscaleColor.a);");
}
if (cc_features.opt_alpha) {
if (cc_features.opt_alpha_threshold) {
append_line(fs_buf, &fs_len, " if (texel.a < 8.0 / 256.0) discard;");
}
if (cc_features.opt_invisible) {
append_line(fs_buf, &fs_len, " texel.a = 0.0;");
}
append_line(fs_buf, &fs_len, " OUTPUT_COLOR = texel;");
} else {
append_line(fs_buf, &fs_len, " OUTPUT_COLOR = vec4(texel, 1.0);");
}
append_line(fs_buf, &fs_len, "}");
vs_buf[vs_len] = '\0';
fs_buf[fs_len] = '\0';
const GLchar* sources[2] = { vs_buf, fs_buf };
const GLint lengths[2] = { (GLint)vs_len, (GLint)fs_len };
GLint success;
GLuint vertex_shader = glCreateShader(GL_VERTEX_SHADER);
glShaderSource(vertex_shader, 1, &sources[0], &lengths[0]);
glCompileShader(vertex_shader);
glGetShaderiv(vertex_shader, GL_COMPILE_STATUS, &success);
if (!success) {
GLint max_length = 1024;
glGetShaderiv(vertex_shader, GL_INFO_LOG_LENGTH, &max_length);
char error_log[1024];
glGetShaderInfoLog(vertex_shader, max_length, &max_length, &error_log[0]);
sysLogPrintf(LOG_ERROR, "Failed to compile this vertex shader (ID %llx, %x):\n%s", shader_id0, shader_id1, vs_buf);
sysFatalError("Vertex shader compilation failed:\n%s", error_log);
}
GLuint fragment_shader = glCreateShader(GL_FRAGMENT_SHADER);
glShaderSource(fragment_shader, 1, &sources[1], &lengths[1]);
glCompileShader(fragment_shader);
glGetShaderiv(fragment_shader, GL_COMPILE_STATUS, &success);
if (!success) {
GLint max_length = 1024;
glGetShaderiv(fragment_shader, GL_INFO_LOG_LENGTH, &max_length);
char error_log[1024];
glGetShaderInfoLog(fragment_shader, max_length, &max_length, &error_log[0]);
sysLogPrintf(LOG_ERROR, "Failed to compile this fragment shader (ID %llx, %x):\n%s", shader_id0, shader_id1, fs_buf);
sysFatalError("Fragment shader compilation failed:\n%s", error_log);
}
GLuint shader_program = glCreateProgram();
glAttachShader(shader_program, vertex_shader);
glAttachShader(shader_program, fragment_shader);
glLinkProgram(shader_program);
glDetachShader(shader_program, vertex_shader);
glDetachShader(shader_program, fragment_shader);
glDeleteShader(vertex_shader);
glDeleteShader(fragment_shader);
size_t cnt = 0;
struct ShaderProgram* prg = &shader_program_pool[make_pair(shader_id0, shader_id1)];
prg->attrib_locations[cnt] = glGetAttribLocation(shader_program, "aVtxPos");
prg->attrib_sizes[cnt] = 4;
++cnt;
for (int i = 0; i < 2; i++) {
if (cc_features.used_textures[i]) {
char name[32];
sprintf(name, "aTexCoord%d", i);
prg->attrib_locations[cnt] = glGetAttribLocation(shader_program, name);
prg->attrib_sizes[cnt] = 2;
++cnt;
for (int j = 0; j < 2; j++) {
if (cc_features.clamp[i][j]) {
sprintf(name, "aTexClamp%s%d", j == 0 ? "S" : "T", i);
prg->attrib_locations[cnt] = glGetAttribLocation(shader_program, name);
prg->attrib_sizes[cnt] = 1;
++cnt;
}
}
}
}
if (cc_features.opt_fog) {
prg->attrib_locations[cnt] = glGetAttribLocation(shader_program, "aFog");
prg->attrib_sizes[cnt] = 4;
++cnt;
}
if (cc_features.opt_grayscale) {
prg->attrib_locations[cnt] = glGetAttribLocation(shader_program, "aGrayscaleColor");
prg->attrib_sizes[cnt] = 4;
++cnt;
}
for (int i = 0; i < cc_features.num_inputs; i++) {
char name[16];
sprintf(name, "aInput%d", i + 1);
prg->attrib_locations[cnt] = glGetAttribLocation(shader_program, name);
prg->attrib_sizes[cnt] = cc_features.opt_alpha ? 4 : 3;
++cnt;
}
prg->opengl_program_id = shader_program;
prg->num_inputs = cc_features.num_inputs;
prg->used_textures[0] = cc_features.used_textures[0];
prg->used_textures[1] = cc_features.used_textures[1];
prg->num_floats = num_floats;
prg->num_attribs = cnt;
glUseProgram(shader_program);
if (cc_features.used_textures[0]) {
GLint sampler_location = glGetUniformLocation(shader_program, "uTex0");
glUniform1i(sampler_location, 0);
}
if (cc_features.used_textures[1]) {
GLint sampler_location = glGetUniformLocation(shader_program, "uTex1");
glUniform1i(sampler_location, 1);
}
prg->frame_count_location = glGetUniformLocation(shader_program, "frame_count");
prg->noise_scale_location = glGetUniformLocation(shader_program, "noise_scale");
prg->three_point_filter_locations[0] = glGetUniformLocation(shader_program, "three_point_filter0");
prg->three_point_filter_locations[1] = glGetUniformLocation(shader_program, "three_point_filter1");
gfx_opengl_load_shader(prg);
return prg;
}
static struct ShaderProgram* gfx_opengl_lookup_shader(uint64_t shader_id0, uint32_t shader_id1) {
auto it = shader_program_pool.find(make_pair(shader_id0, shader_id1));
return it == shader_program_pool.end() ? nullptr : &it->second;
}
static void gfx_opengl_shader_get_info(struct ShaderProgram* prg, uint8_t* num_inputs, bool used_textures[2]) {
*num_inputs = prg->num_inputs;
used_textures[0] = prg->used_textures[0];
used_textures[1] = prg->used_textures[1];
}
static void gfx_opengl_clear_shaders(void) {
glUseProgram(0);
for (auto& pair : shader_program_pool) {
glDeleteProgram(pair.second.opengl_program_id);
}
shader_program_pool.clear();
}
static GLuint gfx_opengl_new_texture(void) {
GLuint ret;
glGenTextures(1, &ret);
return ret;
}
static void gfx_opengl_delete_texture(uint32_t texID) {
glDeleteTextures(1, &texID);
}
static void gfx_opengl_select_texture(int tile, GLuint texture_id, bool linear_filter) {
glActiveTexture(GL_TEXTURE0 + tile);
glBindTexture(GL_TEXTURE_2D, texture_id);
current_textures_linear_filter[tile] = linear_filter;
}
static void gfx_opengl_upload_texture(const uint8_t* rgba32_buf, uint32_t width, uint32_t height, bool gen_mipmaps) {
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, rgba32_buf);
if (gen_mipmaps || current_filter_mode == FILTER_THREE_POINT) {
glGenerateMipmap(GL_TEXTURE_2D);
}
}
static uint32_t gfx_cm_to_opengl(uint32_t val) {
switch (val) {
case G_TX_NOMIRROR | G_TX_CLAMP:
return GL_CLAMP_TO_EDGE;
case G_TX_MIRROR | G_TX_WRAP:
return GL_MIRRORED_REPEAT;
case G_TX_MIRROR | G_TX_CLAMP:
return gl_mirror_clamp;
case G_TX_NOMIRROR | G_TX_WRAP:
return GL_REPEAT;
}
return 0;
}
static void gfx_opengl_set_sampler_parameters(int tile, bool linear_filter, uint32_t cms, uint32_t cmt, bool mipmaps) {
const GLint min_filters[3][3] = {
// MIPMAP_DISABLED MIPMAP_NEAREST MIPMAP_LINEAR
{GL_NEAREST, GL_NEAREST_MIPMAP_NEAREST, GL_NEAREST_MIPMAP_LINEAR}, // FILTER_NONE
{GL_LINEAR, GL_LINEAR_MIPMAP_NEAREST, GL_LINEAR_MIPMAP_LINEAR}, // FILTER_BILINEAR
{GL_LINEAR_MIPMAP_LINEAR, GL_LINEAR_MIPMAP_LINEAR, GL_LINEAR_MIPMAP_LINEAR}, // FILTER_TRILINEAR
};
mipmaps = mipmaps && (current_mipmap_filter_mode != MIPMAP_DISABLED);
if (linear_filter && current_filter_mode == FILTER_THREE_POINT)
linear_filter = false;
const int mip_idx = mipmaps ? current_mipmap_filter_mode : 0;
const GLint min_filter = linear_filter ? min_filters[current_filter_mode][mip_idx] : GL_NEAREST;
const GLint max_filter = linear_filter && (current_filter_mode != FILTER_NONE) ? GL_LINEAR : GL_NEAREST;
glActiveTexture(GL_TEXTURE0 + tile);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, min_filter);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, max_filter);
if (mipmaps) {
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY, current_anisotropy_level);
}
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, gfx_cm_to_opengl(cms));
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, gfx_cm_to_opengl(cmt));
}
static void gfx_opengl_set_depth_mode(bool depth_test, bool depth_update, bool depth_compare, bool depth_source_prim, uint16_t zmode) {
if (depth_test) {
glEnable(GL_DEPTH_TEST);
glDepthMask(depth_update ? GL_TRUE : GL_FALSE);
current_depth_mask = depth_update;
if (depth_compare) {
switch (zmode) {
case ZMODE_INTER:
glDepthFunc(GL_LEQUAL);
glDisable(GL_POLYGON_OFFSET_FILL);
glPolygonOffset(0, 0);
break;
case ZMODE_OPA:
case ZMODE_XLU:
if (depth_source_prim) {
glDepthFunc(GL_LEQUAL);
} else {
glDepthFunc(GL_LESS);
}
glDisable(GL_POLYGON_OFFSET_FILL);
glPolygonOffset(0, 0);
break;
case ZMODE_DEC:
glDepthFunc(GL_LEQUAL);
glEnable(GL_POLYGON_OFFSET_FILL);
glPolygonOffset(-2, -2);
break;
}
} else {
glDepthFunc(GL_ALWAYS);
glDisable(GL_POLYGON_OFFSET_FILL);
glPolygonOffset(0, 0);
}
} else {
glDisable(GL_DEPTH_TEST);
}
}
static void gfx_opengl_set_depth_range(float znear, float zfar) {
if (glDepthRangef) {
glDepthRangef(znear, zfar);
} else {
glDepthRange(znear, zfar);
}
}
static void gfx_opengl_set_viewport(int x, int y, int width, int height) {
glViewport(x, y, width, height);
}
static void gfx_opengl_set_scissor(int x, int y, int width, int height) {
glScissor(x, y, width, height);
}
static void gfx_opengl_set_use_alpha(bool use_alpha, bool modulate) {
if (use_alpha) {
glEnable(GL_BLEND);
} else {
glDisable(GL_BLEND);
}
if (modulate) {
glBlendFunc(GL_DST_COLOR, GL_ZERO);
} else {
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
}
}
static void gfx_opengl_draw_triangles(float buf_vbo[], size_t buf_vbo_len, size_t buf_vbo_num_tris) {
// printf("flushing %d tris\n", buf_vbo_num_tris);
glBufferData(GL_ARRAY_BUFFER, sizeof(float) * buf_vbo_len, buf_vbo, GL_STREAM_DRAW);
glDrawArrays(GL_TRIANGLES, 0, 3 * buf_vbo_num_tris);
}
typedef void (APIENTRY *DEBUGPROC)(GLenum source,
GLenum type,
GLuint id,
GLenum severity,
GLsizei length,
const GLchar *message,
const void *userParam);
static void APIENTRY gl_debug(GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei length, const GLchar *msg, const void *p) {
sysLogPrintf(LOG_WARNING, "GL: (%05x) %s", id, msg);
}
static void gfx_opengl_enable_debug(void) {
if (GLAD_GL_KHR_debug) {
glEnable(GL_DEBUG_OUTPUT);
}
if (glDebugMessageControl != NULL) {
// enable everything except some specific spam messages
const GLuint disable[] = {
0x20061, /* "Framebuffer detailed info" */
0x20071 /* "Buffer detailed info" */
};
glDebugMessageControl(GL_DONT_CARE, GL_DONT_CARE, GL_DONT_CARE, 0, NULL, GL_TRUE);
glDebugMessageControl(GL_DEBUG_SOURCE_API, GL_DEBUG_TYPE_OTHER, GL_DONT_CARE, 2, disable, GL_FALSE);
}
if (glDebugMessageCallback != NULL) {
glDebugMessageCallback(gl_debug, NULL);
}
}
static bool gfx_opengl_supports_framebuffers(void) {
if (GLVersion.major > 2) {
// GL3.0+ supports everything we need, but we'll still check it for sanity
return (glad_glFramebufferRenderbuffer && glad_glBlitFramebuffer && glad_glRenderbufferStorageMultisample);
}
if (GLAD_GL_ARB_framebuffer_object) {
// some implementations might be missing these
return (glad_glBlitFramebuffer && glad_glRenderbufferStorageMultisample);
}
if (GLAD_GL_EXT_framebuffer_object && GLAD_GL_EXT_framebuffer_blit && GLAD_GL_EXT_framebuffer_multisample) {
// sanity check
return (glad_glFramebufferRenderbuffer && glad_glBlitFramebuffer && glad_glRenderbufferStorageMultisample);
}
// nothing
return false;
}
static bool gfx_opengl_supports_shaders(void) {
if (GLVersion.major > 2) {
// should support GLSL130+
return true;
}
// check supported GLSL version
const char *ver = (const char *)glGetString(GL_SHADING_LANGUAGE_VERSION);
if (ver) {
int maj = 0, min = 0;
sscanf(ver, "%d.%d", &maj, &min);
if (maj > 1 || (maj == 1 && min > 20)) {
// above 120, should be fine
return true;
}
}
// check for extension that adds textureSize
return GLAD_GL_EXT_gpu_shader4;
}
static void gfx_opengl_log_info(void) {
const char *version = (const char *)glGetString(GL_VERSION);
const char *vendor = (const char *)glGetString(GL_VENDOR);
const char *renderer = (const char *)glGetString(GL_RENDERER);
const char *glsl_version = (const char *)glGetString(GL_SHADING_LANGUAGE_VERSION);
sysLogPrintf(LOG_NOTE, "GL: version: %s", version ? version : "unknown");
sysLogPrintf(LOG_NOTE, "GL: vendor: %s", vendor ? vendor : "unknown");
sysLogPrintf(LOG_NOTE, "GL: renderer: %s", renderer ? renderer : "unknown");
sysLogPrintf(LOG_NOTE, "GL: GLSL version: %s", glsl_version ? glsl_version : "unknown");
sysLogPrintf(LOG_NOTE, "GL: ARB_framebuffer_object: %s", gfx_opengl_supports_framebuffers() ? "yes" : "no");
sysLogPrintf(LOG_NOTE, "GL: ARB_depth_clamp: %s", GLAD_GL_ARB_depth_clamp ? "yes" : "no");
sysLogPrintf(LOG_NOTE, "GL: ARB_texture_mirror_clamp_to_edge: %s", GLAD_GL_ARB_texture_mirror_clamp_to_edge ? "yes" : "no");
}
static void *gl_load_proc(const char *name) {
void *ret = SDL_GL_GetProcAddress(name);
if (ret) {
return ret;
}
// try with postfixes
static const char *post[] = { "ARB", "EXT" };
char tmp[256] = { 0 };
for (size_t i = 0; i < sizeof(post) / sizeof(*post); ++i) {
snprintf(tmp, sizeof(tmp), "%s%s", name, post[i]);
ret = SDL_GL_GetProcAddress(tmp);
if (ret) {
return ret;
}
}
sysLogPrintf(LOG_ERROR, "GL: could not find function: %s", name);
return NULL;
}
static void gfx_opengl_init_extensions(void) {
// patch up some extension values and pointers
if (!GLAD_GL_ARB_depth_clamp) {
if (GLAD_GL_EXT_depth_clamp || GLAD_GL_NV_depth_clamp) {
GLAD_GL_ARB_depth_clamp = 1;
} else if (!gl_es && GLVersion.major >= 3) {
// GL3.2+ should have depth_clamp as part of the spec, but some devices don't report it for some reason
GLAD_GL_ARB_depth_clamp = (GLVersion.major > 3 || (GLVersion.major == 3 && GLVersion.minor >= 2));
}
}
if (!GLAD_GL_ARB_texture_mirror_clamp_to_edge) {
GLAD_GL_ARB_texture_mirror_clamp_to_edge = GLAD_GL_EXT_texture_mirror_clamp_to_edge;
}
if (GLVersion.major < 3 && !GLAD_GL_ARB_framebuffer_object) {
if (GLAD_GL_EXT_framebuffer_object && GLAD_GL_EXT_framebuffer_blit && GLAD_GL_EXT_framebuffer_multisample) {
// because of the way glad works we'll have to copy the pointers over
glad_glGenFramebuffers = glad_glGenFramebuffersEXT;
glad_glGenRenderbuffers = glad_glGenRenderbuffersEXT;
glad_glDeleteFramebuffers = glad_glDeleteFramebuffersEXT;
glad_glDeleteRenderbuffers = glad_glDeleteRenderbuffersEXT;
glad_glBindFramebuffer = glad_glBindFramebufferEXT;
glad_glBindRenderbuffer = glad_glBindRenderbufferEXT;
glad_glFramebufferRenderbuffer = glad_glFramebufferRenderbufferEXT;
glad_glFramebufferTexture2D = glad_glFramebufferTexture2DEXT;
glad_glRenderbufferStorage = glad_glRenderbufferStorageEXT;
glad_glRenderbufferStorageMultisample = glad_glRenderbufferStorageMultisampleEXT;
glad_glBlitFramebuffer = glad_glBlitFramebufferEXT;
}
}
}
static void gfx_opengl_init(void) {
if (!gladLoadGLLoader(gl_load_proc) || glGetString == NULL || glEnable == NULL) {
sysFatalError("Could not load OpenGL.\nReported SDL error: %s", SDL_GetError());
}
// check if we're using ES or core, which have more limited feature sets
int val = 0;
SDL_GL_GetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, &val);
gl_core_profile = (val == SDL_GL_CONTEXT_PROFILE_CORE);
gl_es = (val == SDL_GL_CONTEXT_PROFILE_ES);
gfx_opengl_init_extensions();
if (sysArgCheck("--debug-gl")) {
gfx_opengl_enable_debug();
// dump version info as early as possible
gfx_opengl_log_info();
}
if (GLVersion.major < 2 || (GLVersion.major == 2 && GLVersion.minor < 1)) {
const char *ver = (const char *)glGetString(GL_VERSION);
sysFatalError("Could not load OpenGL 2.1.\nReported version: %d.%d (%s)",
GLVersion.major, GLVersion.minor, ver ? ver : "unknown");
}
if (!gfx_opengl_supports_shaders()) {
sysLogPrintf(LOG_WARNING, "GL: GLSL 1.30 may be unsupported");
// maybe replace this with sysFatalError, though the GLSL compiler will cause that later anyway
}
if (!gfx_framebuffers_enabled) {
sysLogPrintf(LOG_WARNING, "GL: framebuffer effects disabled by user");
} else if (!gfx_opengl_supports_framebuffers()) {
sysLogPrintf(LOG_WARNING, "GL: GL_ARB_framebuffer_object unsupported, framebuffer effects disabled");
gfx_framebuffers_enabled = false;
}
if ((GLVersion.major < 4 || GLVersion.minor < 4) && !GLAD_GL_ARB_texture_mirror_clamp_to_edge) {
// GL_MIRROR_CLAMP_TO_EDGE unsupported
gl_mirror_clamp = GL_MIRRORED_REPEAT;
}
// determine GLSL version
if (gl_es) {
// ES has its own numbering scheme, but it should support 300 even on 3.1 and 3.2
gl_glsl_version = 300;
snprintf(gl_glsl_version_str, sizeof(gl_glsl_version_str), "%d es", gl_glsl_version);
} else if (!gl_core_profile) {
// in compat profile we can just request the lowest possible
gl_glsl_version = 130;
snprintf(gl_glsl_version_str, sizeof(gl_glsl_version_str), "%d", gl_glsl_version);
} else {
// otherwise we have to pick a specific version
if (GLVersion.major == 3 && GLVersion.minor == 2) {
// 3.2core is the earliest core version and it follows the old numbering scheme
gl_glsl_version = 150;
} else {
// 3.3+ follow the new numbering scheme
gl_glsl_version = GLVersion.major * 100 + GLVersion.minor * 10;
}
snprintf(gl_glsl_version_str, sizeof(gl_glsl_version_str), "%d core", gl_glsl_version);
}
sysLogPrintf(LOG_NOTE, "GL: using GLSL version %s", gl_glsl_version_str);
glGenBuffers(1, &opengl_vbo);
glBindBuffer(GL_ARRAY_BUFFER, opengl_vbo);
if (gl_core_profile || gl_es) {
// warn user that funny things can happen
sysLogPrintf(LOG_WARNING, "GL: using core profile or ES, watch out for errors");
// core/es will explode if we don't use a VAO for our VBO
glGenVertexArrays(1, &opengl_vao);
glBindVertexArray(opengl_vao);
}
if (GLAD_GL_ARB_depth_clamp) {
glEnable(GL_DEPTH_CLAMP);
}
glDepthFunc(GL_LEQUAL);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
framebuffers.resize(1); // for the default screen buffer
}
static void gfx_opengl_on_resize(void) {
}
static void gfx_opengl_start_frame(void) {
frame_count++;
}
static void gfx_opengl_end_frame(void) {
glFlush();
}
static void gfx_opengl_finish_render(void) {
}
static int gfx_opengl_create_framebuffer() {
size_t i = framebuffers.size();
framebuffers.resize(i + 1);
GLuint clrbuf;
glGenTextures(1, &clrbuf);
glBindTexture(GL_TEXTURE_2D, clrbuf);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB8, 1, 1, 0, GL_RGB, GL_UNSIGNED_BYTE, NULL);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glBindTexture(GL_TEXTURE_2D, 0);
framebuffers[i].clrbuf = clrbuf;
if (!gfx_framebuffers_enabled) {
return i;
}
GLuint clrbuf_msaa;
glGenRenderbuffers(1, &clrbuf_msaa);
framebuffers[i].clrbuf_msaa = clrbuf_msaa;
GLuint rbo;
glGenRenderbuffers(1, &rbo);
glBindRenderbuffer(GL_RENDERBUFFER, rbo);
glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, 1, 1);
glBindRenderbuffer(GL_RENDERBUFFER, 0);
framebuffers[i].rbo = rbo;
GLuint fbo;
glGenFramebuffers(1, &fbo);
framebuffers[i].fbo = fbo;
return i;
}
static void gfx_opengl_update_framebuffer_parameters(int fb_id, uint32_t width, uint32_t height, uint32_t msaa_level,
bool opengl_invert_y, bool render_target, bool has_depth_buffer,
bool can_extract_depth) {
Framebuffer& fb = framebuffers[fb_id];
width = max(width, 1U);
height = max(height, 1U);
if (gfx_framebuffers_enabled) {
glBindFramebuffer(GL_FRAMEBUFFER, fb.fbo);
if (fb_id != 0) {
if (fb.width != width || fb.height != height || fb.msaa_level != msaa_level) {
if (msaa_level <= 1) {
glBindTexture(GL_TEXTURE_2D, fb.clrbuf);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB8, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, NULL);
glBindTexture(GL_TEXTURE_2D, 0);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, fb.clrbuf, 0);
} else {
glBindRenderbuffer(GL_RENDERBUFFER, fb.clrbuf_msaa);
glRenderbufferStorageMultisample(GL_RENDERBUFFER, msaa_level, GL_RGB8, width, height);
glBindRenderbuffer(GL_RENDERBUFFER, 0);
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, fb.clrbuf_msaa);
}
}
if (has_depth_buffer &&
(fb.width != width || fb.height != height || fb.msaa_level != msaa_level || !fb.has_depth_buffer)) {
glBindRenderbuffer(GL_RENDERBUFFER, fb.rbo);
if (msaa_level <= 1) {
glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, width, height);
} else {
glRenderbufferStorageMultisample(GL_RENDERBUFFER, msaa_level, GL_DEPTH24_STENCIL8, width, height);
}
glBindRenderbuffer(GL_RENDERBUFFER, 0);
}
if (!fb.has_depth_buffer && has_depth_buffer) {
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER, fb.rbo);
} else if (fb.has_depth_buffer && !has_depth_buffer) {
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER, 0);
}
}
} else {
has_depth_buffer = false;
}
fb.width = width;
fb.height = height;
fb.has_depth_buffer = has_depth_buffer;
fb.msaa_level = msaa_level;
fb.invert_y = opengl_invert_y;
}
bool gfx_opengl_start_draw_to_framebuffer(int fb_id, float noise_scale) {
if (gfx_framebuffers_enabled && fb_id < (int)framebuffers.size()) {
Framebuffer& fb = framebuffers[fb_id];
if (noise_scale != 0.0f) {
current_noise_scale = 1.0f / noise_scale;
}
glBindFramebuffer(GL_FRAMEBUFFER, fb.fbo);
current_framebuffer = fb_id;
return true;
} else {
return false;
}
}
void gfx_opengl_clear_framebuffer(bool clear_color, bool clear_depth) {
glDisable(GL_SCISSOR_TEST);
GLbitfield mask = 0;
if (clear_color) {
glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
mask |= GL_COLOR_BUFFER_BIT;
}
if (clear_depth) {
glDepthMask(GL_TRUE);
mask |= GL_DEPTH_BUFFER_BIT;
}
glClear(mask);
if (clear_depth) {
glDepthMask(current_depth_mask ? GL_TRUE : GL_FALSE);
}
glEnable(GL_SCISSOR_TEST);
}
void gfx_opengl_resolve_msaa_color_buffer(int fb_id_target, int fb_id_source) {
if (!gfx_framebuffers_enabled) {
return;
}
Framebuffer& fb_dst = framebuffers[fb_id_target];
Framebuffer& fb_src = framebuffers[fb_id_source];
glDisable(GL_SCISSOR_TEST);
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, fb_dst.fbo);
glBindFramebuffer(GL_READ_FRAMEBUFFER, fb_src.fbo);
glBlitFramebuffer(0, 0, fb_src.width, fb_src.height, 0, 0, fb_dst.width, fb_dst.height, GL_COLOR_BUFFER_BIT,
GL_NEAREST);
glBindFramebuffer(GL_FRAMEBUFFER, current_framebuffer);
glEnable(GL_SCISSOR_TEST);
}
void* gfx_opengl_get_framebuffer_texture_id(int fb_id) {
return (void*)(uintptr_t)framebuffers[fb_id].clrbuf;
}
void gfx_opengl_select_texture_fb(int fb_id) {
// glDisable(GL_DEPTH_TEST);
glActiveTexture(GL_TEXTURE0 + 0);
glBindTexture(GL_TEXTURE_2D, framebuffers[fb_id].clrbuf);
current_textures_linear_filter[0] = true;
}
void gfx_opengl_copy_framebuffer(int fb_dst, int fb_src, int left, int top, bool flip_y, bool use_back) {
if (!gfx_framebuffers_enabled || fb_dst >= (int)framebuffers.size() || fb_src >= (int)framebuffers.size()) {
return;
}
const Framebuffer& src = framebuffers[fb_src];
const Framebuffer& dst = framebuffers[fb_dst];
int srcX0, srcY0, srcX1, srcY1;
int dstX0, dstY0, dstX1, dstY1;
dstX0 = dstY0 = 0;
dstX1 = dst.width;
dstY1 = dst.height;
if (left >= 0 && top >= 0) {
// unscaled rect copy
srcX0 = left;
srcY0 = top;
srcX1 = left + dst.width;
srcY1 = top + dst.height;
} else {
// scaled full copy
srcX0 = 0;
srcY0 = 0;
srcX1 = src.width;
srcY1 = src.height;
}
glDisable(GL_SCISSOR_TEST);
glBindFramebuffer(GL_READ_FRAMEBUFFER, src.fbo);
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, dst.fbo);
if (flip_y) {
// flip the dst rect to mirror the image vertically
std::swap(dstY0, dstY1);
}
if (fb_src == 0) {
// GLES does not support GL_FRONT here
glReadBuffer((use_back || gl_es) ? GL_BACK : GL_FRONT);
} else {
glReadBuffer(GL_COLOR_ATTACHMENT0);
}
glBlitFramebuffer(srcX0, srcY0, srcX1, srcY1, dstX0, dstY0, dstX1, dstY1, GL_COLOR_BUFFER_BIT, GL_NEAREST);
glBindFramebuffer(GL_FRAMEBUFFER, framebuffers[current_framebuffer].fbo);
glReadBuffer(GL_BACK);
glEnable(GL_SCISSOR_TEST);
}
void gfx_opengl_set_texture_filter(FilteringMode mode) {
current_filter_mode = mode;
}
FilteringMode gfx_opengl_get_texture_filter(void) {
return current_filter_mode;
}
void gfx_opengl_set_mipmap_filter(MipmapFilteringMode mode) {
current_mipmap_filter_mode = mode;
}
static int gfx_opengl_get_max_anisotropy_level() {
GLfloat max_aniso_level;
glGetFloatv(GL_MAX_TEXTURE_MAX_ANISOTROPY, &max_aniso_level);
return (int)max_aniso_level;
}
static void gfx_opengl_set_anisotropy_level(int level) {
current_anisotropy_level = level;
}
struct GfxRenderingAPI gfx_opengl_api = {
gfx_opengl_get_name,
gfx_opengl_get_max_texture_size,
gfx_opengl_get_clip_parameters,
gfx_opengl_unload_shader,
gfx_opengl_load_shader,
gfx_opengl_create_and_load_new_shader,
gfx_opengl_lookup_shader,
gfx_opengl_shader_get_info,
gfx_opengl_clear_shaders,
gfx_opengl_new_texture,
gfx_opengl_select_texture,
gfx_opengl_upload_texture,
gfx_opengl_set_sampler_parameters,
gfx_opengl_set_depth_mode,
gfx_opengl_set_depth_range,
gfx_opengl_set_viewport,
gfx_opengl_set_scissor,
gfx_opengl_set_use_alpha,
gfx_opengl_draw_triangles,
gfx_opengl_init,
gfx_opengl_on_resize,
gfx_opengl_start_frame,
gfx_opengl_end_frame,
gfx_opengl_finish_render,
gfx_opengl_create_framebuffer,
gfx_opengl_update_framebuffer_parameters,
gfx_opengl_start_draw_to_framebuffer,
gfx_opengl_copy_framebuffer,
gfx_opengl_clear_framebuffer,
gfx_opengl_resolve_msaa_color_buffer,
gfx_opengl_get_framebuffer_texture_id,
gfx_opengl_select_texture_fb,
gfx_opengl_delete_texture,
gfx_opengl_set_texture_filter,
gfx_opengl_get_texture_filter,
gfx_opengl_set_mipmap_filter,
gfx_opengl_set_anisotropy_level,
gfx_opengl_get_max_anisotropy_level
};