Cogs.Core
ManageLodLevels.cpp
1#include "Context.h"
2#include "Services/Variables.h"
3#include "Services/QualityService.h"
4#include "Systems/Core/CameraSystem.h"
5#include "Systems/Core/TransformSystem.h"
6#include "Systems/Core/ClipShapeSystem.h"
7#include "Renderer/Renderer.h"
8#include "Utilities/Math.h"
9
10#include "PotreeSystem.h"
11
12#include "Foundation/Logging/Logger.h"
13#include "Foundation/BitTwiddling/PowerOfTwo.h"
14
15#include "Rendering/IGraphicsDevice.h"
16#include "Rendering/ICapabilities.h"
17
18
19#include <algorithm>
20
21namespace {
22 using namespace Cogs::Core;
24
25 const Cogs::StringView lodFreezeName = "potree.lodFreeze";
26
27 const Cogs::StringView pointsMaxName = "potree.points.maxCount";
28 constexpr int pointsMaxDefault = 3'000'000;
29 const Cogs::StringView chunksMaxName = "potree.chunks.maxCount";
30 constexpr int chunksMaxDefault = 1000;
31
32 const Cogs::StringView requestsMaxConcurrentName = "potree.requests.maxConcurrent";
33 constexpr int requestsMaxConcurrentDefault = 20;
34 const Cogs::StringView requestsMaxPerFrameName = "potree.requests.maxPerFrame";
35 constexpr int requestsMaxPerFrameDefault = 2;
36
37 struct ManageState
38 {
39 Context* context;
40 PotreeSystem* poSystem;
41 glm::mat4 P;
42 glm::mat4 V;
43 float viewHeightPerPixel;
44 uint32_t pointsCount;
45 uint32_t pointsMaxCount;
46 uint32_t activeCellCount;
47 uint32_t maxActiveCells;
48 uint32_t requestsInFlight;
49 uint32_t requestsIssuedThisFrame;
50 uint32_t maxConcurrentRequests;
51 uint32_t maxPerFrameRequests;
52 uint32_t maxOctreeSize; // Max size of octtree, currently limited by texture 2D max X size.
53
54 // Accounting for debugging presented via variables
55 uint32_t insideTestEarly1 = 0;
56 uint32_t insideTestEarly2 = 0;
57 uint32_t insideTestClip = 0;
58 uint32_t insideTestSeparated = 0;
59 uint32_t insideTestPassed = 0;
60
61 bool wantAnotherFrameUpdate = false;
62 };
63
64 bool potreeNodeHeapCmp(const PotreeNodeHeapItem& a, const PotreeNodeHeapItem& b)
65 {
66 return a.priority < b.priority;
67 }
68
69 struct Updater
70 {
71
72 typedef List<PotreeCell, offsetof(PotreeCell, activeNode)> ActiveList;
73
74 const PotreeComponent* poComp = nullptr;
75 PotreeData* poData = nullptr;
76
77 ActiveList& activeCellsNew;
78 ActiveList activeCellsOld;
79
80 glm::u8vec4* tex = nullptr;
81
82 glm::mat4 VM;
83 glm::mat4 PVM;
84 glm::mat4 PVMinv;
85
86 glm::vec3 eye; // Camera origin in model space
87
88 static constexpr size_t directionsCount = 7 * 3;
89 glm::vec3 directions[directionsCount];
90 float frustumMin[directionsCount];
91 float frustumMax[directionsCount];
92
93
94 glm::vec4 clipPlanes[PotreeData::MaxClipPlanes];
95 size_t clipPlaneCount = 0;
96 bool clipTestInvert = false;
97
98 uint32_t numActiveCells = 0;
99
100 float VMScale = 1.f; // Overall scale factor for the view-modelview transform
101
102 float toleranceSquared = 0.f;
103 float rootToleranceSquared = 0.f;
104 float priority = 0.f;
105 float distanceCutoffScale = 1.f;
106 float distanceCutoffMinimum = -1.f;
107 float distanceCutoffSquared = 0.f;
108
109 Updater(ManageState& state,
110 const PotreeComponent* poComp,
111 PotreeData* poData)
112 : poComp(poComp),
113 poData(poData),
114 activeCellsNew(poData->activeCells)
115 {
116 // Move current active set to activeCellsOld and clean current active set.
117 activeCellsOld.swap(activeCellsNew);
118 const glm::mat4& M = poData->worldFromOcttreeFrame;
119
120 if (poComp->clipPlanes.empty()) {
121
122 clipPlaneCount = 0;
123 if (const ClipShapeComponent* clipComp = poComp->clipShapeComponent.resolveComponent<ClipShapeComponent>(); clipComp) {
124 const ClipShapeData& clipData = state.context->clipShapeSystem->getData(clipComp);
125
126 switch (clipData.shape) {
127 case ClipShapeType::None:
128 break;
129 case ClipShapeType::Cube:
130 case ClipShapeType::InvertedCube:
131 assert(clipData.planeCount == 6);
132 clipPlaneCount = clipData.planeCount;
133 for (size_t i = 0; i < clipPlaneCount; i++) {
134 clipPlanes[i] = clipData.planes[i] * M;
135 }
136 clipTestInvert = clipData.shape == ClipShapeType::InvertedCube;
137 break;
138 default:
139 assert(false && "Unhandled case");
140 break;
141 }
142 }
143 }
144 else {
145
146 clipPlaneCount = std::min(poComp->disableCustomClipping ? size_t(0) : PotreeData::MaxClipPlanes, poComp->clipPlanes.size());
147 for (size_t i = 0; i < clipPlaneCount; i++) {
148 clipPlanes[i] = poComp->clipPlanes[i] * M;
149 }
150 }
151
152 for (size_t i = clipPlaneCount; i < PotreeData::MaxClipPlanes; i++) {
153 clipPlanes[i] = glm::vec4(0, 0, 0, 1); // Not used. always positive regardlesss of position
154 }
155
156
157 VM = state.V * M;
158
159 {
160 // We use the projection/frustm quite heavily for culling. To improve
161 // numerical stability, we restrict the depth range of the frustum to
162 // the range where the point cloud exists.
163
164 // First, find the depth range of the point cloud in the full frustum
165 const glm::vec3& boundsMin = poData->octtreeFrame.tightBBoxMin;
166 const glm::vec3& boundsMax = poData->octtreeFrame.tightBBoxMax;
167 glm::vec3 boundsCorners[8] = {
168 glm::vec3(boundsMin.x, boundsMin.y, boundsMin.z),
169 glm::vec3(boundsMax.x, boundsMin.y, boundsMin.z),
170 glm::vec3(boundsMin.x, boundsMax.y, boundsMin.z),
171 glm::vec3(boundsMax.x, boundsMax.y, boundsMin.z),
172 glm::vec3(boundsMin.x, boundsMin.y, boundsMax.z),
173 glm::vec3(boundsMax.x, boundsMin.y, boundsMax.z),
174 glm::vec3(boundsMin.x, boundsMax.y, boundsMax.z),
175 glm::vec3(boundsMax.x, boundsMax.y, boundsMax.z),
176 };
177 float clipZMin = std::numeric_limits<float>::max();
178 float clipZMax = -std::numeric_limits<float>::max();
179 glm::mat4 PVM_ = state.P * VM;
180 for (size_t i = 0; i < 8; i++) {
181 glm::vec4 h = PVM_ * glm::vec4(boundsCorners[i], 1.f);
182 float z = h.z / std::abs(h.w);
183 if (!std::isfinite(z)) {
184 // Division by zero, skip. Assume that corner landed in plane through eye
185 // in a perspective projection, and thus the near plane should work as a
186 // depth value.
187 z = state.context->variables->get("renderer.reverseDepth", false) ? 1.f : -1.f;
188 }
189 clipZMin = std::min(clipZMin, z);
190 clipZMax = std::max(clipZMax, z);
191 }
192 clipZMin = std::max(clipZMin, -1.f);
193 clipZMax = std::min(clipZMax, 1.f);
194
195 // Add a scale & bias matrix after application of the
196 // projection mapping the depth range of the point
197 // cloud to [-1, 1].
198 float scale = 2.f / (clipZMax - clipZMin);
199 float bias = -(1.f + scale * clipZMin);
200 PVM = glm::mat4(1.f, 0.f, 0.f, 0.f,
201 0.f, 1.f, 0.f, 0.f,
202 0.f, 0.f, scale, 0.f,
203 0.f, 0.f, bias, 1.f) * state.P * VM;
204 }
205
206 PVMinv = glm::inverse(PVM);
207 VMScale = std::cbrt(std::abs(glm::determinant(glm::mat3(VM))));
208
209 poData->frustum = poData->worldFromOcttreeFrame * PVMinv;
210
211 eye = euclidean(glm::inverse(VM) * glm::vec4(0, 0, 0, 1));
212
213 if(0.f < poComp->distanceCutoffScale && 0.f < poComp->distanceCutoffMinimum) {
214
215 // Calculate distance-based cutoff controlled by elevation above the hierarchy's z minimum value.
216
217 // Add max-norm distance to boundary volume (that is, it is zero inside the volume) to the elevation used for distance-based cutoff
218 // so that the effect diminished sharply when the camera is not inside the point cloud.
219 glm::vec3 distancesToBoundaryVolume = glm::abs(eye - glm::clamp(eye, poData->root->tbmin, poData->root->tbmax));
220 float maxDistanceToBoundaryVolue = std::max(distancesToBoundaryVolume.x, std::max(distancesToBoundaryVolume.y, distancesToBoundaryVolume.z));
221 float distanceCutoff = std::max(poComp->distanceCutoffMinimum, poComp->distanceCutoffScale * (maxDistanceToBoundaryVolue + std::abs(eye.z - poData->root->tbmin.z)));
222 distanceCutoffSquared = distanceCutoff * distanceCutoff;
223 }
224 else {
225 distanceCutoffSquared = -1.f;
226 }
227
228
229 // Set up directions for fine-grained frustum tests
230 {
231 // Frustum corners in model space
232 glm::vec3 F[8] = {
233 euclidean(PVMinv * glm::vec4(-1, -1, -1, 1)),
234 euclidean(PVMinv * glm::vec4( 1, -1, -1, 1)),
235 euclidean(PVMinv * glm::vec4(-1, 1, -1, 1)),
236 euclidean(PVMinv * glm::vec4( 1, 1, -1, 1)),
237 euclidean(PVMinv * glm::vec4(-1, -1, 1, 1)),
238 euclidean(PVMinv * glm::vec4( 1, -1, 1, 1)),
239 euclidean(PVMinv * glm::vec4(-1, 1, 1, 1)),
240 euclidean(PVMinv * glm::vec4( 1, 1, 1, 1))
241 };
242
243 // Edges of frustum in model space
244 glm::vec3 f[6] = {
245 F[4] - F[0],
246 F[5] - F[1],
247 F[6] - F[2],
248 F[7] - F[3],
249 F[1] - F[0],
250 F[2] - F[0]
251 };
252
253 // Cross between one frustum edge and one chunk aabb
254 directions[ 0] = glm::cross(f[0], glm::vec3(1, 0, 0));
255 directions[ 1] = glm::cross(f[0], glm::vec3(0, 1, 0));
256 directions[ 2] = glm::cross(f[0], glm::vec3(0, 0, 1));
257
258 directions[ 3] = glm::cross(f[1], glm::vec3(1, 0, 0));
259 directions[ 4] = glm::cross(f[1], glm::vec3(0, 1, 0));
260 directions[ 5] = glm::cross(f[1], glm::vec3(0, 0, 1));
261
262 directions[ 6] = glm::cross(f[2], glm::vec3(1, 0, 0));
263 directions[ 7] = glm::cross(f[2], glm::vec3(0, 1, 0));
264 directions[ 8] = glm::cross(f[2], glm::vec3(0, 0, 1));
265
266 directions[ 9] = glm::cross(f[3], glm::vec3(1, 0, 0));
267 directions[10] = glm::cross(f[3], glm::vec3(0, 1, 0));
268 directions[11] = glm::cross(f[3], glm::vec3(0, 0, 1));
269
270 directions[12] = glm::cross(f[4], glm::vec3(1, 0, 0));
271 directions[13] = glm::cross(f[4], glm::vec3(0, 1, 0));
272 directions[14] = glm::cross(f[4], glm::vec3(0, 0, 1));
273
274 directions[15] = glm::cross(f[5], glm::vec3(1, 0, 0));
275 directions[16] = glm::cross(f[5], glm::vec3(0, 1, 0));
276 directions[17] = glm::cross(f[5], glm::vec3(0, 0, 1));
277 // Face directions of aabb in model space
278 directions[18] = glm::vec3(1, 0, 0);
279 directions[19] = glm::vec3(0, 1, 0);
280 directions[20] = glm::vec3(0, 0, 1);
281 // Frustum faces in model space, this is checked by early tests, no need to check again
282 //directions[21] = glm::cross(f[0], f[4]);
283 //directions[22] = glm::cross(f[1], f[5]);
284 //directions[23] = glm::cross(f[2], f[4]);
285 //directions[24] = glm::cross(f[3], f[5]);
286 //directions[25] = glm::cross(f[4], f[5]);
287
288 for (size_t i = 0; i < directionsCount; i++) {
289
290 // Normalize dir or make it zero if it is degenerate
291 float s = 1.f / glm::length(directions[i]);
292 if (std::isfinite(s)) {
293 directions[i] *= s;
294 }
295 else {
296 directions[i] = glm::vec3(0.f);
297 }
298
299 // Project frustum onto direction and calc cover
300 float max_ = -std::numeric_limits<float>::max();
301 float min_ = std::numeric_limits<float>::max();
302 for (size_t k = 0; k < 8; k++) {
303 float e = dot(directions[i], F[k]);
304 max_ = std::max(max_, e);
305 min_ = std::min(min_, e);
306 }
307 frustumMin[i] = min_;
308 frustumMax[i] = max_;
309 }
310 }
311
312 // Convert screen pixel tolerances to normalized view-height
313 float tolerance = state.viewHeightPerPixel * poData->tolerance;
314 toleranceSquared = tolerance * tolerance;
315
316 float rootTolerance = state.viewHeightPerPixel * poData->rootTolerance;
317 rootToleranceSquared = rootTolerance * rootTolerance;
318
319 // Traverse tree breadth first.
320 poData->lodHeap.clear();
321
322 float rootFootprint = calcCellPriority(state, poData->root);
323 if (0 <= rootFootprint) {
324 poData->lodHeap.push_back(PotreeNodeHeapItem{ poData->root, rootFootprint });
325 priority = rootFootprint;
326 numActiveCells++;
327 }
328 }
329
330 [[nodiscard]] bool cellInsideFrustum(ManageState& state, const PotreeCell* cell)
331 {
332 const glm::vec3 cellMin = cell->tbmin;
333 const glm::vec3 cellMax = cell->tbmax;
334 glm::vec3 cellCorners[8] = {
335 glm::vec3(cellMin.x, cellMin.y, cellMin.z),
336 glm::vec3(cellMin.x, cellMin.y, cellMax.z),
337 glm::vec3(cellMin.x, cellMax.y, cellMin.z),
338 glm::vec3(cellMin.x, cellMax.y, cellMax.z),
339 glm::vec3(cellMax.x, cellMin.y, cellMin.z),
340 glm::vec3(cellMax.x, cellMin.y, cellMax.z),
341 glm::vec3(cellMax.x, cellMax.y, cellMin.z),
342 glm::vec3(cellMax.x, cellMax.y, cellMax.z),
343 };
344
345
346 uint32_t allInsideMask = 0b111111;
347 {
348 uint32_t anyInsideMask = 0u;
349 for (size_t i = 0; i < 8; i++) {
350 glm::vec4 p = PVM * glm::vec4(cellCorners[i], 1.f);
351 uint32_t cornerMask =
352 (p.x <= p.w ? 1 : 0) |
353 (p.y <= p.w ? 2 : 0) |
354 (p.z <= p.w ? 4 : 0) |
355 (-p.w <= p.x ? 8 : 0) |
356 (-p.w <= p.y ? 16 : 0) |
357 (-p.w <= p.z ? 32 : 0);
358 anyInsideMask = anyInsideMask | cornerMask;
359 allInsideMask = allInsideMask & cornerMask;
360 }
361
362 if (anyInsideMask != 0b111111) {
363 // At least one plane where all corners are outside => outside.
364 state.insideTestEarly1++;
365 return false;
366 }
367 }
368
369 if (clipPlaneCount) {
370 // Cull against clip planes, inactive planes are [0,0,0,1] which is always positive/inside.
371 uint32_t anyInsideClippedMask = 0u;
372 uint32_t allInsideClippedMask = 0b111111;
373 for (size_t i = 0; i < 8; i++) {
374 glm::vec4 q = glm::vec4(cellCorners[i], 1.f);
375 uint32_t cornerClippedMask =
376 (0.f <= dot(q, clipPlanes[0]) ? (1 << 0) : 0) |
377 (0.f <= dot(q, clipPlanes[1]) ? (1 << 1) : 0) |
378 (0.f <= dot(q, clipPlanes[2]) ? (1 << 2) : 0) |
379 (0.f <= dot(q, clipPlanes[3]) ? (1 << 3) : 0) |
380 (0.f <= dot(q, clipPlanes[4]) ? (1 << 4) : 0) |
381 (0.f <= dot(q, clipPlanes[5]) ? (1 << 5) : 0);
382 anyInsideClippedMask = anyInsideClippedMask | cornerClippedMask;
383 allInsideClippedMask = allInsideClippedMask & cornerClippedMask;
384 }
385
386 if (clipTestInvert == false) {
387 // If one of the planes have no point inside, the cell is completely outside clip region.
388 if (anyInsideClippedMask != 0b111111) {
389 state.insideTestClip++;
390 return false;
391 }
392 }
393 else {
394 // If all planes have all corners inside the region, the cell is completely inside the cut-out region.
395 if (allInsideClippedMask == 0b111111) {
396 state.insideTestClip++;
397 return false;
398 }
399 }
400 }
401
402 if (allInsideMask == 0b111111) {
403 // At least one plane where all corners are outside => outside.
404 state.insideTestEarly2++;
405 return true;
406 }
407
408
409 // Try a set of directions to separate the sets
410 for (size_t i = 0; i < directionsCount; i++) {
411 float ma_ = -std::numeric_limits<float>::max();
412 float mi_ = std::numeric_limits<float>::max();
413 for (size_t k = 0; k < 8; k++) {
414 float e = dot(directions[i], cellCorners[k]);
415 ma_ = std::max(ma_, e);
416 mi_ = std::min(mi_, e);
417 }
418
419 if (ma_ < frustumMin[i] || frustumMax[i] < mi_) {
420 // Convex hull of frustum and convex hull of aabb projected onto this direction does not overlap.
421 state.insideTestSeparated++;
422 return false;
423 }
424
425 }
426 state.insideTestPassed++;
427 return true;
428 }
429
430
431 [[nodiscard]] float calcCellPriority(ManageState& state, PotreeCell* cell)
432 {
433 if (cellInsideFrustum(state, cell)) {
434
435 const glm::vec3 cellMin = cell->tbmin;
436 const glm::vec3 cellMax = cell->tbmax;
437
438 const glm::vec3 neareastPoint = glm::clamp(eye, cellMin, cellMax);
439 const float eyeDistanceSquared = glm::distance2(eye, neareastPoint);
440 if (0.f <= distanceCutoffSquared && distanceCutoffSquared < eyeDistanceSquared) {
441 return -1.f;
442 }
443
444
445
446 // Cell center in view space
447 glm::vec3 c = euclidean(VM * glm::vec4(cellMin + cellMax, 2.f));
448
449 // Squared distance from eye to cell center
450 float d2 = dot(c, c);
451
452
453 // Cell diagonal.
454 glm::vec3 d = cellMax - cellMin;
455
456 // Squared cell radius in view space
457 float r2 = VMScale * VMScale * dot(d, d);
458
459 float squaredWeight = r2 / d2;
460 if (std::isfinite(squaredWeight)) {
461
462 if (cell->isRootNode()) {
463 if (rootToleranceSquared <= squaredWeight) return squaredWeight;
464 }
465 else {
466 if (toleranceSquared <= squaredWeight) return squaredWeight;
467 }
468 }
469 }
470 return -1.f;
471 }
472
473 [[nodiscard]] PotreeCell* handleSubtreeLink(Context* context, ManageState& state, PotreeCell * cell)
474 {
475 assert(cell->kind == PotreeCell::Kind::SubtreeLink);
476
477 // Node is actually the root node of a child subtree.
478
479 // We haven't populated this subtree, request data if we are allowed.
480 if (cell->linkedSubtree == nullptr) {
481
482 if (state.requestsInFlight < state.maxConcurrentRequests) {
483
484 if (state.requestsIssuedThisFrame < state.maxPerFrameRequests) {
485 assert(cell->state == PotreeCell::State::None);
486 cell->state = PotreeCell::State::Issued;
487 state.poSystem->issueSubtreeFetch(context, poComp, poData, cell);
488 state.requestsInFlight++;
489 state.requestsIssuedThisFrame++;
490 }
491 else {
492 // Cannot issue more this frame, continue in next frame
493 state.wantAnotherFrameUpdate = true;
494 }
495 }
496
497 // We don't have any data on the subtree, ignore this cell for now.
498 return nullptr;
499 }
500
501 // We have indeed a subtree. If subtree request has been fully processed,
502 // and we have a root node, recurse into the subtree.
503 auto* subtree = cell->linkedSubtree;
504 if (subtree->state != PotreeSubtree::State::Ready) {
505
506 // We're waiting for data on the subtree, skip this part of the hierarchy for now.
507 return nullptr;
508 }
509
510 if (subtree->cells.empty()) {
511 // No cells in subtree, just quit
512 return nullptr;
513 }
514
515 auto* subtreeRoot = subtree->cells.front();
516
517 assert(subtreeRoot->kind == PotreeCell::Kind::RegularNode && "Invalid subtree");
518
519 // Link node occupies same space as root of subtree, so we sneak the root in
520 // into the active list and continue on its children. Note, we do not increment
521 // activeCellCount on purpose.
522 if (subtreeRoot->isActive()) {
523 activeCellsOld.remove(subtreeRoot);
524 }
525 subtreeRoot->setActive();
526 activeCellsNew.push(subtreeRoot);
527
528 // Continue with root in subtree
529 return subtreeRoot;
530 }
531
532
533 [[nodiscard]] bool updateIteration(Context* context, ManageState& state)
534 {
535 assert(!poData->lodHeap.empty());
536
537 // Pop top of heap.
538 std::pop_heap(poData->lodHeap.begin(), poData->lodHeap.end(), potreeNodeHeapCmp);
539 PotreeNodeHeapItem item = poData->lodHeap.back();
540 poData->lodHeap.pop_back();
541
542 if (state.maxActiveCells <= state.activeCellCount) {
543 return false;
544 }
545
546 if (state.maxOctreeSize <= numActiveCells) {
547 // We have filled up the hierarchy texture, stop processing.
548 return false;
549 }
550
551 PotreeCell* cell = item.cell;
552
553 if (state.pointsMaxCount < state.pointsCount + cell->pointCount) {
554 // We have reached the point budget, stop processing.
555 return false;
556 }
557
558 // Cell have passed all tests, cell is now included, update state
559 state.activeCellCount++;
560 state.pointsCount += cell->pointCount;
561
562 // If cell was already active, remove it from activeOld before putting it into active.
563 if ((cell->flags & PotreeCell::Flags::Active) == PotreeCell::Flags::Active) {
564 activeCellsOld.remove(cell);
565 }
566
567 cell->setActive();
568 activeCellsNew.push(cell);
569
570 // Node is actually the root node of a child subtree.
571 // Handle the transition into the subtree.
572 if (cell->kind == PotreeCell::Kind::SubtreeLink) {
573 cell = handleSubtreeLink(context, state, cell);
574 if (cell == nullptr) {
575 return !poData->lodHeap.empty(); // Tree not ready, but do continue to process other cells.
576 }
577 }
578 assert(cell->kind == PotreeCell::Kind::RegularNode);
579
580 // Check if cell has data, and if not, request if we are allowed.
581 // Don't recurse into children unless current node has
582 // point data available. Makes hierarchy follow where
583 // we actually have data.
584 if (cell->state == PotreeCell::State::None) { // Cell lacks data.
585
586 // Handle empty cells. This do happen sometimes, we just set it to ready
587 // and the renderer will skip it since it has no data.
588 if (cell->byteCount == 0) {
589 cell->state = PotreeCell::State::Ready;
590 for (PotreeCell* child : cell->children) {
591 if (child && child->state != PotreeCell::State::Ready) {
592 child->scaleFactor = cell->scaleFactor;
593 }
594 }
595 }
596
597 // Request data if we are allowed an request.
598 else if (state.requestsInFlight < state.maxConcurrentRequests) {
599
600 if (state.requestsIssuedThisFrame < state.maxPerFrameRequests) {
601 cell->fetchData(context, state.poSystem, poComp, poData);
602 state.requestsInFlight++;
603 state.requestsIssuedThisFrame++;
604 }
605 else {
606 // Cannot issue more this frame, continue in next frame
607 state.wantAnotherFrameUpdate = true;
608 }
609 }
610
611 return !poData->lodHeap.empty(); // Cell not ready, but do continue to process other cells.
612 }
613
614 // Push all visible children onto the next iteration
615 for (unsigned i = 0; i < 8; i++) {
616 if (auto* child = cell->children[i]; child) {
617
618 float importance = calcCellPriority(state, child);
619
620 // If child is visible, have a significant footprint, and there is room in the octtree texture
621
622 // We should set childmask here and handle dead-ends when traversing.
623 if (0 <= importance) {
624 poData->lodHeap.push_back(PotreeNodeHeapItem{ child, importance });
625 std::push_heap(poData->lodHeap.begin(), poData->lodHeap.end(), potreeNodeHeapCmp);
626
627 // Update priority of this tree with front of queue
628#if 1
629 priority = poData->lodHeap.front().priority;
630#else
631 std::pop_heap(poData->lodHeap.begin(), poData->lodHeap.end(), potreeNodeHeapCmp);
632 priority = poData->lodHeap.back().priority;
633 std::push_heap(poData->lodHeap.begin(), poData->lodHeap.end(), potreeNodeHeapCmp);
634#endif
635 }
636 }
637 }
638
639
640 return !poData->lodHeap.empty();
641 }
642
643 void populateTexture(ManageState& state)
644 {
645 poData->octtreeTextureData.resize(state.maxOctreeSize, false);
646 std::memset(static_cast<void*>(poData->octtreeTextureData.data()), ~0u, poData->octtreeTextureData.byteSize());
647
648 // Process queue, numCells denote tail of queue and i is head.
649 // We process cell from the front of the queue and if the cell
650 // has active children, we push them on the back of the queue.
651 //
652 // This ends up as a breadth-first traversal of the cell hierarchy.
653 //
654 // We size the queue to hold all active cells, so we don't have
655 // to think about resize or wraparound.
656 poData->tmpCellPointers.resize(state.maxOctreeSize, false);
657 std::memset(poData->tmpCellPointers.data(), 0u, poData->tmpCellPointers.byteSize());
658
659 glm::u8vec4* tex = poData->octtreeTextureData.data();
660 PotreeCell** cells = poData->tmpCellPointers.data();
661
662 size_t numCells = 0;
663 if (poData->root->isActive()) {
664 cells[numCells++] = poData->root;
665 }
666 else {
667 tex[0].r = 0xffu; // Empty tree, store invalid data to trigger asserts
668 tex[0].g = 0xffu;
669 tex[0].b = 0xffu;
670 tex[0].a = 0xffu;
671 }
672
673 for (size_t currIx = 0; currIx < numCells; currIx++) {
674 PotreeCell* cell = cells[currIx];
675 size_t firstChild = numCells;
676 cell->texOffset = static_cast<uint16_t>(currIx);
677
678 unsigned childMask = 0;
679 for (unsigned k = 0; k < 8; k++) {
680 if (auto* child = cell->children[k]; (child != nullptr) && (child->isActive())) {
681 if (child->kind == PotreeCell::Kind::SubtreeLink) {
682 if (child->linkedSubtree == nullptr ||
683 child->linkedSubtree->state != PotreeSubtree::State::Ready ||
684 child->linkedSubtree->cells.empty())
685 {
686 continue; // Subtree not ready
687 }
688 child = child->linkedSubtree->cells.front();
689 assert(child->kind == PotreeCell::Kind::RegularNode);
690 }
691 if (HandleIsValid(child->pointData)) {
692 // If child exists, is active, and has data available, it is
693 // included in this tree representation.
694 //
695 // Children of a cells will lie back-to-back in in their proper order.
696 assert(numCells < state.maxOctreeSize);
697 childMask |= 1u << k;
698 cells[numCells++] = child;
699 }
700 }
701 }
702
703 if (childMask) {
704 tex[currIx].g = static_cast<uint8_t>(firstChild & 0xffu); // offset to first child, MSB
705 tex[currIx].b = static_cast<uint8_t>(firstChild >> 8); // offset to first child, LSB
706 }
707 else {
708 tex[currIx].g = 0xffu; // No children, store invalid offset to trigger asserts
709 tex[currIx].b = 0xffu;
710 }
711 tex[currIx].r = static_cast<uint8_t>(childMask);
712 tex[currIx].a = cell->scaleFactor;
713 }
714 }
715
716 void cleanup(Context* context)
717 {
718 // Retire cells that was active but is no longer active.
719 List<PotreeSubtree, offsetof(PotreeSubtree, member)> inactiveSubtrees;
720
721 while (!activeCellsOld.empty()) {
722 auto* cell = activeCellsOld.shift();
723
724 cell->texOffset = static_cast<uint16_t>(~0u); // illegal offset to trigger assert if anything goes wrong.
725 cell->flags &= ~PotreeCell::Flags::Active;
726 cell->releaseData(context, poData);
727
728 if (cell->kind == PotreeCell::Kind::SubtreeLink && cell->linkedSubtree) {
729 poData->subtrees.remove(cell->linkedSubtree);
730 inactiveSubtrees.push(cell->linkedSubtree);
731 cell->linkedSubtree = nullptr;
732 }
733 }
734
735 while (!inactiveSubtrees.empty()) {
736 auto* subtree = inactiveSubtrees.shift();
737 assert(subtree->containingCell && "Don't delete the root tree");
738 //LOG_DEBUG(logger, "Deleting subtree %s", subtree->path.c_str());
739 subtree->release(context, poData);
740 poData->subtreeStore.destroy(subtree);
741 }
742 }
743
744
745 };
746
747 bool updaterPointerCmp(const Updater* a, const Updater* b)
748 {
749 assert(!a->poData->lodHeap.empty());
750 assert(!b->poData->lodHeap.empty());
751 return a->priority < b->priority;
752 }
753
754
755}
756
757
758void Cogs::Core::PotreeSystem::updateLodLevels(Context* context)
759{
760 // Set up state that is used for figuring out the lod levels
761 const CameraComponent* mainCamComp = context->cameraSystem->getMainCamera();
762 const CameraData& mainCamData = context->cameraSystem->getData(mainCamComp);
763
764 ManageState state{};
765 state.context = context;
766 state.poSystem = this;
767 state.P = mainCamData.rawProjectionMatrix;
768 state.V = mainCamData.viewMatrix;
769
771 state.viewHeightPerPixel = std::sin(mainCamComp->fieldOfView) / std::max(1.f, glm::length(mainCamData.viewportSize));
772 }
773 else {
774 state.viewHeightPerPixel = 1.f / std::max(1.f, glm::length(mainCamData.viewportSize));
775 }
776
778
779 // Set up variables controlling lod levels
780 if (!context->variables->exist(lodFreezeName)) { context->variables->set(lodFreezeName, false); }
781 if (!context->variables->exist(pointsMaxName)) { context->variables->set(pointsMaxName, pointsMaxDefault); }
782 if (!context->variables->exist(chunksMaxName)) { context->variables->set(chunksMaxName, std::min(int(state.maxOctreeSize), chunksMaxDefault)); }
783 if (!context->variables->exist(requestsMaxConcurrentName)) { context->variables->set(requestsMaxConcurrentName, requestsMaxConcurrentDefault); }
784 if (!context->variables->exist(requestsMaxPerFrameName)) { context->variables->set(requestsMaxPerFrameName, requestsMaxPerFrameDefault); }
785
786
787 // Skip updating if we are lod-freezing
788 if (const Variable* var = context->variables->get(lodFreezeName); var->getBool()) {
789 return;
790 }
791
792 state.pointsCount = 0;
793 state.pointsMaxCount = static_cast<unsigned>(std::max(1, static_cast<int>(context->qualityService->potreeSystemChunkCountScale * context->variables->get(pointsMaxName, pointsMaxDefault))));
794
795 // Desktop async fetches return immediately, hence we
796 // use an extra variable here to make sure that we don't
797 // issue more than max number of requests per frame.
798 state.activeCellCount = 0;
799 state.maxActiveCells = static_cast<unsigned>(std::max(1, static_cast<int>(context->qualityService->potreeSystemChunkCountScale * context->variables->get(chunksMaxName, chunksMaxDefault))));
800 state.requestsInFlight = requestsInFlight;
801 state.requestsIssuedThisFrame = 0;
802 state.maxConcurrentRequests = static_cast<unsigned>(std::max(0, context->variables->get(requestsMaxConcurrentName, requestsMaxConcurrentDefault)));
803 state.maxPerFrameRequests = static_cast<uint32_t>(std::max(0, context->variables->get(requestsMaxPerFrameName, requestsMaxPerFrameDefault)));
804
805
806 // Create a set of updaters, one per visible, running potree instance with data.
807 std::vector<Updater> updaters;
808 updaters.reserve(pool.size());
809 for (auto& poComp : pool) {
810 PotreeData* poData = getData(&poComp).poData.get();
811 if (!poComp.isVisible() || poData->state != PotreeState::Running || poData->root == nullptr) {
812 continue;
813 }
814 if (poComp.disableLodUpdates) {
815 continue;
816 }
817 assert(!poData->subtrees.empty());
818 updaters.emplace_back(state, &poComp, poData);
819 }
820
821 // Limit max number of cells to the theoretical limit of cells
822 state.maxActiveCells = std::min(state.maxActiveCells, state.maxOctreeSize * static_cast<uint32_t>(updaters.size()));
823
824 // Keep list of active updaters in a heap ordered by the instance's most important cell
825 std::vector<Updater*> updaterHeap;
826 updaterHeap.reserve(updaters.size());
827 for (auto& updater : updaters) {
828 if (!updater.poData->lodHeap.empty()) {
829 updaterHeap.push_back(&updater);
830 }
831 }
832 std::make_heap(updaterHeap.begin(), updaterHeap.end(), updaterPointerCmp);
833
834 // Repeatedly process the most important instance, removing instances that
835 // has nothing more to do or has exhausted resource limits, until all instances
836 // are done.
837 while (!updaterHeap.empty()) {
838 std::pop_heap(updaterHeap.begin(), updaterHeap.end(), updaterPointerCmp);
839 if (updaterHeap.back()->updateIteration(context, state)) { // returns true if more work to do
840 std::push_heap(updaterHeap.begin(), updaterHeap.end(), updaterPointerCmp);
841 }
842 else {
843 updaterHeap.pop_back();
844 }
845 }
846 for (auto& updater : updaters) {
847 updater.cleanup(context);
848 updater.populateTexture(state);
849 }
850
851
852 if (state.wantAnotherFrameUpdate) {
853 context->engine->triggerUpdate();
854 }
855 context->variables->set("potree.instrument.wantAnotherFrameUpdate", int(state.wantAnotherFrameUpdate));
856
857 context->variables->set("potree.instrument.currentPointCount", int(state.pointsCount));
858 context->variables->set("potree.instrument.activeCellCount", int(state.activeCellCount));
859
860 context->variables->set("potree.instrument.requests.issuedThisFrame", int(state.requestsIssuedThisFrame));
861 context->variables->set("potree.instrument.requests.inFlight", int(state.requestsInFlight));
862
863 uint32_t insideTestTotal = state.insideTestEarly1 + state.insideTestEarly2 + state.insideTestSeparated + state.insideTestPassed;
864 context->variables->set("potree.instrument.insideTest.total", int(insideTestTotal));
865 context->variables->set("potree.instrument.insideTest.early1", int(state.insideTestEarly1));
866 context->variables->set("potree.instrument.insideTest.clip", int(state.insideTestClip));
867 context->variables->set("potree.instrument.insideTest.early2", int(state.insideTestEarly2));
868 context->variables->set("potree.instrument.insideTest.separated", int(state.insideTestSeparated));
869 context->variables->set("potree.instrument.insideTest.passed", int(state.insideTestPassed));
870}
float fieldOfView
Vertical field of view, given in radians.
ProjectionMode projectionMode
The projection mode to use for the camera.
Sets up a clipping shape that can be used by multiple entities.
Context * context
Pointer to the Context instance the system lives in.
ComponentPool< ComponentType > pool
Pool of components managed by the system.
A Context instance contains all the services, systems and runtime components needed to use Cogs.
Definition: Context.h:83
class IRenderer * renderer
Renderer.
Definition: Context.h:228
std::unique_ptr< class QualityService > qualityService
Quality service instance.
Definition: Context.h:201
std::unique_ptr< class Variables > variables
Variables service instance.
Definition: Context.h:180
std::unique_ptr< class Engine > engine
Engine instance.
Definition: Context.h:222
virtual IGraphicsDevice * getDevice()=0
Get the graphics device used by the renderer.
ComponentModel::ComponentHandle clipShapeComponent
Handle to the currently active clip component, if any.
constexpr bool isVisible() const
Check if the entity is visible or not.
virtual ICapabilities * getCapabilities()=0
Get a pointer to the capability management interface used to query the graphics device capability fla...
Log implementation class.
Definition: LogManager.h:139
Provides a weakly referenced view over the contents of a string.
Definition: StringView.h:24
Contains the Engine, Renderer, resource managers and other systems needed to run Cogs....
bool HandleIsValid(const ResourceHandle_t< T > &handle)
Check if the given resource is valid, that is not equal to NoHandle or InvalidHandle.
@ Perspective
Perspective projection.
constexpr Log getLogger(const char(&name)[LEN]) noexcept
Definition: LogManager.h:180
ComponentType * resolveComponent() const
Definition: Component.h:90
Contains data describing a Camera instance and its derived data structured such as matrix data and vi...
Definition: CameraSystem.h:67
glm::mat4 rawProjectionMatrix
Projection matrix that has not been adjusted by the renderer, and is thus appropriate for direct scre...
Definition: CameraSystem.h:129
glm::vec4 planes[6]
Clipping planes in world space.
Component for Point Cloud Display.
std::vector< glm::vec4 > clipPlanes
Deprecated, use clip shapes instead.
bool disableLodUpdates
Debug trigger to disable update of lod calculations.
bool disableCustomClipping
Debug switch to disable clipping of points against custom clipping planes.
float distanceCutoffScale
Cut off chunks that are further away than camera elevation above pointcloud z - minimum times this sc...
float distanceCutoffMinimum
Minimum value for distance-based cut-off, cut-off distance will never be less than this value.
std::vector< PotreeNodeHeapItem > lodHeap
heap used while calculating lod levels
Definition: PotreeSystem.h:209
static constexpr size_t MaxClipPlanes
Max available in shader.
Definition: PotreeSystem.h:216
Runtime control variable.
Definition: Variables.h:27
uint32_t MaxTexture2DSize
Using D3D11_REQ_TEXTURE2D_U_OR_V_DIMENSION as default.
Definition: ICapabilities.h:80
virtual const GraphicsDeviceCapabilities & getDeviceCapabilities() const
Gets the device capabilities in a structure.