diff --git a/port/fast3d/gfx_opengl.cpp b/port/fast3d/gfx_opengl.cpp index 236f535c7..f172ce96d 100644 --- a/port/fast3d/gfx_opengl.cpp +++ b/port/fast3d/gfx_opengl.cpp @@ -34,7 +34,9 @@ struct ShaderProgram { GLint noise_scale_location; GLint three_point_filter_locations[2]; GLint decal_mode_location; + GLint decal_scale_location; int decal_mode; + float decal_scale; }; struct Framebuffer { @@ -64,8 +66,10 @@ static MipmapFilteringMode current_mipmap_filter_mode = MIPMAP_LINEAR; static bool current_textures_linear_filter[2] = {false, false}; static struct ShaderProgram* current_shader_program; -// Decals are clipped to the geometry they lie on, using a copy of the depth buffer +// N64 decal depth mode: pixels only pass when close to the stored depth, using a copy of the depth buffer #define DECAL_DEPTH_TEXTURE_UNIT 2 +// Height of the N64 frame; the RDP's depth slope tolerance is measured in N64 pixels +#define DECAL_N64_SCREEN_HEIGHT 240.0f static bool depth_copy_supported; static bool depth_copy_dirty = true; static size_t depth_copy_source; @@ -399,6 +403,7 @@ static struct ShaderProgram* gfx_opengl_create_and_load_new_shader(uint64_t shad 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, "uniform int uDecalMode;"); + append_line(fs_buf, &fs_len, "uniform highp float uDecalPixelScale;"); append_line(fs_buf, &fs_len, "uniform highp sampler2D uDepthTex;"); append_line(fs_buf, &fs_len, "float random(in vec3 value) {"); @@ -460,12 +465,13 @@ static struct ShaderProgram* gfx_opengl_create_and_load_new_shader(uint64_t shad append_line(fs_buf, &fs_len, "void main() {"); - // Discard decal pixels hanging past an edge, i.e. where the stored surface is clearly further away than the decal. - // (1 - z) is roughly proportional to 1 / distance, so this allows a gap of about 3% of the distance, plus slope. + // RDP decal Z mode: pass only if |z - stored z| <= dz (the per-pixel depth slope), and never over cleared depth. + // The slope is computed before anything can discard, since derivatives are undefined after a neighbor discards. append_line(fs_buf, &fs_len, " highp float decalSlope = fwidth(gl_FragCoord.z);"); append_line(fs_buf, &fs_len, " if (uDecalMode != 0) {"); append_line(fs_buf, &fs_len, " highp float storedZ = texelFetch(uDepthTex, ivec2(gl_FragCoord.xy), 0).r;"); - append_line(fs_buf, &fs_len, " if (storedZ - gl_FragCoord.z > 0.03 * (1.0 - gl_FragCoord.z) + 3.0 * decalSlope) discard;"); + append_line(fs_buf, &fs_len, " highp float dz = max(decalSlope * uDecalPixelScale, 1.0 / 262144.0);"); + append_line(fs_buf, &fs_len, " if (storedZ >= 1.0 || abs(gl_FragCoord.z - storedZ) > dz) discard;"); append_line(fs_buf, &fs_len, " }"); for (int i = 0; i < 2; i++) { @@ -679,7 +685,9 @@ static struct ShaderProgram* gfx_opengl_create_and_load_new_shader(uint64_t shad prg->three_point_filter_locations[0] = glGetUniformLocation(shader_program, "three_point_filter0"); prg->three_point_filter_locations[1] = glGetUniformLocation(shader_program, "three_point_filter1"); prg->decal_mode_location = glGetUniformLocation(shader_program, "uDecalMode"); + prg->decal_scale_location = glGetUniformLocation(shader_program, "uDecalPixelScale"); prg->decal_mode = 0; + prg->decal_scale = 0.0f; if (prg->decal_mode_location >= 0) { glUniform1i(prg->decal_mode_location, 0); glUniform1i(glGetUniformLocation(shader_program, "uDepthTex"), DECAL_DEPTH_TEXTURE_UNIT); @@ -926,11 +934,29 @@ static void gfx_opengl_set_decal_uniforms(struct ShaderProgram* prg, bool enable glUniform1i(prg->decal_mode_location, mode); prg->decal_mode = mode; } + + const float scale = framebuffers[current_framebuffer].height / DECAL_N64_SCREEN_HEIGHT; + if (enabled && prg->decal_scale != scale) { + glUniform1f(prg->decal_scale_location, scale); + prg->decal_scale = scale; + } } 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); - gfx_opengl_set_decal_uniforms(current_shader_program, current_depth_decal && gfx_opengl_update_depth_copy()); + if (current_depth_decal) { + // The shader replaces the depth test like the RDP does; fall back to polygon offset if depth can't be copied + const bool decal = gfx_opengl_update_depth_copy(); + glDepthFunc(decal ? GL_ALWAYS : GL_LEQUAL); + if (decal) { + glDisable(GL_POLYGON_OFFSET_FILL); + } else { + glEnable(GL_POLYGON_OFFSET_FILL); + } + gfx_opengl_set_decal_uniforms(current_shader_program, decal); + } else { + gfx_opengl_set_decal_uniforms(current_shader_program, false); + } glBufferData(GL_ARRAY_BUFFER, sizeof(float) * buf_vbo_len, buf_vbo, GL_STREAM_DRAW); glDrawArrays(GL_TRIANGLES, 0, 3 * buf_vbo_num_tris);