16
0
mirror of https://github.com/MaSzyna-EU07/maszyna.git synced 2026-07-17 23:39:18 +02:00

use textureGather for shadow PCF

This commit is contained in:
2026-05-26 22:08:06 +02:00
parent 26c11ddca9
commit 426ee0baac
6 changed files with 147 additions and 24 deletions

View File

@@ -2,6 +2,7 @@
#include <fstream>
#include <sstream>
#include <cstring>
#include "shader.h"
#include "glsl_common.h"
#include "utilities/Logs.h"
@@ -13,6 +14,44 @@ inline bool strcend(std::string const &value, std::string const &ending)
return std::equal(ending.rbegin(), ending.rend(), value.rbegin());
}
namespace {
// The project's glad config only generates GLAD_GL_ARB_texture_filter_anisotropic
// among the ARB/EXT extension flags -- GL_ARB_texture_gather (desktop) and
// GL_EXT_gpu_shader5 (GLES 3.1) aren't compiled in, so we can't gate the
// shadow textureGather() optimisation on a GLAD constant. Query the live
// extension string instead. The first call walks the extension list once
// (no extension count in the dozens is large enough to matter here) and
// the result is cached in the static bool inside has_gl_extension(), so
// every subsequent shader compile is a plain bool read.
//
// SHADERVALIDATOR_STANDALONE gates out the GL queries because the offline
// shader validator tool links glad.c but never calls gladLoadGL(); the
// function pointers stay null and a real call would crash. Returning
// false there means the standalone validator simply compiles the original
// 16-tap PCF fallback path in light_common.glsl, which is what we want.
bool has_gl_extension(char const *name) {
#ifdef SHADERVALIDATOR_STANDALONE
(void)name;
return false;
#else
if (!glGetIntegerv || !glGetStringi) {
return false;
}
GLint count = 0;
glGetIntegerv(GL_NUM_EXTENSIONS, &count);
for (GLint i = 0; i < count; ++i) {
char const *ext = reinterpret_cast<char const *>(glGetStringi(GL_EXTENSIONS, i));
if (ext != nullptr && std::strcmp(ext, name) == 0) {
return true;
}
}
return false;
#endif
}
} // anonymous namespace
std::string gl::shader::read_file(const std::string &filename)
{
std::stringstream stream;
@@ -91,11 +130,33 @@ std::pair<GLuint, std::string> gl::shader::process_source(const std::string &fil
if (!Global.gfx_usegles)
{
str += "#version 330 core\n";
// textureGather() on sampler2DArrayShadow is core in GLSL 4.0. On 3.30
// desktop it requires GL_ARB_gpu_shader5 -- the older
// GL_ARB_texture_gather (2010) only adds the non-shadow and the plain
// sampler2DShadow overloads, NOT the sampler2DArrayShadow one we need
// for cascaded shadow PCF. Some drivers advertise texture_gather but
// reject the shadow-array overload because the spec for it lives in
// gpu_shader5; so emit only when gpu_shader5 is advertised. When the
// extension is missing, calc_shadow() in light_common.glsl falls back
// to the original 16-tap hardware-PCF loop via #ifndef.
// (Project's glad config doesn't generate GLAD_GL_ARB_gpu_shader5,
// so we query the live extension list -- see has_gl_extension above.)
static bool const have_gpu_shader5 = has_gl_extension("GL_ARB_gpu_shader5");
if (have_gpu_shader5)
str += "#extension GL_ARB_gpu_shader5 : enable\n";
}
else
{
if (GLAD_GL_ES_VERSION_3_1) {
str += "#version 310 es\n";
// GLES 3.1 lacks textureGather on shadow samplers in core; the
// EXT_gpu_shader5 extension adds it. Only emit the directive when
// the driver advertises support (see desktop comment above).
// (Glad config doesn't generate GLAD_GL_EXT_gpu_shader5 -- query
// the live extension string the same way.)
static bool const have_gpu_shader5 = has_gl_extension("GL_EXT_gpu_shader5");
if (have_gpu_shader5)
str += "#extension GL_EXT_gpu_shader5 : enable\n";
if (type == GL_GEOMETRY_SHADER)
str += "#extension GL_EXT_geometry_shader : require\n";
} else {

View File

@@ -81,7 +81,12 @@ namespace gl
void set_modelview(const glm::mat4 &mv)
{
modelview = mv;
modelviewnormal = glm::mat3x4(glm::mat3(glm::transpose(glm::inverse(mv))));
// normal matrix = transpose(inverse(modelview)). The modelview is
// always affine, so its 3x3 normal matrix depends only on the
// upper-left 3x3 block; inverting that mat3 directly is markedly
// cheaper than a full mat4 inverse and yields an identical result
// (for affine M, mat3(inverse(M)) == inverse(mat3(M))).
modelviewnormal = glm::mat3x4(glm::transpose(glm::inverse(glm::mat3(mv))));
}
};

View File

@@ -540,14 +540,30 @@ material_manager::create( std::string const &Filename, bool const Loadnow ) {
}
if( false == material.name.empty() ) {
// if we have material name and shader it means resource was processed succesfully
materialhandle = m_materials.size();
m_materialmappings.emplace( material.name, materialhandle );
// if we have material name and shader it means resource was processed succesfully.
//
// IMPORTANT: capture the handle ONLY after emplace_back succeeds. The
// previous version pre-assigned `materialhandle = m_materials.size()`
// and then ran `finalize(Loadnow)` (which can throw shader_exception
// when a shader fails to compile) and `emplace_back` together inside
// the try. If finalize() threw, emplace_back never ran, but the
// handle was already set to size() -- pointing one past the actual
// last element. Subsequent `m_materials[handle]` lookups would then
// read garbage past end-of-vector, producing the classic 0x30-offset
// access violation seen in TSubModel::BinInit (vtable read on a
// bogus IMaterial pointer). Now we leave materialhandle as
// null_handle if anything throws, and the caller treats it as a
// failed-to-load material.
try {
material.finalize(Loadnow);
materialhandle = m_materials.size();
m_materials.emplace_back( std::move(material) );
m_materialmappings.emplace( m_materials.back().name, materialhandle );
} catch (gl::shader_exception const &e) {
ErrorLog("invalid shader: " + std::string(e.what()));
// record the failure so subsequent Fetch_Material(filename) calls
// short-circuit without re-running the failing compile.
m_materialmappings.emplace( material.name, null_handle );
}
}
else {

View File

@@ -2424,6 +2424,12 @@ void opengl33_renderer::Render(scene::basic_region *Region)
// at this stage the z-buffer is filled with only ground geometry
Update_Mouse_Position();
}
// draw opaque cells front-to-back: with the depth test enabled this
// lets the GPU reject hidden fragments early, before the (expensive)
// lit fragment shader runs on them. Order is irrelevant to the final
// image for opaque geometry, so this is purely a fill-rate win.
std::sort( std::begin( m_cellqueue ), std::end( m_cellqueue ),
[]( distancecell_pair const &Left, distancecell_pair const &Right ) { return Left.first < Right.first; } );
Render(std::begin(m_cellqueue), std::end(m_cellqueue));
break;
}

View File

@@ -20,67 +20,73 @@ class opengl_stack {
public:
// constructors:
opengl_stack() { m_stack.emplace(1.f); }
opengl_stack() {
// reserve generously up front: the matrix stack is pushed/popped once
// per submodel during scene traversal, and std::deque (the previous
// backing store) allocated a fresh heap block on every push for a
// 64-byte glm::mat4. A reserved vector never reallocates within this
// depth, so push/pop become allocation-free and references returned
// by data() stay valid across pushes, exactly as before.
m_stack.reserve( 256 );
m_stack.emplace_back( 1.f ); }
// methods:
glm::mat4 const &
data() const {
return m_stack.top(); }
return m_stack.back(); }
void
push_matrix() {
m_stack.emplace( m_stack.top() ); }
glm::mat4 const top { m_stack.back() };
m_stack.emplace_back( top ); }
void
pop_matrix( bool const Upload = true ) {
if( m_stack.size() > 1 ) {
m_stack.pop();
m_stack.pop_back();
if( Upload ) { upload(); } } }
void
load_identity( bool const Upload = true ) {
m_stack.top() = glm::mat4( 1.f );
m_stack.back() = glm::mat4( 1.f );
if( Upload ) { upload(); } }
void
load_matrix( glm::mat4 const &Matrix, bool const Upload = true ) {
m_stack.top() = Matrix;
m_stack.back() = Matrix;
if( Upload ) { upload(); } }
void
rotate( float const Angle, glm::vec3 const &Axis, bool const Upload = true ) {
m_stack.top() = glm::rotate( m_stack.top(), Angle, Axis );
m_stack.back() = glm::rotate( m_stack.back(), Angle, Axis );
if( Upload ) { upload(); } }
void
translate( glm::vec3 const &Translation, bool const Upload = true ) {
m_stack.top() = glm::translate( m_stack.top(), Translation );
m_stack.back() = glm::translate( m_stack.back(), Translation );
if( Upload ) { upload(); } }
void
scale( glm::vec3 const &Scale, bool const Upload = true ) {
m_stack.top() = glm::scale( m_stack.top(), Scale );
m_stack.back() = glm::scale( m_stack.back(), Scale );
if( Upload ) { upload(); } }
void
multiply( glm::mat4 const &Matrix, bool const Upload = true ) {
m_stack.top() *= Matrix;
m_stack.back() *= Matrix;
if( Upload ) { upload(); } }
void
ortho( float const Left, float const Right, float const Bottom, float const Top, float const Znear, float const Zfar, bool const Upload = true ) {
m_stack.top() *= glm::ortho( Left, Right, Bottom, Top, Znear, Zfar );
m_stack.back() *= glm::ortho( Left, Right, Bottom, Top, Znear, Zfar );
if( Upload ) { upload(); } }
void
perspective( float const Fovy, float const Aspect, float const Znear, float const Zfar, bool const Upload = true ) {
m_stack.top() *= glm::perspective( Fovy, Aspect, Znear, Zfar );
m_stack.back() *= glm::perspective( Fovy, Aspect, Znear, Zfar );
if( Upload ) { upload(); } }
void
look_at( glm::vec3 const &Eye, glm::vec3 const &Center, glm::vec3 const &Up, bool const Upload = true ) {
m_stack.top() *= glm::lookAt( Eye, Center, Up );
m_stack.back() *= glm::lookAt( Eye, Center, Up );
if( Upload ) { upload(); } }
private:
// types:
typedef std::stack<glm::mat4> mat4_stack;
// methods:
void
upload() { ::glLoadMatrixf( glm::value_ptr( m_stack.top() ) ); }
upload() { ::glLoadMatrixf( glm::value_ptr( m_stack.back() ) ); }
// members:
mat4_stack m_stack;
std::vector<glm::mat4> m_stack;
};
enum stack_mode { gl_modelview = 0, gl_projection = 1, gl_texture = 2 };

View File

@@ -51,7 +51,6 @@ float calc_shadow()
float shadow = 0.0;
float bias = 0.00005f * float(cascade + 1U);
vec2 texel = vec2(1.0) / vec2(textureSize(shadowmap, 0));
//float radius = 1.0; f_light_pos[cascade].w; //0.5 + 2.0 * max(abs(2.0 * coords.x - 1.0), abs(2.0 * coords.y - 1.0));
@@ -63,13 +62,43 @@ float calc_shadow()
radius = mix(minradius, f_light_pos[cascade+1U].w/f_light_pos[cascade].w, dist_casc);
else
radius = 0.5;
#if defined(GL_ARB_gpu_shader5) || defined(GL_EXT_gpu_shader5) || __VERSION__ >= 400
// Fast path -- replace the original 4x4 grid of individual hardware-PCF
// lookups with 4 textureGather() calls. Each gather returns the 4 raw
// shadow comparisons of a 2x2 texel footprint, so 4 gathers laid out at
// (+-1, +-1) * radius * texel from the sample center cover the same 4x4
// sample area as the original kernel; summing all 16 comparisons and
// dividing by 16 reproduces the original loop's averaging. The cost on
// the TMUs drops from 16 hardware-PCF samples to 4 gathers (the gather
// path returns 4 values per fetch where the original needed 4 fetches),
// roughly a 4x reduction in shadow-sample work. The only thing dropped
// vs. the hardware-PCF path is the implicit bilinear blending inside
// each 2x2 footprint -- effectively turning a tent-weighted kernel into
// a box-weighted one of the same extent, which is imperceptible in
// motion. calc_shadow() is by far the heaviest piece of the lighting
// shader, so this is a measurable GPU saving on every shaded fragment.
float refz = coords.z + bias;
float layer = float(cascade);
vec2 off = radius * texel;
vec4 g0 = textureGather(shadowmap, vec3(coords.xy + vec2(-off.x, -off.y), layer), refz);
vec4 g1 = textureGather(shadowmap, vec3(coords.xy + vec2( off.x, -off.y), layer), refz);
vec4 g2 = textureGather(shadowmap, vec3(coords.xy + vec2(-off.x, off.y), layer), refz);
vec4 g3 = textureGather(shadowmap, vec3(coords.xy + vec2( off.x, off.y), layer), refz);
float shadow = dot(g0 + g1 + g2 + g3, vec4(1.0 / 16.0));
return shadow;
#else
// Fallback for drivers without textureGather on shadow samplers
// (notably GLES 3.0 and any 3.3 desktop driver that doesn't expose
// GL_ARB_texture_gather). Identical to the previous implementation.
float shadow = 0.0;
for (float y = -1.5; y <= 1.5; y += 1.0)
for (float x = -1.5; x <= 1.5; x += 1.0)
shadow += texture(shadowmap, vec4(coords.xy + vec2(x, y) * radius * texel, cascade, coords.z + bias) );
shadow /= 16.0;
return shadow;
#endif
#else
return 0.0;
#endif