#include #include #include #include #include #include #include #include #ifndef _LANGUAGE_C #define _LANGUAGE_C #endif #include #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, 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 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_NEAREST, GL_NEAREST_MIPMAP_NEAREST, GL_NEAREST_MIPMAP_LINEAR }, // FILTER_THREE_POINT }; mipmaps = mipmaps && (current_mipmap_filter_mode != MIPMAP_DISABLED); 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_LINEAR) ? 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 };