Files
FlaxEngine/Source/Engine/Renderer/EyeAdaptationPass.cpp

305 lines
10 KiB
C++

// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved.
#include "EyeAdaptationPass.h"
#include "HistogramPass.h"
#include "RenderList.h"
#include "Engine/Core/Math/Vector2.h"
#include "Engine/Content/Assets/Shader.h"
#include "Engine/Content/Content.h"
#include "Engine/Graphics/GPUDevice.h"
#include "Engine/Graphics/GPUContext.h"
#include "Engine/Graphics/GPULimits.h"
#include "Engine/Graphics/Shaders/GPUShader.h"
#include "Engine/Graphics/RenderTask.h"
#include "Engine/Graphics/RenderTargetPool.h"
#include "Engine/Graphics/RenderBuffers.h"
#include "Engine/Engine/Engine.h"
#include "Engine/Engine/Time.h"
PACK_STRUCT(struct EyeAdaptationData {
float MinBrightness;
float MaxBrightness;
float SpeedUp;
float SpeedDown;
float PreExposure;
float DeltaTime;
float HistogramMul;
float HistogramAdd;
float HistogramLowPercent;
float HistogramHighPercent;
float DropHistory;
float Dummy1;
});
void EyeAdaptationPass::Render(RenderContext& renderContext, GPUTexture* colorBuffer)
{
auto device = GPUDevice::Instance;
auto context = device->GetMainContext();
auto& view = renderContext.View;
auto& settings = renderContext.List->Settings.EyeAdaptation;
bool dropHistory = renderContext.Buffers->LastEyeAdaptationTime < ZeroTolerance || renderContext.Task->IsCameraCut;
const float time = Time::Draw.UnscaledTime.GetTotalSeconds();
//const float frameDelta = Time::ElapsedGameTime.GetTotalSeconds();
const float frameDelta = Math::Clamp(time - renderContext.Buffers->LastEyeAdaptationTime, 0.0f, 1.0f);
renderContext.Buffers->LastEyeAdaptationTime = 0.0f;
if ((view.Flags & ViewFlags::EyeAdaptation) == ViewFlags::None || settings.Mode == EyeAdaptationMode::None || checkIfSkipPass())
return;
PROFILE_GPU_CPU("Eye Adaptation");
// Setup constants
GPUBuffer* histogramBuffer = nullptr;
EyeAdaptationData data;
data.MinBrightness = Math::Max(Math::Min(settings.MinBrightness, settings.MaxBrightness), 0.0001f);
data.MaxBrightness = Math::Max(settings.MinBrightness, settings.MaxBrightness);
data.PreExposure = Math::Exp2(settings.PreExposure);
auto mode = settings.Mode == EyeAdaptationMode::AutomaticHistogram && !_canUseHistogram ? EyeAdaptationMode::AutomaticAverageLuminance : settings.Mode;
switch (mode)
{
case EyeAdaptationMode::Manual:
{
if (Math::IsZero(settings.PreExposure))
return;
break;
}
case EyeAdaptationMode::AutomaticHistogram:
{
ASSERT(_canUseHistogram);
data.HistogramLowPercent = Math::Clamp(settings.HistogramLowPercent * 0.01f, 0.01f, 0.99f);
data.HistogramHighPercent = Math::Clamp(settings.HistogramHighPercent * 0.01f, data.HistogramLowPercent, 1.0f);
HistogramPass::Instance()->GetHistogramMad(data.HistogramMul, data.HistogramAdd);
// Render histogram
histogramBuffer = HistogramPass::Instance()->Render(renderContext, colorBuffer);
if (!histogramBuffer)
return;
break;
}
case EyeAdaptationMode::AutomaticAverageLuminance:
{
dropHistory |= renderContext.Buffers->LuminanceMap == nullptr;
break;
}
default: ;
}
data.SpeedUp = settings.SpeedUp;
data.SpeedDown = settings.SpeedDown;
data.DeltaTime = frameDelta;
data.DropHistory = dropHistory ? 1.0f : 0.0f;
// Update constants
const auto shader = _shader->GetShader();
const auto cb0 = shader->GetCB(0);
context->UpdateCB(cb0, &data);
context->BindCB(0, cb0);
if (mode == EyeAdaptationMode::Manual)
{
// Apply fixed manual exposure
context->SetRenderTarget(*colorBuffer);
context->SetViewportAndScissors((float)colorBuffer->Width(), (float)colorBuffer->Height());
context->SetState(_psManual);
context->DrawFullscreenTriangle();
return;
}
GPUTexture* previousLuminanceMap = renderContext.Buffers->LuminanceMap;
if (dropHistory && previousLuminanceMap)
{
RenderTargetPool::Release(previousLuminanceMap);
previousLuminanceMap = nullptr;
}
GPUTexture* currentLuminanceMap = RenderTargetPool::Get(GPUTextureDescription::New2D(1, 1, PixelFormat::R16_Float));
RENDER_TARGET_POOL_SET_NAME(currentLuminanceMap, "EyeAdaptation.LuminanceMap");
switch (mode)
{
case EyeAdaptationMode::AutomaticHistogram:
{
// Blend luminance with the histogram-based luminance
context->BindSR(0, histogramBuffer->View());
context->BindSR(1, previousLuminanceMap);
context->SetRenderTarget(*currentLuminanceMap);
context->SetViewportAndScissors(1, 1);
context->SetState(_psHistogram);
context->DrawFullscreenTriangle();
context->UnBindSR(1);
context->ResetRenderTarget();
break;
}
case EyeAdaptationMode::AutomaticAverageLuminance:
{
const Int2 luminanceMapSize(colorBuffer->Width() / 2, colorBuffer->Height() / 2);
GPUTexture* luminanceMap = RenderTargetPool::Get(GPUTextureDescription::New2D(luminanceMapSize.X, luminanceMapSize.Y, 0, PixelFormat::R16_Float, GPUTextureFlags::ShaderResource | GPUTextureFlags::RenderTarget | GPUTextureFlags::PerMipViews));
RENDER_TARGET_POOL_SET_NAME(luminanceMap, "EyeAdaptation.LuminanceMap");
// Calculate the luminance for the scene color
context->BindSR(0, *colorBuffer);
context->SetRenderTarget(luminanceMap->View(0, 0));
context->SetViewportAndScissors((float)luminanceMapSize.X, (float)luminanceMapSize.Y);
context->SetState(_psLuminanceMap);
context->DrawFullscreenTriangle();
context->ResetRenderTarget();
// Generate the mip chain
int32 mipLevel = 1;
Int2 mipSize = luminanceMapSize / 2;
const int32 totalMips = luminanceMap->MipLevels();
while (mipLevel < totalMips)
{
// Downscale
context->SetRenderTarget(luminanceMap->View(0, mipLevel));
context->SetViewportAndScissors((float)mipSize.X, (float)mipSize.Y);
context->Draw(luminanceMap->View(0, mipLevel - 1));
context->ResetRenderTarget();
// Move down
if (mipSize.X > 1)
mipSize.X >>= 1;
if (mipSize.Y > 1)
mipSize.Y >>= 1;
mipLevel++;
}
if (dropHistory)
{
// Copy 1x1 luminance value from the last mip map
context->SetRenderTarget(*currentLuminanceMap);
context->SetViewportAndScissors(1, 1);
context->Draw(luminanceMap->View(0, mipLevel - 1));
context->ResetRenderTarget();
}
else
{
// Blend luminance and copy it from last mip to the separate 1x1 texture
context->BindSR(0, luminanceMap->View(0, mipLevel - 1));
context->BindSR(1, previousLuminanceMap);
context->SetRenderTarget(*currentLuminanceMap);
context->SetViewportAndScissors(1, 1);
context->SetState(_psBlendLuminance);
context->DrawFullscreenTriangle();
context->UnBindSR(1);
context->ResetRenderTarget();
}
RenderTargetPool::Release(luminanceMap);
break;
}
default: ;
}
// Apply the luminance
context->BindSR(0, *currentLuminanceMap);
context->SetRenderTarget(*colorBuffer);
context->SetViewportAndScissors((float)colorBuffer->Width(), (float)colorBuffer->Height());
context->SetState(_psApplyLuminance);
context->DrawFullscreenTriangle();
context->UnBindSR(0);
// Update the luminance map buffer
renderContext.Buffers->LastEyeAdaptationTime = time;
renderContext.Buffers->LastFrameLuminanceMap = Engine::FrameCount;
renderContext.Buffers->LuminanceMap = currentLuminanceMap;
// Cleanup
if (previousLuminanceMap)
RenderTargetPool::Release(previousLuminanceMap);
}
String EyeAdaptationPass::ToString() const
{
return TEXT("EyeAdaptationPass");
}
bool EyeAdaptationPass::Init()
{
auto device = GPUDevice::Instance;
_canUseHistogram = device->Limits.HasCompute;
// Create pipeline states
_psManual = device->CreatePipelineState();
_psLuminanceMap = device->CreatePipelineState();
_psBlendLuminance = device->CreatePipelineState();
_psApplyLuminance = device->CreatePipelineState();
_psHistogram = device->CreatePipelineState();
// Load shaders
_shader = Content::LoadAsyncInternal<Shader>(TEXT("Shaders/EyeAdaptation"));
if (_shader == nullptr)
return true;
#if COMPILE_WITH_DEV_ENV
_shader.Get()->OnReloading.Bind<EyeAdaptationPass, &EyeAdaptationPass::OnShaderReloading>(this);
#endif
return false;
}
void EyeAdaptationPass::Dispose()
{
// Base
RendererPass::Dispose();
// Cleanup
SAFE_DELETE_GPU_RESOURCE(_psManual);
SAFE_DELETE_GPU_RESOURCE(_psLuminanceMap);
SAFE_DELETE_GPU_RESOURCE(_psBlendLuminance);
SAFE_DELETE_GPU_RESOURCE(_psApplyLuminance);
SAFE_DELETE_GPU_RESOURCE(_psHistogram);
_shader = nullptr;
}
bool EyeAdaptationPass::setupResources()
{
// Wait for shader
if (!_shader->IsLoaded())
return true;
const auto shader = _shader->GetShader();
// Validate shader constant buffer size
if (shader->GetCB(0)->GetSize() != sizeof(EyeAdaptationData))
{
REPORT_INVALID_SHADER_PASS_CB_SIZE(shader, 0, EyeAdaptationData);
return true;
}
// Create pipeline stages
GPUPipelineState::Description psDesc = GPUPipelineState::Description::DefaultFullscreenTriangle;
if (!_psLuminanceMap->IsValid())
{
psDesc.PS = shader->GetPS("PS_LuminanceMap");
if (_psLuminanceMap->Init(psDesc))
return true;
}
if (!_psBlendLuminance->IsValid())
{
psDesc.PS = shader->GetPS("PS_BlendLuminance");
if (_psBlendLuminance->Init(psDesc))
return true;
}
if (!_psHistogram->IsValid())
{
psDesc.PS = shader->GetPS("PS_Histogram");
if (_psHistogram->Init(psDesc))
return true;
}
psDesc.BlendMode = BlendingMode::Multiply;
if (!_psManual->IsValid())
{
psDesc.PS = shader->GetPS("PS_Manual");
if (_psManual->Init(psDesc))
return true;
}
if (!_psApplyLuminance->IsValid())
{
psDesc.PS = shader->GetPS("PS_ApplyLuminance");
if (_psApplyLuminance->Init(psDesc))
return true;
}
return false;
}