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 };