1//************************************ bs::framework - Copyright 2018 Marko Pintera **************************************//
2//*********** Licensed under the MIT license. See LICENSE.md for full terms. This notice is not to be removed. ***********//
3#include "Renderer/BsLightProbeVolume.h"
4#include "Private/RTTI/BsLightProbeVolumeRTTI.h"
5#include "Renderer/BsRenderer.h"
6#include "Renderer/BsLight.h"
7#include "Image/BsTexture.h"
8#include "Renderer/BsIBLUtility.h"
9#include "Scene/BsSceneObject.h"
10#include "CoreThread/BsCoreObjectSync.h"
11
12namespace bs
13{
14 LightProbeVolume::LightProbeVolume(const AABox& volume, const Vector3I& cellCount)
15 :mVolume(volume), mCellCount(cellCount)
16 {
17 reset();
18 }
19
20 LightProbeVolume::~LightProbeVolume()
21 {
22 if (mRendererTask)
23 mRendererTask->cancel();
24 }
25
26 UINT32 LightProbeVolume::addProbe(const Vector3& position)
27 {
28 UINT32 handle = mNextProbeId++;
29 mProbes[handle] = ProbeInfo(LightProbeFlags::Clean, position);
30
31 _markCoreDirty();
32 return handle;
33 }
34
35 void LightProbeVolume::removeProbe(UINT32 handle)
36 {
37 auto iterFind = mProbes.find(handle);
38 if (iterFind != mProbes.end() && mProbes.size() > 4)
39 {
40 iterFind->second.flags = LightProbeFlags::Removed;
41 _markCoreDirty();
42 }
43 }
44
45 void LightProbeVolume::setProbePosition(UINT32 handle, const Vector3& position)
46 {
47 auto iterFind = mProbes.find(handle);
48 if (iterFind != mProbes.end())
49 {
50 iterFind->second.position = position;
51 _markCoreDirty();
52 }
53 }
54
55 Vector3 LightProbeVolume::getProbePosition(UINT32 handle) const
56 {
57 auto iterFind = mProbes.find(handle);
58 if (iterFind != mProbes.end())
59 return iterFind->second.position;
60
61 return Vector3::ZERO;
62 }
63
64 Vector<LightProbeInfo> LightProbeVolume::getProbes() const
65 {
66 Vector<LightProbeInfo> output;
67
68 for(auto& entry : mProbes)
69 {
70 if (entry.second.flags == LightProbeFlags::Removed || entry.second.flags == LightProbeFlags::Empty)
71 continue;
72
73 LightProbeInfo info;
74 info.position = entry.second.position;
75 info.handle = entry.first;
76 info.shCoefficients = entry.second.coefficients;
77
78 output.push_back(info);
79 }
80
81 return output;
82 }
83
84 void LightProbeVolume::resize(const AABox& volume, const Vector3I& cellCount)
85 {
86 UINT32 numProbesX = std::max(1, mCellCount.x) + 1;
87 UINT32 numProbesY = std::max(1, mCellCount.y) + 1;
88 UINT32 numProbesZ = std::max(1, mCellCount.z) + 1;
89
90 Vector3 size = mVolume.getSize();
91 for(UINT32 z = 0; z < numProbesZ; ++z)
92 {
93 for(UINT32 y = 0; y < numProbesY; ++y)
94 {
95 for(UINT32 x = 0; x < numProbesX; ++x)
96 {
97 Vector3 position = mVolume.getMin();
98 position.x += size.x * (x / (float)numProbesX);
99 position.y += size.y * (y / (float)numProbesY);
100 position.z += size.z * (z / (float)numProbesZ);
101
102 if (mVolume.contains(position))
103 continue;
104
105 addProbe(position);
106 }
107 }
108 }
109
110 mVolume = volume;
111 mCellCount = cellCount;
112
113 _markCoreDirty();
114 }
115
116 void LightProbeVolume::reset()
117 {
118 UINT32 numProbesX = std::max(1, mCellCount.x) + 1;
119 UINT32 numProbesY = std::max(1, mCellCount.y) + 1;
120 UINT32 numProbesZ = std::max(1, mCellCount.z) + 1;
121
122 UINT32 numProbes = numProbesX * numProbesY * numProbesZ;
123
124 // Make sure there are adequate number of probes to fill the volume
125 while((UINT32)mProbes.size() < numProbes)
126 addProbe(Vector3::ZERO);
127
128 UINT32 idx = 0;
129 UINT32 rowPitch = numProbesX;
130 UINT32 slicePitch = numProbesX * numProbesY;
131
132 Vector3 size = mVolume.getSize();
133
134 auto iter = mProbes.begin();
135 while (iter != mProbes.end())
136 {
137 UINT32 x = idx % numProbesX;
138 UINT32 y = (idx / rowPitch) % numProbesY;
139 UINT32 z = (idx / slicePitch);
140
141 Vector3 position = mVolume.getMin();
142 position.x += size.x * (x / (float)(numProbesX - 1));
143 position.y += size.y * (y / (float)(numProbesY - 1));
144 position.z += size.z * (z / (float)(numProbesZ - 1));
145
146 iter->second.position = position;
147 iter->second.flags = LightProbeFlags::Clean;
148
149 ++idx;
150 ++iter;
151
152 if (idx >= numProbes)
153 break;
154 }
155
156 // Set remaining probes to removed state
157 while(iter != mProbes.end())
158 {
159 iter->second.flags = LightProbeFlags::Removed;
160 ++iter;
161 }
162
163 _markCoreDirty();
164 }
165
166 void LightProbeVolume::clip()
167 {
168 for (auto& entry : mProbes)
169 {
170 if (!mVolume.contains(entry.second.position))
171 entry.second.flags = LightProbeFlags::Removed;
172 }
173
174 _markCoreDirty();
175 }
176
177 void LightProbeVolume::renderProbe(UINT32 handle)
178 {
179 auto iterFind = mProbes.find(handle);
180 if (iterFind != mProbes.end())
181 {
182 if (iterFind->second.flags == LightProbeFlags::Clean)
183 {
184 iterFind->second.flags = LightProbeFlags::Dirty;
185
186 _markCoreDirty();
187 runRenderProbeTask();
188 }
189 }
190 }
191
192 void LightProbeVolume::renderProbes()
193 {
194 bool anyModified = false;
195 for(auto& entry : mProbes)
196 {
197 if (entry.second.flags == LightProbeFlags::Clean)
198 {
199 entry.second.flags = LightProbeFlags::Dirty;
200 anyModified = true;
201 }
202 }
203
204 if (anyModified)
205 {
206 _markCoreDirty();
207 runRenderProbeTask();
208 }
209 }
210
211 void LightProbeVolume::runRenderProbeTask()
212 {
213 // If a task is already running cancel it
214 // Note: If the task is just about to start processing, cancelling it will skip the update this frame
215 // (which might be fine if we just changed positions of dirty probes it was about to update, but it might also
216 // waste a frame if those positions needed to be updated anyway). For now I'm ignoring it as it seems like a rare
217 // enough situation, plus it's one that will only happen during development time.
218 if (mRendererTask)
219 mRendererTask->cancel();
220
221 auto renderComplete = [this]()
222 {
223 mRendererTask = nullptr;
224 };
225
226 SPtr<ct::LightProbeVolume> coreProbeVolume = getCore();
227 auto renderProbes = [coreProbeVolume]()
228 {
229 return coreProbeVolume->renderProbes(3);
230 };
231
232 mRendererTask = ct::RendererTask::create("RenderLightProbes", renderProbes);
233
234 mRendererTask->onComplete.connect(renderComplete);
235 ct::gRenderer()->addTask(mRendererTask);
236 }
237
238 void LightProbeVolume::updateCoefficients()
239 {
240 // Ensure all light probe coefficients are generated
241 if (mRendererTask)
242 mRendererTask->wait();
243
244 ct::LightProbeVolume* coreVolume = getCore().get();
245
246 Vector<LightProbeCoefficientInfo> coeffInfo;
247 auto getSaveData = [coreVolume, &coeffInfo]()
248 {
249 coreVolume->getProbeCoefficients(coeffInfo);
250 };
251
252 gCoreThread().queueCommand(getSaveData);
253 gCoreThread().submit(true);
254
255 for(auto& entry : coeffInfo)
256 {
257 auto iterFind = mProbes.find(entry.handle);
258 if (iterFind == mProbes.end())
259 continue;
260
261 iterFind->second.coefficients = entry.coefficients;
262 }
263 }
264
265 SPtr<ct::LightProbeVolume> LightProbeVolume::getCore() const
266 {
267 return std::static_pointer_cast<ct::LightProbeVolume>(mCoreSpecific);
268 }
269
270 SPtr<LightProbeVolume> LightProbeVolume::create(const AABox& volume, const Vector3I& cellCount)
271 {
272 LightProbeVolume* probeVolume = new (bs_alloc<LightProbeVolume>()) LightProbeVolume(volume, cellCount);
273 SPtr<LightProbeVolume> probeVolumePtr = bs_core_ptr<LightProbeVolume>(probeVolume);
274 probeVolumePtr->_setThisPtr(probeVolumePtr);
275 probeVolumePtr->initialize();
276
277 return probeVolumePtr;
278 }
279
280 SPtr<LightProbeVolume> LightProbeVolume::createEmpty()
281 {
282 LightProbeVolume* probeVolume = new (bs_alloc<LightProbeVolume>()) LightProbeVolume();
283 SPtr<LightProbeVolume> probleVolumePtr = bs_core_ptr<LightProbeVolume>(probeVolume);
284 probleVolumePtr->_setThisPtr(probleVolumePtr);
285
286 return probleVolumePtr;
287 }
288
289 SPtr<ct::CoreObject> LightProbeVolume::createCore() const
290 {
291 ct::LightProbeVolume* handler = new (bs_alloc<ct::LightProbeVolume>()) ct::LightProbeVolume(mProbes);
292 SPtr<ct::LightProbeVolume> handlerPtr = bs_shared_ptr<ct::LightProbeVolume>(handler);
293 handlerPtr->_setThisPtr(handlerPtr);
294
295 return handlerPtr;
296 }
297
298 CoreSyncData LightProbeVolume::syncToCore(FrameAlloc* allocator)
299 {
300 UINT32 size = 0;
301 UINT8* buffer = nullptr;
302
303 bs_frame_mark();
304 {
305 FrameVector<std::pair<UINT32, ProbeInfo>> dirtyProbes;
306 FrameVector<UINT32> removedProbes;
307 for (auto& probe : mProbes)
308 {
309 if (probe.second.flags == LightProbeFlags::Dirty)
310 {
311 dirtyProbes.push_back(std::make_pair(probe.first, probe.second));
312 probe.second.flags = LightProbeFlags::Clean;
313 }
314 else if (probe.second.flags == LightProbeFlags::Removed)
315 {
316 removedProbes.push_back(probe.first);
317 probe.second.flags = LightProbeFlags::Empty;
318 }
319 }
320
321 for (auto& probe : removedProbes)
322 mProbes.erase(probe);
323
324 UINT32 numDirtyProbes = (UINT32)dirtyProbes.size();
325 UINT32 numRemovedProbes = (UINT32)removedProbes.size();
326
327 size += coreSyncGetElemSize((SceneActor&)*this);
328 size += rttiGetElemSize(numDirtyProbes);
329 size += rttiGetElemSize(numRemovedProbes);
330 size += (sizeof(UINT32) + sizeof(Vector3) + sizeof(LightProbeFlags)) * numDirtyProbes;
331 size += sizeof(UINT32) * numRemovedProbes;
332
333 buffer = allocator->alloc(size);
334
335 char* dataPtr = (char*)buffer;
336 dataPtr = coreSyncWriteElem((SceneActor&)*this, dataPtr);
337 dataPtr = rttiWriteElem(numDirtyProbes, dataPtr);
338 dataPtr = rttiWriteElem(numRemovedProbes, dataPtr);
339
340 for (auto& entry : dirtyProbes)
341 {
342 dataPtr = rttiWriteElem(entry.first, dataPtr);
343 dataPtr = rttiWriteElem(entry.second.position, dataPtr);
344 dataPtr = rttiWriteElem(entry.second.flags, dataPtr);
345 }
346
347 for(auto& entry : removedProbes)
348 dataPtr = rttiWriteElem(entry, dataPtr);
349 }
350 bs_frame_clear();
351
352 return CoreSyncData(buffer, size);
353 }
354
355 void LightProbeVolume::_markCoreDirty(ActorDirtyFlag dirtyFlag)
356 {
357 markCoreDirty((UINT32)dirtyFlag);
358 }
359
360 RTTITypeBase* LightProbeVolume::getRTTIStatic()
361 {
362 return LightProbeVolumeRTTI::instance();
363 }
364
365 RTTITypeBase* LightProbeVolume::getRTTI() const
366 {
367 return LightProbeVolume::getRTTIStatic();
368 }
369
370 namespace ct
371 {
372 LightProbeVolume::LightProbeVolume(const UnorderedMap<UINT32, bs::LightProbeVolume::ProbeInfo>& probes)
373 {
374 mInitCoefficients.resize(probes.size());
375 mProbePositions.resize(probes.size());
376 mProbeInfos.resize(probes.size());
377
378 UINT32 probeIdx = 0;
379 for(auto& entry : probes)
380 {
381 mProbeMap[entry.first] = probeIdx;
382 mProbePositions[probeIdx] = entry.second.position;
383
384 LightProbeInfo probeInfo;
385 probeInfo.flags = LightProbeFlags::Dirty;
386 probeInfo.bufferIdx = probeIdx;
387 probeInfo.handle = entry.first;
388
389 mProbeInfos[probeIdx] = probeInfo;
390 mInitCoefficients[probeIdx] = entry.second.coefficients;
391
392 probeIdx++;
393 }
394 }
395
396 LightProbeVolume::~LightProbeVolume()
397 {
398 gRenderer()->notifyLightProbeVolumeRemoved(this);
399 }
400
401 void LightProbeVolume::initialize()
402 {
403 // Set SH coefficients loaded from the file
404 UINT32 numCoefficients = (UINT32)mInitCoefficients.size();
405 assert(mInitCoefficients.size() == mProbeMap.size());
406
407 resizeCoefficientTexture(std::max(32U, numCoefficients));
408
409 SPtr<PixelData> coeffData = mCoefficients->getProperties().allocBuffer(0, 0);
410 coeffData->setColors(Color::ZERO);
411
412 UINT32 probesPerRow = coeffData->getWidth() / 9;
413 UINT32 probeIdx = 0;
414 for(UINT32 y = 0; y < coeffData->getHeight(); ++y)
415 {
416 for(UINT32 x = 0; x < probesPerRow; ++x)
417 {
418 if(probeIdx >= numCoefficients)
419 break;
420
421 for(UINT32 i = 0; i < 9; i++)
422 {
423 Color value;
424 value.r = mInitCoefficients[probeIdx].coeffsR[i];
425 value.g = mInitCoefficients[probeIdx].coeffsG[i];
426 value.b = mInitCoefficients[probeIdx].coeffsB[i];
427
428 coeffData->setColorAt(value, x * 9, y);
429 }
430
431 probeIdx++;
432 }
433 }
434
435 mCoefficients->writeData(*coeffData, 0, 0, true);
436 mInitCoefficients.clear();
437
438 gRenderer()->notifyLightProbeVolumeAdded(this);
439 CoreObject::initialize();
440 }
441
442 bool LightProbeVolume::renderProbes(UINT32 maxProbes)
443 {
444 // Probe map only contains active probes
445 UINT32 numUsedProbes = (UINT32)mProbeMap.size();
446 if(numUsedProbes > mCoeffBufferSize)
447 resizeCoefficientTexture(std::max(32U, numUsedProbes * 2));
448
449 UINT32 numProbeUpdates = 0;
450 for (; mFirstDirtyProbe < (UINT32)mProbeInfos.size(); ++mFirstDirtyProbe)
451 {
452 LightProbeInfo& probeInfo = mProbeInfos[mFirstDirtyProbe];
453
454 if(probeInfo.flags == LightProbeFlags::Dirty)
455 {
456 TEXTURE_DESC cubemapDesc;
457 cubemapDesc.type = TEX_TYPE_CUBE_MAP;
458 cubemapDesc.format = PF_RGBA16F;
459 cubemapDesc.width = 256; // Note: Test different sizes and their effect on quality
460 cubemapDesc.height = 256;
461 cubemapDesc.usage = TU_STATIC | TU_RENDERTARGET;
462
463 SPtr<Texture> cubemap = Texture::create(cubemapDesc);
464
465 Vector3 localPos = mProbePositions[mFirstDirtyProbe];
466
467 const Transform& tfrm = getTransform();
468 const Vector3& position = tfrm.getPosition();
469 const Quaternion& rotation = tfrm.getRotation();
470 Vector3 transformedPos = rotation.rotate(localPos) + position;
471
472 gRenderer()->captureSceneCubeMap(cubemap, transformedPos, CaptureSettings());
473 gIBLUtility().filterCubemapForIrradiance(cubemap, mCoefficients, probeInfo.bufferIdx);
474
475 probeInfo.flags = LightProbeFlags::Clean;
476 numProbeUpdates++;
477 }
478
479 if (maxProbes != 0 && numProbeUpdates >= maxProbes)
480 break;
481 }
482
483 gRenderer()->notifyLightProbeVolumeUpdated(this);
484
485 return mFirstDirtyProbe == (UINT32)mProbeInfos.size();
486 }
487
488 void LightProbeVolume::syncToCore(const CoreSyncData& data)
489 {
490 char* dataPtr = (char*)data.getBuffer();
491
492 bool oldIsActive = mActive;
493
494 dataPtr = coreSyncReadElem((SceneActor&)*this, dataPtr);
495
496 UINT32 numDirtyProbes, numRemovedProbes;
497 dataPtr = rttiReadElem(numDirtyProbes, dataPtr);
498 dataPtr = rttiReadElem(numRemovedProbes, dataPtr);
499
500 for (UINT32 i = 0; i < numDirtyProbes; ++i)
501 {
502 UINT32 handle;
503 dataPtr = rttiReadElem(handle, dataPtr);
504
505 Vector3 position;
506 dataPtr = rttiReadElem(position, dataPtr);
507
508 LightProbeFlags flags;
509 dataPtr = rttiReadElem(flags, dataPtr);
510
511 auto iterFind = mProbeMap.find(handle);
512 if(iterFind != mProbeMap.end())
513 {
514 // Update existing probe information
515 UINT32 compactIdx = iterFind->second;
516
517 mProbeInfos[compactIdx].flags = LightProbeFlags::Dirty;
518 mProbePositions[compactIdx] = position;
519
520 mFirstDirtyProbe = std::min(compactIdx, mFirstDirtyProbe);
521 }
522 else // Add a new probe
523 {
524 // Empty slots always start at a specific index because we always move them to the back of the array
525 UINT32 emptyProbeStartIdx = (UINT32)mProbeMap.size();
526 UINT32 numProbes = (UINT32)mProbeInfos.size();
527
528 // Find an empty slot to place the probe information at
529 UINT32 compactIdx = -1;
530 for(UINT32 j = emptyProbeStartIdx; j < numProbes; ++j)
531 {
532 if(mProbeInfos[j].flags == LightProbeFlags::Empty)
533 {
534 compactIdx = j;
535 break;
536 }
537 }
538
539 // Found an empty slot
540 if (compactIdx == (UINT32)-1)
541 {
542 compactIdx = (UINT32)mProbeInfos.size();
543
544 LightProbeInfo info;
545 info.flags = LightProbeFlags::Dirty;
546 info.bufferIdx = compactIdx;
547 info.handle = handle;
548
549 mProbeInfos.push_back(info);
550 mProbePositions.push_back(position);
551 }
552 else // No empty slot, add a new one
553 {
554 LightProbeInfo& info = mProbeInfos[compactIdx];
555 info.flags = LightProbeFlags::Dirty;
556 info.handle = handle;
557
558 mProbePositions[compactIdx] = position;
559 }
560
561 mProbeMap[handle] = compactIdx;
562 mFirstDirtyProbe = std::min(compactIdx, mFirstDirtyProbe);
563 }
564 }
565
566 // Mark slots for removed probes as empty, and move them back to the end of the array
567 for (UINT32 i = 0; i < numRemovedProbes; ++i)
568 {
569 UINT32 idx;
570 dataPtr = rttiReadElem(idx, dataPtr);
571
572 auto iterFind = mProbeMap.find(idx);
573 if(iterFind != mProbeMap.end())
574 {
575 UINT32 compactIdx = iterFind->second;
576
577 LightProbeInfo& info = mProbeInfos[compactIdx];
578 info.flags = LightProbeFlags::Empty;
579
580 // Move the empty info to the back of the array so all non-empty probes are contiguous
581 // Search from back to current index, and find first non-empty probe to switch switch
582 UINT32 lastSearchIdx = (UINT32)mProbeInfos.size() - 1;
583 while (lastSearchIdx >= (UINT32)compactIdx)
584 {
585 LightProbeFlags flags = mProbeInfos[lastSearchIdx].flags;
586 if (flags != LightProbeFlags::Empty)
587 {
588 std::swap(mProbeInfos[i], mProbeInfos[lastSearchIdx]);
589 std::swap(mProbePositions[i], mProbePositions[lastSearchIdx]);
590
591 mProbeMap[mProbeInfos[lastSearchIdx].handle] = i;
592 break;
593 }
594
595 lastSearchIdx--;
596 }
597
598 mProbeMap.erase(iterFind);
599 }
600 }
601
602 if (oldIsActive != mActive)
603 {
604 if (mActive)
605 gRenderer()->notifyLightProbeVolumeAdded(this);
606 else
607 gRenderer()->notifyLightProbeVolumeRemoved(this);
608 }
609 }
610
611 void LightProbeVolume::getProbeCoefficients(Vector<LightProbeCoefficientInfo>& output) const
612 {
613 UINT32 numActiveProbes = (UINT32)mProbeMap.size();
614 if (numActiveProbes == 0)
615 return;
616
617 output.resize(numActiveProbes);
618
619 LightProbeSHCoefficients* coefficients = bs_stack_alloc<LightProbeSHCoefficients>(numActiveProbes);
620
621 SPtr<PixelData> coeffData = mCoefficients->getProperties().allocBuffer(0, 0);
622 mCoefficients->readData(*coeffData);
623
624 UINT32 probesPerRow = coeffData->getWidth() / 9;
625 UINT32 probeIdx = 0;
626 for(UINT32 y = 0; y < coeffData->getHeight(); ++y)
627 {
628 for(UINT32 x = 0; x < probesPerRow; ++x)
629 {
630 if(probeIdx >= numActiveProbes)
631 break;
632
633 for(UINT32 i = 0; i < 9; i++)
634 {
635 Color value = coeffData->getColorAt(x * 9, y);
636
637 coefficients[probeIdx].coeffsR[i] = value.r;
638 coefficients[probeIdx].coeffsG[i] = value.g;
639 coefficients[probeIdx].coeffsB[i] = value.b;
640 }
641
642 probeIdx++;
643 }
644 }
645
646 for(UINT32 i = 0; i < numActiveProbes; ++i)
647 {
648 output[i].coefficients = coefficients[mProbeInfos[i].bufferIdx];
649 output[i].handle = mProbeInfos[i].handle;
650 }
651
652 bs_stack_free(coefficients);
653 }
654
655 void LightProbeVolume::resizeCoefficientTexture(UINT32 count)
656 {
657 Vector2I texSize = IBLUtility::getSHCoeffTextureSize(count, 3);
658
659 TEXTURE_DESC desc;
660 desc.width = (UINT32)texSize.x;
661 desc.height = (UINT32)texSize.y;
662 desc.usage = TU_LOADSTORE | TU_RENDERTARGET;
663 desc.format = PF_RGBA32F;
664
665 SPtr<Texture> newTexture = Texture::create(desc);
666
667 if (mCoefficients)
668 mCoefficients->copy(newTexture);
669
670 mCoefficients = newTexture;
671 mCoeffBufferSize = count;
672 }
673}}
674