|
|
@@ -5,7 +5,9 @@
|
|
|
#include "BsFrameAlloc.h"
|
|
|
#include "BsRenderer.h"
|
|
|
#include "BsLight.h"
|
|
|
-#include <atlalloc.h>
|
|
|
+#include "BsGpuBuffer.h"
|
|
|
+#include "BsTexture.h"
|
|
|
+#include "BsIBLUtility.h"
|
|
|
|
|
|
namespace bs
|
|
|
{
|
|
|
@@ -21,10 +23,16 @@ namespace bs
|
|
|
// TODO - Generates probes in the grid volume
|
|
|
}
|
|
|
|
|
|
+ LightProbeVolume::~LightProbeVolume()
|
|
|
+ {
|
|
|
+ if (mRendererTask)
|
|
|
+ mRendererTask->cancel();
|
|
|
+ }
|
|
|
+
|
|
|
UINT32 LightProbeVolume::addProbe(const Vector3& position)
|
|
|
{
|
|
|
UINT32 handle = mNextProbeId++;
|
|
|
- mProbes[handle] = ProbeInfo(LightProbeFlags::Dirty, position);
|
|
|
+ mProbes[handle] = ProbeInfo(LightProbeFlags::Clean, position);
|
|
|
|
|
|
_markCoreDirty();
|
|
|
return handle;
|
|
|
@@ -45,7 +53,6 @@ namespace bs
|
|
|
auto iterFind = mProbes.find(handle);
|
|
|
if (iterFind != mProbes.end())
|
|
|
{
|
|
|
- iterFind->second.flags = LightProbeFlags::Dirty;
|
|
|
iterFind->second.position = position;
|
|
|
_markCoreDirty();
|
|
|
}
|
|
|
@@ -60,6 +67,94 @@ namespace bs
|
|
|
return Vector3::ZERO;
|
|
|
}
|
|
|
|
|
|
+ void LightProbeVolume::renderProbe(UINT32 handle)
|
|
|
+ {
|
|
|
+ auto iterFind = mProbes.find(handle);
|
|
|
+ if (iterFind != mProbes.end())
|
|
|
+ {
|
|
|
+ if (iterFind->second.flags == LightProbeFlags::Clean)
|
|
|
+ {
|
|
|
+ iterFind->second.flags = LightProbeFlags::Dirty;
|
|
|
+
|
|
|
+ _markCoreDirty();
|
|
|
+ runRenderProbeTask();
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ void LightProbeVolume::renderProbes()
|
|
|
+ {
|
|
|
+ bool anyModified = false;
|
|
|
+ for(auto& entry : mProbes)
|
|
|
+ {
|
|
|
+ if (entry.second.flags == LightProbeFlags::Clean)
|
|
|
+ {
|
|
|
+ entry.second.flags = LightProbeFlags::Dirty;
|
|
|
+ anyModified = true;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if (anyModified)
|
|
|
+ {
|
|
|
+ _markCoreDirty();
|
|
|
+ runRenderProbeTask();
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ void LightProbeVolume::runRenderProbeTask()
|
|
|
+ {
|
|
|
+ // If a task is already running cancel it
|
|
|
+ // Note: If the task is just about to start processing, cancelling it will skip the update this frame
|
|
|
+ // (which might be fine if we just changed positions of dirty probes it was about to update, but it might also
|
|
|
+ // waste a frame if those positions needed to be updated anyway). For now I'm ignoring it as it seems like a rare
|
|
|
+ // enough situation, plus it's one that will only happen during development time.
|
|
|
+ if (mRendererTask)
|
|
|
+ mRendererTask->cancel();
|
|
|
+
|
|
|
+ auto renderComplete = [this]()
|
|
|
+ {
|
|
|
+ mRendererTask = nullptr;
|
|
|
+ };
|
|
|
+
|
|
|
+ SPtr<ct::LightProbeVolume> coreProbeVolume = getCore();
|
|
|
+ auto renderProbes = [coreProbeVolume]()
|
|
|
+ {
|
|
|
+ return coreProbeVolume->renderProbes(3);
|
|
|
+ };
|
|
|
+
|
|
|
+ mRendererTask = ct::RendererTask::create("RenderLightProbes", renderProbes);
|
|
|
+
|
|
|
+ mRendererTask->onComplete.connect(renderComplete);
|
|
|
+ ct::gRenderer()->addTask(mRendererTask);
|
|
|
+ }
|
|
|
+
|
|
|
+ void LightProbeVolume::updateCoefficients()
|
|
|
+ {
|
|
|
+ // Ensure all light probe coefficients are generated
|
|
|
+ if (mRendererTask)
|
|
|
+ mRendererTask->wait();
|
|
|
+
|
|
|
+ ct::LightProbeVolume* coreVolume = getCore().get();
|
|
|
+
|
|
|
+ Vector<LightProbeCoefficientInfo> coeffInfo;
|
|
|
+ auto getSaveData = [coreVolume, &coeffInfo]()
|
|
|
+ {
|
|
|
+ coreVolume->getProbeCoefficients(coeffInfo);
|
|
|
+ };
|
|
|
+
|
|
|
+ gCoreThread().queueCommand(getSaveData);
|
|
|
+ gCoreThread().submit(true);
|
|
|
+
|
|
|
+ for(auto& entry : coeffInfo)
|
|
|
+ {
|
|
|
+ auto iterFind = mProbes.find(entry.handle);
|
|
|
+ if (iterFind == mProbes.end())
|
|
|
+ continue;
|
|
|
+
|
|
|
+ iterFind->second.coefficients = entry.coefficients;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
SPtr<ct::LightProbeVolume> LightProbeVolume::getCore() const
|
|
|
{
|
|
|
return std::static_pointer_cast<ct::LightProbeVolume>(mCoreSpecific);
|
|
|
@@ -116,6 +211,9 @@ namespace bs
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+ for (auto& probe : removedProbes)
|
|
|
+ mProbes.erase(probe);
|
|
|
+
|
|
|
UINT32 numDirtyProbes = (UINT32)dirtyProbes.size();
|
|
|
UINT32 numRemovedProbes = (UINT32)removedProbes.size();
|
|
|
|
|
|
@@ -170,6 +268,8 @@ namespace bs
|
|
|
{
|
|
|
LightProbeVolume::LightProbeVolume(const UnorderedMap<UINT32, bs::LightProbeVolume::ProbeInfo>& probes)
|
|
|
{
|
|
|
+ mInitCoefficients.resize(probes.size());
|
|
|
+
|
|
|
UINT32 probeIdx = 0;
|
|
|
for(auto& entry : probes)
|
|
|
{
|
|
|
@@ -178,10 +278,12 @@ namespace bs
|
|
|
|
|
|
LightProbeInfo probeInfo;
|
|
|
probeInfo.flags = LightProbeFlags::Dirty;
|
|
|
- probeInfo.bufferIdx = -1;
|
|
|
- probeInfo.handle = probeIdx;
|
|
|
+ probeInfo.bufferIdx = probeIdx;
|
|
|
+ probeInfo.handle = entry.first;
|
|
|
|
|
|
mProbeInfos[probeIdx] = probeInfo;
|
|
|
+ mInitCoefficients[probeIdx] = entry.second.coefficients;
|
|
|
+
|
|
|
probeIdx++;
|
|
|
}
|
|
|
}
|
|
|
@@ -193,73 +295,55 @@ namespace bs
|
|
|
|
|
|
void LightProbeVolume::initialize()
|
|
|
{
|
|
|
- gRenderer()->notifyLightProbeVolumeAdded(this);
|
|
|
+ // Set SH coefficients loaded from the file
|
|
|
+ UINT32 numCoefficients = (UINT32)mInitCoefficients.size();
|
|
|
+ assert(mInitCoefficients.size() == mProbeMap.size());
|
|
|
+
|
|
|
+ resizeCoefficientBuffer(std::max(32U, numCoefficients));
|
|
|
+ mCoefficients->writeData(0, sizeof(LightProbeSHCoefficients) * numCoefficients, mInitCoefficients.data());
|
|
|
+ mInitCoefficients.clear();
|
|
|
|
|
|
+ gRenderer()->notifyLightProbeVolumeAdded(this);
|
|
|
CoreObject::initialize();
|
|
|
}
|
|
|
|
|
|
- void LightProbeVolume::prune(Vector<UINT32>& freedEntries, bool freeAll)
|
|
|
+ bool LightProbeVolume::renderProbes(UINT32 maxProbes)
|
|
|
{
|
|
|
- UINT32 numProbes = (UINT32)mProbeInfos.size();
|
|
|
- INT32 lastSearchIdx = numProbes - 1;
|
|
|
-
|
|
|
- for (UINT32 i = 0; i < (UINT32)mProbeInfos.size(); ++i)
|
|
|
+ // Probe map only contains active probes
|
|
|
+ UINT32 numUsedProbes = (UINT32)mProbeMap.size();
|
|
|
+ if(numUsedProbes > mCoeffBufferSize)
|
|
|
+ resizeCoefficientBuffer(std::max(32U, numUsedProbes * 2));
|
|
|
+
|
|
|
+ UINT32 numProbeUpdates = 0;
|
|
|
+ for (; mFirstDirtyProbe < (UINT32)mProbeInfos.size(); ++mFirstDirtyProbe)
|
|
|
{
|
|
|
- LightProbeInfo& info = mProbeInfos[i];
|
|
|
+ LightProbeInfo& probeInfo = mProbeInfos[mFirstDirtyProbe];
|
|
|
|
|
|
- if (info.flags == LightProbeFlags::Removed)
|
|
|
+ if(probeInfo.flags == LightProbeFlags::Dirty)
|
|
|
{
|
|
|
- if (info.bufferIdx != -1)
|
|
|
- freedEntries.push_back(info.bufferIdx);
|
|
|
-
|
|
|
- info.flags = LightProbeFlags::Empty;
|
|
|
-
|
|
|
- // Replace the empty spot with an element from the back
|
|
|
- while (lastSearchIdx >= (INT32)i)
|
|
|
- {
|
|
|
- bool foundNonEmpty = false;
|
|
|
- LightProbeFlags flags = mProbeInfos[lastSearchIdx].flags;
|
|
|
- if (flags != LightProbeFlags::Empty)
|
|
|
- {
|
|
|
- std::swap(mProbeInfos[i], mProbeInfos[lastSearchIdx]);
|
|
|
- std::swap(mProbePositions[i], mProbePositions[lastSearchIdx]);
|
|
|
+ TEXTURE_DESC cubemapDesc;
|
|
|
+ cubemapDesc.type = TEX_TYPE_CUBE_MAP;
|
|
|
+ cubemapDesc.format = PF_FLOAT16_RGB;
|
|
|
+ cubemapDesc.width = IBLUtility::IRRADIANCE_CUBEMAP_SIZE;
|
|
|
+ cubemapDesc.height = IBLUtility::IRRADIANCE_CUBEMAP_SIZE;
|
|
|
+ cubemapDesc.usage = TU_STATIC | TU_RENDERTARGET;
|
|
|
|
|
|
- mProbeMap[mProbeInfos[lastSearchIdx].handle] = i;
|
|
|
- foundNonEmpty = true;
|
|
|
- }
|
|
|
+ SPtr<Texture> cubemap = Texture::create(cubemapDesc);
|
|
|
|
|
|
- // Remove last element
|
|
|
- mProbeInfos.erase(mProbeInfos.begin() + lastSearchIdx);
|
|
|
- mProbePositions.erase(mProbePositions.begin() + lastSearchIdx);
|
|
|
- lastSearchIdx--;
|
|
|
+ gRenderer()->captureSceneCubeMap(cubemap, mProbePositions[mFirstDirtyProbe], true);
|
|
|
+ gIBLUtility().filterCubemapForIrradiance(cubemap, mCoefficients, probeInfo.bufferIdx);
|
|
|
|
|
|
- // Search is done, we found an element to fill the empty spot
|
|
|
- if (foundNonEmpty)
|
|
|
- break;
|
|
|
- }
|
|
|
+ probeInfo.flags = LightProbeFlags::Clean;
|
|
|
+ numProbeUpdates++;
|
|
|
}
|
|
|
- }
|
|
|
|
|
|
- if(freeAll)
|
|
|
- {
|
|
|
- // Add all remaining (non-removed) probes to the free list, and mark them as dirty so when/if those probes
|
|
|
- // get used again, the systems knows they are out of date
|
|
|
- for (UINT32 i = 0; i < (UINT32)mProbeInfos.size(); ++i)
|
|
|
- {
|
|
|
- LightProbeInfo& info = mProbeInfos[i];
|
|
|
+ if (maxProbes != 0 && numProbeUpdates >= maxProbes)
|
|
|
+ break;
|
|
|
+ }
|
|
|
|
|
|
- if (info.flags != LightProbeFlags::Empty)
|
|
|
- {
|
|
|
- if (info.bufferIdx != -1)
|
|
|
- {
|
|
|
- freedEntries.push_back(info.bufferIdx);
|
|
|
- info.bufferIdx = -1;
|
|
|
- }
|
|
|
+ gRenderer()->notifyLightProbeVolumeUpdated(this, true);
|
|
|
|
|
|
- info.flags = LightProbeFlags::Dirty;
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
+ return mFirstDirtyProbe == (UINT32)mProbeInfos.size();
|
|
|
}
|
|
|
|
|
|
void LightProbeVolume::syncToCore(const CoreSyncData& data)
|
|
|
@@ -290,27 +374,59 @@ namespace bs
|
|
|
auto iterFind = mProbeMap.find(handle);
|
|
|
if(iterFind != mProbeMap.end())
|
|
|
{
|
|
|
+ // Update existing probe information
|
|
|
UINT32 compactIdx = iterFind->second;
|
|
|
|
|
|
mProbeInfos[compactIdx].flags = LightProbeFlags::Dirty;
|
|
|
mProbePositions[compactIdx] = position;
|
|
|
+
|
|
|
+ mFirstDirtyProbe = std::min(compactIdx, mFirstDirtyProbe);
|
|
|
}
|
|
|
- else
|
|
|
+ else // Add a new probe
|
|
|
{
|
|
|
- UINT32 compactIdx = (UINT32)mProbeInfos.size();
|
|
|
+ // Empty slots always start at a specific index because we always move them to the back of the array
|
|
|
+ UINT32 emptyProbeStartIdx = (UINT32)mProbeMap.size();
|
|
|
+ UINT32 numProbes = (UINT32)mProbeInfos.size();
|
|
|
|
|
|
- LightProbeInfo info;
|
|
|
- info.flags = LightProbeFlags::Dirty;
|
|
|
- info.bufferIdx = -1;
|
|
|
- info.handle = handle;
|
|
|
+ // Find an empty slot to place the probe information at
|
|
|
+ UINT32 compactIdx = -1;
|
|
|
+ for(UINT32 j = emptyProbeStartIdx; j < numProbes; ++j)
|
|
|
+ {
|
|
|
+ if(mProbeInfos[j].flags == LightProbeFlags::Empty)
|
|
|
+ {
|
|
|
+ compactIdx = j;
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // Found an empty slot
|
|
|
+ if (compactIdx == -1)
|
|
|
+ {
|
|
|
+ compactIdx = (UINT32)mProbeInfos.size();
|
|
|
+
|
|
|
+ LightProbeInfo info;
|
|
|
+ info.flags = LightProbeFlags::Dirty;
|
|
|
+ info.bufferIdx = compactIdx;
|
|
|
+ info.handle = handle;
|
|
|
+
|
|
|
+ mProbeInfos.push_back(info);
|
|
|
+ mProbePositions.push_back(position);
|
|
|
+ }
|
|
|
+ else // No empty slot, add a new one
|
|
|
+ {
|
|
|
+ LightProbeInfo& info = mProbeInfos[compactIdx];
|
|
|
+ info.flags = LightProbeFlags::Dirty;
|
|
|
+ info.handle = handle;
|
|
|
|
|
|
- mProbeInfos.push_back(info);
|
|
|
- mProbePositions.push_back(position);
|
|
|
+ mProbePositions[compactIdx] = position;
|
|
|
+ }
|
|
|
|
|
|
mProbeMap[handle] = compactIdx;
|
|
|
+ mFirstDirtyProbe = std::min(compactIdx, mFirstDirtyProbe);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+ // Mark slots for removed probes as empty, and move them back to the end of the array
|
|
|
for (UINT32 i = 0; i < numRemovedProbes; ++i)
|
|
|
{
|
|
|
UINT32 idx;
|
|
|
@@ -322,7 +438,25 @@ namespace bs
|
|
|
UINT32 compactIdx = iterFind->second;
|
|
|
|
|
|
LightProbeInfo& info = mProbeInfos[compactIdx];
|
|
|
- info.flags = LightProbeFlags::Removed;
|
|
|
+ info.flags = LightProbeFlags::Empty;
|
|
|
+
|
|
|
+ // Move the empty info to the back of the array so all non-empty probes are contiguous
|
|
|
+ // Search from back to current index, and find first non-empty probe to switch switch
|
|
|
+ UINT32 lastSearchIdx = (UINT32)mProbeInfos.size() - 1;
|
|
|
+ while (lastSearchIdx >= (INT32)compactIdx)
|
|
|
+ {
|
|
|
+ LightProbeFlags flags = mProbeInfos[lastSearchIdx].flags;
|
|
|
+ if (flags != LightProbeFlags::Empty)
|
|
|
+ {
|
|
|
+ std::swap(mProbeInfos[i], mProbeInfos[lastSearchIdx]);
|
|
|
+ std::swap(mProbePositions[i], mProbePositions[lastSearchIdx]);
|
|
|
+
|
|
|
+ mProbeMap[mProbeInfos[lastSearchIdx].handle] = i;
|
|
|
+ break;
|
|
|
+ }
|
|
|
+
|
|
|
+ lastSearchIdx--;
|
|
|
+ }
|
|
|
|
|
|
mProbeMap.erase(iterFind);
|
|
|
}
|
|
|
@@ -338,7 +472,44 @@ namespace bs
|
|
|
else
|
|
|
{
|
|
|
if(mIsActive)
|
|
|
- gRenderer()->notifyLightProbeVolumeUpdated(this);
|
|
|
+ gRenderer()->notifyLightProbeVolumeUpdated(this, false);
|
|
|
}
|
|
|
}
|
|
|
+
|
|
|
+ void LightProbeVolume::getProbeCoefficients(Vector<LightProbeCoefficientInfo>& output) const
|
|
|
+ {
|
|
|
+ UINT32 numActiveProbes = (UINT32)mProbeMap.size();
|
|
|
+ if (numActiveProbes == 0)
|
|
|
+ return;
|
|
|
+
|
|
|
+ output.resize(numActiveProbes);
|
|
|
+
|
|
|
+ LightProbeSHCoefficients* coefficients = bs_stack_alloc<LightProbeSHCoefficients>(numActiveProbes);
|
|
|
+ mCoefficients->readData(0, sizeof(LightProbeSHCoefficients) * numActiveProbes, coefficients);
|
|
|
+
|
|
|
+ for(UINT32 i = 0; i < numActiveProbes; ++i)
|
|
|
+ {
|
|
|
+ output[i].coefficients = coefficients[mProbeInfos[i].bufferIdx];
|
|
|
+ output[i].handle = mProbeInfos[i].handle;
|
|
|
+ }
|
|
|
+
|
|
|
+ bs_stack_free(coefficients);
|
|
|
+ }
|
|
|
+
|
|
|
+ void LightProbeVolume::resizeCoefficientBuffer(UINT32 count)
|
|
|
+ {
|
|
|
+ GPU_BUFFER_DESC desc;
|
|
|
+ desc.type = GBT_STRUCTURED;
|
|
|
+ desc.elementSize = sizeof(LightProbeSHCoefficients);
|
|
|
+ desc.elementCount = count;
|
|
|
+ desc.usage = GBU_STATIC;
|
|
|
+ desc.format = BF_UNKNOWN;
|
|
|
+
|
|
|
+ SPtr<GpuBuffer> newBuffer = GpuBuffer::create(desc);
|
|
|
+ if (mCoefficients)
|
|
|
+ newBuffer->copyData(*mCoefficients, 0, 0, mCoefficients->getSize(), true);
|
|
|
+
|
|
|
+ mCoefficients = newBuffer;
|
|
|
+ mCoeffBufferSize = count;
|
|
|
+ }
|
|
|
}}
|