mirror of
https://github.com/MaSzyna-EU07/maszyna.git
synced 2026-07-18 00:49:19 +02:00
use textureGather for shadow PCF
This commit is contained in:
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
|
#include <cstring>
|
||||||
#include "shader.h"
|
#include "shader.h"
|
||||||
#include "glsl_common.h"
|
#include "glsl_common.h"
|
||||||
#include "utilities/Logs.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());
|
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::string gl::shader::read_file(const std::string &filename)
|
||||||
{
|
{
|
||||||
std::stringstream stream;
|
std::stringstream stream;
|
||||||
@@ -91,11 +130,33 @@ std::pair<GLuint, std::string> gl::shader::process_source(const std::string &fil
|
|||||||
if (!Global.gfx_usegles)
|
if (!Global.gfx_usegles)
|
||||||
{
|
{
|
||||||
str += "#version 330 core\n";
|
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
|
else
|
||||||
{
|
{
|
||||||
if (GLAD_GL_ES_VERSION_3_1) {
|
if (GLAD_GL_ES_VERSION_3_1) {
|
||||||
str += "#version 310 es\n";
|
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)
|
if (type == GL_GEOMETRY_SHADER)
|
||||||
str += "#extension GL_EXT_geometry_shader : require\n";
|
str += "#extension GL_EXT_geometry_shader : require\n";
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
7
gl/ubo.h
7
gl/ubo.h
@@ -81,7 +81,12 @@ namespace gl
|
|||||||
void set_modelview(const glm::mat4 &mv)
|
void set_modelview(const glm::mat4 &mv)
|
||||||
{
|
{
|
||||||
modelview = 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))));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -540,14 +540,30 @@ material_manager::create( std::string const &Filename, bool const Loadnow ) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if( false == material.name.empty() ) {
|
if( false == material.name.empty() ) {
|
||||||
// if we have material name and shader it means resource was processed succesfully
|
// if we have material name and shader it means resource was processed succesfully.
|
||||||
materialhandle = m_materials.size();
|
//
|
||||||
m_materialmappings.emplace( material.name, materialhandle );
|
// 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 {
|
try {
|
||||||
material.finalize(Loadnow);
|
material.finalize(Loadnow);
|
||||||
|
materialhandle = m_materials.size();
|
||||||
m_materials.emplace_back( std::move(material) );
|
m_materials.emplace_back( std::move(material) );
|
||||||
|
m_materialmappings.emplace( m_materials.back().name, materialhandle );
|
||||||
} catch (gl::shader_exception const &e) {
|
} catch (gl::shader_exception const &e) {
|
||||||
ErrorLog("invalid shader: " + std::string(e.what()));
|
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 {
|
else {
|
||||||
|
|||||||
@@ -2424,6 +2424,12 @@ void opengl33_renderer::Render(scene::basic_region *Region)
|
|||||||
// at this stage the z-buffer is filled with only ground geometry
|
// at this stage the z-buffer is filled with only ground geometry
|
||||||
Update_Mouse_Position();
|
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));
|
Render(std::begin(m_cellqueue), std::end(m_cellqueue));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,67 +20,73 @@ class opengl_stack {
|
|||||||
|
|
||||||
public:
|
public:
|
||||||
// constructors:
|
// 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:
|
// methods:
|
||||||
glm::mat4 const &
|
glm::mat4 const &
|
||||||
data() const {
|
data() const {
|
||||||
return m_stack.top(); }
|
return m_stack.back(); }
|
||||||
void
|
void
|
||||||
push_matrix() {
|
push_matrix() {
|
||||||
m_stack.emplace( m_stack.top() ); }
|
glm::mat4 const top { m_stack.back() };
|
||||||
|
m_stack.emplace_back( top ); }
|
||||||
void
|
void
|
||||||
pop_matrix( bool const Upload = true ) {
|
pop_matrix( bool const Upload = true ) {
|
||||||
if( m_stack.size() > 1 ) {
|
if( m_stack.size() > 1 ) {
|
||||||
m_stack.pop();
|
m_stack.pop_back();
|
||||||
if( Upload ) { upload(); } } }
|
if( Upload ) { upload(); } } }
|
||||||
void
|
void
|
||||||
load_identity( bool const Upload = true ) {
|
load_identity( bool const Upload = true ) {
|
||||||
m_stack.top() = glm::mat4( 1.f );
|
m_stack.back() = glm::mat4( 1.f );
|
||||||
if( Upload ) { upload(); } }
|
if( Upload ) { upload(); } }
|
||||||
void
|
void
|
||||||
load_matrix( glm::mat4 const &Matrix, bool const Upload = true ) {
|
load_matrix( glm::mat4 const &Matrix, bool const Upload = true ) {
|
||||||
m_stack.top() = Matrix;
|
m_stack.back() = Matrix;
|
||||||
if( Upload ) { upload(); } }
|
if( Upload ) { upload(); } }
|
||||||
void
|
void
|
||||||
rotate( float const Angle, glm::vec3 const &Axis, bool const Upload = true ) {
|
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(); } }
|
if( Upload ) { upload(); } }
|
||||||
void
|
void
|
||||||
translate( glm::vec3 const &Translation, bool const Upload = true ) {
|
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(); } }
|
if( Upload ) { upload(); } }
|
||||||
void
|
void
|
||||||
scale( glm::vec3 const &Scale, bool const Upload = true ) {
|
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(); } }
|
if( Upload ) { upload(); } }
|
||||||
void
|
void
|
||||||
multiply( glm::mat4 const &Matrix, bool const Upload = true ) {
|
multiply( glm::mat4 const &Matrix, bool const Upload = true ) {
|
||||||
m_stack.top() *= Matrix;
|
m_stack.back() *= Matrix;
|
||||||
if( Upload ) { upload(); } }
|
if( Upload ) { upload(); } }
|
||||||
void
|
void
|
||||||
ortho( float const Left, float const Right, float const Bottom, float const Top, float const Znear, float const Zfar, bool const Upload = true ) {
|
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(); } }
|
if( Upload ) { upload(); } }
|
||||||
void
|
void
|
||||||
perspective( float const Fovy, float const Aspect, float const Znear, float const Zfar, bool const Upload = true ) {
|
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(); } }
|
if( Upload ) { upload(); } }
|
||||||
void
|
void
|
||||||
look_at( glm::vec3 const &Eye, glm::vec3 const &Center, glm::vec3 const &Up, bool const Upload = true ) {
|
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(); } }
|
if( Upload ) { upload(); } }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
// types:
|
|
||||||
typedef std::stack<glm::mat4> mat4_stack;
|
|
||||||
|
|
||||||
// methods:
|
// methods:
|
||||||
void
|
void
|
||||||
upload() { ::glLoadMatrixf( glm::value_ptr( m_stack.top() ) ); }
|
upload() { ::glLoadMatrixf( glm::value_ptr( m_stack.back() ) ); }
|
||||||
|
|
||||||
// members:
|
// members:
|
||||||
mat4_stack m_stack;
|
std::vector<glm::mat4> m_stack;
|
||||||
};
|
};
|
||||||
|
|
||||||
enum stack_mode { gl_modelview = 0, gl_projection = 1, gl_texture = 2 };
|
enum stack_mode { gl_modelview = 0, gl_projection = 1, gl_texture = 2 };
|
||||||
|
|||||||
@@ -51,7 +51,6 @@ float calc_shadow()
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
float shadow = 0.0;
|
|
||||||
float bias = 0.00005f * float(cascade + 1U);
|
float bias = 0.00005f * float(cascade + 1U);
|
||||||
vec2 texel = vec2(1.0) / vec2(textureSize(shadowmap, 0));
|
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));
|
//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));
|
||||||
@@ -64,12 +63,42 @@ float calc_shadow()
|
|||||||
else
|
else
|
||||||
radius = 0.5;
|
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 y = -1.5; y <= 1.5; y += 1.0)
|
||||||
for (float x = -1.5; x <= 1.5; x += 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 += texture(shadowmap, vec4(coords.xy + vec2(x, y) * radius * texel, cascade, coords.z + bias) );
|
||||||
shadow /= 16.0;
|
shadow /= 16.0;
|
||||||
|
|
||||||
return shadow;
|
return shadow;
|
||||||
|
#endif
|
||||||
#else
|
#else
|
||||||
return 0.0;
|
return 0.0;
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
Reference in New Issue
Block a user