diff --git a/code/cmdline/cmdline.cpp b/code/cmdline/cmdline.cpp index 1351c72f1db..66aed86214b 100644 --- a/code/cmdline/cmdline.cpp +++ b/code/cmdline/cmdline.cpp @@ -173,6 +173,7 @@ Flag exe_params[] = { "-fb_thrusters", "Enable Framebuffer Thrusters", true, EASY_ALL_ON, EASY_DEFAULT, "Graphics", "http://www.hard-light.net/wiki/index.php/Command-Line_Reference#-fb_thrusters", }, { "-no_deferred", "Disable Deferred Lighting", true, EASY_DEFAULT | EASY_HI_MEM_OFF, EASY_ALL_ON | EASY_HI_MEM_ON, "Graphics", "http://www.hard-light.net/wiki/index.php/Command-Line_Reference#-no_deferred"}, { "-enable_shadows", "Enable Shadows", true, EASY_ALL_ON | EASY_HI_MEM_ON, EASY_DEFAULT | EASY_HI_MEM_OFF, "Graphics", "http://www.hard-light.net/wiki/index.php/Command-Line_Reference#-enable_shadows"}, + { "-reversed_depth", "Reversed depth (less distant flicker)", true, 0, EASY_DEFAULT, "Graphics", "http://www.hard-light.net/wiki/index.php/Command-Line_Reference#-reversed_depth"}, { "-deferred_cockpit", "Enable Deferred Lighting for Cockpits", true, EASY_ALL_ON | EASY_HI_MEM_ON, EASY_DEFAULT | EASY_HI_MEM_OFF, "Graphics", "http://www.hard-light.net/wiki/index.php/Command-Line_Reference#-deferred_cockpit"}, //flag launcher text FSO on_flags off_flags category reference URL @@ -357,6 +358,7 @@ cmdline_parm fb_explosions_arg("-fb_explosions", NULL, AT_NONE); cmdline_parm fb_thrusters_arg("-fb_thrusters", NULL, AT_NONE); cmdline_parm shadow_quality_arg("-shadow_quality", NULL, AT_INT); cmdline_parm enable_shadows_arg("-enable_shadows", NULL, AT_NONE); +cmdline_parm reversed_depth_arg("-reversed_depth", nullptr, AT_NONE); // Gr_enable_reversed_depth cmdline_parm rt_shadows_arg("-rt_shadows", nullptr, AT_NONE); cmdline_parm no_deferred_lighting_arg("-no_deferred", NULL, AT_NONE); // Cmdline_no_deferred cmdline_parm deferred_lighting_cockpit_arg("-deferred_cockpit", nullptr, AT_NONE); @@ -2274,6 +2276,14 @@ bool SetCmdlineParams() Cmdline_reparse_mainhall = 1; } + if (reversed_depth_arg.found()) { + Gr_enable_reversed_depth = true; + // in-game options are only read in mods that turn them on; the flag works everywhere + options::OptionsManager::instance()->setOverride("Graphics.ReversedDepth", + std::unique_ptr(json_true()), + "-reversed_depth"); + } + if( enable_shadows_arg.found() ) { // if only using `enable_shadows` then default quality level can be overriden in mod_settings.tbl --wookieejedi diff --git a/code/def_files/data/effects/decal-f.sdr b/code/def_files/data/effects/decal-f.sdr index 34d65a67ba8..068627d1d8d 100644 --- a/code/def_files/data/effects/decal-f.sdr +++ b/code/def_files/data/effects/decal-f.sdr @@ -87,7 +87,12 @@ vec3 computeViewPosition(vec2 textureCoord) { clipSpaceLocation.z = texelFetch(gDepthBuffer, ivec2(textureCoord), 0).r; #else clipSpaceLocation.xy = normalizedCoord * 2.0f - 1.0f; +#ifdef REVERSED_Z + // reversed depth sets the depth range to [0, 1], as in Vulkan + clipSpaceLocation.z = texelFetch(gDepthBuffer, ivec2(textureCoord), 0).r; +#else clipSpaceLocation.z = texelFetch(gDepthBuffer, ivec2(textureCoord), 0).r * 2.0f - 1.0f; +#endif #endif clipSpaceLocation.w = 1.0f; diff --git a/code/def_files/data/effects/effect-f.sdr b/code/def_files/data/effects/effect-f.sdr index f46147830e5..4c7798a5da8 100644 --- a/code/def_files/data/effects/effect-f.sdr +++ b/code/def_files/data/effects/effect-f.sdr @@ -65,8 +65,13 @@ void main() } fragDepthLinear = -fragPosition.z; } else { +#ifdef REVERSED_Z + sceneDepthLinear = ( farZ * nearZ ) / ( nearZ + sceneDepth.x * (farZ-nearZ) ); + fragDepthLinear = ( farZ * nearZ ) / ( nearZ + gl_FragCoord.z * (farZ-nearZ) ); +#else sceneDepthLinear = ( 2.0 * farZ * nearZ ) / ( farZ + nearZ - sceneDepth.x * (farZ-nearZ) ); fragDepthLinear = ( 2.0 * farZ * nearZ ) / ( farZ + nearZ - gl_FragCoord.z * (farZ-nearZ) ); +#endif } // assume UV of 0.5, 0.5 is the centroid of this sphere volume float depthOffset = sqrt((fragRadius*fragRadius) - (offset_len*offset_len)); diff --git a/code/def_files/data/effects/fog-f.sdr b/code/def_files/data/effects/fog-f.sdr index 6c822bb67e3..d38e3f517f7 100644 --- a/code/def_files/data/effects/fog-f.sdr +++ b/code/def_files/data/effects/fog-f.sdr @@ -39,12 +39,17 @@ void main() // If the scene is in fog then we change the color we operate on based on the depth of the pixel float depth_val = texture(depth_tex, fragTexCoord.xy).x; +#ifdef REVERSED_Z + float view_depth = zNear * zFar / (zNear + depth_val * (zFar - zNear)); + float skybox_compat_fake = depth_val <= 0.0 ? 1.0 : 0.0; +#else // Transform depth value from [0, 1] to [-1, 1] float depth_normalized = 2 * depth_val - 1; // Now we compute the depth value in projection space using the clipping plane information float view_depth = 2.0 * zNear * zFar / (zFar + zNear - depth_normalized * (zFar - zNear)); float skybox_compat_fake = step(1.0, depth_val); +#endif view_depth = clamp(view_depth, 0.0, mix(clip_dist, clip_inf_dist, skybox_compat_fake)); if (isinf(view_depth)) { diff --git a/code/def_files/data/effects/ls-f.sdr b/code/def_files/data/effects/ls-f.sdr index cc4ff755da0..f62d9d21bb8 100644 --- a/code/def_files/data/effects/ls-f.sdr +++ b/code/def_files/data/effects/ls-f.sdr @@ -37,14 +37,27 @@ void main() float decay = 1.0; vec4 sum = vec4(0.0); vec4 mask = texture(cockpit, fragTexCoord.st); - if (mask.r < 1.0) { +#if defined(REVERSED_Z) && defined(VULKAN) + // Vulkan binds no cockpit depth here; its placeholder texture is white, which reads as the near plane + bool cockpit_covers = false; +#elif defined(REVERSED_Z) + bool cockpit_covers = mask.r > 0.0; +#else + bool cockpit_covers = mask.r < 1.0; +#endif + if (cockpit_covers) { fragOut0 = vec4(cp_intensity); return; } +#ifdef REVERSED_Z + const float sky_depth = 0.0; +#else + const float sky_depth = 1.0; +#endif for(int i=0; i < SAMPLE_NUM ; i++) { pos.st -= step; vec4 tex_sample = texture(scene, pos); - if (tex_sample.r == 1.0) + if (tex_sample.r == sky_depth) sum += decay * weight; decay *= falloff; } diff --git a/code/def_files/data/effects/main-v.sdr b/code/def_files/data/effects/main-v.sdr index a0adda205a5..d03e222d5ec 100644 --- a/code/def_files/data/effects/main-v.sdr +++ b/code/def_files/data/effects/main-v.sdr @@ -231,7 +231,12 @@ void main() // Fog distance #prereplace IF_FLAG MODEL_SDR_FLAG_FOG +#ifdef REVERSED_Z + // clip z no longer grows with distance; w is the view depth + vertOut.fogDist = gl_Position.w - fogNear; +#else vertOut.fogDist = gl_Position.z - fogNear; +#endif #prereplace ENDIF_FLAG //MODEL_SDR_FLAG_FOG #ifdef VULKAN #prereplace IF_NOT_FLAG MODEL_SDR_FLAG_FOG diff --git a/code/def_files/data/effects/shadows.sdr b/code/def_files/data/effects/shadows.sdr index b82937e139c..04f2da5744d 100644 --- a/code/def_files/data/effects/shadows.sdr +++ b/code/def_files/data/effects/shadows.sdr @@ -138,7 +138,7 @@ vec4 transformToShadowMap(mat4 shadow_proj_matrix, int i, vec4 pos) { vec4 shadow_proj; shadow_proj = shadow_proj_matrix * pos; -#ifdef VULKAN +#if defined(VULKAN) || defined(REVERSED_Z) // Vulkan shadow projection: XY is [-1,1] → [0,1], Z is already [0,1] shadow_proj.xy = shadow_proj.xy * 0.5 + 0.5; #else diff --git a/code/def_files/data/effects/volumetric-f.sdr b/code/def_files/data/effects/volumetric-f.sdr index b00e721317c..a7373aaf200 100644 --- a/code/def_files/data/effects/volumetric-f.sdr +++ b/code/def_files/data/effects/volumetric-f.sdr @@ -94,7 +94,9 @@ void main() vec2 fragcoordAngle = (fragTexCoord.xy - 0.5) * fov; fragcoordAngle.x *= aspect; float rawDepth = texture(depth_tex, fragTexCoord.xy).x; -#ifdef VULKAN +#if defined(REVERSED_Z) + float fragDepth = (zNear * zFar / (zNear + rawDepth * (zFar - zNear))) * sqrt(1.0 + tan(fragcoordAngle.x) * tan(fragcoordAngle.x) + tan(fragcoordAngle.y) * tan(fragcoordAngle.y)); +#elif defined(VULKAN) // Vulkan depth range is [0,1] float fragDepth = (zNear * zFar / (zFar - rawDepth * (zFar - zNear))) * sqrt(1.0 + tan(fragcoordAngle.x) * tan(fragcoordAngle.x) + tan(fragcoordAngle.y) * tan(fragcoordAngle.y)); #else diff --git a/code/graphics/2d.cpp b/code/graphics/2d.cpp index e83a786169a..3ec0e3307a3 100644 --- a/code/graphics/2d.cpp +++ b/code/graphics/2d.cpp @@ -987,6 +987,20 @@ static auto HDROption __UNUSED = options::OptionBuilder("Graphics.HDR", .parser(parse_hdr_func) .finish(); +bool Gr_enable_reversed_depth = false; +bool Gr_reversed_depth_active = false; + +// coverity[GLOBAL_INIT_ORDER] -- safe; OptionBuilder::finish() uses Meyers singleton +static auto ReversedDepthOption __UNUSED = options::OptionBuilder("Graphics.ReversedDepth", + std::pair{"Reversed Depth", -1}, + std::pair{"Reduces flicker between distant surfaces. Needs Vulkan or OpenGL 4.5. Requires a restart to take effect.", -1}) + .category(std::make_pair("Graphics", 1825)) + .level(options::ExpertLevel::Advanced) + .default_val(false) + .bind_to_once(&Gr_enable_reversed_depth) + .importance(67) + .finish(); + float Gr_hdr_paperwhite_nits = 200.0f; static void parse_hdr_paperwhite_func() diff --git a/code/graphics/2d.h b/code/graphics/2d.h index 437bc85368d..0134f0ad3a0 100644 --- a/code/graphics/2d.h +++ b/code/graphics/2d.h @@ -77,6 +77,17 @@ extern bool Gr_enable_vsync; // HDR10 (PQ/ST.2084 + BT.2020) output. Currently only honored by the Vulkan renderer. extern bool Gr_enable_hdr; + +// Reversed depth: the request (an option, read at startup) and whether the renderer turned it on. +// With it active, the near plane is depth 1, the far plane depth 0, and closer surfaces pass with GREATER. +extern bool Gr_enable_reversed_depth; +extern bool Gr_reversed_depth_active; + +// the depth of an empty pixel: what the depth buffer is cleared to +inline float gr_far_depth() +{ + return Gr_reversed_depth_active ? 0.0f : 1.0f; +} // True once the renderer has actually negotiated an HDR10 swap chain (read-only, // set by the active renderer). Distinct from Gr_enable_hdr, which is the request. extern bool Gr_hdr_output_active; diff --git a/code/graphics/matrix.cpp b/code/graphics/matrix.cpp index 3c1be80c5d2..ed576428a6f 100644 --- a/code/graphics/matrix.cpp +++ b/code/graphics/matrix.cpp @@ -69,6 +69,14 @@ static void create_perspective_projection_matrix(matrix4 *out, float left, float out->a1d[10] = -(far_dist + near_dist) / (far_dist - near_dist); out->a1d[14] = -2.0f * far_dist * near_dist / (far_dist - near_dist); } + + if (Gr_reversed_depth_active) { + // near maps to 1 and far to 0, in the [0, 1] range reversed depth uses on both APIs. Written out rather + // than flipped from the values above, where float rounding would move the far plane when far is much + // larger than near. + out->a1d[10] = near_dist / (far_dist - near_dist); + out->a1d[14] = far_dist * near_dist / (far_dist - near_dist); + } } static void create_orthographic_projection_matrix(matrix4* out, float left, float right, float bottom, float top, float near_dist, float far_dist) @@ -81,8 +89,8 @@ static void create_orthographic_projection_matrix(matrix4* out, float left, floa out->a1d[13] = -(top + bottom) / (top - bottom); out->a1d[15] = 1.0f; - if (gr_screen.mode == GraphicsAPI::Vulkan) { - // Vulkan NDC Z range is [0, 1] (OpenGL is [-1, 1]) + if (gr_screen.mode == GraphicsAPI::Vulkan || Gr_reversed_depth_active) { + // Vulkan NDC Z range is [0, 1] (OpenGL is [-1, 1], unless reversed depth set it to [0, 1]) // Y-flip is handled by negative viewport height (VK_KHR_maintenance1) out->a1d[10] = -1.0f / (far_dist - near_dist); out->a1d[14] = -near_dist / (far_dist - near_dist); @@ -91,6 +99,12 @@ static void create_orthographic_projection_matrix(matrix4* out, float left, floa out->a1d[10] = -2.0f / (far_dist - near_dist); out->a1d[14] = -(far_dist + near_dist) / (far_dist - near_dist); } + + if (Gr_reversed_depth_active) { + // depth becomes 1 - depth: near maps to 1, far to 0 + out->a1d[10] = -out->a1d[10]; + out->a1d[14] = 1.0f - out->a1d[14]; + } } void gr_start_instance_matrix(const vec3d *offset, const matrix *rotation) diff --git a/code/graphics/opengl/gropengl.cpp b/code/graphics/opengl/gropengl.cpp index 444515b797a..d5794c07ffb 100644 --- a/code/graphics/opengl/gropengl.cpp +++ b/code/graphics/opengl/gropengl.cpp @@ -863,15 +863,33 @@ void gr_opengl_set_fill_mode(int mode) GL_state.SetPolygonMode(GL_FRONT_AND_BACK, GL_FILL); } +// GL 4.5 drivers advertise ARB_clip_control too, so one check covers both +static void opengl_enable_reversed_depth() +{ +#ifndef USE_OPENGL_ES + if (!GLAD_GL_ARB_clip_control) { + mprintf((" Reversed depth needs ARB_clip_control; using normal depth\n")); + return; + } + + glClipControl(GL_LOWER_LEFT, GL_ZERO_TO_ONE); + glClearDepth(0.0); + Gr_reversed_depth_active = true; + mprintf((" Using reversed depth\n")); +#endif +} + void gr_opengl_zbias(int bias) { if (bias) { + // with reversed depth, closer means larger depth, so the offset changes sign + const float sign = Gr_reversed_depth_active ? -1.0f : 1.0f; GL_state.PolygonOffsetFill(GL_TRUE); if(bias < 0) { - GL_state.SetPolygonOffset(1.0, -i2fl(bias)); + GL_state.SetPolygonOffset(sign * 1.0f, sign * -i2fl(bias)); } else { - GL_state.SetPolygonOffset(0.0, -i2fl(bias)); + GL_state.SetPolygonOffset(0.0, sign * -i2fl(bias)); } } else { GL_state.PolygonOffsetFill(GL_FALSE); @@ -1415,6 +1433,10 @@ bool gr_opengl_init(std::unique_ptr&& graphicsOps) GL_initted = true; + if (Gr_enable_reversed_depth) { + opengl_enable_reversed_depth(); + } + #ifndef NDEBUG // Set up the debug extension if present if (GLAD_GL_ARB_debug_output) { @@ -1442,6 +1464,7 @@ bool gr_opengl_init(std::unique_ptr&& graphicsOps) mprintf((" Layered viewport support : %s\n", GLAD_GL_ARB_shader_viewport_layer_array ? NOX("YES") : NOX("NO"))); mprintf((" S3TC texture support : %s\n", GLAD_GL_EXT_texture_compression_s3tc ? NOX("YES") : NOX("NO"))); mprintf((" BPTC texture support : %s\n", GLAD_GL_ARB_texture_compression_bptc ? NOX("YES") : NOX("NO"))); + mprintf((" Clip control support : %s\n", GLAD_GL_ARB_clip_control ? NOX("YES") : NOX("NO"))); #ifdef USE_OPENGL_ES GLint maxBuffers; diff --git a/code/graphics/opengl/gropengldraw.cpp b/code/graphics/opengl/gropengldraw.cpp index 774fb956a59..5e91c2f3f27 100644 --- a/code/graphics/opengl/gropengldraw.cpp +++ b/code/graphics/opengl/gropengldraw.cpp @@ -290,12 +290,12 @@ void opengl_setup_scene_textures() glTexImage2D(GL_TEXTURE_2D, 0, - GL_DEPTH24_STENCIL8, + Gr_reversed_depth_active ? GL_DEPTH32F_STENCIL8 : GL_DEPTH24_STENCIL8, Scene_texture_width, Scene_texture_height, 0, - GL_DEPTH_COMPONENT, - GL_FLOAT, + Gr_reversed_depth_active ? GL_DEPTH_STENCIL : GL_DEPTH_COMPONENT, + Gr_reversed_depth_active ? GL_FLOAT_32_UNSIGNED_INT_24_8_REV : GL_FLOAT, nullptr); opengl_set_object_label(GL_TEXTURE, Cockpit_depth_texture, "Cockpit depth texture"); @@ -319,12 +319,12 @@ void opengl_setup_scene_textures() glTexImage2D(GL_TEXTURE_2D, 0, - GL_DEPTH24_STENCIL8, + Gr_reversed_depth_active ? GL_DEPTH32F_STENCIL8 : GL_DEPTH24_STENCIL8, Scene_texture_width, Scene_texture_height, 0, - GL_DEPTH_COMPONENT, - GL_FLOAT, + Gr_reversed_depth_active ? GL_DEPTH_STENCIL : GL_DEPTH_COMPONENT, + Gr_reversed_depth_active ? GL_FLOAT_32_UNSIGNED_INT_24_8_REV : GL_FLOAT, nullptr); opengl_set_object_label(GL_TEXTURE, Scene_depth_texture, "Scene depth texture"); glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, Scene_depth_texture, 0); @@ -513,7 +513,7 @@ void opengl_setup_scene_textures() glTexImage2DMultisample(GL_TEXTURE_2D_MULTISAMPLE, Cmdline_msaa_enabled, - GL_DEPTH24_STENCIL8, + Gr_reversed_depth_active ? GL_DEPTH32F_STENCIL8 : GL_DEPTH24_STENCIL8, Scene_texture_width, Scene_texture_height, GL_TRUE); @@ -569,12 +569,12 @@ void opengl_setup_scene_textures() glTexImage2D(GL_TEXTURE_2D, 0, - GL_DEPTH24_STENCIL8, + Gr_reversed_depth_active ? GL_DEPTH32F_STENCIL8 : GL_DEPTH24_STENCIL8, gr_screen.max_w, gr_screen.max_h, 0, GL_DEPTH_STENCIL, - GL_UNSIGNED_INT_24_8, + Gr_reversed_depth_active ? GL_FLOAT_32_UNSIGNED_INT_24_8_REV : GL_UNSIGNED_INT_24_8, nullptr); opengl_set_object_label(GL_TEXTURE, Back_depth_texture, "Backbuffer depth texture"); diff --git a/code/graphics/opengl/gropenglstate.cpp b/code/graphics/opengl/gropenglstate.cpp index 824bc09b365..b6c49762098 100644 --- a/code/graphics/opengl/gropenglstate.cpp +++ b/code/graphics/opengl/gropenglstate.cpp @@ -512,7 +512,7 @@ void opengl_state::SetZbufferType(gr_zbuffer_type zt) break; case ZBUFFER_TYPE_READ: - GL_state.DepthFunc(GL_LESS); + GL_state.DepthFunc(Gr_reversed_depth_active ? GL_GREATER : GL_LESS); GL_state.DepthMask(GL_FALSE); break; @@ -522,7 +522,7 @@ void opengl_state::SetZbufferType(gr_zbuffer_type zt) break; case ZBUFFER_TYPE_FULL: - GL_state.DepthFunc(GL_LESS); + GL_state.DepthFunc(Gr_reversed_depth_active ? GL_GREATER : GL_LESS); GL_state.DepthMask(GL_TRUE); break; diff --git a/code/graphics/opengl/gropengltexture.cpp b/code/graphics/opengl/gropengltexture.cpp index 9375b353468..35b10827b93 100644 --- a/code/graphics/opengl/gropengltexture.cpp +++ b/code/graphics/opengl/gropengltexture.cpp @@ -1952,7 +1952,10 @@ int opengl_make_render_target( int handle, int *w, int *h, int *bpp, int *mm_lvl if (flags & BMP_FLAG_RENDER_TARGET_DEPTH_ATTACHMENT) { glGenRenderbuffers(1, &new_fbo->renderbuffer_id); glBindRenderbuffer(GL_RENDERBUFFER, new_fbo->renderbuffer_id); - glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, *w, *h); + glRenderbufferStorage(GL_RENDERBUFFER, + Gr_reversed_depth_active ? GL_DEPTH32F_STENCIL8 : GL_DEPTH24_STENCIL8, + *w, + *h); glBindRenderbuffer(GL_RENDERBUFFER, 0); } diff --git a/code/graphics/opengl/gropengltnl.cpp b/code/graphics/opengl/gropengltnl.cpp index 7061314fb85..4ecae73a27c 100644 --- a/code/graphics/opengl/gropengltnl.cpp +++ b/code/graphics/opengl/gropengltnl.cpp @@ -476,6 +476,7 @@ static bool opengl_init_shadow_framebuffer(int size) glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_COMPARE_MODE, GL_COMPARE_REF_TO_TEXTURE); + glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_COMPARE_FUNC, Gr_reversed_depth_active ? GL_GEQUAL : GL_LEQUAL); glTexImage3D(GL_TEXTURE_2D_ARRAY, 0, GL_DEPTH_COMPONENT24, size, size, Num_shadow_cascades + Num_cockpit_shadow_cascades, 0, GL_DEPTH_COMPONENT, GL_FLOAT, nullptr); glFramebufferTexture(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, Shadow_map_depth_texture, 0); diff --git a/code/graphics/shader_preprocess.cpp b/code/graphics/shader_preprocess.cpp index b86e33297c5..1f071e8cee5 100644 --- a/code/graphics/shader_preprocess.cpp +++ b/code/graphics/shader_preprocess.cpp @@ -154,6 +154,9 @@ SCP_string shader_preprocess_defines(const SCP_string& filename, const SCP_strin //In any shader, define GLOBAL_FAR_Z output << "#define GLOBAL_FAR_Z " << std::fixed << std::setprecision(2) << Max_draw_distance << std::defaultfloat << '\n'; + if (Gr_reversed_depth_active) { + output << "#define REVERSED_Z\n"; + } const char* PREDEFINE_STRING = "#predefine"; const char* PREREPLACE_STRING = "#prereplace"; diff --git a/code/graphics/shadows.cpp b/code/graphics/shadows.cpp index 34ff36fb590..eedfb9714ed 100644 --- a/code/graphics/shadows.cpp +++ b/code/graphics/shadows.cpp @@ -305,8 +305,8 @@ void shadows_construct_light_proj(light_frustum_info *shadow_data) shadow_data->proj_matrix.a1d[13] = -(shadow_data->max.xyz.y + shadow_data->min.xyz.y) / ( shadow_data->max.xyz.y - shadow_data->min.xyz.y ); shadow_data->proj_matrix.a1d[15] = 1.0f; - if (gr_screen.mode == GraphicsAPI::Vulkan) { - // Vulkan uses [0, 1] depth range + if (gr_screen.mode == GraphicsAPI::Vulkan || Gr_reversed_depth_active) { + // Vulkan uses [0, 1] depth range, and so does OpenGL with reversed depth shadow_data->proj_matrix.a1d[10] = -1.0f / ( shadow_data->max.xyz.z - shadow_data->min.xyz.z ); shadow_data->proj_matrix.a1d[14] = -shadow_data->min.xyz.z / ( shadow_data->max.xyz.z - shadow_data->min.xyz.z ); } else { @@ -314,6 +314,12 @@ void shadows_construct_light_proj(light_frustum_info *shadow_data) shadow_data->proj_matrix.a1d[10] = -2.0f / ( shadow_data->max.xyz.z - shadow_data->min.xyz.z ); shadow_data->proj_matrix.a1d[14] = -(shadow_data->max.xyz.z + shadow_data->min.xyz.z) / ( shadow_data->max.xyz.z - shadow_data->min.xyz.z ); } + + // shadow maps share the depth test with everything else, so they are reversed as well + if (Gr_reversed_depth_active) { + shadow_data->proj_matrix.a1d[10] = -shadow_data->proj_matrix.a1d[10]; + shadow_data->proj_matrix.a1d[14] = 1.0f - shadow_data->proj_matrix.a1d[14]; + } } void shadows_debug_show_frustum(matrix* orient, vec3d *pos, float fov, float aspect, float z_near, float z_far) diff --git a/code/graphics/vulkan/VulkanConvert.cpp b/code/graphics/vulkan/VulkanConvert.cpp index a5cf3713dad..9889258afbf 100644 --- a/code/graphics/vulkan/VulkanConvert.cpp +++ b/code/graphics/vulkan/VulkanConvert.cpp @@ -47,7 +47,7 @@ void convertDepthMode(gr_zbuffer_type type, vk::CompareOp& compareOp, bool& writ writeEnable = false; break; case ZBUFFER_TYPE_READ: - compareOp = vk::CompareOp::eLess; + compareOp = Gr_reversed_depth_active ? vk::CompareOp::eGreater : vk::CompareOp::eLess; writeEnable = false; break; case ZBUFFER_TYPE_WRITE: @@ -55,7 +55,7 @@ void convertDepthMode(gr_zbuffer_type type, vk::CompareOp& compareOp, bool& writ writeEnable = true; break; case ZBUFFER_TYPE_FULL: - compareOp = vk::CompareOp::eLess; + compareOp = Gr_reversed_depth_active ? vk::CompareOp::eGreater : vk::CompareOp::eLess; writeEnable = true; break; default: diff --git a/code/graphics/vulkan/VulkanDeferred.cpp b/code/graphics/vulkan/VulkanDeferred.cpp index ef97b04c440..58d33e59422 100644 --- a/code/graphics/vulkan/VulkanDeferred.cpp +++ b/code/graphics/vulkan/VulkanDeferred.cpp @@ -175,7 +175,8 @@ void vulkan_deferred_lighting_begin(bool clearNonColorBufs) clearValues[VulkanDeferredGBuffer::GBUF_ATT_POSITION].color.setFloat32({0.0f, 0.0f, 0.0f, 0.0f}); clearValues[VulkanDeferredGBuffer::GBUF_ATT_NORMAL].color.setFloat32({0.0f, 0.0f, 0.0f, 0.0f}); clearValues[VulkanDeferredGBuffer::GBUF_ATT_SPECULAR].color.setFloat32({0.0f, 0.0f, 0.0f, 0.0f}); - clearValues[VulkanDeferredGBuffer::MSAA_COLOR_ATTACHMENT_COUNT].depthStencil = vk::ClearDepthStencilValue(1.0f, 0); + clearValues[VulkanDeferredGBuffer::MSAA_COLOR_ATTACHMENT_COUNT].depthStencil = + vk::ClearDepthStencilValue(gr_far_depth(), 0); rpBegin.clearValueCount = static_cast(clearValues.size()); rpBegin.pClearValues = clearValues.data(); cmd.beginRenderPass(rpBegin, vk::SubpassContents::eInline); @@ -231,7 +232,8 @@ void vulkan_deferred_lighting_begin(bool clearNonColorBufs) rpBegin.renderArea.offset = vk::Offset2D(0, 0); rpBegin.renderArea.extent = extent; std::array clearValues{}; - clearValues[VulkanDeferredGBuffer::GBUF_COLOR_ATTACHMENT_COUNT].depthStencil = vk::ClearDepthStencilValue(1.0f, 0); + clearValues[VulkanDeferredGBuffer::GBUF_COLOR_ATTACHMENT_COUNT].depthStencil = + vk::ClearDepthStencilValue(gr_far_depth(), 0); rpBegin.clearValueCount = static_cast(clearValues.size()); rpBegin.pClearValues = clearValues.data(); cmd.beginRenderPass(rpBegin, vk::SubpassContents::eInline); @@ -314,7 +316,8 @@ void vulkan_deferred_lighting_msaa() rpBegin.renderArea.extent = extent; // 6 attachments: 5 color + depth. loadOp=eDontCare for all (fully overwritten). std::array clearValues{}; - clearValues[VulkanDeferredGBuffer::MSAA_COLOR_ATTACHMENT_COUNT].depthStencil = vk::ClearDepthStencilValue(1.0f, 0); + clearValues[VulkanDeferredGBuffer::MSAA_COLOR_ATTACHMENT_COUNT].depthStencil = + vk::ClearDepthStencilValue(gr_far_depth(), 0); rpBegin.clearValueCount = static_cast(clearValues.size()); rpBegin.pClearValues = clearValues.data(); cmd.beginRenderPass(rpBegin, vk::SubpassContents::eInline); @@ -499,7 +502,7 @@ void vulkan_deferred_lighting_msaa() rpBegin.renderArea.offset = vk::Offset2D(0, 0); rpBegin.renderArea.extent = extent; std::array clearValues{}; - clearValues[6].depthStencil = vk::ClearDepthStencilValue(1.0f, 0); + clearValues[6].depthStencil = vk::ClearDepthStencilValue(gr_far_depth(), 0); rpBegin.clearValueCount = static_cast(clearValues.size()); rpBegin.pClearValues = clearValues.data(); cmd.beginRenderPass(rpBegin, vk::SubpassContents::eInline); @@ -612,7 +615,7 @@ void vulkan_deferred_lighting_finish() rpBegin.renderArea.extent = extent; std::array clearValues; clearValues[0].color.setFloat32({0.0f, 0.0f, 0.0f, 1.0f}); - clearValues[1].depthStencil = vk::ClearDepthStencilValue(1.0f, 0); + clearValues[1].depthStencil = vk::ClearDepthStencilValue(gr_far_depth(), 0); rpBegin.clearValueCount = static_cast(clearValues.size()); rpBegin.pClearValues = clearValues.data(); cmd.beginRenderPass(rpBegin, vk::SubpassContents::eInline); @@ -708,7 +711,7 @@ void vulkan_shadow_map_start(matrix4* shadow_view_matrix, const matrix* light_ma rpBegin.renderArea.extent = vk::Extent2D(static_cast(shadowSize), static_cast(shadowSize)); vk::ClearValue clearValue; - clearValue.depthStencil = vk::ClearDepthStencilValue(1.0f, 0); + clearValue.depthStencil = vk::ClearDepthStencilValue(gr_far_depth(), 0); rpBegin.clearValueCount = 1; rpBegin.pClearValues = &clearValue; @@ -783,7 +786,8 @@ void vulkan_shadow_map_end() rpBegin.renderArea.offset = vk::Offset2D(0, 0); rpBegin.renderArea.extent = extent; std::array clearValues{}; - clearValues[VulkanDeferredGBuffer::MSAA_COLOR_ATTACHMENT_COUNT].depthStencil = vk::ClearDepthStencilValue(1.0f, 0); + clearValues[VulkanDeferredGBuffer::MSAA_COLOR_ATTACHMENT_COUNT].depthStencil = + vk::ClearDepthStencilValue(gr_far_depth(), 0); rpBegin.clearValueCount = static_cast(clearValues.size()); rpBegin.pClearValues = clearValues.data(); cmd.beginRenderPass(rpBegin, vk::SubpassContents::eInline); @@ -823,7 +827,7 @@ void vulkan_shadow_map_end() rpBegin.renderArea.extent = extent; std::array clearValues{}; - clearValues[6].depthStencil = vk::ClearDepthStencilValue(1.0f, 0); + clearValues[6].depthStencil = vk::ClearDepthStencilValue(gr_far_depth(), 0); rpBegin.clearValueCount = static_cast(clearValues.size()); rpBegin.pClearValues = clearValues.data(); @@ -908,7 +912,7 @@ void vulkan_start_decal_pass() rpBegin.renderArea.extent = extent; std::array clearValues{}; - clearValues[6].depthStencil = vk::ClearDepthStencilValue(1.0f, 0); + clearValues[6].depthStencil = vk::ClearDepthStencilValue(gr_far_depth(), 0); rpBegin.clearValueCount = static_cast(clearValues.size()); rpBegin.pClearValues = clearValues.data(); diff --git a/code/graphics/vulkan/VulkanDraw.cpp b/code/graphics/vulkan/VulkanDraw.cpp index bb38156ca29..2fd7dc26ccb 100644 --- a/code/graphics/vulkan/VulkanDraw.cpp +++ b/code/graphics/vulkan/VulkanDraw.cpp @@ -415,7 +415,7 @@ void VulkanDrawManager::zbufferClear(int mode) // Clear depth buffer (clamped: gr_screen can exceed a smaller render target) vk::ClearAttachment clearAttachment; clearAttachment.aspectMask = vk::ImageAspectFlagBits::eDepth; - clearAttachment.clearValue.depthStencil.depth = 1.0f; + clearAttachment.clearValue.depthStencil.depth = gr_far_depth(); clearAttachment.clearValue.depthStencil.stencil = 0; vk::ClearRect clearRect; @@ -467,7 +467,7 @@ void VulkanDrawManager::stencilClear() // Clear stencil buffer (clamped: gr_screen can exceed a smaller render target) vk::ClearAttachment clearAttachment; clearAttachment.aspectMask = vk::ImageAspectFlagBits::eStencil; - clearAttachment.clearValue.depthStencil.depth = 1.0f; + clearAttachment.clearValue.depthStencil.depth = gr_far_depth(); clearAttachment.clearValue.depthStencil.stencil = 0; vk::ClearRect clearRect; @@ -748,7 +748,7 @@ void VulkanDrawManager::renderShadowDraw(gr_buffer_handle ubo_handle, size_t ubo } stateTracker->bindPipeline(pipeline, pipelineManager->getPipelineLayout()); - stateTracker->setDepthBias(-1024.0f, 0.0f); + stateTracker->setDepthBias(Gr_reversed_depth_active ? 1024.0f : -1024.0f, 0.0f); // Bind descriptor sets: shadowMapData (per-draw) and shadowCascadeParams // (bound separately, per-frame, via shadow_cascade_params_bind) both live in @@ -1481,7 +1481,8 @@ bool VulkanDrawManager::applyMaterial(material* mat, primitive_type prim_type, v } // Set depth bias if needed - stateTracker->setDepthBias(static_cast(mat->get_depth_bias()), 0.0f); + stateTracker->setDepthBias((Gr_reversed_depth_active ? -1.0f : 1.0f) * static_cast(mat->get_depth_bias()), + 0.0f); return true; } diff --git a/code/graphics/vulkan/VulkanDrawAPI.cpp b/code/graphics/vulkan/VulkanDrawAPI.cpp index 80ad91e1e0c..8a43510a089 100644 --- a/code/graphics/vulkan/VulkanDrawAPI.cpp +++ b/code/graphics/vulkan/VulkanDrawAPI.cpp @@ -231,7 +231,7 @@ void vulkan_scene_texture_begin() clearAttachments[0].clearValue.color.setFloat32({0.0f, 0.0f, 0.0f, 1.0f}); clearAttachments[1].aspectMask = vk::ImageAspectFlagBits::eDepth; - clearAttachments[1].clearValue.depthStencil = vk::ClearDepthStencilValue(1.0f, 0); + clearAttachments[1].clearValue.depthStencil = vk::ClearDepthStencilValue(gr_far_depth(), 0); vk::ClearRect clearRect; clearRect.rect.offset = vk::Offset2D(0, 0); diff --git a/code/graphics/vulkan/VulkanPostProcessingShadow.cpp b/code/graphics/vulkan/VulkanPostProcessingShadow.cpp index 4085dacb5ad..af64d29256d 100644 --- a/code/graphics/vulkan/VulkanPostProcessingShadow.cpp +++ b/code/graphics/vulkan/VulkanPostProcessingShadow.cpp @@ -176,7 +176,7 @@ bool VulkanShadowMap::init(PostProcessContext& ctx) samplerInfo.addressModeV = vk::SamplerAddressMode::eClampToEdge; samplerInfo.addressModeW = vk::SamplerAddressMode::eClampToEdge; samplerInfo.compareEnable = VK_TRUE; - samplerInfo.compareOp = vk::CompareOp::eLessOrEqual; + samplerInfo.compareOp = Gr_reversed_depth_active ? vk::CompareOp::eGreaterOrEqual : vk::CompareOp::eLessOrEqual; samplerInfo.borderColor = vk::BorderColor::eFloatOpaqueWhite; try { diff --git a/code/graphics/vulkan/VulkanRenderer.cpp b/code/graphics/vulkan/VulkanRenderer.cpp index 55a24154dfc..ce2134fa458 100644 --- a/code/graphics/vulkan/VulkanRenderer.cpp +++ b/code/graphics/vulkan/VulkanRenderer.cpp @@ -240,6 +240,9 @@ vk::Format VulkanRenderer::findDepthFormat() for (auto format : candidates) { auto props = m_physicalDevice.getFormatProperties(format); if (props.optimalTilingFeatures & vk::FormatFeatureFlagBits::eDepthStencilAttachment) { + if (Gr_reversed_depth_active && format == vk::Format::eD24UnormS8Uint) { + mprintf((" Reversed depth on a 24-bit depth buffer gains little precision\n")); + } return format; } } diff --git a/code/graphics/vulkan/VulkanRendererLoop.cpp b/code/graphics/vulkan/VulkanRendererLoop.cpp index 673839fc669..eb5d342aae6 100644 --- a/code/graphics/vulkan/VulkanRendererLoop.cpp +++ b/code/graphics/vulkan/VulkanRendererLoop.cpp @@ -148,7 +148,7 @@ void VulkanRenderer::setupFrame() // Begin the composition render pass (clears color to black, depth to far plane) std::array clearValues; clearValues[0].color.setFloat32({0.0f, 0.0f, 0.0f, 1.0f}); - clearValues[1].depthStencil = vk::ClearDepthStencilValue(1.0f, 0); + clearValues[1].depthStencil = vk::ClearDepthStencilValue(gr_far_depth(), 0); PassBeginDesc pass; pass.renderPass = m_renderPass.get(); @@ -283,7 +283,8 @@ void VulkanRenderer::beginSceneRendering() clearValues[VulkanDeferredGBuffer::GBUF_ATT_SPECULAR].color.setFloat32({0.0f, 0.0f, 0.0f, 0.0f}); clearValues[VulkanDeferredGBuffer::GBUF_ATT_EMISSIVE].color.setFloat32({0.0f, 0.0f, 0.0f, 0.0f}); clearValues[VulkanDeferredGBuffer::GBUF_ATT_COMPOSITE].color.setFloat32({0.0f, 0.0f, 0.0f, 0.0f}); - clearValues[VulkanDeferredGBuffer::GBUF_COLOR_ATTACHMENT_COUNT].depthStencil = vk::ClearDepthStencilValue(1.0f, 0); + clearValues[VulkanDeferredGBuffer::GBUF_COLOR_ATTACHMENT_COUNT].depthStencil = + vk::ClearDepthStencilValue(gr_far_depth(), 0); pass.renderPass = m_postProcessor->deferred().renderPass(); pass.framebuffer = m_postProcessor->deferred().framebuffer(); @@ -293,7 +294,7 @@ void VulkanRenderer::beginSceneRendering() } else { std::array clearValues; clearValues[0].color.setFloat32({0.0f, 0.0f, 0.0f, 1.0f}); - clearValues[1].depthStencil = vk::ClearDepthStencilValue(1.0f, 0); + clearValues[1].depthStencil = vk::ClearDepthStencilValue(gr_far_depth(), 0); pass.renderPass = m_postProcessor->getSceneRenderPass(); pass.framebuffer = m_postProcessor->getSceneFramebuffer(); @@ -343,7 +344,7 @@ void VulkanRenderer::endSceneRendering() // Vulkan orientation); restored to flipped for HUD rendering below. std::array clearValues; clearValues[0].color.setFloat32({0.0f, 0.0f, 0.0f, 1.0f}); - clearValues[1].depthStencil = vk::ClearDepthStencilValue(1.0f, 0); + clearValues[1].depthStencil = vk::ClearDepthStencilValue(gr_far_depth(), 0); PassBeginDesc pass; pass.renderPass = m_renderPassLoad.get(); @@ -401,7 +402,8 @@ void VulkanRenderer::resumeScenePassAfterCopy() if (m_useGbufRenderPass) { // Clear values ignored for eLoad but array must cover all attachments std::array clearValues{}; - clearValues[VulkanDeferredGBuffer::GBUF_COLOR_ATTACHMENT_COUNT].depthStencil = vk::ClearDepthStencilValue(1.0f, 0); + clearValues[VulkanDeferredGBuffer::GBUF_COLOR_ATTACHMENT_COUNT].depthStencil = + vk::ClearDepthStencilValue(gr_far_depth(), 0); pass.renderPass = m_postProcessor->deferred().renderPassLoad(); pass.framebuffer = m_postProcessor->deferred().framebuffer(); @@ -411,7 +413,7 @@ void VulkanRenderer::resumeScenePassAfterCopy() } else { std::array clearValues; clearValues[0].color.setFloat32({0.0f, 0.0f, 0.0f, 1.0f}); - clearValues[1].depthStencil = vk::ClearDepthStencilValue(1.0f, 0); + clearValues[1].depthStencil = vk::ClearDepthStencilValue(gr_far_depth(), 0); pass.renderPass = m_postProcessor->getSceneRenderPassLoad(); pass.framebuffer = m_postProcessor->getSceneFramebuffer(); @@ -540,7 +542,7 @@ void VulkanRenderer::beginRenderTarget(tcache_slot_vulkan* ts, int face) // depth at index 1 when this target has a depth attachment. std::array clearValues; clearValues[0].color = m_stateTracker->getClearColor(); - clearValues[1].depthStencil = vk::ClearDepthStencilValue(1.0f, 0); + clearValues[1].depthStencil = vk::ClearDepthStencilValue(gr_far_depth(), 0); const bool hasDepth = static_cast(ts->depthImage); PassBeginDesc pass; @@ -597,7 +599,7 @@ void VulkanRenderer::resumeSwapChainPass() { std::array clearValues; clearValues[0].color.setFloat32({0.0f, 0.0f, 0.0f, 1.0f}); - clearValues[1].depthStencil = vk::ClearDepthStencilValue(1.0f, 0); + clearValues[1].depthStencil = vk::ClearDepthStencilValue(gr_far_depth(), 0); PassBeginDesc pass; pass.renderPass = m_renderPassLoad.get(); @@ -611,7 +613,7 @@ void VulkanRenderer::restartCompositionPass() { std::array clearValues; clearValues[0].color.setFloat32({0.0f, 0.0f, 0.0f, 1.0f}); - clearValues[1].depthStencil = vk::ClearDepthStencilValue(1.0f, 0); + clearValues[1].depthStencil = vk::ClearDepthStencilValue(gr_far_depth(), 0); PassBeginDesc pass; pass.renderPass = m_renderPass.get(); @@ -638,7 +640,7 @@ void VulkanRenderer::resumeRenderTargetPass(tcache_slot_vulkan* ts) // index layout matches the pass attachments. std::array clearValues; clearValues[0].color = m_stateTracker->getClearColor(); - clearValues[1].depthStencil = vk::ClearDepthStencilValue(1.0f, 0); + clearValues[1].depthStencil = vk::ClearDepthStencilValue(gr_far_depth(), 0); const bool hasDepth = static_cast(ts->depthImage); PassBeginDesc pass; diff --git a/code/graphics/vulkan/VulkanRendererSetup.cpp b/code/graphics/vulkan/VulkanRendererSetup.cpp index d338d55ee84..7b3a9f4e7cd 100644 --- a/code/graphics/vulkan/VulkanRendererSetup.cpp +++ b/code/graphics/vulkan/VulkanRendererSetup.cpp @@ -353,6 +353,12 @@ bool VulkanRenderer::initialize() // Load the RenderDoc API if available before doing anything with OpenGL renderdoc::loadApi(); + // Vulkan's depth range is already [0, 1], so reversed depth needs nothing from the device + Gr_reversed_depth_active = Gr_enable_reversed_depth; + if (Gr_reversed_depth_active) { + mprintf((" Using reversed depth\n")); + } + // Capture the render resolution BEFORE the window exists. Creating it can // queue an SDL resize event, and osapi's handler answers those with an // unconditional gr_screen_resize() to the *window* size -- which would diff --git a/code/graphics/vulkan/VulkanState.cpp b/code/graphics/vulkan/VulkanState.cpp index a7acb744c1d..ee10353e3a1 100644 --- a/code/graphics/vulkan/VulkanState.cpp +++ b/code/graphics/vulkan/VulkanState.cpp @@ -349,11 +349,13 @@ void vulkan_zbias(int bias) auto* drawManager = getDrawManager(); if (bias) { + // with reversed depth, closer means larger depth, so the bias changes sign + const float sign = Gr_reversed_depth_active ? -1.0f : 1.0f; drawManager->setDepthBiasEnabled(true); if (bias < 0) { - stateTracker->setDepthBias(1.0f, static_cast(-bias)); + stateTracker->setDepthBias(sign * 1.0f, sign * static_cast(-bias)); } else { - stateTracker->setDepthBias(0.0f, static_cast(-bias)); + stateTracker->setDepthBias(0.0f, sign * static_cast(-bias)); } } else { drawManager->setDepthBiasEnabled(false); diff --git a/lib/opengl/gl/glad/include/glad/glad.h b/lib/opengl/gl/glad/include/glad/glad.h index 300da24fe01..bd3de6a552a 100644 --- a/lib/opengl/gl/glad/include/glad/glad.h +++ b/lib/opengl/gl/glad/include/glad/glad.h @@ -1,6 +1,6 @@ /* - OpenGL loader generated by glad 0.1.36 on Thu Jun 11 03:03:22 2026. + OpenGL loader generated by glad 0.1.36 on Thu Oct 15 06:51:31 2026. Language/Generator: C/C++ Specification: gl @@ -8,6 +8,7 @@ Profile: core Extensions: GL_ARB_buffer_storage, + GL_ARB_clip_control, GL_ARB_debug_output, GL_ARB_draw_buffers_blend, GL_ARB_get_program_binary, @@ -27,9 +28,9 @@ Reproducible: False Commandline: - --profile="core" --api="gl=4.3" --generator="c" --spec="gl" --no-loader --extensions="GL_ARB_buffer_storage,GL_ARB_debug_output,GL_ARB_draw_buffers_blend,GL_ARB_get_program_binary,GL_ARB_gpu_shader5,GL_ARB_shader_texture_lod,GL_ARB_shader_viewport_layer_array,GL_ARB_texture_compression_bptc,GL_ARB_texture_storage,GL_ARB_timer_query,GL_ARB_vertex_attrib_binding,GL_EXT_texture_compression_s3tc,GL_EXT_texture_filter_anisotropic,GL_KHR_debug" + --profile="core" --api="gl=4.3" --generator="c" --spec="gl" --no-loader --extensions="GL_ARB_buffer_storage,GL_ARB_clip_control,GL_ARB_debug_output,GL_ARB_draw_buffers_blend,GL_ARB_get_program_binary,GL_ARB_gpu_shader5,GL_ARB_shader_texture_lod,GL_ARB_shader_viewport_layer_array,GL_ARB_texture_compression_bptc,GL_ARB_texture_storage,GL_ARB_timer_query,GL_ARB_vertex_attrib_binding,GL_EXT_texture_compression_s3tc,GL_EXT_texture_filter_anisotropic,GL_KHR_debug" Online: - https://glad.dav1d.de/#profile=core&language=c&specification=gl&api=gl%3D4.3&extensions=GL_ARB_buffer_storage&extensions=GL_ARB_debug_output&extensions=GL_ARB_draw_buffers_blend&extensions=GL_ARB_get_program_binary&extensions=GL_ARB_gpu_shader5&extensions=GL_ARB_shader_texture_lod&extensions=GL_ARB_shader_viewport_layer_array&extensions=GL_ARB_texture_compression_bptc&extensions=GL_ARB_texture_storage&extensions=GL_ARB_timer_query&extensions=GL_ARB_vertex_attrib_binding&extensions=GL_EXT_texture_compression_s3tc&extensions=GL_EXT_texture_filter_anisotropic&extensions=GL_KHR_debug + https://glad.dav1d.de/#profile=core&language=c&specification=gl&api=gl%3D4.3&extensions=GL_ARB_buffer_storage&extensions=GL_ARB_clip_control&extensions=GL_ARB_debug_output&extensions=GL_ARB_draw_buffers_blend&extensions=GL_ARB_get_program_binary&extensions=GL_ARB_gpu_shader5&extensions=GL_ARB_shader_texture_lod&extensions=GL_ARB_shader_viewport_layer_array&extensions=GL_ARB_texture_compression_bptc&extensions=GL_ARB_texture_storage&extensions=GL_ARB_timer_query&extensions=GL_ARB_vertex_attrib_binding&extensions=GL_EXT_texture_compression_s3tc&extensions=GL_EXT_texture_filter_anisotropic&extensions=GL_KHR_debug */ @@ -3211,6 +3212,10 @@ GLAPI PFNGLGETPOINTERVPROC glad_glGetPointerv; #define GL_CLIENT_MAPPED_BUFFER_BARRIER_BIT 0x00004000 #define GL_BUFFER_IMMUTABLE_STORAGE 0x821F #define GL_BUFFER_STORAGE_FLAGS 0x8220 +#define GL_NEGATIVE_ONE_TO_ONE 0x935E +#define GL_ZERO_TO_ONE 0x935F +#define GL_CLIP_ORIGIN 0x935C +#define GL_CLIP_DEPTH_MODE 0x935D #define GL_DEBUG_OUTPUT_SYNCHRONOUS_ARB 0x8242 #define GL_DEBUG_NEXT_LOGGED_MESSAGE_LENGTH_ARB 0x8243 #define GL_DEBUG_CALLBACK_FUNCTION_ARB 0x8244 @@ -3290,6 +3295,13 @@ typedef void (APIENTRYP PFNGLBUFFERSTORAGEPROC)(GLenum target, GLsizeiptr size, GLAPI PFNGLBUFFERSTORAGEPROC glad_glBufferStorage; #define glBufferStorage glad_glBufferStorage #endif +#ifndef GL_ARB_clip_control +#define GL_ARB_clip_control 1 +GLAPI int GLAD_GL_ARB_clip_control; +typedef void (APIENTRYP PFNGLCLIPCONTROLPROC)(GLenum origin, GLenum depth); +GLAPI PFNGLCLIPCONTROLPROC glad_glClipControl; +#define glClipControl glad_glClipControl +#endif #ifndef GL_ARB_debug_output #define GL_ARB_debug_output 1 GLAPI int GLAD_GL_ARB_debug_output; diff --git a/lib/opengl/gl/glad/src/glad.c b/lib/opengl/gl/glad/src/glad.c index 0986c04d66d..c3dbadaef3f 100644 --- a/lib/opengl/gl/glad/src/glad.c +++ b/lib/opengl/gl/glad/src/glad.c @@ -1,6 +1,6 @@ /* - OpenGL loader generated by glad 0.1.36 on Thu Jun 11 03:03:22 2026. + OpenGL loader generated by glad 0.1.36 on Thu Oct 15 06:51:31 2026. Language/Generator: C/C++ Specification: gl @@ -8,6 +8,7 @@ Profile: core Extensions: GL_ARB_buffer_storage, + GL_ARB_clip_control, GL_ARB_debug_output, GL_ARB_draw_buffers_blend, GL_ARB_get_program_binary, @@ -27,9 +28,9 @@ Reproducible: False Commandline: - --profile="core" --api="gl=4.3" --generator="c" --spec="gl" --no-loader --extensions="GL_ARB_buffer_storage,GL_ARB_debug_output,GL_ARB_draw_buffers_blend,GL_ARB_get_program_binary,GL_ARB_gpu_shader5,GL_ARB_shader_texture_lod,GL_ARB_shader_viewport_layer_array,GL_ARB_texture_compression_bptc,GL_ARB_texture_storage,GL_ARB_timer_query,GL_ARB_vertex_attrib_binding,GL_EXT_texture_compression_s3tc,GL_EXT_texture_filter_anisotropic,GL_KHR_debug" + --profile="core" --api="gl=4.3" --generator="c" --spec="gl" --no-loader --extensions="GL_ARB_buffer_storage,GL_ARB_clip_control,GL_ARB_debug_output,GL_ARB_draw_buffers_blend,GL_ARB_get_program_binary,GL_ARB_gpu_shader5,GL_ARB_shader_texture_lod,GL_ARB_shader_viewport_layer_array,GL_ARB_texture_compression_bptc,GL_ARB_texture_storage,GL_ARB_timer_query,GL_ARB_vertex_attrib_binding,GL_EXT_texture_compression_s3tc,GL_EXT_texture_filter_anisotropic,GL_KHR_debug" Online: - https://glad.dav1d.de/#profile=core&language=c&specification=gl&api=gl%3D4.3&extensions=GL_ARB_buffer_storage&extensions=GL_ARB_debug_output&extensions=GL_ARB_draw_buffers_blend&extensions=GL_ARB_get_program_binary&extensions=GL_ARB_gpu_shader5&extensions=GL_ARB_shader_texture_lod&extensions=GL_ARB_shader_viewport_layer_array&extensions=GL_ARB_texture_compression_bptc&extensions=GL_ARB_texture_storage&extensions=GL_ARB_timer_query&extensions=GL_ARB_vertex_attrib_binding&extensions=GL_EXT_texture_compression_s3tc&extensions=GL_EXT_texture_filter_anisotropic&extensions=GL_KHR_debug + https://glad.dav1d.de/#profile=core&language=c&specification=gl&api=gl%3D4.3&extensions=GL_ARB_buffer_storage&extensions=GL_ARB_clip_control&extensions=GL_ARB_debug_output&extensions=GL_ARB_draw_buffers_blend&extensions=GL_ARB_get_program_binary&extensions=GL_ARB_gpu_shader5&extensions=GL_ARB_shader_texture_lod&extensions=GL_ARB_shader_viewport_layer_array&extensions=GL_ARB_texture_compression_bptc&extensions=GL_ARB_texture_storage&extensions=GL_ARB_timer_query&extensions=GL_ARB_vertex_attrib_binding&extensions=GL_EXT_texture_compression_s3tc&extensions=GL_EXT_texture_filter_anisotropic&extensions=GL_KHR_debug */ #include @@ -57,7 +58,7 @@ static int get_exts(void) { exts = (const char *)glGetString(GL_EXTENSIONS); #ifdef _GLAD_IS_SOME_NEW_VERSION } else { - int index; + unsigned int index; num_exts_i = 0; glGetIntegerv(GL_NUM_EXTENSIONS, &num_exts_i); @@ -69,7 +70,7 @@ static int get_exts(void) { return 0; } - for(index = 0; index < num_exts_i; index++) { + for(index = 0; index < (unsigned)num_exts_i; index++) { const char *gl_str_tmp = (const char*)glGetStringi(GL_EXTENSIONS, index); size_t len = strlen(gl_str_tmp); @@ -717,6 +718,7 @@ PFNGLVIEWPORTINDEXEDFPROC glad_glViewportIndexedf = NULL; PFNGLVIEWPORTINDEXEDFVPROC glad_glViewportIndexedfv = NULL; PFNGLWAITSYNCPROC glad_glWaitSync = NULL; int GLAD_GL_ARB_buffer_storage = 0; +int GLAD_GL_ARB_clip_control = 0; int GLAD_GL_ARB_debug_output = 0; int GLAD_GL_ARB_draw_buffers_blend = 0; int GLAD_GL_ARB_get_program_binary = 0; @@ -731,6 +733,7 @@ int GLAD_GL_EXT_texture_compression_s3tc = 0; int GLAD_GL_EXT_texture_filter_anisotropic = 0; int GLAD_GL_KHR_debug = 0; PFNGLBUFFERSTORAGEPROC glad_glBufferStorage = NULL; +PFNGLCLIPCONTROLPROC glad_glClipControl = NULL; PFNGLDEBUGMESSAGECONTROLARBPROC glad_glDebugMessageControlARB = NULL; PFNGLDEBUGMESSAGEINSERTARBPROC glad_glDebugMessageInsertARB = NULL; PFNGLDEBUGMESSAGECALLBACKARBPROC glad_glDebugMessageCallbackARB = NULL; @@ -1370,6 +1373,10 @@ static void load_GL_ARB_buffer_storage(GLADloadproc load) { if(!GLAD_GL_ARB_buffer_storage) return; glad_glBufferStorage = (PFNGLBUFFERSTORAGEPROC)load("glBufferStorage"); } +static void load_GL_ARB_clip_control(GLADloadproc load) { + if(!GLAD_GL_ARB_clip_control) return; + glad_glClipControl = (PFNGLCLIPCONTROLPROC)load("glClipControl"); +} static void load_GL_ARB_debug_output(GLADloadproc load) { if(!GLAD_GL_ARB_debug_output) return; glad_glDebugMessageControlARB = (PFNGLDEBUGMESSAGECONTROLARBPROC)load("glDebugMessageControlARB"); @@ -1439,6 +1446,7 @@ static void load_GL_KHR_debug(GLADloadproc load) { static int find_extensionsGL(void) { if (!get_exts()) return 0; GLAD_GL_ARB_buffer_storage = has_ext("GL_ARB_buffer_storage"); + GLAD_GL_ARB_clip_control = has_ext("GL_ARB_clip_control"); GLAD_GL_ARB_debug_output = has_ext("GL_ARB_debug_output"); GLAD_GL_ARB_draw_buffers_blend = has_ext("GL_ARB_draw_buffers_blend"); GLAD_GL_ARB_get_program_binary = has_ext("GL_ARB_get_program_binary"); @@ -1539,6 +1547,7 @@ int gladLoadGLLoader(GLADloadproc load) { if (!find_extensionsGL()) return 0; load_GL_ARB_buffer_storage(load); + load_GL_ARB_clip_control(load); load_GL_ARB_debug_output(load); load_GL_ARB_draw_buffers_blend(load); load_GL_ARB_get_program_binary(load); diff --git a/test/src/graphics/test_reversed_depth.cpp b/test/src/graphics/test_reversed_depth.cpp new file mode 100644 index 00000000000..6dca47b1fb3 --- /dev/null +++ b/test/src/graphics/test_reversed_depth.cpp @@ -0,0 +1,155 @@ +#include "graphics/2d.h" +#include "graphics/matrix.h" +#include "graphics/shader_preprocess.h" +#include "graphics/shadows.h" + +#include + +#include + +// not in a header; defined in shadows.cpp +void shadows_construct_light_proj(light_frustum_info* shadow_data); + +namespace { +float shadow_depth(const light_frustum_info& info, float z) +{ + return info.proj_matrix.a1d[10] * z + info.proj_matrix.a1d[14]; +} +} // namespace + +TEST(ReversedDepth, ShadowProjectionFlipsTheDepthRange) +{ + const auto saved_mode = gr_screen.mode; + gr_screen.mode = GraphicsAPI::Vulkan; + + light_frustum_info info{}; + info.min.xyz = {-50.0f, -50.0f, 10.0f}; + info.max.xyz = {50.0f, 50.0f, 500.0f}; + + Gr_reversed_depth_active = false; + shadows_construct_light_proj(&info); + const light_frustum_info normal = info; + + Gr_reversed_depth_active = true; + shadows_construct_light_proj(&info); + Gr_reversed_depth_active = false; + gr_screen.mode = saved_mode; + + for (float z : {-10.0f, -100.0f, -255.0f, -500.0f}) { + EXPECT_NEAR(shadow_depth(info, z), 1.0f - shadow_depth(normal, z), 1e-5f) << "z " << z; + } + EXPECT_NEAR(shadow_depth(normal, -10.0f), 0.0f, 1e-5f); + EXPECT_NEAR(shadow_depth(info, -10.0f), 1.0f, 1e-5f); +} + +TEST(ReversedDepth, OpenGLShadowProjectionUsesTheReversedRange) +{ + const auto saved_mode = gr_screen.mode; + gr_screen.mode = GraphicsAPI::OpenGL; + + light_frustum_info info{}; + info.min.xyz = {-50.0f, -50.0f, 10.0f}; + info.max.xyz = {50.0f, 50.0f, 500.0f}; + Gr_reversed_depth_active = true; + shadows_construct_light_proj(&info); + Gr_reversed_depth_active = false; + gr_screen.mode = saved_mode; + + EXPECT_NEAR(shadow_depth(info, -10.0f), 1.0f, 1e-5f); + EXPECT_NEAR(shadow_depth(info, -500.0f), 0.0f, 1e-5f); +} + +TEST(ReversedDepth, FarDepthIsTheClearValue) +{ + Gr_reversed_depth_active = false; + EXPECT_EQ(gr_far_depth(), 1.0f); + Gr_reversed_depth_active = true; + EXPECT_EQ(gr_far_depth(), 0.0f); + Gr_reversed_depth_active = false; +} + +namespace { +// window depth of a point at view distance d for the current projection; OpenGL without reversed depth maps [-1, 1] +float window_depth(float d) +{ + const float z = gr_projection_matrix.a1d[10] * -d + gr_projection_matrix.a1d[14]; + const float ndc = z / d; + const bool zero_to_one = gr_screen.mode == GraphicsAPI::Vulkan || Gr_reversed_depth_active; + return zero_to_one ? ndc : ndc * 0.5f + 0.5f; +} + +// sets up a perspective projection with near 1 m and far 1000 km, then restores the engine state +class ReversedDepthProjection : public ::testing::TestWithParam { + protected: + void SetUp() override + { + saved_mode = gr_screen.mode; + saved_viewport = gr_screen.gf_set_viewport; + saved_matrix = gr_projection_matrix; + gr_screen.mode = GetParam(); + gr_screen.gf_set_viewport = [](int, int, int, int) {}; + } + void TearDown() override + { + Gr_reversed_depth_active = false; + gr_screen.mode = saved_mode; + gr_screen.gf_set_viewport = saved_viewport; + gr_projection_matrix = saved_matrix; + } + + GraphicsAPI saved_mode = GraphicsAPI::OpenGL; + decltype(gr_screen.gf_set_viewport) saved_viewport; + matrix4 saved_matrix{}; +}; +} // namespace + +TEST_P(ReversedDepthProjection, NearIsOneAndFarIsZero) +{ + Gr_reversed_depth_active = true; + gr_set_proj_matrix(0.75f, 1.6f, 1.0f, 1e6f); + EXPECT_NEAR(window_depth(1.0f), 1.0f, 1e-5f); + EXPECT_NEAR(window_depth(1e6f), 0.0f, 1e-5f); + float previous = 2.0f; + for (float d : {1.0f, 10.0f, 1e3f, 1e5f, 1e6f}) { + const float depth = window_depth(d); + EXPECT_LT(depth, previous) << "distance " << d; + // the shaders' linear distance for reversed depth gives the distance back + EXPECT_NEAR(1.0f * 1e6f / (1.0f + depth * (1e6f - 1.0f)), d, d * 1e-3f) << "distance " << d; + previous = depth; + } +} + +TEST_P(ReversedDepthProjection, SeparatesSurfacesAtLongRange) +{ + // two surfaces half a meter apart, 20 km away + gr_set_proj_matrix(0.75f, 1.6f, 1.0f, 1e6f); + const auto normal24 = [](float depth) { return std::lround(static_cast(depth) * ((1 << 24) - 1)); }; + EXPECT_EQ(normal24(window_depth(20000.0f)), normal24(window_depth(20000.5f))); + + Gr_reversed_depth_active = true; + gr_set_proj_matrix(0.75f, 1.6f, 1.0f, 1e6f); + EXPECT_NE(window_depth(20000.0f), window_depth(20000.5f)); +} + +TEST_P(ReversedDepthProjection, OrthographicNearIsOneAndFarIsZero) +{ + // gr_end_proj_matrix() sets the 2D orthographic projection with near -1 and far 1 + Gr_reversed_depth_active = true; + gr_end_proj_matrix(); + const auto ortho_depth = [](float z) { return gr_projection_matrix.a1d[10] * z + gr_projection_matrix.a1d[14]; }; + EXPECT_NEAR(ortho_depth(1.0f), 1.0f, 1e-5f); + EXPECT_NEAR(ortho_depth(-1.0f), 0.0f, 1e-5f); +} + +INSTANTIATE_TEST_SUITE_P(BothRenderers, + ReversedDepthProjection, + ::testing::Values(GraphicsAPI::OpenGL, GraphicsAPI::Vulkan)); + +TEST(ReversedDepth, ShadersGetTheDefineOnlyWhenActive) +{ + EXPECT_EQ(shader_preprocess_defines("test.sdr", "").find("#define REVERSED_Z"), SCP_string::npos); + Gr_reversed_depth_active = true; + const auto defines = shader_preprocess_defines("test.sdr", ""); + Gr_reversed_depth_active = false; + EXPECT_NE(defines.find("#define REVERSED_Z"), SCP_string::npos); +} diff --git a/test/src/graphics/vulkan/test_vulkan_depth.cpp b/test/src/graphics/vulkan/test_vulkan_depth.cpp new file mode 100644 index 00000000000..437229e8057 --- /dev/null +++ b/test/src/graphics/vulkan/test_vulkan_depth.cpp @@ -0,0 +1,26 @@ +#include "graphics/2d.h" +#include "graphics/vulkan/VulkanConvert.h" + +#include + +using graphics::vulkan::convertDepthMode; + +TEST(VulkanDepthMode, ReversedDepthComparesWithGreater) +{ + vk::CompareOp op = vk::CompareOp::eAlways; + bool write = false; + + convertDepthMode(ZBUFFER_TYPE_FULL, op, write); + EXPECT_EQ(op, vk::CompareOp::eLess); + convertDepthMode(ZBUFFER_TYPE_READ, op, write); + EXPECT_EQ(op, vk::CompareOp::eLess); + + Gr_reversed_depth_active = true; + convertDepthMode(ZBUFFER_TYPE_FULL, op, write); + const auto full = op; + convertDepthMode(ZBUFFER_TYPE_READ, op, write); + const auto read = op; + Gr_reversed_depth_active = false; + EXPECT_EQ(full, vk::CompareOp::eGreater); + EXPECT_EQ(read, vk::CompareOp::eGreater); +} diff --git a/test/src/source_groups.cmake b/test/src/source_groups.cmake index 1c268dda86e..1917cfa612a 100644 --- a/test/src/source_groups.cmake +++ b/test/src/source_groups.cmake @@ -25,10 +25,12 @@ add_file_folder("Globalincs" add_file_folder("Graphics" graphics/test_font.cpp + graphics/test_reversed_depth.cpp ) if (FSO_BUILD_WITH_VULKAN) add_file_folder("Graphics\\\\Vulkan" + graphics/vulkan/test_vulkan_depth.cpp graphics/vulkan/test_vulkan_raytracing.cpp ) endif()