diff options
author | Paul Lemire <paul.lemire@kdab.com> | 2020-04-30 16:09:46 +0200 |
---|---|---|
committer | Paul Lemire <paul.lemire@kdab.com> | 2020-06-04 14:02:33 +0200 |
commit | 80e73b0a8537c794be187e8847f84d376c30bd66 (patch) | |
tree | a28c70f7985678880f16982a7da1260beebc0c06 /src/plugins/renderers/opengl/renderer/renderviewbuilder.cpp | |
parent | 0c9c4e163458f99ac350aef4979754cbe147dac3 (diff) |
Leverage RV cache to reuse render commands when possible
- Only perform render command filtering when needed (when camera has moved usually)
- Keep RenderCommand around so that we only update them if needed:
-> We keep a double set of commands we per RV and switch in between every frame
- Introduce EntityRenderCommandDataView as a wrapper around RenderCommands
-> Use std::vector for RenderCommand storage (avoids hidden detachments)
-> Filter and sort indices into the RenderCommand vector rather than the commands
directly
- Cleanup RenderView
-> Remove InnerData field
-> Hide direct RenderCommand access
- Next steps
-> Only update uniforms that need to be updated
-> Most likely lights/standard uniforms won't all change every frame
-> Only update light sources for entity when needed
Change-Id: I153822a16c0989a8ac5b83d756385056c96572aa
Reviewed-by: Mike Krus <mike.krus@kdab.com>
(cherry picked from commit 59345bc1a733ee035b342feecadc923e062a850c)
Diffstat (limited to 'src/plugins/renderers/opengl/renderer/renderviewbuilder.cpp')
-rw-r--r-- | src/plugins/renderers/opengl/renderer/renderviewbuilder.cpp | 185 |
1 files changed, 129 insertions, 56 deletions
diff --git a/src/plugins/renderers/opengl/renderer/renderviewbuilder.cpp b/src/plugins/renderers/opengl/renderer/renderviewbuilder.cpp index 6ba1ac361..e971c3f3c 100644 --- a/src/plugins/renderers/opengl/renderer/renderviewbuilder.cpp +++ b/src/plugins/renderers/opengl/renderer/renderviewbuilder.cpp @@ -114,10 +114,12 @@ class SyncRenderViewPostCommandUpdate public: explicit SyncRenderViewPostCommandUpdate(const RenderViewInitializerJobPtr &renderViewJob, const QVector<RenderViewCommandUpdaterJobPtr> &renderViewCommandUpdateJobs, - Renderer *renderer) + Renderer *renderer, + FrameGraphNode *leafNode) : m_renderViewJob(renderViewJob) , m_renderViewCommandUpdaterJobs(renderViewCommandUpdateJobs) , m_renderer(renderer) + , m_leafNode(leafNode) {} void operator()() @@ -125,15 +127,30 @@ public: // Append all the commands and sort them RenderView *rv = m_renderViewJob->renderView(); - const EntityRenderCommandDataPtr commandData = m_renderViewCommandUpdaterJobs.first()->renderables(); - - if (commandData) { - const QVector<RenderCommand> commands = std::move(commandData->commands); - rv->setCommands(commands); + if (!rv->noDraw()) { + RendererCache *cache = m_renderer->cache(); + RendererCache::LeafNodeData &writableCacheForLeaf = cache->leafNodeCache[m_leafNode]; - // TO DO: Find way to store commands once or at least only when required - // Sort the commands + // Sort command on RenderView rv->sort(); + + // Flip between the 2 EntityRenderCommandDataView on the leaf node case + { + const int currentViewIdx = writableCacheForLeaf.viewIdx; + const int nextViewIdx = 1 - currentViewIdx; + EntityRenderCommandDataViewPtr currentDataView = writableCacheForLeaf.filteredRenderCommandDataViews[currentViewIdx]; + + // In case the next view has yet to be initialized, we make a copy of the current + // view + if (writableCacheForLeaf.filteredRenderCommandDataViews[nextViewIdx].isNull()) { + EntityRenderCommandDataViewPtr nextDataView = EntityRenderCommandDataViewPtr::create(); + nextDataView->data = currentDataView->data; + nextDataView->indices = currentDataView->indices; + writableCacheForLeaf.filteredRenderCommandDataViews[nextViewIdx] = nextDataView; + } + // Flip index for next frame + writableCacheForLeaf.viewIdx = nextViewIdx; + } } // TO DO: Record the commandData information with the idea of being to @@ -148,6 +165,7 @@ private: RenderViewInitializerJobPtr m_renderViewJob; QVector<RenderViewCommandUpdaterJobPtr> m_renderViewCommandUpdaterJobs; Renderer *m_renderer; + FrameGraphNode *m_leafNode; }; class SyncPreFrustumCulling @@ -275,11 +293,27 @@ public: // no conflict const bool isDraw = !rv->isCompute(); - const RendererCache::LeafNodeData &dataCacheForLeaf = cache->leafNodeCache[m_leafNode]; + RendererCache::LeafNodeData &cacheForLeaf = cache->leafNodeCache[m_leafNode]; const bool fullRebuild = m_rebuildFlags.testFlag(RebuildFlag::FullCommandRebuild); const bool layerFilteringRebuild = m_rebuildFlags.testFlag(RebuildFlag::LayerCacheRebuild); const bool lightsCacheRebuild = m_rebuildFlags.testFlag(RebuildFlag::LightCacheRebuild); + const bool cameraDirty = cacheForLeaf.viewProjectionMatrix != rv->viewProjectionMatrix(); + const bool hasProximityFilter = !rv->proximityFilterIds().empty(); + const bool commandFilteringRequired = + fullRebuild || + layerFilteringRebuild || + lightsCacheRebuild || + cameraDirty || + hasProximityFilter || + cacheForLeaf.requestFilteringAtNextFrame; + + // Reset flag on leaf + cacheForLeaf.requestFilteringAtNextFrame = false; + + // If we have no filteredRenderCommandDataViews then we should have fullRebuild set to true + // otherwise something is wrong + Q_ASSERT(fullRebuild || cacheForLeaf.filteredRenderCommandDataViews[cacheForLeaf.viewIdx]); // Rebuild RenderCommands if required // This should happen fairly infrequently (FrameGraph Change, Geometry/Material change) @@ -297,92 +331,130 @@ public: } // Store new cache - RendererCache::LeafNodeData &writableCacheForLeaf = cache->leafNodeCache[m_leafNode]; - writableCacheForLeaf.renderCommandData = std::move(commandData); + EntityRenderCommandDataViewPtr dataView = EntityRenderCommandDataViewPtr::create(); + dataView->data = std::move(commandData); + // Store the update dataView + cacheForLeaf.filteredRenderCommandDataViews[cacheForLeaf.viewIdx] = dataView; + // Clear the other dataView + cacheForLeaf.filteredRenderCommandDataViews[1 - cacheForLeaf.viewIdx].clear(); } - const EntityRenderCommandData commandData = dataCacheForLeaf.renderCommandData; // Should be fairly infrequent if (layerFilteringRebuild || fullRebuild) { - RendererCache::LeafNodeData &writableCacheForLeaf = cache->leafNodeCache[m_leafNode]; - // Filter out renderable entities that weren't selected by the layer filters and store that in cache - writableCacheForLeaf.layeredFilteredRenderables = RenderViewBuilder::entitiesInSubset( + cacheForLeaf.layeredFilteredRenderables = RenderViewBuilder::entitiesInSubset( isDraw ? cache->renderableEntities : cache->computeEntities, - dataCacheForLeaf.filterEntitiesByLayer); + cacheForLeaf.filterEntitiesByLayer); + // Set default value for filteredAndCulledRenderables + if (isDraw) + cacheForLeaf.filteredAndCulledRenderables = cacheForLeaf.layeredFilteredRenderables; } + // Should be fairly infrequent if (lightsCacheRebuild) { // Filter out renderable entities that weren't selected by the // layer filters and store that in cache - const QVector<Entity *> &layeredFilteredEntities = dataCacheForLeaf.filterEntitiesByLayer; + const QVector<Entity *> &layeredFilteredEntities = cacheForLeaf.filterEntitiesByLayer; QVector<LightSource> filteredLightSources = cache->gatheredLights; for (int i = 0; i < filteredLightSources.count(); ++i) { if (!layeredFilteredEntities.contains(filteredLightSources[i].entity)) filteredLightSources.removeAt(i--); } - RendererCache::LeafNodeData &writableCacheForLeaf = cache->leafNodeCache[m_leafNode]; - writableCacheForLeaf.layeredFilteredLightSources = filteredLightSources; + cacheForLeaf.layeredFilteredLightSources = filteredLightSources; } - QVector<Entity *> renderableEntities = dataCacheForLeaf.layeredFilteredRenderables; - rv->setMaterialParameterTable(dataCacheForLeaf.materialParameterGatherer); + // This is likely very frequent + if (cameraDirty) { + // Record the updated viewProjectionMatrix in the cache to allow check to be performed + // next frame + cacheForLeaf.viewProjectionMatrix = rv->viewProjectionMatrix(); + + // Filter out frustum culled entity for drawable entities and store in cache + if (isDraw && rv->frustumCulling()) { + cacheForLeaf.filteredAndCulledRenderables = RenderViewBuilder::entitiesInSubset( + cacheForLeaf.layeredFilteredRenderables, + m_frustumCullingJob->visibleEntities()); + } + } + + rv->setMaterialParameterTable(cacheForLeaf.materialParameterGatherer); rv->setEnvironmentLight(cache->environmentLight); // Set the light sources, with layer filters applied. - rv->setLightSources(dataCacheForLeaf.layeredFilteredLightSources); + rv->setLightSources(cacheForLeaf.layeredFilteredLightSources); + + QVector<Entity *> renderableEntities = isDraw ? cacheForLeaf.filteredAndCulledRenderables : cacheForLeaf.layeredFilteredRenderables; + // TO DO: Find a way to do that only if proximity entities has changed if (isDraw) { - // Filter out frustum culled entity for drawable entities - if (rv->frustumCulling()) - renderableEntities = RenderViewBuilder::entitiesInSubset(renderableEntities, m_frustumCullingJob->visibleEntities()); // Filter out entities which didn't satisfy proximity filtering - if (!rv->proximityFilterIds().empty()) - renderableEntities = RenderViewBuilder::entitiesInSubset(renderableEntities, m_filterProximityJob->filteredEntities()); + if (hasProximityFilter) + renderableEntities = RenderViewBuilder::entitiesInSubset(renderableEntities, + m_filterProximityJob->filteredEntities()); } + EntityRenderCommandDataViewPtr filteredCommandData = cacheForLeaf.filteredRenderCommandDataViews[cacheForLeaf.viewIdx]; + + // Set RenderCommandDataView on RV (will be used later on to sort commands ...) + rv->setRenderCommandDataView(filteredCommandData); + // Early return in case we have nothing to filter if (renderableEntities.size() == 0) return; // Filter out Render commands for which the Entity wasn't selected because // of frustum, proximity or layer filtering - EntityRenderCommandDataPtr filteredCommandData = EntityRenderCommandDataPtr::create(); - filteredCommandData->reserve(renderableEntities.size()); - // Because dataCacheForLeaf.renderableEntities or computeEntities are sorted - // What we get out of EntityRenderCommandData is also sorted by Entity - auto eIt = renderableEntities.cbegin(); - const auto eEnd = renderableEntities.cend(); - int cIt = 0; - const int cEnd = commandData.size(); - - while (eIt != eEnd) { - const Entity *targetEntity = *eIt; - // Advance until we have commands whose Entity has a lower address - // than the selected filtered entity - while (cIt != cEnd && commandData.entities.at(cIt) < targetEntity) - ++cIt; - - // Push pointers to command data for all commands that match the - // entity - while (cIt != cEnd && commandData.entities.at(cIt) == targetEntity) { - filteredCommandData->push_back(commandData.entities.at(cIt), - commandData.commands.at(cIt), - commandData.passesData.at(cIt)); - ++cIt; + if (commandFilteringRequired) { + const std::vector<Entity *> &entities = filteredCommandData->data.entities; + // Because cacheForLeaf.renderableEntities or computeEntities are sorted + // What we get out of EntityRenderCommandData is also sorted by Entity + auto eIt = renderableEntities.cbegin(); + const auto eEnd = renderableEntities.cend(); + size_t cIt = 0; + const size_t cEnd = entities.size(); + + std::vector<size_t> filteredCommandIndices; + filteredCommandIndices.reserve(renderableEntities.size()); + + while (eIt != eEnd) { + const Entity *targetEntity = *eIt; + // Advance until we have commands whose Entity has a lower address + // than the selected filtered entity + while (cIt != cEnd && entities[cIt] < targetEntity) + ++cIt; + + // Push pointers to command data for all commands that match the + // entity + while (cIt != cEnd && entities[cIt] == targetEntity) { + filteredCommandIndices.push_back(cIt); + ++cIt; + } + ++eIt; } - ++eIt; + + // Store result in cache + cacheForLeaf.filteredRenderCommandDataViews[cacheForLeaf.viewIdx]->indices = std::move(filteredCommandIndices); + + // Request filtering at next frame (indices for view0 and view1 + // could mistmatch if something is dirty for frame 0 and not at + // frame 1 (given we have 2 views we alternate with) + cacheForLeaf.requestFilteringAtNextFrame = true; } // Split among the number of command updaters const int jobCount = m_renderViewCommandUpdaterJobs.size(); - const int idealPacketSize = std::min(std::max(10, filteredCommandData->size() / jobCount), filteredCommandData->size()); - const int m = findIdealNumberOfWorkers(filteredCommandData->size(), idealPacketSize, jobCount); + const int commandCount = filteredCommandData->size(); + const int idealPacketSize = std::min(std::max(10, commandCount), commandCount); + const int m = findIdealNumberOfWorkers(commandCount, idealPacketSize, jobCount); for (int i = 0; i < m; ++i) { + // TO DO: Based on whether we had to update the commands + // we should be able to know what needs to be recomputed + // -> lights/standard uniforms ... might no have to be set over and over again + // if they are identical const RenderViewCommandUpdaterJobPtr &renderViewCommandUpdater = m_renderViewCommandUpdaterJobs.at(i); - const int count = (i == m - 1) ? filteredCommandData->size() - (i * idealPacketSize) : idealPacketSize; - renderViewCommandUpdater->setRenderables(filteredCommandData, i * idealPacketSize, count); + const size_t count = (i == m - 1) ? commandCount - (i * idealPacketSize) : idealPacketSize; + renderViewCommandUpdater->setRenderablesSubView({filteredCommandData, size_t(i * idealPacketSize), count}); } } } @@ -655,7 +727,8 @@ void RenderViewBuilder::prepareJobs() m_syncRenderViewPostCommandUpdateJob = CreateSynchronizerJobPtr(SyncRenderViewPostCommandUpdate(m_renderViewJob, m_renderViewCommandUpdaterJobs, - m_renderer), + m_renderer, + m_leafNode), JobTypes::SyncRenderViewPostCommandUpdate); m_syncRenderViewPostInitializationJob = CreateSynchronizerJobPtr(SyncRenderViewPostInitialization(m_renderViewJob, |