Optimize Color Grading LUT to be cached if unchanged from the previous frame

This commit is contained in:
Wojtek Figat
2025-08-01 23:16:57 +02:00
parent 0f81c64964
commit 5c5341e346
4 changed files with 55 additions and 15 deletions

View File

@@ -3,11 +3,13 @@
#include "ColorGradingPass.h"
#include "RenderList.h"
#include "Engine/Content/Content.h"
#include "Engine/Engine/Engine.h"
#include "Engine/Graphics/GPUContext.h"
#include "Engine/Graphics/GPULimits.h"
#include "Engine/Graphics/Graphics.h"
#include "Engine/Graphics/RenderTargetPool.h"
#include "Engine/Graphics/RenderTask.h"
#include "Engine/Graphics/RenderBuffers.h"
GPU_CB_STRUCT(Data {
Float4 ColorSaturationShadows;
@@ -37,6 +39,21 @@ GPU_CB_STRUCT(Data {
float LutWeight;
});
// Custom render buffer for caching Color Grading LUT.
class ColorGradingCustomBuffer : public RenderBuffers::CustomBuffer
{
public:
GPUTexture* LUT = nullptr;
Data CachedData;
ToneMappingMode Mode = ToneMappingMode::None;
Texture* LutTexture = nullptr;
~ColorGradingCustomBuffer()
{
RenderTargetPool::Release(LUT);
}
};
String ColorGradingPass::ToString() const
{
return TEXT("ColorGradingPass");
@@ -92,6 +109,8 @@ void ColorGradingPass::Dispose()
GPUTexture* ColorGradingPass::RenderLUT(RenderContext& renderContext)
{
PROFILE_CPU();
// Check if can use volume texture (3D) for a LUT (faster on modern platforms, requires geometry shader)
const auto device = GPUDevice::Instance;
bool use3D = GPU_ALLOW_GEOMETRY_SHADERS && Graphics::PostProcessing::ColorGradingVolumeLUT;
@@ -108,7 +127,6 @@ GPUTexture* ColorGradingPass::RenderLUT(RenderContext& renderContext)
// Ensure to have valid data
if (checkIfSkipPass())
return nullptr;
PROFILE_GPU_CPU("Color Grading LUT");
// Pick a proper LUT pixels format
auto lutFormat = PixelFormat::R10G10B10A2_UNorm;
@@ -125,11 +143,24 @@ GPUTexture* ColorGradingPass::RenderLUT(RenderContext& renderContext)
lutDesc = GPUTextureDescription::New3D(lutSize, lutSize, lutSize, 1, lutFormat);
else
lutDesc = GPUTextureDescription::New2D(lutSize * lutSize, lutSize, 1, lutFormat);
const auto lut = RenderTargetPool::Get(lutDesc);
RENDER_TARGET_POOL_SET_NAME(lut, "ColorGrading.LUT");
// Use existing texture or allocate a new one
auto& colorGradingBuffer = *renderContext.Buffers->GetCustomBuffer<ColorGradingCustomBuffer>(TEXT("ColorGrading"));
colorGradingBuffer.LastFrameUsed = Engine::FrameCount;
if (colorGradingBuffer.LUT && colorGradingBuffer.LUT->Width() != lutDesc.Width)
{
RenderTargetPool::Release(colorGradingBuffer.LUT);
colorGradingBuffer.LUT = nullptr;
}
if (!colorGradingBuffer.LUT)
{
colorGradingBuffer.LUT = RenderTargetPool::Get(lutDesc);
RENDER_TARGET_POOL_SET_NAME(colorGradingBuffer.LUT, "ColorGrading.LUT");
}
// Prepare the parameters
Data data;
data.Dummy = Float2::Zero;
auto& toneMapping = renderContext.List->Settings.ToneMapping;
auto& colorGrading = renderContext.List->Settings.ColorGrading;
// White Balance
@@ -156,23 +187,35 @@ GPUTexture* ColorGradingPass::RenderLUT(RenderContext& renderContext)
data.ColorOffsetHighlights = colorGrading.ColorOffsetHighlights + colorGrading.ColorOffset;
data.ColorCorrectionHighlightsMin = colorGrading.HighlightsMin;
//
const bool useLut = colorGrading.LutTexture && colorGrading.LutTexture->IsLoaded() && colorGrading.LutTexture->GetResidentMipLevels() > 0 && colorGrading.LutWeight > ZeroTolerance;
Texture* lutTexture = colorGrading.LutTexture.Get();
const bool useLut = lutTexture && lutTexture->IsLoaded() && lutTexture->GetResidentMipLevels() > 0 && colorGrading.LutWeight > ZeroTolerance;
data.LutWeight = useLut ? colorGrading.LutWeight : 0.0f;
// Prepare
// Check if LUT parameter hasn't been changed since the last time
if (Platform::MemoryCompare(&colorGradingBuffer.CachedData , &data, sizeof(Data)) == 0 &&
colorGradingBuffer.Mode == toneMapping.Mode &&
colorGradingBuffer.LutTexture == lutTexture)
{
// Resue existing texture
return colorGradingBuffer.LUT;
}
colorGradingBuffer.CachedData = data;
colorGradingBuffer.Mode = toneMapping.Mode;
colorGradingBuffer.LutTexture = lutTexture;
// Render LUT
PROFILE_GPU("Color Grading LUT");
auto context = device->GetMainContext();
const auto cb = _shader->GetShader()->GetCB(0);
context->UpdateCB(cb, &data);
context->BindCB(0, cb);
context->SetViewportAndScissors((float)lutDesc.Width, (float)lutDesc.Height);
context->SetState(_psLut.Get((int32)toneMapping.Mode));
context->BindSR(0, useLut ? colorGrading.LutTexture->GetTexture() : nullptr);
// Draw
context->BindSR(0, useLut ? lutTexture->GetTexture() : nullptr);
#if GPU_ALLOW_GEOMETRY_SHADERS
if (use3D)
{
context->SetRenderTarget(lut->ViewVolume());
context->SetRenderTarget(colorGradingBuffer.LUT->ViewVolume());
// Render one fullscreen-triangle per slice intersecting the bounds
const int32 numInstances = lutDesc.Depth;
@@ -181,10 +224,10 @@ GPUTexture* ColorGradingPass::RenderLUT(RenderContext& renderContext)
else
#endif
{
context->SetRenderTarget(lut->View());
context->SetRenderTarget(colorGradingBuffer.LUT->View());
context->DrawFullscreenTriangle();
}
context->UnBindSR(0);
return lut;
return colorGradingBuffer.LUT;
}

View File

@@ -20,7 +20,7 @@ public:
/// Renders Look Up table with color grading parameters mixed in.
/// </summary>
/// <param name="renderContext">The rendering context.</param>
/// <returns>Allocated temp render target with a rendered LUT. Can be 2d or 3d based on current graphics hardware caps. Release after usage (via RenderTargetPool::Release).</returns>
/// <returns>Allocated temp render target with a rendered LUT - cached within Render Buffers, released automatically.</returns>
GPUTexture* RenderLUT(RenderContext& renderContext);
private:

View File

@@ -1447,7 +1447,6 @@ void GlobalSurfaceAtlasPass::RenderDebug(RenderContext& renderContext, GPUContex
auto colorGradingLUT = ColorGradingPass::Instance()->RenderLUT(renderContext);
EyeAdaptationPass::Instance()->Render(renderContext, tempBuffer);
PostProcessingPass::Instance()->Render(renderContext, tempBuffer, output, colorGradingLUT);
RenderTargetPool::Release(colorGradingLUT);
RenderTargetPool::Release(tempBuffer);
context->ResetRenderTarget();

View File

@@ -624,7 +624,6 @@ void RenderInner(SceneRenderTask* task, RenderContext& renderContext, RenderCont
RENDER_TARGET_POOL_SET_NAME(tempBuffer, "TempBuffer");
EyeAdaptationPass::Instance()->Render(renderContext, lightBuffer);
PostProcessingPass::Instance()->Render(renderContext, lightBuffer, tempBuffer, colorGradingLUT);
RenderTargetPool::Release(colorGradingLUT);
context->ResetRenderTarget();
if (aaMode == AntialiasingMode::TemporalAntialiasing)
{
@@ -745,7 +744,6 @@ void RenderInner(SceneRenderTask* task, RenderContext& renderContext, RenderCont
// Post-processing
EyeAdaptationPass::Instance()->Render(renderContext, frameBuffer);
PostProcessingPass::Instance()->Render(renderContext, frameBuffer, tempBuffer, colorGradingLUT);
RenderTargetPool::Release(colorGradingLUT);
Swap(frameBuffer, tempBuffer);
// Cleanup