From afd8d7c8f9754a66dd25c14bd4cc269855ea82f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Wed, 9 Jun 2021 15:32:07 +0200 Subject: [PATCH] Shaders: introduce Phong::Flag::LightCulling. After several failed attempts to make UBO performance not suck on Intel Mesa and Windows drivers, I ended up hiding the dynamic aspect under a flag. That way it's still possible to get the proper perf in UBO workflows that don't do light culling, and for workflows where light culling matters the 2x slowdown might be still better than looping through several extra lights that don't contribute anything. --- src/Magnum/Shaders/Phong.frag | 26 ++++- src/Magnum/Shaders/Phong.h | 30 +++--- src/Magnum/Shaders/PhongGL.cpp | 9 +- src/Magnum/Shaders/PhongGL.h | 28 ++++-- src/Magnum/Shaders/Test/PhongGLTest.cpp | 95 ++++++++++++++++++- .../Shaders/Test/ShadersGLBenchmark.cpp | 3 +- 6 files changed, 161 insertions(+), 30 deletions(-) diff --git a/src/Magnum/Shaders/Phong.frag b/src/Magnum/Shaders/Phong.frag index 478fa2996..fde89744a 100644 --- a/src/Magnum/Shaders/Phong.frag +++ b/src/Magnum/Shaders/Phong.frag @@ -422,7 +422,7 @@ void main() { highp const vec3 cameraDirection = normalize(-transformedPosition); /* Add diffuse color for each light */ - #ifndef UNIFORM_BUFFERS + #ifndef LIGHT_CULLING for(int i = 0; i < LIGHT_COUNT; ++i) #else for(uint i = 0u, actualLightCount = min(uint(LIGHT_COUNT), draws[drawId].draw_lightCount); i < actualLightCount; ++i) @@ -432,21 +432,33 @@ void main() { #ifndef UNIFORM_BUFFERS lightColors[i] #else - lights[lightOffset + i].light_color + lights[ + #ifdef LIGHT_CULLING + lightOffset + + #endif + i].light_color #endif ; lowp const vec3 lightSpecularColor = #ifndef UNIFORM_BUFFERS lightSpecularColors[i] #else - lights[lightOffset + i].light_specularColor + lights[ + #ifdef LIGHT_CULLING + lightOffset + + #endif + i].light_specularColor #endif ; lowp const float lightRange = #ifndef UNIFORM_BUFFERS lightRanges[i] #else - lights[lightOffset + i].light_range + lights[ + #ifdef LIGHT_CULLING + lightOffset + + #endif + i].light_range #endif ; @@ -454,7 +466,11 @@ void main() { #ifndef UNIFORM_BUFFERS lightPositions[i] #else - lights[lightOffset + i].position + lights[ + #ifdef LIGHT_CULLING + lightOffset + + #endif + i].position #endif ; highp const vec4 lightDirection = vec4(lightPosition.xyz - transformedPosition*lightPosition.w, lightPosition.w); diff --git a/src/Magnum/Shaders/Phong.h b/src/Magnum/Shaders/Phong.h index 8ac861067..2e469f30e 100644 --- a/src/Magnum/Shaders/Phong.h +++ b/src/Magnum/Shaders/Phong.h @@ -188,6 +188,9 @@ struct PhongDrawUniform { * References the first light in the @ref PhongLightUniform array. Should * be less than the light count passed to @ref PhongGL constructor. Default * value is @cpp 0 @ce. + * + * Used only if @ref PhongGL::Flag::LightCulling is enabled, otherwise + * light offset is implicitly @cpp 0 @ce. */ UnsignedInt lightOffset; @@ -198,6 +201,9 @@ struct PhongDrawUniform { * @ref PhongLightUniform array. Gets clamped by the shader so it's * together with @ref lightOffset not larger than the light count passed to * @ref PhongGL constructor. Default value is @cpp 0xffffffffu @ce. + * + * Used only if @ref PhongGL::Flag::LightCulling is enabled, otherwise + * light count is implicitly @ref PhongGL::lightCount(). */ UnsignedInt lightCount; }; @@ -307,9 +313,9 @@ struct PhongMaterialUniform { * * Default value is @cpp 0xffffffff_rgbaf @ce. * - * Used only if @ref PhongDrawUniform::lightCount is not zero, ignored - * otherwise. If @ref PhongGL::Flag::VertexColor is enabled, the color is - * multiplied with a color coming from the @ref PhongGL::Color3 / + * Used only if the effective light count for given draw is not zero, + * ignored otherwise. If @ref PhongGL::Flag::VertexColor is enabled, the + * color is multiplied with a color coming from the @ref PhongGL::Color3 / * @ref PhongGL::Color4 attribute. * @see @ref PhongGL::setDiffuseColor() */ @@ -320,8 +326,8 @@ struct PhongMaterialUniform { * * Default value is @cpp 0xffffff00_rgbaf @ce. * - * Used only if @ref PhongDrawUniform::lightCount is not zero, ignored - * otherwise. + * Used only if the effective light count for given draw is not zero, + * ignored otherwise. * @see @ref PhongGL::setSpecularColor() */ Color4 specularColor; @@ -333,8 +339,8 @@ struct PhongMaterialUniform { * meaning the normal texture is not changed in any way; a value of * @cpp 0.0f @ce disables the normal texture effect altogether. * - * Used only if @ref PhongGL::Flag::NormalTexture is enabled and - * @ref PhongDrawUniform::lightCount is not zero, ignored otherwise. + * Used only if @ref PhongGL::Flag::NormalTexture is enabled and the + * effective light count for given draw is not zero, ignored otherwise. * @see @ref PhongGL::setNormalTextureScale() */ Float normalTextureScale; @@ -345,8 +351,8 @@ struct PhongMaterialUniform { * The larger value, the harder surface (smaller specular highlight). * Default value is @cpp 80.0f @ce. * - * Used only if @ref PhongDrawUniform::lightCount is not zero, ignored - * otherwise. + * Used only if the effective light count for given draw is not zero, + * ignored otherwise. * @see @ref PhongGL::setShininess() */ Float shininess; @@ -378,8 +384,10 @@ struct PhongMaterialUniform { @brief Light parameters for Phong shaders @m_since_latest -Describes light properties referenced by the @ref PhongDrawUniform::lightOffset -and @ref PhongDrawUniform::lightCount range. +Describes light properties for each light used by the shader, either all +@ref PhongGL::lightCount() or the subrange referenced by the +@ref PhongDrawUniform::lightOffset and @ref PhongDrawUniform::lightCount range +if @ref PhongGL::Flag::LightCulling is enabled. @see @ref PhongGL::bindLightBuffer() */ struct PhongLightUniform { diff --git a/src/Magnum/Shaders/PhongGL.cpp b/src/Magnum/Shaders/PhongGL.cpp index 586f15cbe..12db5b999 100644 --- a/src/Magnum/Shaders/PhongGL.cpp +++ b/src/Magnum/Shaders/PhongGL.cpp @@ -107,6 +107,8 @@ PhongGL::PhongGL(const Flags flags, const UnsignedInt lightCount "Shaders::PhongGL: texture arrays enabled but the shader is not textured", ); CORRADE_ASSERT(!(flags & Flag::UniformBuffers) || !(flags & Flag::TextureArrays) || flags >= (Flag::TextureArrays|Flag::TextureTransformation), "Shaders::PhongGL: texture arrays require texture transformation enabled as well if uniform buffers are used", ); + CORRADE_ASSERT(!(flags & Flag::LightCulling) || (flags & Flag::UniformBuffers), + "Shaders::PhongGL: light culling requires uniform buffers to be enabled", ); #endif #ifndef MAGNUM_TARGET_GLES @@ -252,7 +254,8 @@ PhongGL::PhongGL(const Flags flags, const UnsignedInt lightCount drawCount, materialCount, lightCount)); - frag.addSource(flags >= Flag::MultiDraw ? "#define MULTI_DRAW\n" : ""); + frag.addSource(flags >= Flag::MultiDraw ? "#define MULTI_DRAW\n" : "") + .addSource(flags >= Flag::LightCulling ? "#define LIGHT_CULLING\n" : ""); } else #endif { @@ -915,6 +918,7 @@ Debug& operator<<(Debug& debug, const PhongGL::Flag value) { _c(UniformBuffers) _c(MultiDraw) _c(TextureArrays) + _c(LightCulling) #endif #undef _c /* LCOV_EXCL_STOP */ @@ -942,7 +946,8 @@ Debug& operator<<(Debug& debug, const PhongGL::Flags value) { #ifndef MAGNUM_TARGET_GLES2 PhongGL::Flag::MultiDraw, /* Superset of UniformBuffers */ PhongGL::Flag::UniformBuffers, - PhongGL::Flag::TextureArrays + PhongGL::Flag::TextureArrays, + PhongGL::Flag::LightCulling #endif }); } diff --git a/src/Magnum/Shaders/PhongGL.h b/src/Magnum/Shaders/PhongGL.h index aef2d67d2..a585e12ea 100644 --- a/src/Magnum/Shaders/PhongGL.h +++ b/src/Magnum/Shaders/PhongGL.h @@ -266,11 +266,12 @@ with one default light, would look like this: For a multidraw workflow enable @ref Flag::MultiDraw (and possibly @ref Flag::TextureArrays) and supply desired light, material and draw count in the @ref PhongGL(Flags, UnsignedInt, UnsignedInt, UnsignedInt) constructor. For -every draw then specify material references and texture offsets/layers, it's -also possible to perform per-draw light culling by supplying a subrange into -the @ref PhongLightUniform array using @ref PhongDrawUniform::lightOffset and -@relativeref{PhongDrawUniform,lightCount}. Besides that, the usage is similar -for all shaders, see @ref shaders-usage-multidraw for an example. +every draw then specify material references and texture offsets/layers. With +@ref Flag::LightCulling it's also possible to perform per-draw light culling by +supplying a subrange into the @ref PhongLightUniform array using +@ref PhongDrawUniform::lightOffset and @relativeref{PhongDrawUniform,lightCount}. +Besides that, the usage is similar for all shaders, see +@ref shaders-usage-multidraw for an example. @requires_gl30 Extension @gl_extension{EXT,texture_array} for texture arrays. @requires_gl31 Extension @gl_extension{ARB,uniform_buffer_object} for uniform @@ -689,7 +690,22 @@ class MAGNUM_SHADERS_EXPORT PhongGL: public GL::AbstractShaderProgram { * @requires_webgl20 Texture arrays are not available in WebGL 1.0. * @m_since_latest */ - TextureArrays = 1 << 14 + TextureArrays = 1 << 14, + + /** + * Enable light culling in uniform buffer workflows using the + * @ref PhongDrawUniform::lightOffset and + * @ref PhongDrawUniform::lightCount fields. If not enabled, all + * @ref lightCount() lights are used for every draw. Expects that + * @ref Flag::UniformBuffers is enabled as well. + * @requires_gl31 Extension @gl_extension{ARB,uniform_buffer_object} + * @requires_gles30 Uniform buffers are not available in OpenGL ES + * 2.0. + * @requires_webgl20 Uniform buffers are not available in WebGL + * 1.0. + * @m_since_latest + */ + LightCulling = 1 << 15 #endif }; diff --git a/src/Magnum/Shaders/Test/PhongGLTest.cpp b/src/Magnum/Shaders/Test/PhongGLTest.cpp index 62208759d..01f144f0a 100644 --- a/src/Magnum/Shaders/Test/PhongGLTest.cpp +++ b/src/Magnum/Shaders/Test/PhongGLTest.cpp @@ -147,6 +147,9 @@ struct PhongGLTest: GL::OpenGLTester { void renderLightsSetOneByOne(); /* This tests just the algorithm, not affected by UBOs */ void renderLowLightAngle(); + #ifndef MAGNUM_TARGET_GLES2 + void renderLightCulling(); + #endif template void renderZeroLights(); @@ -253,6 +256,7 @@ constexpr struct { /* SwiftShader has 256 uniform vectors at most, per-3D-draw is 4+4, per-material 4, per-light 4 plus 4 for projection */ {"multiple lights, materials, draws", PhongGL::Flag::UniformBuffers, 8, 8, 24}, + {"multiple lights, materials, draws + light culling", PhongGL::Flag::UniformBuffers|PhongGL::Flag::LightCulling, 8, 8, 24}, {"zero lights", PhongGL::Flag::UniformBuffers, 0, 16, 24}, {"ambient + diffuse + specular texture", PhongGL::Flag::UniformBuffers|PhongGL::Flag::AmbientTexture|PhongGL::Flag::DiffuseTexture|PhongGL::Flag::SpecularTexture, 1, 1, 1}, {"ambient + diffuse + specular texture + texture transformation", PhongGL::Flag::UniformBuffers|PhongGL::Flag::AmbientTexture|PhongGL::Flag::DiffuseTexture|PhongGL::Flag::SpecularTexture|PhongGL::Flag::TextureTransformation, 1, 1, 1}, @@ -261,7 +265,7 @@ constexpr struct { {"normal texture + separate bitangents", PhongGL::Flag::UniformBuffers|PhongGL::Flag::NormalTexture|PhongGL::Flag::Bitangent, 1, 1, 1}, {"alpha mask", PhongGL::Flag::UniformBuffers|PhongGL::Flag::AlphaMask, 1, 1, 1}, {"object ID", PhongGL::Flag::UniformBuffers|PhongGL::Flag::ObjectId, 1, 1, 1}, - {"multidraw with all the things", PhongGL::Flag::MultiDraw|PhongGL::Flag::TextureTransformation|PhongGL::Flag::DiffuseTexture|PhongGL::Flag::AmbientTexture|PhongGL::Flag::SpecularTexture|PhongGL::Flag::NormalTexture|PhongGL::Flag::TextureArrays|PhongGL::Flag::AlphaMask|PhongGL::Flag::ObjectId|PhongGL::Flag::InstancedTextureOffset|PhongGL::Flag::InstancedTransformation|PhongGL::Flag::InstancedObjectId, 8, 16, 24} + {"multidraw with all the things", PhongGL::Flag::MultiDraw|PhongGL::Flag::TextureTransformation|PhongGL::Flag::DiffuseTexture|PhongGL::Flag::AmbientTexture|PhongGL::Flag::SpecularTexture|PhongGL::Flag::NormalTexture|PhongGL::Flag::TextureArrays|PhongGL::Flag::AlphaMask|PhongGL::Flag::ObjectId|PhongGL::Flag::InstancedTextureOffset|PhongGL::Flag::InstancedTransformation|PhongGL::Flag::InstancedObjectId|PhongGL::Flag::LightCulling, 8, 16, 24} }; #endif @@ -294,7 +298,9 @@ constexpr struct { {"zero materials", PhongGL::Flag::UniformBuffers, 1, 0, 1, "material count can't be zero"}, {"texture arrays but no transformation", PhongGL::Flag::UniformBuffers|PhongGL::Flag::DiffuseTexture|PhongGL::Flag::TextureArrays, 1, 1, 1, - "texture arrays require texture transformation enabled as well if uniform buffers are used"} + "texture arrays require texture transformation enabled as well if uniform buffers are used"}, + {"light culling but no UBOs", PhongGL::Flag::LightCulling, 1, 1, 1, + "light culling requires uniform buffers to be enabled"} }; #endif @@ -899,8 +905,13 @@ PhongGLTest::PhongGLTest() { &PhongGLTest::renderSetup, &PhongGLTest::renderTeardown); - addTests({&PhongGLTest::renderLightsSetOneByOne, - &PhongGLTest::renderLowLightAngle}, + addTests({ + &PhongGLTest::renderLightsSetOneByOne, + &PhongGLTest::renderLowLightAngle, + #ifndef MAGNUM_TARGET_GLES2 + &PhongGLTest::renderLightCulling + #endif + }, &PhongGLTest::renderSetup, &PhongGLTest::renderTeardown); @@ -3044,6 +3055,80 @@ void PhongGLTest::renderLowLightAngle() { (DebugTools::CompareImageToFile{_manager, maxThreshold, meanThreshold})); } +#ifndef MAGNUM_TARGET_GLES2 +void PhongGLTest::renderLightCulling() { + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::uniform_buffer_object::string() << "is not supported."); + #endif + + #if defined(MAGNUM_TARGET_GLES) && !defined(MAGNUM_TARGET_WEBGL) + if(GL::Context::current().detectedDriver() & GL::Context::DetectedDriver::SwiftShader) + CORRADE_SKIP("UBOs with dynamically indexed (light) arrays are a crashy dumpster fire on SwiftShader, can't test."); + #endif + + GL::Mesh sphere = MeshTools::compile(Primitives::uvSphereSolid(16, 32)); + + GL::Buffer projectionUniform{GL::Buffer::TargetHint::Uniform, { + ProjectionUniform3D{} + .setProjectionMatrix(Matrix4::perspectiveProjection(60.0_degf, 1.0f, 0.1f, 10.0f)) + }}; + GL::Buffer transformationUniform{GL::Buffer::TargetHint::Uniform, { + TransformationUniform3D{} + .setTransformationMatrix(Matrix4::translation(Vector3::zAxis(-2.15f))) + }}; + GL::Buffer drawUniform{GL::Buffer::TargetHint::Uniform, { + PhongDrawUniform{} + .setLightOffsetCount(57, 2) + }}; + GL::Buffer materialUniform{GL::Buffer::TargetHint::Uniform, { + PhongMaterialUniform{} + .setAmbientColor(0x330033_rgbf) + .setDiffuseColor(0xccffcc_rgbf) + .setSpecularColor(0x6666ff_rgbf) + }}; + /* Put one light into the first 32-bit component, one into the second to + test that both halves are checked correctly */ + PhongLightUniform lights[64]; + lights[57] = PhongLightUniform{} + .setPosition({-3.0f, -3.0f, 2.0f, 0.0f}) + .setColor(0x993366_rgbf); + lights[58] = PhongLightUniform{} + .setPosition({3.0f, -3.0f, 2.0f, 0.0f}) + .setColor(0x669933_rgbf); + GL::Buffer lightUniform{lights}; + + PhongGL shader{PhongGL::Flag::UniformBuffers|PhongGL::Flag::LightCulling, 64}; + shader + .bindProjectionBuffer(projectionUniform) + .bindTransformationBuffer(transformationUniform) + .bindDrawBuffer(drawUniform) + .bindMaterialBuffer(materialUniform) + .bindLightBuffer(lightUniform) + .draw(sphere); + + MAGNUM_VERIFY_NO_GL_ERROR(); + + if(!(_manager.loadState("AnyImageImporter") & PluginManager::LoadState::Loaded) || + !(_manager.loadState("TgaImporter") & PluginManager::LoadState::Loaded)) + CORRADE_SKIP("AnyImageImporter / TgaImporter plugins not found."); + + #if !(defined(MAGNUM_TARGET_GLES2) && defined(MAGNUM_TARGET_WEBGL)) + /* SwiftShader has some minor rounding differences (max = 1). ARM Mali G71 + and Apple A8 has bigger rounding differences. */ + const Float maxThreshold = 8.34f, meanThreshold = 0.100f; + #else + /* WebGL 1 doesn't have 8bit renderbuffer storage, so it's way worse */ + const Float maxThreshold = 15.34f, meanThreshold = 3.33f; + #endif + CORRADE_COMPARE_WITH( + /* Dropping the alpha channel, as it's always 1.0 */ + Containers::arrayCast(_framebuffer.read(_framebuffer.viewport(), {PixelFormat::RGBA8Unorm}).pixels()), + Utility::Directory::join(_testDir, "PhongTestFiles/colored.tga"), + (DebugTools::CompareImageToFile{_manager, maxThreshold, meanThreshold})); +} +#endif + template void PhongGLTest::renderZeroLights() { if(!(_manager.loadState("AnyImageImporter") & PluginManager::LoadState::Loaded) || !(_manager.loadState("TgaImporter") & PluginManager::LoadState::Loaded)) @@ -3613,7 +3698,7 @@ void PhongGLTest::renderMulti() { CORRADE_SKIP("UBOs with dynamically indexed arrays are a crashy dumpster fire on SwiftShader, can't test."); #endif - PhongGL shader{PhongGL::Flag::UniformBuffers|PhongGL::Flag::ObjectId|data.flags, data.lightCount, data.materialCount, data.drawCount}; + PhongGL shader{PhongGL::Flag::UniformBuffers|PhongGL::Flag::ObjectId|PhongGL::Flag::LightCulling|data.flags, data.lightCount, data.materialCount, data.drawCount}; GL::Texture2D diffuse{NoCreate}; GL::Texture2DArray diffuseArray{NoCreate}; diff --git a/src/Magnum/Shaders/Test/ShadersGLBenchmark.cpp b/src/Magnum/Shaders/Test/ShadersGLBenchmark.cpp index 5ec5d80f3..1569561e0 100644 --- a/src/Magnum/Shaders/Test/ShadersGLBenchmark.cpp +++ b/src/Magnum/Shaders/Test/ShadersGLBenchmark.cpp @@ -197,7 +197,8 @@ const struct { {"UBO single, ADS texture arrays + transformation", PhongGL::Flag::UniformBuffers|PhongGL::Flag::AmbientTexture|PhongGL::Flag::DiffuseTexture|PhongGL::Flag::SpecularTexture|PhongGL::Flag::TextureArrays|PhongGL::Flag::TextureTransformation, 1, 1, 1}, {"UBO multi, one light", PhongGL::Flag::UniformBuffers, 1, 32, 128}, {"multidraw, one light", PhongGL::Flag::MultiDraw, 1, 32, 128}, - {"multidraw, 64 lights, five used", PhongGL::Flag::MultiDraw, 64, 32, 128}, + {"multidraw, one light, light culling enabled", PhongGL::Flag::MultiDraw|PhongGL::Flag::LightCulling, 1, 32, 128}, + {"multidraw, 64 lights, light culling enabled, five used", PhongGL::Flag::MultiDraw|PhongGL::Flag::LightCulling, 64, 32, 128}, #endif };