diff --git a/filament/src/ShadowMap.h b/filament/src/ShadowMap.h index 1da8b10e2b1..e997dce14cf 100644 --- a/filament/src/ShadowMap.h +++ b/filament/src/ShadowMap.h @@ -340,7 +340,7 @@ class ShadowMap { { 2, 6, 7, 3 }, // top }; - mutable ShadowMapDescriptorSet mPerShadowMapUniforms; // 4 + mutable ShadowMapDescriptorSet mPerShadowMapUniforms; // 48 FCamera* mCamera = nullptr; // 8 FCamera* mDebugCamera = nullptr; // 8 @@ -352,9 +352,10 @@ class ShadowMap { uint16_t mShadowIndex = 0; // our index in the shadowMap vector // 2 uint8_t mLayer = 0; // our layer in the shadowMap texture // 1 ShadowType mShadowType : 2; // :2 - bool mHasVisibleShadows : 2; // :2 + bool mHasVisibleShadows : 1; // :1 uint8_t mFace : 3; // :3 math::ushort2 mOffset{}; // 4 + UTILS_UNUSED uint8_t reserved[4]; // 4 }; } // namespace filament diff --git a/filament/src/ShadowMapManager.cpp b/filament/src/ShadowMapManager.cpp index 25e8815280d..b6ae94ab82d 100644 --- a/filament/src/ShadowMapManager.cpp +++ b/filament/src/ShadowMapManager.cpp @@ -15,6 +15,7 @@ */ #include "ShadowMapManager.h" +#include "AtlasAllocator.h" #include "RenderPass.h" #include "ShadowMap.h" @@ -22,6 +23,7 @@ #include #include +#include #include #include "components/RenderableManager.h" @@ -54,6 +56,7 @@ #include #include #include +#include #include #include #include @@ -75,6 +78,8 @@ ShadowMapManager::ShadowMapManager(FEngine& engine) &engine.debug.shadowmap.disable_light_frustum_align); debugRegistry.registerProperty("d.shadowmap.depth_clamp", &engine.debug.shadowmap.depth_clamp); + + mFeatureShadowAllocator = engine.features.engine.shadows.use_shadow_atlas; } ShadowMapManager::~ShadowMapManager() { @@ -101,6 +106,10 @@ void ShadowMapManager::terminate(FEngine& engine, } } +size_t ShadowMapManager::getMaxShadowMapCount() const noexcept { + return mFeatureShadowAllocator ? CONFIG_MAX_SHADOWMAPS : CONFIG_MAX_SHADOW_LAYERS; +} + void ShadowMapManager::terminate(FEngine& engine) { if (UTILS_UNLIKELY(mInitialized)) { DriverApi& driver = engine.getDriverApi(); @@ -232,7 +241,7 @@ FrameGraphId ShadowMapManager::render(FEngine& engine, FrameG // ------------------------------------------------------------------------------------------- struct PrepareShadowPassData { - struct ShadowPass { + struct ShadowPass { // 112 bytes mutable RenderPass::Executor executor; ShadowMap* shadowMap; utils::Range range; @@ -248,7 +257,7 @@ FrameGraphId ShadowMapManager::render(FEngine& engine, FrameG auto& prepareShadowPass = fg.addPass("Prepare Shadow Pass", [&](FrameGraph::Builder& builder, auto& data) { - data.passList.reserve(CONFIG_MAX_SHADOWMAPS); + data.passList.reserve(getMaxShadowMapCount()); data.shadows = builder.createTexture("Shadowmap", { .width = textureRequirements.size, .height = textureRequirements.size, .depth = textureRequirements.layers, @@ -308,7 +317,18 @@ FrameGraphId ShadowMapManager::render(FEngine& engine, FrameG } } - assert_invariant(passList.size() <= textureRequirements.layers); + assert_invariant(mFeatureShadowAllocator || + passList.size() <= textureRequirements.layers); + + if (mFeatureShadowAllocator) { + // sort shadow passes by layer so that we can update all the shadow maps of + // a layer in one render pass. + std::sort(passList.begin(), passList.end(), []( + PrepareShadowPassData::ShadowPass const& lhs, + PrepareShadowPassData::ShadowPass const& rhs) { + return lhs.shadowMap->getLayer() < rhs.shadowMap->getLayer(); + }); + } // This pass must be declared as having a side effect because it never gets a // "read" from one of its resource (only writes), so the FrameGraph culls it. @@ -336,7 +356,8 @@ FrameGraphId ShadowMapManager::render(FEngine& engine, FrameG // same command buffer for all shadow map, but then we'd generate // a lot of unneeded draw calls. // To do this efficiently, we'd need a way to cull draw calls already - // recorded in the command buffer, per shadow map. + // recorded in the command buffer, per shadow map. Maybe this could + // be done with indirect draw calls. // Note: the output of culling below is stored in scene->getRenderableData() @@ -429,13 +450,26 @@ FrameGraphId ShadowMapManager::render(FEngine& engine, FrameG }; auto const& passList = prepareShadowPass.getData().passList; - for (auto const& entry: passList) { + auto first = passList.begin(); + while (first != passList.end()) { + auto const& entry = *first; + const uint8_t layer = entry.shadowMap->getLayer(); const auto* options = entry.shadowMap->getShadowOptions(); const auto msaaSamples = textureRequirements.msaaSamples; const bool blur = entry.shadowMap->hasVisibleShadows() && view.hasVSM() && options->vsm.blurWidth > 0.0f; + auto last = first; + // loop over each shadow pass to find its layer range + while (last != passList.end() && last->shadowMap->getLayer() == layer) { + ++last; + } + + assert_invariant(mFeatureShadowAllocator || + std::distance(first, last) == 1); + + // And render all shadow pass of a given layer as a single render pass auto& shadowPass = fg.addPass("Shadow Pass", [&](FrameGraph::Builder& builder, auto& data) { @@ -507,7 +541,7 @@ FrameGraphId ShadowMapManager::render(FEngine& engine, FrameG // blurring. data.rt = blur ? data.rt : rt; }, - [=, &engine, &entry](FrameGraphResources const& resources, + [=, &engine](FrameGraphResources const& resources, auto const& data, DriverApi& driver) { // Note: we capture entry by reference here. That's actually okay because @@ -520,22 +554,31 @@ FrameGraphId ShadowMapManager::render(FEngine& engine, FrameG auto rt = resources.getRenderPassInfo(data.rt); driver.beginRenderPass(rt.target, rt.params); - // if we know there are no visible shadows, we can skip rendering, but - // we need the render-pass to clear/initialize the shadow-map - // Note: this is always true for directional/cascade shadows. - if (entry.shadowMap->hasVisibleShadows()) { - entry.shadowMap->bind(driver); - entry.executor.overrideScissor(entry.shadowMap->getScissor()); - entry.executor.execute(engine, driver); + + for (auto curr = first; curr != last; curr++) { + // if we know there are no visible shadows, we can skip rendering, but + // we need the render-pass to clear/initialize the shadow-map + // Note: this is always true for directional/cascade shadows. + if (curr->shadowMap->hasVisibleShadows()) { + curr->shadowMap->bind(driver); + curr->executor.overrideScissor(curr->shadowMap->getScissor()); + curr->executor.execute(engine, driver); + } } + driver.endRenderPass(); }); + first = last; // now emit the blurring passes if needed if (UTILS_UNLIKELY(blur)) { auto& ppm = engine.getPostProcessManager(); + // FIXME: this `options` is for the first shadowmap in the list, but it applies to + // the whole layer. Blurring should happen per shadowmap, not for the whole + // layer. + const float blurWidth = options->vsm.blurWidth; if (blurWidth > 0.0f) { const float sigma = (blurWidth + 1.0f) / 6.0f; @@ -547,6 +590,9 @@ FrameGraphId ShadowMapManager::render(FEngine& engine, FrameG false, kernelWidth, sigma); } + // FIXME: mipmapping here is broken because it'll access texels from adjacent + // shadow maps. + // If the shadow texture has more than one level, mipmapping was requested, either directly // or indirectly via anisotropic filtering. // So generate the mipmaps for each layer @@ -968,32 +1014,59 @@ ShadowMapManager::ShadowTechnique ShadowMapManager::updateSpotShadowMaps(FEngine void ShadowMapManager::calculateTextureRequirements(FEngine& engine, FView& view, FScene::LightSoa const&) noexcept { - // Lay out the shadow maps. For now, we take the largest requested dimension and allocate a - // texture of that size. Each cascade / shadow map gets its own layer in the array texture. - // The directional shadow cascades start on layer 0, followed by spotlights. - uint8_t layer = 0; uint32_t maxDimension = 0; bool elvsm = false; - for (ShadowMap& shadowMap : getCascadedShadowMap()) { + + for (ShadowMap const& shadowMap : getCascadedShadowMap()) { // Shadow map size should be the same for all cascades. auto const& options = shadowMap.getShadowOptions(); maxDimension = std::max(maxDimension, options->mapSize); elvsm = elvsm || options->vsm.elvsm; - shadowMap.setAllocation(layer++, {}); } - for (ShadowMap& shadowMap : getSpotShadowMaps()) { + + for (ShadowMap const& shadowMap : getSpotShadowMaps()) { auto const& options = shadowMap.getShadowOptions(); maxDimension = std::max(maxDimension, options->mapSize); elvsm = elvsm || options->vsm.elvsm; - shadowMap.setAllocation(layer++, {}); } - const uint8_t layersNeeded = layer; + uint8_t layersNeeded = 0; + + std::function const allocateFromAtlas = + [&layersNeeded, allocator = AtlasAllocator{ maxDimension }]( + ShadowMap* pShadowMap) mutable { + // Allocate shadowmap from our Atlas Allocator + auto const& options = pShadowMap->getShadowOptions(); + auto [layer, pos] = allocator.allocate(options->mapSize); + assert_invariant(layer >= 0); + assert_invariant(!pos.empty()); + pShadowMap->setAllocation(layer, pos); + layersNeeded = std::max(uint8_t(layer + 1), layersNeeded); + }; + + std::function const allocateFromTextureArray = + [&layersNeeded, layer = 0](ShadowMap* pShadowMap) mutable { + // Layout the shadow maps. For now, we take the largest requested dimension and allocate a + // texture of that size. Each cascade / shadow map gets its own layer in the array texture. + // The directional shadow cascades start on layer 0, followed by spotlights. + pShadowMap->setAllocation(layer, {}); + layersNeeded = ++layer; + }; + + auto& allocateShadowmapTexture = mFeatureShadowAllocator ? + allocateFromAtlas : allocateFromTextureArray; + + for (ShadowMap& shadowMap : getCascadedShadowMap()) { + allocateShadowmapTexture(&shadowMap); + } + for (ShadowMap& shadowMap : getSpotShadowMaps()) { + allocateShadowmapTexture(&shadowMap); + } // Generate mipmaps for VSM when anisotropy is enabled or when requested auto const& vsmShadowOptions = view.getVsmShadowOptions(); const bool useMipmapping = view.hasVSM() && - ((vsmShadowOptions.anisotropy > 0) || vsmShadowOptions.mipmapping); + ((vsmShadowOptions.anisotropy > 0) || vsmShadowOptions.mipmapping); uint8_t msaaSamples = vsmShadowOptions.msaaSamples; if (engine.getDriverApi().isWorkaroundNeeded(Workaround::DISABLE_BLIT_INTO_TEXTURE_ARRAY)) { diff --git a/filament/src/ShadowMapManager.h b/filament/src/ShadowMapManager.h index b2d43514479..1f0f080f541 100644 --- a/filament/src/ShadowMapManager.h +++ b/filament/src/ShadowMapManager.h @@ -119,6 +119,8 @@ class ShadowMapManager { static void terminate(FEngine& engine, std::unique_ptr& shadowMapManager); + size_t getMaxShadowMapCount() const noexcept; + // Updates all the shadow maps and performs culling. // Returns true if any of the shadow maps have visible shadows. ShadowMapManager::ShadowTechnique update(Builder const& builder, @@ -227,17 +229,18 @@ class ShadowMapManager { // Inline storage for all our ShadowMap objects, we can't easily use a std::array<> directly. // Because ShadowMap doesn't have a default ctor, and we avoid out-of-line allocations. - // Each ShadowMap is currently 40 bytes (total of 2.5KB for 64 shadow maps) - using ShadowMapStorage = std::aligned_storage::type; + // Each ShadowMap is currently 88 bytes (total of ~12KB for 128 shadow maps) + using ShadowMapStorage = std::aligned_storage::type; using ShadowMapCacheContainer = std::array; ShadowMapCacheContainer mShadowMapCache; uint32_t mDirectionalShadowMapCount = 0; uint32_t mSpotShadowMapCount = 0; bool const mIsDepthClampSupported; bool mInitialized = false; + bool mFeatureShadowAllocator = false; ShadowMap& getShadowMap(size_t index) noexcept { - assert_invariant(index < CONFIG_MAX_SHADOWMAPS); + assert_invariant(index < mShadowMapCache.size()); return *std::launder(reinterpret_cast(&mShadowMapCache[index])); } diff --git a/filament/src/components/LightManager.cpp b/filament/src/components/LightManager.cpp index 1b173ef1ffa..cc9a539413f 100644 --- a/filament/src/components/LightManager.cpp +++ b/filament/src/components/LightManager.cpp @@ -19,14 +19,26 @@ #include "components/LightManager.h" #include "details/Engine.h" +#include "utils/ostream.h" #include +#include #include #include +#include #include #include +#include +#include + +#include +#include + +#include +#include +#include using namespace filament::math; using namespace utils; @@ -58,12 +70,12 @@ struct LightManager::BuilderDetails { }; using BuilderType = LightManager; -BuilderType::Builder::Builder(Type type) noexcept: BuilderBase(type) {} +BuilderType::Builder::Builder(Type type) noexcept: BuilderBase(type) {} BuilderType::Builder::~Builder() noexcept = default; -BuilderType::Builder::Builder(BuilderType::Builder const& rhs) noexcept = default; -BuilderType::Builder::Builder(BuilderType::Builder&& rhs) noexcept = default; -BuilderType::Builder& BuilderType::Builder::operator=(BuilderType::Builder const& rhs) noexcept = default; -BuilderType::Builder& BuilderType::Builder::operator=(BuilderType::Builder&& rhs) noexcept = default; +BuilderType::Builder::Builder(Builder const& rhs) noexcept = default; +BuilderType::Builder::Builder(Builder&& rhs) noexcept = default; +BuilderType::Builder& BuilderType::Builder::operator=(Builder const& rhs) noexcept = default; +BuilderType::Builder& BuilderType::Builder::operator=(Builder&& rhs) noexcept = default; LightManager::Builder& LightManager::Builder::castShadows(bool enable) noexcept { mImpl->mCastShadows = enable; @@ -167,7 +179,7 @@ FLightManager::~FLightManager() { void FLightManager::init(FEngine&) noexcept { } -void FLightManager::create(const FLightManager::Builder& builder, utils::Entity entity) { +void FLightManager::create(const Builder& builder, Entity entity) { auto& manager = mManager; if (UTILS_UNLIKELY(manager.hasComponent(entity))) { @@ -206,7 +218,7 @@ void FLightManager::create(const FLightManager::Builder& builder, utils::Entity void FLightManager::prepare(backend::DriverApi&) const noexcept { } -void FLightManager::destroy(utils::Entity e) noexcept { +void FLightManager::destroy(Entity e) noexcept { Instance const i = getInstance(e); if (i) { auto& manager = mManager; @@ -227,13 +239,13 @@ void FLightManager::terminate() noexcept { } } } -void FLightManager::gc(utils::EntityManager& em) noexcept { +void FLightManager::gc(EntityManager& em) noexcept { mManager.gc(em, [this](Entity e) { destroy(e); }); } -void FLightManager::setShadowOptions(Instance i, ShadowOptions const& options) noexcept { +void FLightManager::setShadowOptions(Instance const i, ShadowOptions const& options) noexcept { ShadowParams& params = mManager[i].shadowParams; params.options = options; params.options.mapSize = clamp(options.mapSize, 8u, 2048u); @@ -294,7 +306,7 @@ void FLightManager::setIntensity(Instance i, float intensity, IntensityUnit unit if (i) { Type const type = getLightType(i).type; float luminousPower = intensity; - float luminousIntensity; + float luminousIntensity = 0.0f; switch (type) { case Type::SUN: case Type::DIRECTIONAL: @@ -335,7 +347,7 @@ void FLightManager::setIntensity(Instance i, float intensity, IntensityUnit unit luminousIntensity = luminousPower * f::ONE_OVER_PI; } else { assert_invariant(unit == IntensityUnit::CANDELA); - // intensity specified directly in candela, no conversion needed + // intensity specified directly in Candela, no conversion needed luminousIntensity = luminousPower; } break; diff --git a/filament/src/details/Engine.cpp b/filament/src/details/Engine.cpp index 576e0e081a8..ac2f7cfff86 100644 --- a/filament/src/details/Engine.cpp +++ b/filament/src/details/Engine.cpp @@ -39,6 +39,7 @@ #include #include +#include #include @@ -1305,6 +1306,11 @@ size_t FEngine::getSkyboxeCount() const noexcept { return mSkyboxes.size(); } size_t FEngine::getColorGradingCount() const noexcept { return mColorGradings.size(); } size_t FEngine::getRenderTargetCount() const noexcept { return mRenderTargets.size(); } +size_t FEngine::getMaxShadowMapCount() const noexcept { + return features.engine.shadows.use_shadow_atlas ? + CONFIG_MAX_SHADOWMAPS : CONFIG_MAX_SHADOW_LAYERS; +} + void* FEngine::streamAlloc(size_t size, size_t alignment) noexcept { // we allow this only for small allocations if (size > 65536) { diff --git a/filament/src/details/Engine.h b/filament/src/details/Engine.h index 1f21849051c..7788c41e596 100644 --- a/filament/src/details/Engine.h +++ b/filament/src/details/Engine.h @@ -247,6 +247,8 @@ class FEngine : public Engine { return mPlatform; } + size_t getMaxShadowMapCount() const noexcept; + // Return a vector of shader languages, in order of preference. utils::FixedCapacityVector getShaderLanguage() const noexcept { switch (mBackend) { @@ -677,6 +679,11 @@ class FEngine : public Engine { } debug; struct { + struct { + struct { + bool use_shadow_atlas = false; + } shadows; + } engine; struct { struct { bool assert_native_window_is_valid = false; @@ -686,7 +693,7 @@ class FEngine : public Engine { } backend; } features; - std::array const mFeatures{{ + std::array const mFeatures{{ { "backend.disable_parallel_shader_compile", "Disable parallel shader compilation in GL and Metal backends.", &features.backend.disable_parallel_shader_compile, true }, @@ -695,10 +702,13 @@ class FEngine : public Engine { &features.backend.disable_handle_use_after_free_check, true }, { "backend.opengl.assert_native_window_is_valid", "Asserts that the ANativeWindow is valid when rendering starts.", - &features.backend.opengl.assert_native_window_is_valid, true } + &features.backend.opengl.assert_native_window_is_valid, true }, + { "engine.shadows.use_shadow_atlas", + "Uses an array of atlases to store shadow maps.", + &features.engine.shadows.use_shadow_atlas, true } }}; - utils::Slice getFeatureFlags() const noexcept { + utils::Slice getFeatureFlags() const noexcept { return { mFeatures.data(), mFeatures.size() }; } diff --git a/filament/src/details/View.cpp b/filament/src/details/View.cpp index 36e38f0153e..24b203e48d2 100644 --- a/filament/src/details/View.cpp +++ b/filament/src/details/View.cpp @@ -364,21 +364,23 @@ void FView::prepareShadowing(FEngine& engine, FScene::RenderableSoa& renderableD if (UTILS_LIKELY(!lcm.isShadowCaster(li))) { // Because we early exit here, we need to make sure we mark the light as non-casting. // See `ShadowMapManager::updateSpotShadowMaps` for const_cast<> justification. - const_cast( - lightData.elementAt(l)).castsShadows = false; + auto& shadowInfo = const_cast( + lightData.elementAt(l)); + shadowInfo.castsShadows = false; continue; // doesn't cast shadows } const bool spotLight = lcm.isSpotLight(li); + const size_t maxShadowMapCount = engine.getMaxShadowMapCount(); const size_t shadowMapCountNeeded = spotLight ? 1 : 6; - if (shadowMapCount + shadowMapCountNeeded <= CONFIG_MAX_SHADOWMAPS) { + if (shadowMapCount + shadowMapCountNeeded <= maxShadowMapCount) { shadowMapCount += shadowMapCountNeeded; const auto& shadowOptions = lcm.getShadowOptions(li); builder.shadowMap(l, spotLight, &shadowOptions); } - if (shadowMapCount >= CONFIG_MAX_SHADOWMAPS) { + if (shadowMapCount >= maxShadowMapCount) { break; // we ran out of spotlight shadow casting } } diff --git a/libs/filabridge/include/private/filament/EngineEnums.h b/libs/filabridge/include/private/filament/EngineEnums.h index c2dd12f6cbd..2ba000c5db0 100644 --- a/libs/filabridge/include/private/filament/EngineEnums.h +++ b/libs/filabridge/include/private/filament/EngineEnums.h @@ -100,11 +100,11 @@ constexpr size_t CONFIG_MAX_LIGHT_INDEX = CONFIG_MAX_LIGHT_COUNT - 1; // Updating this value necessitates a material version bump. constexpr size_t CONFIG_MAX_RESERVED_SPEC_CONSTANTS = 16; -// The maximum number of shadowmaps. -// There is currently a maximum limit of 128 shadowmaps. +// The maximum number of shadow maps possible. +// There is currently a maximum limit of 128 shadow maps. // Factors contributing to this limit: // - minspec for UBOs is 16KiB, which currently can hold a maximum of 128 entries -constexpr size_t CONFIG_MAX_SHADOWMAPS = 64; +constexpr size_t CONFIG_MAX_SHADOWMAPS = 128; // The maximum number of shadow layers. // There is currently a maximum limit of 255 layers. @@ -112,7 +112,8 @@ constexpr size_t CONFIG_MAX_SHADOWMAPS = 64; // - minspec for 2d texture arrays layer is 256 // - we're using uint8_t to store the number of layers (255 max) // - nonsensical to be larger than the number of shadowmaps -constexpr size_t CONFIG_MAX_SHADOW_LAYERS = CONFIG_MAX_SHADOWMAPS; +// - AtlasAllocator depth limits it to 64 +constexpr size_t CONFIG_MAX_SHADOW_LAYERS = 64; // The maximum number of shadow cascades that can be used for directional lights. constexpr size_t CONFIG_MAX_SHADOW_CASCADES = 4;