Bake probes on the GPU without downloading them to system memory

Only apply probes after a full pass has completed
Stop baking after 5 iterations
This commit is contained in:
Magnus Norddahl 2025-07-10 01:03:30 +02:00
commit 7f77dc3d70
10 changed files with 393 additions and 164 deletions

View file

@ -1,15 +1,14 @@
#include "hw_lightprobe.h"
#include "v_video.h"
void LightProbeIncrementalBuilder::Step(const TArray<LightProbe>& probes, std::function<void(const LightProbe&, TArrayView<uint16_t>&, TArrayView<uint16_t>&)> renderScene, std::function<void(const TArray<uint16_t>&, const TArray<uint16_t>&)> uploadEnv)
void LightProbeIncrementalBuilder::Step(const TArray<LightProbe>& probes, std::function<void(int probeIndex, const LightProbe& probe)> renderScene)
{
if (probes.size() == 0)
return;
if (lastIndex >= probes.size())
{
lastIndex = 0;
if (!probes.size())
{
return;
}
}
if (cubemapsAllocated != probes.size())
@ -17,39 +16,35 @@ void LightProbeIncrementalBuilder::Step(const TArray<LightProbe>& probes, std::f
int newSegments = probes.size();
int lastSegments = cubemapsAllocated;
irradianceMaps.resize(probes.size() * irradianceBytes);
prefilterMaps.resize(probes.size() * prefilterBytes);
cubemapsAllocated = probes.size();
lastIndex = 0;
collected = 0;
iterations = 0;
// needed because otherwise it somehow gets corrupted
memset(irradianceMaps.Data(), 0, irradianceMaps.Size() * sizeof(uint16_t));
memset(prefilterMaps.Data(), 0, prefilterMaps.Size() * sizeof(uint16_t));
// workaround for lack of boundary checking in GPU
uploadEnv(this->irradianceMaps, this->prefilterMaps);
screen->ResetLightProbes();
return;
}
auto irradianceBuffer = TArrayView<uint16_t>(irradianceMaps.data() + lastIndex * irradianceBytes, irradianceBytes);
auto prefilterBuffer = TArrayView<uint16_t>(prefilterMaps.data() + lastIndex * prefilterBytes, prefilterBytes);
if (iterations >= 5)
return; // We are done baking
renderScene(probes[lastIndex++], irradianceBuffer, prefilterBuffer);
++collected;
renderScene(lastIndex, probes[lastIndex]);
lastIndex++;
collected++;
if (lastIndex >= probes.size())
{
if (collected == probes.size())
{
uploadEnv(this->irradianceMaps, this->prefilterMaps);
screen->EndLightProbePass();
}
collected = 0;
iterations++;
}
}
void LightProbeIncrementalBuilder::Full(const TArray<LightProbe>& probes, std::function<void(const LightProbe&, TArrayView<uint16_t>&, TArrayView<uint16_t>&)> renderScene, std::function<void(const TArray<uint16_t>&, const TArray<uint16_t>&)> uploadEnv)
void LightProbeIncrementalBuilder::Full(const TArray<LightProbe>& probes, std::function<void(int probeIndex, const LightProbe& probe)> renderScene)
{
if (lastIndex >= probes.size())
{
@ -63,6 +58,6 @@ void LightProbeIncrementalBuilder::Full(const TArray<LightProbe>& probes, std::f
while (lastIndex < probes.size())
{
Step(probes, renderScene, uploadEnv);
Step(probes, renderScene);
}
}

View file

@ -15,26 +15,15 @@ struct LightProbeTarget
class LightProbeIncrementalBuilder
{
int lastIndex = 0;
int collected = 0;
int cubemapsAllocated = 0;
TArray<uint16_t> irradianceMaps;
TArray<uint16_t> prefilterMaps;
size_t irradianceBytes;
size_t prefilterBytes;
public:
LightProbeIncrementalBuilder(size_t irradianceTexels, size_t prefilterTexels, size_t irradianceChannels, size_t prefilterChannels) :
irradianceBytes(irradianceTexels * irradianceChannels),
prefilterBytes(prefilterTexels * prefilterChannels)
{
}
void Step(const TArray<LightProbe>& probes, std::function<void(const LightProbe&, TArrayView<uint16_t>&, TArrayView<uint16_t>&)> renderScene, std::function<void(const TArray<uint16_t>&, const TArray<uint16_t>&)> uploadEnv);
void Full(const TArray<LightProbe>& probes, std::function<void(const LightProbe&, TArrayView<uint16_t>&, TArrayView<uint16_t>&)> renderScene, std::function<void(const TArray<uint16_t>&, const TArray<uint16_t>&)> uploadEnv);
void Step(const TArray<LightProbe>& probes, std::function<void(int probeIndex, const LightProbe& probe)> renderScene);
void Full(const TArray<LightProbe>& probes, std::function<void(int probeIndex, const LightProbe& probe)> renderScene);
auto GetStep() const { return lastIndex; }
auto GetBufferSize() const { return irradianceMaps.size() && prefilterMaps.size(); }
};
private:
int lastIndex = 0;
int collected = 0;
int cubemapsAllocated = 0;
int iterations = 0;
};

View file

@ -229,8 +229,11 @@ public:
// Get the array index for the material in the textures array accessible from shaders
virtual int GetBindlessTextureIndex(FMaterial* material, int clampmode, int translation, bool paletteMode) { return -1; }
virtual void RenderEnvironmentMap(std::function<void(IntRect& bounds, int side)> renderFunc, TArrayView<uint16_t>& irradianceMap, TArrayView<uint16_t>& prefilterMap) {}
virtual void UploadEnvironmentMaps(int cubemapCount, const TArray<uint16_t>& irradianceMaps, const TArray<uint16_t>& prefilterMaps) {}
virtual void ResetLightProbes() {}
virtual void RenderLightProbe(int probeIndex, std::function<void(IntRect& bounds, int side)> renderFunc) {}
virtual void EndLightProbePass() {}
virtual void DownloadLightProbes(int probeCount, TArrayView<uint16_t> irradianceMaps, TArrayView<uint16_t> prefilterMaps) {}
virtual void UploadLightProbes(int probeCount, const TArray<uint16_t>& irradianceMaps, const TArray<uint16_t>& prefilterMaps) {}
// Screen wiping
virtual FTexture *WipeStartScreen();

View file

@ -414,20 +414,138 @@ void VkTextureManager::CreatePrefiltermap()
UploadPrefiltermap(1, std::move(data));
}
void VkTextureManager::UploadIrradiancemap(int cubeCount, const TArray<uint16_t>& srcPixels)
void VkTextureManager::ResetLightProbes()
{
// Special thanks to Khronos for making it so simple to clear an image...
auto cmdbuffer = fb->GetCommands()->GetTransferCommands();
int miplevels = MAX_REFLECTION_LOD + 1;
VkImageTransition barrier0;
for (auto& map : Prefiltermaps)
{
if (map.Image)
barrier0.AddImage(&map, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, true, 0, miplevels, 0, 6);
}
for (auto& map : Irradiancemaps)
{
if (map.Image)
barrier0.AddImage(&map, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, true, 0, 1, 0, 6);
}
barrier0.Execute(cmdbuffer);
VkClearColorValue color = {};
VkImageSubresourceRange range = {};
range.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
range.layerCount = 6;
range.levelCount = miplevels;
for (auto& map : Prefiltermaps)
{
if (map.Image)
cmdbuffer->clearColorImage(map.Image->image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, &color, 1, &range);
}
range.levelCount = 1;
for (auto& map : Irradiancemaps)
{
if (map.Image)
cmdbuffer->clearColorImage(map.Image->image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, &color, 1, &range);
}
VkImageTransition barrier1;
for (auto& map : Prefiltermaps)
{
if (map.Image)
barrier1.AddImage(&map, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, false, 0, miplevels, 0, 6);
}
for (auto& map : Irradiancemaps)
{
if (map.Image)
barrier1.AddImage(&map, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, false, 0, 1, 0, 6);
}
barrier1.Execute(cmdbuffer);
}
void VkTextureManager::CopyIrradiancemap(const std::vector<std::unique_ptr<VulkanImage>>& probes)
{
CheckIrradiancemapSize(probes.size());
auto cmdbuffer = fb->GetCommands()->GetDrawCommands();
VkImageTransition barrier0;
for (size_t i = 0; i < probes.size(); i++)
barrier0.AddImage(&Irradiancemaps[i], VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, true, 0, 1, 0, 6);
barrier0.Execute(cmdbuffer);
VkImageCopy region = {};
region.extent.width = IrradiancemapSize;
region.extent.height = IrradiancemapSize;
region.extent.depth = 1;
region.srcSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
region.srcSubresource.layerCount = 6;
region.dstSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
region.dstSubresource.layerCount = 6;
for (size_t i = 0; i < probes.size(); i++)
{
cmdbuffer->copyImage(probes[i]->image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, Irradiancemaps[i].Image->image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, &region);
}
VkImageTransition barrier1;
for (size_t i = 0; i < probes.size(); i++)
barrier1.AddImage(&Irradiancemaps[i], VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, false, 0, 1, 0, 6);
barrier1.Execute(cmdbuffer);
}
void VkTextureManager::CopyPrefiltermap(const std::vector<std::unique_ptr<VulkanImage>>& probes)
{
CheckPrefiltermapSize(probes.size());
auto cmdbuffer = fb->GetCommands()->GetDrawCommands();
int miplevels = MAX_REFLECTION_LOD + 1;
VkImageTransition barrier0;
for (size_t i = 0; i < probes.size(); i++)
barrier0.AddImage(&Prefiltermaps[i], VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, true, 0, miplevels, 0, 6);
barrier0.Execute(cmdbuffer);
std::vector<VkImageCopy> regions;
for (int level = 0; level < miplevels; level++)
{
VkImageCopy region = {};
region.extent.width = PrefiltermapSize >> level;
region.extent.height = PrefiltermapSize >> level;
region.extent.depth = 1;
region.srcSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
region.srcSubresource.layerCount = 6;
region.srcSubresource.mipLevel = level;
region.dstSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
region.dstSubresource.layerCount = 6;
region.dstSubresource.mipLevel = level;
regions.push_back(region);
}
for (size_t i = 0; i < probes.size(); i++)
{
cmdbuffer->copyImage(probes[i]->image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, Prefiltermaps[i].Image->image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, (uint32_t)regions.size(), regions.data());
}
VkImageTransition barrier1;
for (size_t i = 0; i < probes.size(); i++)
barrier1.AddImage(&Prefiltermaps[i], VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, false, 0, miplevels, 0, 6);
barrier1.Execute(cmdbuffer);
}
void VkTextureManager::CheckIrradiancemapSize(int cubeCount)
{
int createStart = Irradiancemaps.size();
if (Irradiancemaps.size() <= (size_t)cubeCount)
Irradiancemaps.resize(cubeCount);
int w = IrradiancemapSize;
int h = IrradiancemapSize;
int pixelsize = 8;
for (int i = createStart; i < cubeCount; i++)
{
Irradiancemaps[i].Image = ImageBuilder()
.Size(w, h, 1, 6)
.Size(IrradiancemapSize, IrradiancemapSize, 1, 6)
.Format(VK_FORMAT_R16G16B16A16_SFLOAT)
.Usage(VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT)
.Flags(VK_IMAGE_CREATE_CUBE_COMPATIBLE_BIT)
@ -440,6 +558,44 @@ void VkTextureManager::UploadIrradiancemap(int cubeCount, const TArray<uint16_t>
.DebugName("VkTextureManager.IrradiancemapView")
.Create(fb->GetDevice());
}
}
void VkTextureManager::CheckPrefiltermapSize(int cubeCount)
{
int createStart = Prefiltermaps.size();
if (Prefiltermaps.size() <= (size_t)cubeCount)
Prefiltermaps.resize(cubeCount);
int w = PrefiltermapSize;
int h = PrefiltermapSize;
int pixelsize = 8;
int miplevels = MAX_REFLECTION_LOD + 1;
for (int i = createStart; i < cubeCount; i++)
{
Prefiltermaps[i].Image = ImageBuilder()
.Size(w, h, miplevels, 6)
.Format(VK_FORMAT_R16G16B16A16_SFLOAT)
.Usage(VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT)
.Flags(VK_IMAGE_CREATE_CUBE_COMPATIBLE_BIT)
.DebugName("VkTextureManager.Prefiltermap")
.Create(fb->GetDevice());
Prefiltermaps[i].View = ImageViewBuilder()
.Type(VK_IMAGE_VIEW_TYPE_CUBE)
.Image(Prefiltermaps[i].Image.get(), VK_FORMAT_R16G16B16A16_SFLOAT)
.DebugName("VkTextureManager.PrefiltermapView")
.Create(fb->GetDevice());
}
}
void VkTextureManager::UploadIrradiancemap(int cubeCount, const TArray<uint16_t>& srcPixels)
{
CheckIrradiancemapSize(cubeCount);
int w = IrradiancemapSize;
int h = IrradiancemapSize;
int pixelsize = 8;
auto cmdbuffer = fb->GetCommands()->GetTransferCommands();
@ -495,32 +651,13 @@ void VkTextureManager::UploadIrradiancemap(int cubeCount, const TArray<uint16_t>
void VkTextureManager::UploadPrefiltermap(int cubeCount, const TArray<uint16_t>& srcPixels)
{
int createStart = Prefiltermaps.size();
if (Prefiltermaps.size() <= (size_t)cubeCount)
Prefiltermaps.resize(cubeCount);
CheckPrefiltermapSize(cubeCount);
int w = PrefiltermapSize;
int h = PrefiltermapSize;
int pixelsize = 8;
int miplevels = MAX_REFLECTION_LOD + 1;
for (int i = createStart; i < cubeCount; i++)
{
Prefiltermaps[i].Image = ImageBuilder()
.Size(w, h, miplevels, 6)
.Format(VK_FORMAT_R16G16B16A16_SFLOAT)
.Usage(VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT)
.Flags(VK_IMAGE_CREATE_CUBE_COMPATIBLE_BIT)
.DebugName("VkTextureManager.Prefiltermap")
.Create(fb->GetDevice());
Prefiltermaps[i].View = ImageViewBuilder()
.Type(VK_IMAGE_VIEW_TYPE_CUBE)
.Image(Prefiltermaps[i].Image.get(), VK_FORMAT_R16G16B16A16_SFLOAT)
.DebugName("VkTextureManager.PrefiltermapView")
.Create(fb->GetDevice());
}
auto cmdbuffer = fb->GetCommands()->GetTransferCommands();
if (srcPixels.size() != 0)
@ -594,6 +731,101 @@ void VkTextureManager::UploadPrefiltermap(int cubeCount, const TArray<uint16_t>&
barrier1.Execute(cmdbuffer);
}
void VkTextureManager::DownloadIrradiancemap(int probeCount, TArrayView<uint16_t> databuffer)
{
#if 0
const int texelCount = DFrameBuffer::irrandiaceMapTexelCount;
if (databuffer.Size() < texelCount)
return;
auto staging = BufferBuilder()
.Size(32 * 32 * 8 * 6)
.Usage(VK_BUFFER_USAGE_TRANSFER_DST_BIT, VMA_MEMORY_USAGE_GPU_TO_CPU)
.Create(fb->GetDevice());
PipelineBarrier barrier1;
for (int i = 0; i < 6; i++)
barrier1.AddImage(irradianceMap.images[i].get(), VK_IMAGE_LAYOUT_GENERAL, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, VK_ACCESS_SHADER_WRITE_BIT, VK_ACCESS_TRANSFER_READ_BIT);
barrier1.Execute(cmdbuffer, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT);
for (int i = 0; i < 6; i++)
{
VkBufferImageCopy region = { };
region.bufferOffset = 32 * 32 * 8 * i;
region.imageExtent.width = irradianceMap.images[i]->width;
region.imageExtent.height = irradianceMap.images[i]->height;
region.imageExtent.depth = 1;
region.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
region.imageSubresource.layerCount = 1;
cmdbuffer->copyImageToBuffer(irradianceMap.images[i]->image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, staging->buffer, 1, &region);
}
fb->GetCommands()->WaitForCommands(false);
// Copy while dropping the alpha channel
auto dst = databuffer.Data();
auto src = (uint16_t*)staging->Map(0, texelCount * 8);
for (int i = 0; i < texelCount; i++)
{
*(dst++) = *(src++);
*(dst++) = *(src++);
*(dst++) = *(src++);
src++;
}
staging->Unmap();
#endif
}
void VkTextureManager::DownloadPrefiltermap(int probeCount, TArrayView<uint16_t> databuffer)
{
#if 0
const int texelCount = DFrameBuffer::prefilterMapTexelCount;
if (databuffer.Size() < texelCount)
return;
auto staging = BufferBuilder()
.Size(prefilterMap.levelsSize * 6 * 8)
.Usage(VK_BUFFER_USAGE_TRANSFER_DST_BIT, VMA_MEMORY_USAGE_GPU_TO_CPU)
.Create(fb->GetDevice());
PipelineBarrier barrier1;
for (int i = 0; i < 6 * prefilterMap.maxlevels; i++)
barrier1.AddImage(prefilterMap.images[i].get(), VK_IMAGE_LAYOUT_GENERAL, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, VK_ACCESS_SHADER_WRITE_BIT, VK_ACCESS_TRANSFER_READ_BIT);
barrier1.Execute(cmdbuffer, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT);
int offset = 0;
for (int i = 0; i < 6 * prefilterMap.maxlevels; i++)
{
VkBufferImageCopy region = { };
region.bufferOffset = offset;
region.imageExtent.width = prefilterMap.images[i]->width;
region.imageExtent.height = prefilterMap.images[i]->height;
region.imageExtent.depth = 1;
region.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
region.imageSubresource.layerCount = 1;
cmdbuffer->copyImageToBuffer(prefilterMap.images[i]->image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, staging->buffer, 1, &region);
offset += prefilterMap.images[i]->width * prefilterMap.images[i]->height * 8;
}
fb->GetCommands()->WaitForCommands(false);
// Copy while dropping the alpha channel
auto dst = databuffer.Data();
auto src = (uint16_t*)staging->Map(0, texelCount * 8);
for (int i = 0; i < texelCount; i++)
{
*(dst++) = *(src++);
*(dst++) = *(src++);
*(dst++) = *(src++);
src++;
}
staging->Unmap();
#endif
}
void VkTextureManager::SetLightmapCount(int size, int count)
{
int startIndex = Lightmaps.size();

View file

@ -33,8 +33,16 @@ public:
void CreateLightmap(int size, int count, const TArray<uint16_t>& data);
void DownloadLightmap(int arrayIndex, uint16_t* buffer);
void ResetLightProbes();
void UploadIrradiancemap(int cubeCount, const TArray<uint16_t>& data);
void UploadPrefiltermap(int cubeCount, const TArray<uint16_t>& data);
void CopyIrradiancemap(const std::vector<std::unique_ptr<VulkanImage>>& probes);
void CopyPrefiltermap(const std::vector<std::unique_ptr<VulkanImage>>& probes);
void DownloadIrradiancemap(int probeCount, TArrayView<uint16_t> irradianceMaps);
void DownloadPrefiltermap(int probeCount, TArrayView<uint16_t> prefilterMaps);
void CheckIrradiancemapSize(int cubeCount);
void CheckPrefiltermapSize(int cubeCount);
void SetGamePalette();

View file

@ -252,20 +252,8 @@ void VkLightprober::CreateIrradianceMap()
}
}
bool VkLightprober::GenerateIrradianceMap(TArrayView<uint16_t>& databuffer)
void VkLightprober::GenerateIrradianceMap(int probeIndex)
{
const int texelCount = DFrameBuffer::irrandiaceMapTexelCount;
if (databuffer.Size() < texelCount)
{
return false;
}
auto staging = BufferBuilder()
.Size(32 * 32 * 8 * 6)
.Usage(VK_BUFFER_USAGE_TRANSFER_DST_BIT, VMA_MEMORY_USAGE_GPU_TO_CPU)
.Create(fb->GetDevice());
WriteDescriptors write;
for (int i = 0; i < 6; i++)
{
@ -317,37 +305,40 @@ bool VkLightprober::GenerateIrradianceMap(TArrayView<uint16_t>& databuffer)
cmdbuffer->dispatch(32, 32, 1);
}
if (irradianceMap.probes.size() <= (size_t)probeIndex)
irradianceMap.probes.resize(probeIndex + 1);
if (!irradianceMap.probes[probeIndex])
{
irradianceMap.probes[probeIndex] = ImageBuilder()
.Size(32, 32, 1, 6)
.Format(VK_FORMAT_R16G16B16A16_SFLOAT)
.Usage(VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT)
.Create(fb->GetDevice());
}
PipelineBarrier barrier1;
for (int i = 0; i < 6; i++)
barrier1.AddImage(irradianceMap.images[i].get(), VK_IMAGE_LAYOUT_GENERAL, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, VK_ACCESS_SHADER_WRITE_BIT, VK_ACCESS_TRANSFER_READ_BIT);
barrier1.AddImage(irradianceMap.probes[probeIndex].get(), VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 0, VK_ACCESS_TRANSFER_WRITE_BIT, VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 6);
barrier1.Execute(cmdbuffer, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT);
for (int i = 0; i < 6; i++)
{
VkBufferImageCopy region = { };
region.bufferOffset = 32 * 32 * 8 * i;
region.imageExtent.width = irradianceMap.images[i]->width;
region.imageExtent.height = irradianceMap.images[i]->height;
region.imageExtent.depth = 1;
region.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
region.imageSubresource.layerCount = 1;
cmdbuffer->copyImageToBuffer(irradianceMap.images[i]->image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, staging->buffer, 1, &region);
VkImageCopy region = {};
region.extent.width = irradianceMap.images[i]->width;
region.extent.height = irradianceMap.images[i]->height;
region.extent.depth = 1;
region.srcSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
region.srcSubresource.layerCount = 1;
region.dstSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
region.dstSubresource.layerCount = 1;
region.dstSubresource.baseArrayLayer = i;
cmdbuffer->copyImage(irradianceMap.images[i]->image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, irradianceMap.probes[probeIndex]->image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, &region);
}
fb->GetCommands()->WaitForCommands(false);
// Copy while dropping the alpha channel
auto dst = databuffer.Data();
auto src = (uint16_t*)staging->Map(0, texelCount * 8);
for (int i = 0; i < texelCount; i++)
{
*(dst++) = *(src++);
*(dst++) = *(src++);
*(dst++) = *(src++);
src++;
}
staging->Unmap();
return true;
PipelineBarrier barrier2;
barrier2.AddImage(irradianceMap.probes[probeIndex].get(), VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, VK_ACCESS_TRANSFER_WRITE_BIT, VK_ACCESS_TRANSFER_READ_BIT, VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 6);
barrier2.Execute(cmdbuffer, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT);
}
void VkLightprober::CreatePrefilterMap()
@ -397,20 +388,8 @@ void VkLightprober::CreatePrefilterMap()
}
}
bool VkLightprober::GeneratePrefilterMap(TArrayView<uint16_t>& databuffer)
void VkLightprober::GeneratePrefilterMap(int probeIndex)
{
const int texelCount = DFrameBuffer::prefilterMapTexelCount;
if (databuffer.Size() < texelCount)
{
return false;
}
auto staging = BufferBuilder()
.Size(prefilterMap.levelsSize * 6 * 8)
.Usage(VK_BUFFER_USAGE_TRANSFER_DST_BIT, VMA_MEMORY_USAGE_GPU_TO_CPU)
.Create(fb->GetDevice());
WriteDescriptors write;
for (int i = 0; i < 6 * prefilterMap.maxlevels; i++)
{
@ -467,40 +446,47 @@ bool VkLightprober::GeneratePrefilterMap(TArrayView<uint16_t>& databuffer)
}
}
if (prefilterMap.probes.size() <= (size_t)probeIndex)
prefilterMap.probes.resize(probeIndex + 1);
if (!prefilterMap.probes[probeIndex])
{
prefilterMap.probes[probeIndex] = ImageBuilder()
.Size(128, 128, prefilterMap.maxlevels, 6)
.Format(VK_FORMAT_R16G16B16A16_SFLOAT)
.Usage(VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT)
.Create(fb->GetDevice());
}
PipelineBarrier barrier1;
for (int i = 0; i < 6 * prefilterMap.maxlevels; i++)
barrier1.AddImage(prefilterMap.images[i].get(), VK_IMAGE_LAYOUT_GENERAL, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, VK_ACCESS_SHADER_WRITE_BIT, VK_ACCESS_TRANSFER_READ_BIT);
barrier1.AddImage(prefilterMap.probes[probeIndex].get(), VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 0, VK_ACCESS_TRANSFER_WRITE_BIT, VK_IMAGE_ASPECT_COLOR_BIT, 0, prefilterMap.maxlevels, 0, 6);
barrier1.Execute(cmdbuffer, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT);
int offset = 0;
for (int i = 0; i < 6 * prefilterMap.maxlevels; i++)
{
VkBufferImageCopy region = { };
region.bufferOffset = offset;
region.imageExtent.width = prefilterMap.images[i]->width;
region.imageExtent.height = prefilterMap.images[i]->height;
region.imageExtent.depth = 1;
region.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
region.imageSubresource.layerCount = 1;
cmdbuffer->copyImageToBuffer(prefilterMap.images[i]->image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, staging->buffer, 1, &region);
offset += prefilterMap.images[i]->width * prefilterMap.images[i]->height * 8;
VkImageCopy region = {};
region.extent.width = prefilterMap.images[i]->width;
region.extent.height = prefilterMap.images[i]->height;
region.extent.depth = 1;
region.srcSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
region.srcSubresource.layerCount = 1;
region.dstSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
region.dstSubresource.layerCount = 1;
region.dstSubresource.baseArrayLayer = i / prefilterMap.maxlevels;
region.dstSubresource.mipLevel = i % prefilterMap.maxlevels;
cmdbuffer->copyImage(prefilterMap.images[i]->image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, prefilterMap.probes[probeIndex]->image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, &region);
}
fb->GetCommands()->WaitForCommands(false);
PipelineBarrier barrier2;
barrier2.AddImage(prefilterMap.probes[probeIndex].get(), VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, VK_ACCESS_TRANSFER_WRITE_BIT, VK_ACCESS_TRANSFER_READ_BIT, VK_IMAGE_ASPECT_COLOR_BIT, 0, prefilterMap.maxlevels, 0, 6);
barrier2.Execute(cmdbuffer, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT);
}
// Copy while dropping the alpha channel
auto dst = databuffer.Data();
auto src = (uint16_t*)staging->Map(0, texelCount * 8);
for (int i = 0; i < texelCount; i++)
{
*(dst++) = *(src++);
*(dst++) = *(src++);
*(dst++) = *(src++);
src++;
}
staging->Unmap();
return true;
void VkLightprober::EndLightProbePass()
{
fb->GetTextureManager()->CopyIrradiancemap(irradianceMap.probes);
fb->GetTextureManager()->CopyPrefiltermap(prefilterMap.probes);
}
std::vector<uint32_t> VkLightprober::CompileShader(const std::string& filename)

View file

@ -35,8 +35,9 @@ public:
~VkLightprober();
void RenderEnvironmentMap(std::function<void(IntRect& bounds, int side)> renderFunc);
bool GenerateIrradianceMap(TArrayView<uint16_t>& databuffer);
bool GeneratePrefilterMap(TArrayView<uint16_t>& databuffer);
void GenerateIrradianceMap(int probeIndex);
void GeneratePrefilterMap(int probeIndex);
void EndLightProbePass();
private:
void CreateBrdfLutResources();
@ -85,6 +86,7 @@ private:
std::unique_ptr<VulkanSampler> sampler;
std::unique_ptr<VulkanImage> images[6];
std::unique_ptr<VulkanImageView> views[6];
std::vector<std::unique_ptr<VulkanImage>> probes;
} irradianceMap;
struct PrefilterMap
@ -102,6 +104,7 @@ private:
std::unique_ptr<VulkanSampler> sampler;
std::unique_ptr<VulkanImage> images[6 * maxlevels];
std::unique_ptr<VulkanImageView> views[6 * maxlevels];
std::vector<std::unique_ptr<VulkanImage>> probes;
} prefilterMap;
VulkanRenderDevice* fb = nullptr;

View file

@ -406,17 +406,33 @@ void VulkanRenderDevice::RenderTextureView(FCanvasTexture* tex, std::function<vo
tex->SetUpdated(true);
}
void VulkanRenderDevice::RenderEnvironmentMap(std::function<void(IntRect& bounds, int side)> renderFunc, TArrayView<uint16_t>& irradianceMap, TArrayView<uint16_t>& prefilterMap)
void VulkanRenderDevice::ResetLightProbes()
{
mLightprober->RenderEnvironmentMap(std::move(renderFunc));
mLightprober->GenerateIrradianceMap(irradianceMap);
mLightprober->GeneratePrefilterMap(prefilterMap);
mTextureManager->ResetLightProbes();
}
void VulkanRenderDevice::UploadEnvironmentMaps(int cubemapCount, const TArray<uint16_t>& irradianceMaps, const TArray<uint16_t>& prefilterMaps)
void VulkanRenderDevice::RenderLightProbe(int probeIndex, std::function<void(IntRect& bounds, int side)> renderFunc)
{
mTextureManager->UploadIrradiancemap(cubemapCount, irradianceMaps);
mTextureManager->UploadPrefiltermap(cubemapCount, prefilterMaps);
mLightprober->RenderEnvironmentMap(std::move(renderFunc));
mLightprober->GenerateIrradianceMap(probeIndex);
mLightprober->GeneratePrefilterMap(probeIndex);
}
void VulkanRenderDevice::EndLightProbePass()
{
mLightprober->EndLightProbePass();
}
void VulkanRenderDevice::DownloadLightProbes(int probeCount, TArrayView<uint16_t> irradianceMaps, TArrayView<uint16_t> prefilterMaps)
{
mTextureManager->DownloadIrradiancemap(probeCount, irradianceMaps);
mTextureManager->DownloadPrefiltermap(probeCount, prefilterMaps);
}
void VulkanRenderDevice::UploadLightProbes(int probeCount, const TArray<uint16_t>& irradianceMaps, const TArray<uint16_t>& prefilterMaps)
{
mTextureManager->UploadIrradiancemap(probeCount, irradianceMaps);
mTextureManager->UploadPrefiltermap(probeCount, prefilterMaps);
}
void VulkanRenderDevice::PostProcessScene(bool swscene, int fixedcm, float flash, bool palettePostprocess, const std::function<void()> &afterBloomDrawEndScene2D)

View file

@ -109,8 +109,13 @@ public:
private:
void RenderTextureView(FCanvasTexture* tex, std::function<void(IntRect &)> renderFunc) override;
void RenderEnvironmentMap(std::function<void(IntRect& bounds, int side)> renderFunc, TArrayView<uint16_t>& irradianceMap, TArrayView<uint16_t>& prefilterMap) override;
void UploadEnvironmentMaps(int cubemapCount, const TArray<uint16_t>& irradianceMaps, const TArray<uint16_t>& prefilterMaps) override;
void ResetLightProbes() override;
void RenderLightProbe(int probeIndex, std::function<void(IntRect& bounds, int side)> renderFunc) override;
void EndLightProbePass() override;
void DownloadLightProbes(int probeCount, TArrayView<uint16_t> irradianceMaps, TArrayView<uint16_t> prefilterMaps) override;
void UploadLightProbes(int probeCount, const TArray<uint16_t>& irradianceMaps, const TArray<uint16_t>& prefilterMaps) override;
void PrintStartupLog();
void CopyScreenToBuffer(int w, int h, uint8_t *data) override;

View file

@ -336,7 +336,7 @@ static void CheckTimer(FRenderState &state, uint64_t ShaderStartTime)
state.firstFrame = screen->FrameTime - 1;
}
LightProbeIncrementalBuilder lightProbeBuilder(DFrameBuffer::irrandiaceMapTexelCount, DFrameBuffer::prefilterMapTexelCount, DFrameBuffer::irradianceMapChannelCount, DFrameBuffer::prefilterMapChannelCount);
LightProbeIncrementalBuilder lightProbeBuilder;
sector_t* RenderView(player_t* player)
{
@ -413,30 +413,22 @@ sector_t* RenderView(player_t* player)
AActor* lightprobe = level.GetThinkerIterator<AActor>(NAME_LightProbe, STAT_INFO).Next();
if (lightprobe)
{
auto renderEnvMap = [&](const LightProbe& probe, TArrayView<uint16_t>& irradianceMap, TArrayView<uint16_t>& prefilteredMap) {
auto renderEnvMap = [&](int probeIndex, const LightProbe& probe) {
lightprobe->SetOrigin(DVector3(probe.position), false); // crime against nature
// The renderer interpolates camera in its own mechanism that has to be disabled when moving around the single probe
bool noInterpolate = r_NoInterpolate;
r_NoInterpolate = true;
screen->RenderEnvironmentMap([&](IntRect& bounds, int side) {
screen->RenderLightProbe(probeIndex, [&](IntRect& bounds, int side) {
FRenderViewpoint probevp;
RenderViewpoint(probevp, lightprobe, &bounds, 90.0, 1.0f, 1.0f, false, false, side);
}, irradianceMap, prefilteredMap);
});
r_NoInterpolate = noInterpolate;
};
auto renderEnvScreen = [&](const TArray<uint16_t>& irradianceMaps, const TArray<uint16_t>& prefilteredMaps) {
screen->UploadEnvironmentMaps(level.lightProbes.size(), irradianceMaps, prefilteredMaps);
};
lightProbeBuilder.Step(
level.lightProbes,
renderEnvMap,
renderEnvScreen
);
lightProbeBuilder.Step(level.lightProbes, renderEnvMap);
}
else
{