From dc191052cf301c44b4b624566db78b88a0fc4b24 Mon Sep 17 00:00:00 2001 From: Dustinspace217 Date: Tue, 13 Oct 2026 09:01:54 -0700 Subject: [PATCH 1/2] Add an opt-in reversed depth buffer Distant surfaces flicker against each other because a 24-bit depth buffer with a 1 m near plane and a 1000 km far plane has almost no precision left at long range. With the new Reversed Depth option (or the -reversed_depth flag, which also works where in-game options are off), the near plane maps to depth 1 and the far plane to 0, closer surfaces pass with GREATER, and the scene depth buffers are 32-bit float, which keeps precision nearly constant out to the far plane. The reversed perspective terms are written out directly, because flipping the normal ones loses enough float precision to move the far plane by about 5%. Vulkan's depth range is already [0, 1]. OpenGL needs glClipControl to use [0, 1], so it is turned on only with OpenGL 4.5 or ARB_clip_control and otherwise the option is ignored. Shadow maps are reversed too, since they share the depth test, depth bias changes sign, depth clears use 0, and the shaders that read depth (fog, volumetrics, decals, soft particles, lightshafts, fog distance, shadow lookup) follow a new REVERSED_Z define, so with the option off every shader is unchanged. --- code/cmdline/cmdline.cpp | 10 ++ code/def_files/data/effects/decal-f.sdr | 5 + code/def_files/data/effects/effect-f.sdr | 5 + code/def_files/data/effects/fog-f.sdr | 5 + code/def_files/data/effects/ls-f.sdr | 17 +- code/def_files/data/effects/main-v.sdr | 5 + code/def_files/data/effects/shadows.sdr | 2 +- code/def_files/data/effects/volumetric-f.sdr | 4 +- code/graphics/2d.cpp | 14 ++ code/graphics/2d.h | 11 ++ code/graphics/matrix.cpp | 18 +- code/graphics/opengl/gropengl.cpp | 41 ++++- code/graphics/opengl/gropengldraw.cpp | 18 +- code/graphics/opengl/gropenglstate.cpp | 4 +- code/graphics/opengl/gropengltexture.cpp | 5 +- code/graphics/opengl/gropengltnl.cpp | 1 + code/graphics/shader_preprocess.cpp | 3 + code/graphics/shadows.cpp | 10 +- code/graphics/vulkan/VulkanConvert.cpp | 4 +- code/graphics/vulkan/VulkanDeferred.cpp | 22 ++- code/graphics/vulkan/VulkanDraw.cpp | 9 +- code/graphics/vulkan/VulkanDrawAPI.cpp | 2 +- .../vulkan/VulkanPostProcessingShadow.cpp | 2 +- code/graphics/vulkan/VulkanRenderer.cpp | 3 + code/graphics/vulkan/VulkanRendererLoop.cpp | 22 +-- code/graphics/vulkan/VulkanRendererSetup.cpp | 6 + code/graphics/vulkan/VulkanState.cpp | 6 +- test/src/graphics/test_reversed_depth.cpp | 155 ++++++++++++++++++ .../src/graphics/vulkan/test_vulkan_depth.cpp | 26 +++ test/src/source_groups.cmake | 2 + 30 files changed, 386 insertions(+), 51 deletions(-) create mode 100644 test/src/graphics/test_reversed_depth.cpp create mode 100644 test/src/graphics/vulkan/test_vulkan_depth.cpp 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..e6ad3c180dc 100644 --- a/code/graphics/opengl/gropengl.cpp +++ b/code/graphics/opengl/gropengl.cpp @@ -863,15 +863,48 @@ void gr_opengl_set_fill_mode(int mode) GL_state.SetPolygonMode(GL_FRONT_AND_BACK, GL_FILL); } +// Reversed depth needs the depth range set to [0, 1] with glClipControl (core in GL 4.5, or ARB_clip_control). +// The bundled GL loader does not include it, so it is fetched by name. Without it the option is ignored. +static void opengl_enable_reversed_depth() +{ +#ifndef USE_OPENGL_ES + bool supported = GL_version >= 45; + if (!supported) { + GLint count = 0; + glGetIntegerv(GL_NUM_EXTENSIONS, &count); + for (GLint i = 0; i < count && !supported; ++i) { + const auto ext = reinterpret_cast(glGetStringi(GL_EXTENSIONS, static_cast(i))); + supported = ext != nullptr && strcmp(ext, "GL_ARB_clip_control") == 0; + } + } + + using clip_control_func = void(APIENTRYP)(GLenum origin, GLenum depth); + const auto clip_control = + supported ? reinterpret_cast(GL_context->getLoaderFunction()("glClipControl")) : nullptr; + if (clip_control == nullptr) { + mprintf((" Reversed depth needs OpenGL 4.5 or ARB_clip_control; using normal depth\n")); + return; + } + + const GLenum zero_to_one = 0x935F; // GL_ZERO_TO_ONE, missing from the bundled loader header + clip_control(GL_LOWER_LEFT, 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 +1448,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) { 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/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() From 29e68ca95e3d136875415809502dc3da98691267 Mon Sep 17 00:00:00 2001 From: Dustinspace217 Date: Thu, 15 Oct 2026 07:07:21 -0700 Subject: [PATCH 2/2] Load glClipControl through glad glad is regenerated at its existing settings (0.1.36, core 4.3, no loader) with GL_ARB_clip_control added, so reversed depth checks GLAD_GL_ARB_clip_control like the other extensions instead of scanning the extension list by hand. The current generator also makes the extension loop index unsigned. --- code/graphics/opengl/gropengl.cpp | 24 +++++------------------- lib/opengl/gl/glad/include/glad/glad.h | 18 +++++++++++++++--- lib/opengl/gl/glad/src/glad.c | 19 ++++++++++++++----- 3 files changed, 34 insertions(+), 27 deletions(-) diff --git a/code/graphics/opengl/gropengl.cpp b/code/graphics/opengl/gropengl.cpp index e6ad3c180dc..d5794c07ffb 100644 --- a/code/graphics/opengl/gropengl.cpp +++ b/code/graphics/opengl/gropengl.cpp @@ -863,31 +863,16 @@ void gr_opengl_set_fill_mode(int mode) GL_state.SetPolygonMode(GL_FRONT_AND_BACK, GL_FILL); } -// Reversed depth needs the depth range set to [0, 1] with glClipControl (core in GL 4.5, or ARB_clip_control). -// The bundled GL loader does not include it, so it is fetched by name. Without it the option is ignored. +// GL 4.5 drivers advertise ARB_clip_control too, so one check covers both static void opengl_enable_reversed_depth() { #ifndef USE_OPENGL_ES - bool supported = GL_version >= 45; - if (!supported) { - GLint count = 0; - glGetIntegerv(GL_NUM_EXTENSIONS, &count); - for (GLint i = 0; i < count && !supported; ++i) { - const auto ext = reinterpret_cast(glGetStringi(GL_EXTENSIONS, static_cast(i))); - supported = ext != nullptr && strcmp(ext, "GL_ARB_clip_control") == 0; - } - } - - using clip_control_func = void(APIENTRYP)(GLenum origin, GLenum depth); - const auto clip_control = - supported ? reinterpret_cast(GL_context->getLoaderFunction()("glClipControl")) : nullptr; - if (clip_control == nullptr) { - mprintf((" Reversed depth needs OpenGL 4.5 or ARB_clip_control; using normal depth\n")); + if (!GLAD_GL_ARB_clip_control) { + mprintf((" Reversed depth needs ARB_clip_control; using normal depth\n")); return; } - const GLenum zero_to_one = 0x935F; // GL_ZERO_TO_ONE, missing from the bundled loader header - clip_control(GL_LOWER_LEFT, zero_to_one); + glClipControl(GL_LOWER_LEFT, GL_ZERO_TO_ONE); glClearDepth(0.0); Gr_reversed_depth_active = true; mprintf((" Using reversed depth\n")); @@ -1479,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/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);