1// Copyright (C) 2020 Klaralvdalens Datakonsult AB (KDAB).
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
4#ifndef QT3DRENDER_RENDER_RENDERSYNCJOBS_H
5#define QT3DRENDER_RENDER_RENDERSYNCJOBS_H
6
7//
8// W A R N I N G
9// -------------
10//
11// This file is not part of the Qt API. It exists for the convenience
12// of other Qt classes. This header file may change from version to
13// version without notice, or even be removed.
14//
15// We mean it.
16//
17
18#include <Qt3DCore/qaspectjob.h>
19#include <Qt3DRender/private/renderviewinitializerjob_p.h>
20#include <Qt3DRender/private/frustumcullingjob_p.h>
21#include <Qt3DRender/private/filterlayerentityjob_p.h>
22#include <Qt3DRender/private/filterproximitydistancejob_p.h>
23#include <Qt3DRender/private/materialparametergathererjob_p.h>
24#include <Qt3DRender/private/renderviewcommandbuilderjob_p.h>
25#include <Qt3DRender/private/renderviewcommandupdaterjob_p.h>
26#include <Qt3DRender/private/renderercache_p.h>
27
28QT_BEGIN_NAMESPACE
29
30namespace Qt3DRender {
31
32namespace Render {
33
34enum RebuildFlag {
35 FullCommandRebuild = 1 << 0,
36 LayerCacheRebuild = 1 << 1,
37 MaterialCacheRebuild = 1 << 2,
38 LightCacheRebuild = 1 << 3
39};
40Q_DECLARE_FLAGS(RebuildFlagSet, RebuildFlag)
41Q_DECLARE_OPERATORS_FOR_FLAGS(RebuildFlagSet)
42
43#define RenderViewInitializerJobPtrAlias RenderViewInitializerJobPtr<RenderView, Renderer>
44#define RenderViewCommandBuilderJobPtrAlias RenderViewCommandBuilderJobPtr<RenderView, RenderCommand>
45#define RenderViewCommandUpdaterJobPtrAlias RenderViewCommandUpdaterJobPtr<RenderView, RenderCommand>
46
47template<class RenderView, class Renderer, class RenderCommand>
48class SyncPreCommandBuilding
49{
50public:
51 explicit SyncPreCommandBuilding(RenderViewInitializerJobPtrAlias renderViewInitializerJob,
52 const std::vector<RenderViewCommandBuilderJobPtrAlias> &renderViewCommandBuilderJobs,
53 Renderer *renderer,
54 FrameGraphNode *leafNode)
55 : m_renderViewInitializer(renderViewInitializerJob)
56 , m_renderViewCommandBuilderJobs(renderViewCommandBuilderJobs)
57 , m_renderer(renderer)
58 , m_leafNode(leafNode)
59 {
60 }
61
62 void operator()()
63 {
64 // Split commands to build among jobs
65
66 // Rebuild RenderCommands for all entities in RV (ignoring filtering)
67 auto *cache = m_renderer->cache();
68 QMutexLocker lock(cache->mutex());
69
70 Q_ASSERT(cache->leafNodeCache.contains(m_leafNode));
71 // The cache leaf should already have been created so we don't need to protect the access
72 const auto &dataCacheForLeaf = cache->leafNodeCache[m_leafNode];
73 RenderView *rv = m_renderViewInitializer->renderView();
74 const auto &entities = !rv->isCompute() ? cache->renderableEntities : cache->computeEntities;
75
76 rv->setMaterialParameterTable(dataCacheForLeaf.materialParameterGatherer);
77
78 // Split among the ideal number of command builders
79 const int jobCount = int(m_renderViewCommandBuilderJobs.size());
80 const int entityCount = int(entities.size());
81 const int idealPacketSize = std::min(a: std::max(a: 10, b: entityCount / jobCount), b: entityCount);
82 // Try to split work into an ideal number of workers
83 const int m = findIdealNumberOfWorkers(elementCount: entityCount, packetSize: idealPacketSize, maxJobCount: jobCount);
84
85 const Entity **entitiesPtr = const_cast<const Entity **>(entities.data());
86 for (int i = 0; i < m; ++i) {
87 const auto &renderViewCommandBuilder = m_renderViewCommandBuilderJobs[i];
88 const int count = (i == m - 1) ? entityCount - (i * idealPacketSize) : idealPacketSize;
89 renderViewCommandBuilder->setEntities(entitiesPtr, i * idealPacketSize, count);
90 }
91 }
92
93private:
94 RenderViewInitializerJobPtrAlias m_renderViewInitializer;
95 std::vector<RenderViewCommandBuilderJobPtrAlias> m_renderViewCommandBuilderJobs;
96 Renderer *m_renderer;
97 FrameGraphNode *m_leafNode;
98};
99
100template<class RenderView, class Renderer, class RenderCommand>
101class SyncRenderViewPostCommandUpdate
102{
103public:
104 explicit SyncRenderViewPostCommandUpdate(const RenderViewInitializerJobPtrAlias &renderViewJob,
105 const std::vector<RenderViewCommandUpdaterJobPtrAlias> &renderViewCommandUpdateJobs,
106 Renderer *renderer)
107 : m_renderViewJob(renderViewJob)
108 , m_renderViewCommandUpdaterJobs(renderViewCommandUpdateJobs)
109 , m_renderer(renderer)
110 {
111 }
112
113 void operator()()
114 {
115 // Append all the commands and sort them
116 RenderView *rv = m_renderViewJob->renderView();
117
118 if (!rv->noDraw()) {
119 // Sort command on RenderView
120 rv->sort();
121 }
122 // Enqueue our fully populated RenderView with the RenderThread
123 m_renderer->enqueueRenderView(rv, m_renderViewJob->submitOrderIndex());
124 }
125
126private:
127 RenderViewInitializerJobPtrAlias m_renderViewJob;
128 std::vector<RenderViewCommandUpdaterJobPtrAlias> m_renderViewCommandUpdaterJobs;
129 Renderer *m_renderer;
130};
131
132template<class RenderView, class Renderer>
133class SyncPreFrustumCulling
134{
135public:
136 explicit SyncPreFrustumCulling(const RenderViewInitializerJobPtrAlias &renderViewJob,
137 const FrustumCullingJobPtr &frustumCulling)
138 : m_renderViewJob(renderViewJob)
139 , m_frustumCullingJob(frustumCulling)
140 {}
141
142 void operator()()
143 {
144 RenderView *rv = m_renderViewJob->renderView();
145
146 // Update matrices now that all transforms have been updated
147 rv->updateMatrices();
148
149 // Frustum culling
150 m_frustumCullingJob->setViewProjection(rv->viewProjectionMatrix());
151 }
152
153private:
154 RenderViewInitializerJobPtrAlias m_renderViewJob;
155 FrustumCullingJobPtr m_frustumCullingJob;
156};
157
158template<class RenderView, class Renderer, class RenderCommand>
159class SyncRenderViewPostInitialization
160{
161public:
162 explicit SyncRenderViewPostInitialization(const RenderViewInitializerJobPtrAlias &renderViewJob,
163 const FrustumCullingJobPtr &frustumCullingJob,
164 const FilterLayerEntityJobPtr &filterEntityByLayerJob,
165 const FilterProximityDistanceJobPtr &filterProximityJob,
166 const std::vector<MaterialParameterGathererJobPtr> &materialGathererJobs,
167 const std::vector<RenderViewCommandUpdaterJobPtrAlias> &renderViewCommandUpdaterJobs,
168 const std::vector<RenderViewCommandBuilderJobPtrAlias> &renderViewCommandBuilderJobs)
169 : m_renderViewJob(renderViewJob)
170 , m_frustumCullingJob(frustumCullingJob)
171 , m_filterEntityByLayerJob(filterEntityByLayerJob)
172 , m_filterProximityJob(filterProximityJob)
173 , m_materialGathererJobs(materialGathererJobs)
174 , m_renderViewCommandUpdaterJobs(renderViewCommandUpdaterJobs)
175 , m_renderViewCommandBuilderJobs(renderViewCommandBuilderJobs)
176 {}
177
178 void operator()()
179 {
180 RenderView *rv = m_renderViewJob->renderView();
181
182 // Layer filtering
183 if (!m_filterEntityByLayerJob.isNull())
184 m_filterEntityByLayerJob->setLayerFilters(rv->layerFilters());
185
186 // Proximity filtering
187 m_filterProximityJob->setProximityFilterIds(rv->proximityFilterIds());
188
189 // Material Parameter building
190 for (const auto &materialGatherer : m_materialGathererJobs) {
191 materialGatherer->setRenderPassFilter(const_cast<RenderPassFilter *>(rv->renderPassFilter()));
192 materialGatherer->setTechniqueFilter(const_cast<TechniqueFilter *>(rv->techniqueFilter()));
193 }
194
195 // Command builders and updates
196 for (const auto &renderViewCommandUpdater : m_renderViewCommandUpdaterJobs)
197 renderViewCommandUpdater->setRenderView(rv);
198 for (const auto &renderViewCommandBuilder : m_renderViewCommandBuilderJobs)
199 renderViewCommandBuilder->setRenderView(rv);
200
201 // Set whether frustum culling is enabled or not
202 m_frustumCullingJob->setActive(rv->frustumCulling());
203 }
204
205private:
206 RenderViewInitializerJobPtrAlias m_renderViewJob;
207 FrustumCullingJobPtr m_frustumCullingJob;
208 FilterLayerEntityJobPtr m_filterEntityByLayerJob;
209 FilterProximityDistanceJobPtr m_filterProximityJob;
210 std::vector<MaterialParameterGathererJobPtr> m_materialGathererJobs;
211 std::vector<RenderViewCommandUpdaterJobPtrAlias> m_renderViewCommandUpdaterJobs;
212 std::vector<RenderViewCommandBuilderJobPtrAlias> m_renderViewCommandBuilderJobs;
213};
214
215template<class RenderView, class Renderer, class RenderCommand>
216class SyncRenderViewPreCommandUpdate
217{
218public:
219 explicit SyncRenderViewPreCommandUpdate(const RenderViewInitializerJobPtrAlias &renderViewJob,
220 const FrustumCullingJobPtr &frustumCullingJob,
221 const FilterProximityDistanceJobPtr &filterProximityJob,
222 const std::vector<MaterialParameterGathererJobPtr> &materialGathererJobs,
223 const std::vector<RenderViewCommandUpdaterJobPtrAlias> &renderViewCommandUpdaterJobs,
224 const std::vector<RenderViewCommandBuilderJobPtrAlias> &renderViewCommandBuilderJobs,
225 Renderer *renderer,
226 FrameGraphNode *leafNode,
227 RebuildFlagSet rebuildFlags)
228 : m_renderViewJob(renderViewJob)
229 , m_frustumCullingJob(frustumCullingJob)
230 , m_filterProximityJob(filterProximityJob)
231 , m_materialGathererJobs(materialGathererJobs)
232 , m_renderViewCommandUpdaterJobs(renderViewCommandUpdaterJobs)
233 , m_renderViewCommandBuilderJobs(renderViewCommandBuilderJobs)
234 , m_renderer(renderer)
235 , m_leafNode(leafNode)
236 , m_rebuildFlags(rebuildFlags)
237 {}
238
239 void operator()()
240 {
241 // Set the result of previous job computations
242 // for final RenderCommand building
243 RenderView *rv = m_renderViewJob->renderView();
244
245 if (!rv->noDraw()) {
246 ///////// CACHE LOCKED ////////////
247 // Retrieve Data from Cache
248 auto *cache = m_renderer->cache();
249 QMutexLocker lock(cache->mutex());
250 Q_ASSERT(cache->leafNodeCache.contains(m_leafNode));
251
252 // We don't need to protect the cache access as
253 // 1) The cache leaf is created
254 // 2) We are only reading conccurently the cache values that are shared across all RV
255 // 3) Each instance of this job is reading and writing in its own cache leaf so there's
256 // no conflict
257
258 const bool isDraw = !rv->isCompute();
259 auto &cacheForLeaf = cache->leafNodeCache[m_leafNode];
260
261 const bool fullRebuild = m_rebuildFlags.testFlag(flag: RebuildFlag::FullCommandRebuild);
262 const bool layerFilteringRebuild = m_rebuildFlags.testFlag(flag: RebuildFlag::LayerCacheRebuild);
263 const bool lightsCacheRebuild = m_rebuildFlags.testFlag(flag: RebuildFlag::LightCacheRebuild);
264 const bool cameraDirty = cacheForLeaf.viewProjectionMatrix != rv->viewProjectionMatrix();
265 const bool hasProximityFilter = !rv->proximityFilterIds().empty();
266 bool commandFilteringRequired =
267 fullRebuild ||
268 layerFilteringRebuild ||
269 lightsCacheRebuild ||
270 cameraDirty ||
271 hasProximityFilter;
272
273 // If we have no filteredRenderCommandDataViews then we should have fullRebuild set to true
274 // otherwise something is wrong
275 Q_ASSERT(fullRebuild || cacheForLeaf.filteredRenderCommandDataViews);
276
277 // Rebuild RenderCommands if required
278 // This should happen fairly infrequently (FrameGraph Change, Geometry/Material change)
279 // and allow to skip that step most of the time
280 if (fullRebuild) {
281 EntityRenderCommandData<RenderCommand> commandData;
282 // Reduction
283 {
284 int totalCommandCount = 0;
285 for (const RenderViewCommandBuilderJobPtrAlias &renderViewCommandBuilder : std::as_const(m_renderViewCommandBuilderJobs))
286 totalCommandCount += int(renderViewCommandBuilder->commandData().size());
287 commandData.reserve(totalCommandCount);
288 for (const RenderViewCommandBuilderJobPtrAlias &renderViewCommandBuilder : std::as_const(m_renderViewCommandBuilderJobs))
289 commandData += std::move(renderViewCommandBuilder->commandData());
290 }
291
292 // Store new cache
293 auto dataView = EntityRenderCommandDataViewPtr<RenderCommand>::create();
294 dataView->data = std::move(commandData);
295 // Store the update dataView
296 cacheForLeaf.filteredRenderCommandDataViews = dataView;
297 }
298
299
300 // Should be fairly infrequent
301 if (layerFilteringRebuild || fullRebuild) {
302 // Filter out renderable entities that weren't selected by the layer filters and store that in cache
303 cacheForLeaf.layeredFilteredRenderables = entitiesInSubset(
304 isDraw ? cache->renderableEntities : cache->computeEntities,
305 cacheForLeaf.filterEntitiesByLayer);
306 // Set default value for filteredAndCulledRenderables
307 if (isDraw)
308 cacheForLeaf.filteredAndCulledRenderables = cacheForLeaf.layeredFilteredRenderables;
309 }
310
311 // Should be fairly infrequent
312 if (lightsCacheRebuild) {
313 // Filter out light sources that weren't selected by the
314 // layer filters and store that in cache
315 const std::vector<Entity *> &layeredFilteredEntities = cacheForLeaf.filterEntitiesByLayer;
316 std::vector<LightSource> filteredLightSources = cache->gatheredLights;
317
318 auto it = filteredLightSources.begin();
319
320 while (it != filteredLightSources.end()) {
321 if (!std::binary_search(first: layeredFilteredEntities.begin(),
322 last: layeredFilteredEntities.end(),
323 val: it->entity))
324 it = filteredLightSources.erase(position: it);
325 else
326 ++it;
327 }
328 cacheForLeaf.layeredFilteredLightSources = std::move(filteredLightSources);
329 }
330
331 // This is likely very frequent
332 if (cameraDirty) {
333 // Record the updated viewProjectionMatrix in the cache to allow check to be performed
334 // next frame
335 cacheForLeaf.viewProjectionMatrix = rv->viewProjectionMatrix();
336 }
337
338 // Filter out frustum culled entity for drawable entities and store in cache
339 // We need to check this regardless of whether the camera has moved since
340 // entities in the scene themselves could have moved
341 if (isDraw && rv->frustumCulling()) {
342 const std::vector<Entity *> &subset = entitiesInSubset(
343 cacheForLeaf.layeredFilteredRenderables,
344 m_frustumCullingJob->visibleEntities());
345 // Force command filtering if what we contain in cache and what we filtered differ
346 commandFilteringRequired |= (subset != cacheForLeaf.filteredAndCulledRenderables);
347 cacheForLeaf.filteredAndCulledRenderables = subset;
348 }
349
350 rv->setMaterialParameterTable(cacheForLeaf.materialParameterGatherer);
351 rv->setEnvironmentLight(cache->environmentLight);
352
353 // Set the light sources, with layer filters applied.
354 rv->setLightSources(cacheForLeaf.layeredFilteredLightSources);
355
356 std::vector<Entity *> renderableEntities = isDraw ? cacheForLeaf.filteredAndCulledRenderables : cacheForLeaf.layeredFilteredRenderables;
357
358 // TO DO: Find a way to do that only if proximity entities has changed
359 if (isDraw) {
360 // Filter out entities which didn't satisfy proximity filtering
361 if (hasProximityFilter)
362 renderableEntities = entitiesInSubset(entities: renderableEntities,
363 subset: m_filterProximityJob->filteredEntities());
364 }
365
366 EntityRenderCommandDataViewPtr<RenderCommand> filteredCommandData = cacheForLeaf.filteredRenderCommandDataViews;
367
368 // Set RenderCommandDataView on RV (will be used later on to sort commands ...)
369 rv->setRenderCommandDataView(filteredCommandData);
370
371 // Filter out Render commands for which the Entity wasn't selected because
372 // of frustum, proximity or layer filtering
373 if (commandFilteringRequired) {
374 const std::vector<const Entity *> &entities = filteredCommandData->data.entities;
375 // Because cacheForLeaf.renderableEntities or computeEntities are sorted
376 // What we get out of EntityRenderCommandData is also sorted by Entity
377 auto eIt = renderableEntities.cbegin();
378 const auto eEnd = renderableEntities.cend();
379 size_t cIt = 0;
380 const size_t cEnd = entities.size();
381
382 std::vector<size_t> filteredCommandIndices;
383 filteredCommandIndices.reserve(n: renderableEntities.size());
384
385 while (eIt != eEnd) {
386 const Entity *targetEntity = *eIt;
387 // Advance until we have commands whose Entity has a lower address
388 // than the selected filtered entity
389 while (cIt != cEnd && entities[cIt] < targetEntity)
390 ++cIt;
391
392 // Push pointers to command data for all commands that match the
393 // entity
394 while (cIt != cEnd && entities[cIt] == targetEntity) {
395 filteredCommandIndices.push_back(x: cIt);
396 ++cIt;
397 }
398 ++eIt;
399 }
400
401 // Store result in cache
402 cacheForLeaf.filteredRenderCommandDataViews->indices = std::move(filteredCommandIndices);
403 }
404
405 // Split among the number of command updaters
406 const int jobCount = int(m_renderViewCommandUpdaterJobs.size());
407 const int commandCount = int(filteredCommandData->size());
408 const int idealPacketSize = std::min(a: std::max(a: 10, b: commandCount), b: commandCount);
409 const int m = findIdealNumberOfWorkers(elementCount: commandCount, packetSize: idealPacketSize, maxJobCount: jobCount);
410
411 for (int i = 0; i < m; ++i) {
412 // TO DO: Based on whether we had to update the commands
413 // we should be able to know what needs to be recomputed
414 // -> lights/standard uniforms ... might no have to be set over and over again
415 // if they are identical
416 const RenderViewCommandUpdaterJobPtrAlias &renderViewCommandUpdater = m_renderViewCommandUpdaterJobs.at(i);
417 const size_t count = (i == m - 1) ? commandCount - (i * idealPacketSize) : idealPacketSize;
418 renderViewCommandUpdater->setRenderablesSubView({filteredCommandData, size_t(i * idealPacketSize), count});
419 }
420 }
421 }
422
423private:
424 RenderViewInitializerJobPtrAlias m_renderViewJob;
425 FrustumCullingJobPtr m_frustumCullingJob;
426 FilterProximityDistanceJobPtr m_filterProximityJob;
427 std::vector<MaterialParameterGathererJobPtr> m_materialGathererJobs;
428 std::vector<RenderViewCommandUpdaterJobPtrAlias> m_renderViewCommandUpdaterJobs;
429 std::vector<RenderViewCommandBuilderJobPtrAlias> m_renderViewCommandBuilderJobs;
430 Renderer *m_renderer;
431 FrameGraphNode *m_leafNode;
432 RebuildFlagSet m_rebuildFlags;
433};
434
435template<class Renderer>
436class SyncFilterEntityByLayer
437{
438public:
439 explicit SyncFilterEntityByLayer(const FilterLayerEntityJobPtr &filterEntityByLayerJob,
440 Renderer *renderer,
441 FrameGraphNode *leafNode)
442 : m_filterEntityByLayerJob(filterEntityByLayerJob)
443 , m_renderer(renderer)
444 , m_leafNode(leafNode)
445 {
446 }
447
448 void operator()()
449 {
450 QMutexLocker lock(m_renderer->cache()->mutex());
451 Q_ASSERT(m_renderer->cache()->leafNodeCache.contains(m_leafNode));
452 // The cache leaf should already have been created so we don't need to protect the access
453 auto &dataCacheForLeaf = m_renderer->cache()->leafNodeCache[m_leafNode];
454 // Save the filtered by layer subset into the cache
455 dataCacheForLeaf.filterEntitiesByLayer = std::move(m_filterEntityByLayerJob->filteredEntities());
456 }
457
458private:
459 FilterLayerEntityJobPtr m_filterEntityByLayerJob;
460 Renderer *m_renderer;
461 FrameGraphNode *m_leafNode;
462};
463
464template<class Renderer>
465class SyncMaterialParameterGatherer
466{
467public:
468 explicit SyncMaterialParameterGatherer(const std::vector<MaterialParameterGathererJobPtr> &materialParameterGathererJobs,
469 Renderer *renderer,
470 FrameGraphNode *leafNode)
471 : m_materialParameterGathererJobs(materialParameterGathererJobs)
472 , m_renderer(renderer)
473 , m_leafNode(leafNode)
474 {
475 }
476
477 void operator()()
478 {
479 // The cache leaf was created by SyncRenderViewPostInitialization on which we depend
480 // so we don't need to protect the access
481 QMutexLocker lock(m_renderer->cache()->mutex());
482 auto &dataCacheForLeaf = m_renderer->cache()->leafNodeCache[m_leafNode];
483 dataCacheForLeaf.materialParameterGatherer.clear();
484
485 for (const auto &materialGatherer : m_materialParameterGathererJobs) {
486 const MaterialParameterGathererData &source = materialGatherer->materialToPassAndParameter();
487 for (auto it = std::begin(cont: source); it != std::end(cont: source); ++it) {
488 Q_ASSERT(!dataCacheForLeaf.materialParameterGatherer.contains(it.key()));
489 dataCacheForLeaf.materialParameterGatherer.insert(it.key(), it.value());
490 }
491 }
492 }
493
494private:
495 std::vector<MaterialParameterGathererJobPtr> m_materialParameterGathererJobs;
496 Renderer *m_renderer;
497 FrameGraphNode *m_leafNode;
498};
499
500} // Render
501
502} // Qt3DRender
503
504QT_END_NAMESPACE
505
506#endif // RENDERSYNCJOBS_H
507

source code of qt3d/src/render/jobs/rendersyncjobs_p.h