Files
FlaxEngine/Source/Engine/ShadowsOfMordor/Builder.BuildCache.cpp
2020-12-07 23:40:54 +01:00

243 lines
8.6 KiB
C++

// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
#include "Builder.h"
#include "Engine/Level/Scene/Lightmap.h"
#include "Engine/Level/Scene/Scene.h"
#include "Engine/Content/Content.h"
#include "Engine/ContentImporters/AssetsImportingManager.h"
#include "Engine/ContentImporters/ImportTexture.h"
#include "Engine/Graphics/PixelFormatExtensions.h"
#include <ThirdParty/DirectXTex/DirectXTex.h>
ShadowsOfMordor::Builder::LightmapBuildCache::~LightmapBuildCache()
{
SAFE_DELETE_GPU_RESOURCE(LightmapData);
}
bool ShadowsOfMordor::Builder::LightmapBuildCache::Init(const LightmapSettings* settings)
{
if (LightmapData)
return false;
LightmapData = GPUDevice::Instance->CreateBuffer(TEXT("LightmapBuildCache"));
const auto elementsCount = (int32)settings->AtlasSize * (int32)settings->AtlasSize * NUM_SH_TARGETS;
if (LightmapData->Init(GPUBufferDescription::Typed(elementsCount, HemispheresFormatToPixelFormat[HEMISPHERES_IRRADIANCE_FORMAT], true)))
return true;
return false;
}
ShadowsOfMordor::Builder::SceneBuildCache::SceneBuildCache()
: Scene(nullptr)
, TempLightmapData(nullptr)
, LightmapsCount(0)
, HemispheresCount(0)
, MergedHemispheresCount(0)
, ImportLightmapIndex(0)
, ImportLightmapTextureIndex(0)
{
}
const LightmapSettings& ShadowsOfMordor::Builder::SceneBuildCache::GetSettings() const
{
ASSERT(Scene);
return Scene->Info.LightmapSettings;
}
bool ShadowsOfMordor::Builder::SceneBuildCache::WaitForLightmaps()
{
Texture* lightmaps[3];
for (int32 lightmapIndex = 0; lightmapIndex < Lightmaps.Count(); lightmapIndex++)
{
const auto lightmap = Scene->LightmapsData.GetLightmap(lightmapIndex);
lightmap->GetTextures(lightmaps);
for (int32 textureIndex = 0; textureIndex < NUM_SH_TARGETS; textureIndex++)
{
AssetReference<Texture> lightmapTexture = lightmaps[textureIndex];
if (lightmapTexture == nullptr)
{
LOG(Error, "Missing lightmap {0} texture{1}", lightmapIndex, textureIndex);
return true;
}
// Wait for loading end and check result
if (lightmapTexture->WaitForLoaded())
{
LOG(Error, "Failed to load lightmap {0} texture {1}", lightmapIndex, textureIndex);
return true;
}
// TODO: disable streaming for lightmap texture here (enable it later after baking)
// Wait texture to be streamed to the target quality
const auto t = lightmapTexture->GetTexture();
const int32 stepSize = 30;
const int32 maxWaitTime = 60000;
int32 stepsCount = static_cast<int32>(maxWaitTime / stepSize);
while ((t->ResidentMipLevels() < t->MipLevels() || t->ResidentMipLevels() == 0) && stepsCount-- > 0)
Platform::Sleep(stepSize);
if (t->ResidentMipLevels() < t->MipLevels() || t->ResidentMipLevels() == 0)
{
LOG(Error, "Waiting for lightmap no. {0} texture {1} to be fully resided timed out (loaded mips: {2}, mips count: {3})", lightmapIndex, textureIndex, t->ResidentMipLevels(), t->MipLevels());
return true;
}
}
}
return false;
}
void ShadowsOfMordor::Builder::SceneBuildCache::UpdateLightmaps()
{
Texture* lightmaps[3];
for (int32 lightmapIndex = 0; lightmapIndex < Lightmaps.Count(); lightmapIndex++)
{
// Cache data
auto& lightmapEntry = Lightmaps[lightmapIndex];
auto lightmap = Scene->LightmapsData.GetLightmap(lightmapIndex);
ASSERT(lightmap);
lightmap->GetTextures(lightmaps);
// Download buffer data
if (lightmapEntry.LightmapData->DownloadData(ImportLightmapTextureData))
{
LOG(Warning, "Cannot download LightmapData.");
return;
}
// Import all textures but don't use file proxy to improve performance
for (int32 textureIndex = 0; textureIndex < NUM_SH_TARGETS; textureIndex++)
{
// Get asset name
String assetPath;
if (lightmaps[textureIndex])
assetPath = lightmaps[textureIndex]->GetPath();
else
Scene->LightmapsData.GetCachedLightmapPath(&assetPath, lightmapIndex, textureIndex);
// Import texture with custom options
#if COMPILE_WITH_ASSETS_IMPORTER
Guid id = Guid::Empty;
ImportTexture::Options options;
options.Type = TextureFormatType::HdrRGBA;
options.IndependentChannels = true;
options.Compress = Scene->GetLightmapSettings().CompressLightmaps;
options.GenerateMipMaps = true;
options.IsAtlas = false;
options.sRGB = false;
options.NeverStream = false;
ImportLightmapIndex = lightmapIndex;
ImportLightmapTextureIndex = textureIndex;
options.InternalLoad.Bind<SceneBuildCache, &SceneBuildCache::onImportLightmap>(this);
if (AssetsImportingManager::Create(AssetsImportingManager::CreateTextureTag, assetPath, id, &options))
{
LOG(Error, "Cannot create new lightmap {0}:{1}", lightmapIndex, textureIndex);
return;
}
const auto result = Content::LoadAsync<Texture>(id);
if (result == nullptr)
#else
#error "Cannot import lightmaps. Assets importer module iss missing."
auto result = nullptr;
#endif
{
LOG(Error, "Cannot load new lightmap {0}:{1}", lightmapIndex, textureIndex);
return;
}
// Update lightmap
lightmap->UpdateTexture(result, textureIndex);
}
#if DEBUG_EXPORT_LIGHTMAPS_PREVIEW
// Temporary save lightmaps (after last bounce)
if (Builder->_giBounceRunningIndex == Builder->_bounceCount - 1)
{
exportLightmapPreview(this, lightmapIndex);
}
#endif
ImportLightmapTextureData.Release();
}
}
bool ShadowsOfMordor::Builder::SceneBuildCache::Init(ShadowsOfMordor::Builder* builder, int32 index, ::Scene* scene)
{
Builder = builder;
SceneIndex = index;
Scene = scene;
const int32 atlasSize = (int32)GetSettings().AtlasSize;
TempLightmapData = GPUDevice::Instance->CreateBuffer(TEXT("LightmapBuildCache"));
const auto elementsCount = atlasSize * atlasSize * NUM_SH_TARGETS;
if (TempLightmapData->Init(GPUBufferDescription::Typed(elementsCount, HemispheresFormatToPixelFormat[HEMISPHERES_IRRADIANCE_FORMAT], true)))
return true;
LOG(Info, "Scene \'{0}\' quality: {1}", scene->GetName(), scene->Info.LightmapSettings.Quality);
return false;
}
void ShadowsOfMordor::Builder::SceneBuildCache::Release()
{
EntriesLocker.Lock();
// Cleanup
Entries.Resize(0);
Lightmaps.Resize(0);
Charts.Resize(0);
Scene = nullptr;
EntriesLocker.Unlock();
SAFE_DELETE_GPU_RESOURCE(TempLightmapData);
}
#if COMPILE_WITH_ASSETS_IMPORTER
bool ShadowsOfMordor::Builder::SceneBuildCache::onImportLightmap(TextureData& image)
{
// Cache data
const int32 lightmapIndex = ImportLightmapIndex;
const int32 textureIndex = ImportLightmapTextureIndex;
// Setup image
image.Width = image.Height = (int32)GetSettings().AtlasSize;
image.Depth = 1;
image.Format = HemispheresFormatToPixelFormat[HEMISPHERES_IRRADIANCE_FORMAT];
image.Items.Resize(1);
image.Items[0].Mips.Resize(1);
auto& mip = image.Items[0].Mips[0];
mip.RowPitch = PixelFormatExtensions::SizeInBytes(image.Format) * image.Width;
mip.DepthPitch = mip.RowPitch * image.Height;
mip.Lines = image.Height;
mip.Data.Allocate(mip.DepthPitch);
#if HEMISPHERES_IRRADIANCE_FORMAT == HEMISPHERES_FORMAT_R32G32B32A32
auto pos = (Vector4*)mip.Data.Get();
const auto textureData = ImportLightmapTextureData.Get<Vector4>();
for (int32 y = 0; y < image.Height; y++)
{
for (int32 x = 0; x < image.Width; x++)
{
const int32 texelAddress = (y * image.Width + x) * NUM_SH_TARGETS;
*pos = textureData[texelAddress + textureIndex];
pos++;
}
}
#elif HEMISPHERES_IRRADIANCE_FORMAT == HEMISPHERES_FORMAT_R16G16B16A16
auto pos = (Half4*)mip.Data.Get();
const auto textureData = ImportLightmapTextureData.Get<Half4>();
for (int32 y = 0; y < image.Height; y++)
{
for (int32 x = 0; x < image.Width; x++)
{
const int32 texelAddress = (y * image.Width + x) * NUM_SH_TARGETS;
*pos = textureData[texelAddress + textureIndex];
pos++;
}
}
#endif
return false;
}
#endif