Refactor Color Grading LUT rendering to have config for 2D/3D mode

This commit is contained in:
Wojtek Figat
2025-07-31 20:05:08 +02:00
parent 7603109dce
commit 0f81c64964
4 changed files with 57 additions and 81 deletions

View File

@@ -26,6 +26,7 @@ Quality Graphics::GIQuality = Quality::High;
bool Graphics::GICascadesBlending = false; bool Graphics::GICascadesBlending = false;
PostProcessSettings Graphics::PostProcessSettings; PostProcessSettings Graphics::PostProcessSettings;
bool Graphics::SpreadWorkload = true; bool Graphics::SpreadWorkload = true;
bool Graphics::PostProcessing::ColorGradingVolumeLUT = true;
#if GRAPHICS_API_NULL #if GRAPHICS_API_NULL
extern GPUDevice* CreateGPUDeviceNull(); extern GPUDevice* CreateGPUDeviceNull();

View File

@@ -84,6 +84,16 @@ public:
/// </summary> /// </summary>
API_FIELD() static bool SpreadWorkload; API_FIELD() static bool SpreadWorkload;
public:
// Post Processing effects rendering configuration.
API_CLASS(Static, Attributes = "DebugCommand") class FLAXENGINE_API PostProcessing
{
DECLARE_SCRIPTING_TYPE_MINIMAL(PostProcessing);
// Toggles between 2D and 3D LUT texture for Color Grading.
API_FIELD() static bool ColorGradingVolumeLUT;
};
public: public:
/// <summary> /// <summary>
/// Disposes the device. /// Disposes the device.

View File

@@ -5,6 +5,7 @@
#include "Engine/Content/Content.h" #include "Engine/Content/Content.h"
#include "Engine/Graphics/GPUContext.h" #include "Engine/Graphics/GPUContext.h"
#include "Engine/Graphics/GPULimits.h" #include "Engine/Graphics/GPULimits.h"
#include "Engine/Graphics/Graphics.h"
#include "Engine/Graphics/RenderTargetPool.h" #include "Engine/Graphics/RenderTargetPool.h"
#include "Engine/Graphics/RenderTask.h" #include "Engine/Graphics/RenderTask.h"
@@ -36,12 +37,6 @@ GPU_CB_STRUCT(Data {
float LutWeight; float LutWeight;
}); });
ColorGradingPass::ColorGradingPass()
: _useVolumeTexture(false)
, _lutFormat()
{
}
String ColorGradingPass::ToString() const String ColorGradingPass::ToString() const
{ {
return TEXT("ColorGradingPass"); return TEXT("ColorGradingPass");
@@ -49,55 +44,28 @@ String ColorGradingPass::ToString() const
bool ColorGradingPass::Init() bool ColorGradingPass::Init()
{ {
// Detect if can use volume texture (3d) for a LUT (faster, requires geometry shader)
const auto device = GPUDevice::Instance;
#if GPU_ALLOW_GEOMETRY_SHADERS
_useVolumeTexture = device->Limits.HasGeometryShaders && device->Limits.HasVolumeTextureRendering;
#endif
// Pick a proper LUT pixels format
_lutFormat = PixelFormat::R10G10B10A2_UNorm;
const auto formatSupport = device->GetFormatFeatures(_lutFormat).Support;
FormatSupport formatSupportFlags = FormatSupport::ShaderSample | FormatSupport::RenderTarget;
if (_useVolumeTexture)
formatSupportFlags |= FormatSupport::Texture3D;
else
formatSupportFlags |= FormatSupport::Texture2D;
if (EnumHasNoneFlags(formatSupport, formatSupportFlags))
{
// Fallback to format that is supported on every washing machine
_lutFormat = PixelFormat::R8G8B8A8_UNorm;
}
// Create pipeline state
_psLut.CreatePipelineStates(); _psLut.CreatePipelineStates();
// Load shader
_shader = Content::LoadAsyncInternal<Shader>(TEXT("Shaders/ColorGrading")); _shader = Content::LoadAsyncInternal<Shader>(TEXT("Shaders/ColorGrading"));
if (_shader == nullptr) if (_shader == nullptr)
return true; return true;
#if COMPILE_WITH_DEV_ENV #if COMPILE_WITH_DEV_ENV
_shader.Get()->OnReloading.Bind<ColorGradingPass, &ColorGradingPass::OnShaderReloading>(this); _shader.Get()->OnReloading.Bind<ColorGradingPass, &ColorGradingPass::OnShaderReloading>(this);
#endif #endif
return false; return false;
} }
bool ColorGradingPass::setupResources() bool ColorGradingPass::setupResources()
{ {
// Wait for shader
if (!_shader || !_shader->IsLoaded()) if (!_shader || !_shader->IsLoaded())
return true; return true;
const auto shader = _shader->GetShader(); const auto shader = _shader->GetShader();
CHECK_INVALID_SHADER_PASS_CB_SIZE(shader, 0, Data); CHECK_INVALID_SHADER_PASS_CB_SIZE(shader, 0, Data);
// Create pipeline stages // Create pipeline stage
GPUPipelineState::Description psDesc = GPUPipelineState::Description::DefaultFullscreenTriangle; auto psDesc = GPUPipelineState::Description::DefaultFullscreenTriangle;
if (!_psLut.IsValid())
{
StringAnsiView psName; StringAnsiView psName;
#if GPU_ALLOW_GEOMETRY_SHADERS #if GPU_ALLOW_GEOMETRY_SHADERS
if (_useVolumeTexture) if (_use3D == 1)
{ {
psDesc.VS = shader->GetVS("VS_WriteToSlice"); psDesc.VS = shader->GetVS("VS_WriteToSlice");
psDesc.GS = shader->GetGS("GS_WriteToSlice"); psDesc.GS = shader->GetGS("GS_WriteToSlice");
@@ -110,42 +78,53 @@ bool ColorGradingPass::setupResources()
} }
if (_psLut.Create(psDesc, shader, psName)) if (_psLut.Create(psDesc, shader, psName))
return true; return true;
}
return false; return false;
} }
void ColorGradingPass::Dispose() void ColorGradingPass::Dispose()
{ {
// Base
RendererPass::Dispose(); RendererPass::Dispose();
// Cleanup
_psLut.Delete(); _psLut.Delete();
_shader = nullptr; _shader = nullptr;
} }
GPUTexture* ColorGradingPass::RenderLUT(RenderContext& renderContext) GPUTexture* ColorGradingPass::RenderLUT(RenderContext& renderContext)
{ {
// 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;
use3D &= device->Limits.HasGeometryShaders && device->Limits.HasVolumeTextureRendering;
use3D &= !PLATFORM_SWITCH; // TODO: move this in future to platform-specific configs
// Rebuild PSO on change
if (_use3D != (int32)use3D)
{
invalidateResources();
_use3D = use3D;
}
// Ensure to have valid data // Ensure to have valid data
if (checkIfSkipPass()) if (checkIfSkipPass())
return nullptr; return nullptr;
PROFILE_GPU_CPU("Color Grading LUT"); PROFILE_GPU_CPU("Color Grading LUT");
// For a 3D texture, the viewport is 16x16 (per slice), for a 2D texture, it's unwrapped to 256x16 // Pick a proper LUT pixels format
const int32 LutSize = 32; // this must match value in shader (see ColorGrading.shader and PostProcessing.shader) auto lutFormat = PixelFormat::R10G10B10A2_UNorm;
const auto formatSupport = device->GetFormatFeatures(lutFormat).Support;
FormatSupport formatSupportFlags = FormatSupport::ShaderSample | FormatSupport::RenderTarget;
formatSupportFlags |= use3D ? FormatSupport::Texture3D : FormatSupport::Texture2D;
if (EnumHasNoneFlags(formatSupport, formatSupportFlags))
lutFormat = PixelFormat::R8G8B8A8_UNorm;
// For a 3D texture, the viewport is 32x32 (per slice), for a 2D texture, it's unwrapped to 1024x32
constexpr int32 lutSize = 32; // this must match value in shader (see ColorGrading.shader and PostProcessing.shader)
GPUTextureDescription lutDesc; GPUTextureDescription lutDesc;
#if GPU_ALLOW_GEOMETRY_SHADERS if (use3D)
if (_useVolumeTexture) lutDesc = GPUTextureDescription::New3D(lutSize, lutSize, lutSize, 1, lutFormat);
{
lutDesc = GPUTextureDescription::New3D(LutSize, LutSize, LutSize, 1, _lutFormat);
}
else else
#endif lutDesc = GPUTextureDescription::New2D(lutSize * lutSize, lutSize, 1, lutFormat);
{
lutDesc = GPUTextureDescription::New2D(LutSize * LutSize, LutSize, 1, _lutFormat);
}
const auto lut = RenderTargetPool::Get(lutDesc); const auto lut = RenderTargetPool::Get(lutDesc);
RENDER_TARGET_POOL_SET_NAME(lut, "ColorGrading.LUT"); RENDER_TARGET_POOL_SET_NAME(lut, "ColorGrading.LUT");
@@ -181,7 +160,6 @@ GPUTexture* ColorGradingPass::RenderLUT(RenderContext& renderContext)
data.LutWeight = useLut ? colorGrading.LutWeight : 0.0f; data.LutWeight = useLut ? colorGrading.LutWeight : 0.0f;
// Prepare // Prepare
auto device = GPUDevice::Instance;
auto context = device->GetMainContext(); auto context = device->GetMainContext();
const auto cb = _shader->GetShader()->GetCB(0); const auto cb = _shader->GetShader()->GetCB(0);
context->UpdateCB(cb, &data); context->UpdateCB(cb, &data);
@@ -192,7 +170,7 @@ GPUTexture* ColorGradingPass::RenderLUT(RenderContext& renderContext)
// Draw // Draw
#if GPU_ALLOW_GEOMETRY_SHADERS #if GPU_ALLOW_GEOMETRY_SHADERS
if (_useVolumeTexture) if (use3D)
{ {
context->SetRenderTarget(lut->ViewVolume()); context->SetRenderTarget(lut->ViewVolume());

View File

@@ -11,30 +11,19 @@
class ColorGradingPass : public RendererPass<ColorGradingPass> class ColorGradingPass : public RendererPass<ColorGradingPass>
{ {
private: private:
int32 _use3D = -1;
bool _useVolumeTexture;
PixelFormat _lutFormat;
AssetReference<Shader> _shader; AssetReference<Shader> _shader;
GPUPipelineStatePermutationsPs<4> _psLut; GPUPipelineStatePermutationsPs<4> _psLut;
public: public:
/// <summary> /// <summary>
/// Init /// Renders Look Up table with color grading parameters mixed in.
/// </summary>
ColorGradingPass();
public:
/// <summary>
/// Performs Look Up Table rendering for the input task.
/// </summary> /// </summary>
/// <param name="renderContext">The rendering context.</param> /// <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.</returns> /// <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>
GPUTexture* RenderLUT(RenderContext& renderContext); GPUTexture* RenderLUT(RenderContext& renderContext);
private: private:
#if COMPILE_WITH_DEV_ENV #if COMPILE_WITH_DEV_ENV
void OnShaderReloading(Asset* obj) void OnShaderReloading(Asset* obj)
{ {
@@ -44,14 +33,12 @@ private:
#endif #endif
public: public:
// [RendererPass] // [RendererPass]
String ToString() const override; String ToString() const override;
bool Init() override; bool Init() override;
void Dispose() override; void Dispose() override;
protected: protected:
// [RendererPass] // [RendererPass]
bool setupResources() override; bool setupResources() override;
}; };