summaryrefslogtreecommitdiffstats
path: root/src/plugins/renderers/opengl/renderer/renderviewbuilder.cpp
diff options
context:
space:
mode:
authorPaul Lemire <paul.lemire@kdab.com>2020-04-30 16:09:46 +0200
committerPaul Lemire <paul.lemire@kdab.com>2020-06-04 14:02:33 +0200
commit80e73b0a8537c794be187e8847f84d376c30bd66 (patch)
treea28c70f7985678880f16982a7da1260beebc0c06 /src/plugins/renderers/opengl/renderer/renderviewbuilder.cpp
parent0c9c4e163458f99ac350aef4979754cbe147dac3 (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.cpp185
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,