Overhaul of LookModification (#3282)
* Fixed log2 shaper equations. Added bspline sampling for lut. Added options for custom log2 or linear lut with custom exposure ranges. Signed-off-by: Ken Pruiksma <pruiksma@amazon.com> * Added support for PQ shaper. Added shader option & cvar for lut sampling quality. Fixed issues in the blend lut shader that were causing considerable quality loss. No longer always changing to the log2 1000 nit shaper when blending luts - if the source luts all use the same shaper, keep using that shaper in the blended lut. Signed-off-by: Ken Pruiksma <pruiksma@amazon.com> * Fixed an integer -> float Signed-off-by: Ken Pruiksma <pruiksma@amazon.com> * Minor PR reveiw updates Signed-off-by: Ken Pruiksma <pruiksma@amazon.com>
This commit is contained in:
@@ -21,6 +21,7 @@
|
||||
|
||||
namespace
|
||||
{
|
||||
static const AZ::RHI::Format LutFormat = AZ::RHI::Format::R16G16B16A16_FLOAT;
|
||||
|
||||
uint16_t ConvertFloatToHalf(const float Value)
|
||||
{
|
||||
@@ -56,395 +57,409 @@ namespace
|
||||
}
|
||||
}
|
||||
|
||||
namespace AZ
|
||||
namespace AZ::Render
|
||||
{
|
||||
namespace Render
|
||||
void AcesDisplayMapperFeatureProcessor::Reflect(ReflectContext* context)
|
||||
{
|
||||
void AcesDisplayMapperFeatureProcessor::Reflect(ReflectContext* context)
|
||||
if (auto* serializeContext = azrtti_cast<SerializeContext*>(context))
|
||||
{
|
||||
if (auto* serializeContext = azrtti_cast<SerializeContext*>(context))
|
||||
{
|
||||
serializeContext
|
||||
->Class<AcesDisplayMapperFeatureProcessor, FeatureProcessor>()
|
||||
->Version(0);
|
||||
}
|
||||
serializeContext
|
||||
->Class<AcesDisplayMapperFeatureProcessor, FeatureProcessor>()
|
||||
->Version(0);
|
||||
}
|
||||
}
|
||||
|
||||
void AcesDisplayMapperFeatureProcessor::Activate()
|
||||
{
|
||||
GetDefaultDisplayMapperConfiguration(m_displayMapperConfiguration);
|
||||
}
|
||||
|
||||
void AcesDisplayMapperFeatureProcessor::Deactivate()
|
||||
{
|
||||
m_ownedLuts.clear();
|
||||
}
|
||||
|
||||
void AcesDisplayMapperFeatureProcessor::Simulate(const FeatureProcessor::SimulatePacket& packet)
|
||||
{
|
||||
AZ_TRACE_METHOD();
|
||||
AZ_UNUSED(packet);
|
||||
}
|
||||
|
||||
void AcesDisplayMapperFeatureProcessor::Render([[maybe_unused]] const FeatureProcessor::RenderPacket& packet)
|
||||
{
|
||||
}
|
||||
|
||||
void AcesDisplayMapperFeatureProcessor::ApplyLdrOdtParameters(DisplayMapperParameters* displayMapperParameters)
|
||||
{
|
||||
AZ_Assert(displayMapperParameters != nullptr, "The pOutParameters must not to be null pointer.");
|
||||
if (displayMapperParameters == nullptr)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
void AcesDisplayMapperFeatureProcessor::Activate()
|
||||
// These values in the ODT parameter are taken from the reference ACES transform.
|
||||
//
|
||||
// The original ACES references.
|
||||
// Common:
|
||||
// https://github.com/ampas/aces-dev/blob/master/transforms/ctl/lib/ACESlib.ODT_Common.ctl
|
||||
// For sRGB:
|
||||
// https://github.com/ampas/aces-dev/tree/master/transforms/ctl/odt/sRGB
|
||||
displayMapperParameters->m_cinemaLimits[0] = 0.02f;
|
||||
displayMapperParameters->m_cinemaLimits[1] = 48.0f;
|
||||
displayMapperParameters->m_acesSplineParams = GetAcesODTParameters(OutputDeviceTransformType_48Nits);
|
||||
displayMapperParameters->m_OutputDisplayTransformFlags = AlterSurround | ApplyDesaturation | ApplyCATD60toD65;
|
||||
displayMapperParameters->m_OutputDisplayTransformMode = Srgb;
|
||||
ColorConvertionMatrixType colorMatrixType = XYZ_To_Rec709;
|
||||
switch (displayMapperParameters->m_OutputDisplayTransformMode)
|
||||
{
|
||||
GetDefaultDisplayMapperConfiguration(m_displayMapperConfiguration);
|
||||
case Srgb:
|
||||
colorMatrixType = XYZ_To_Rec709;
|
||||
break;
|
||||
case PerceptualQuantizer:
|
||||
case Ldr:
|
||||
colorMatrixType = XYZ_To_Bt2020;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
displayMapperParameters->m_XYZtoDisplayPrimaries = GetColorConvertionMatrix(colorMatrixType);
|
||||
|
||||
displayMapperParameters->m_surroundGamma = 0.9811f;
|
||||
displayMapperParameters->m_gamma = 2.2f;
|
||||
}
|
||||
|
||||
void AcesDisplayMapperFeatureProcessor::ApplyHdrOdtParameters(DisplayMapperParameters* displayMapperParameters, const OutputDeviceTransformType& odtType)
|
||||
{
|
||||
AZ_Assert(displayMapperParameters != nullptr, "The pOutParameters must not to be null pointer.");
|
||||
if (displayMapperParameters == nullptr)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
void AcesDisplayMapperFeatureProcessor::Deactivate()
|
||||
// Dynamic range limit values taken from NVIDIA HDR sample.
|
||||
// These values represent and low and high end of the dynamic range in terms of stops from middle grey (0.18)
|
||||
float lowerDynamicRangeInStops = -12.f;
|
||||
float higherDynamicRangeInStops = 10.f;
|
||||
const float MIDDLE_GREY = 0.18f;
|
||||
|
||||
switch (odtType)
|
||||
{
|
||||
m_ownedLuts.clear();
|
||||
case OutputDeviceTransformType_1000Nits:
|
||||
higherDynamicRangeInStops = 10.f;
|
||||
break;
|
||||
case OutputDeviceTransformType_2000Nits:
|
||||
higherDynamicRangeInStops = 11.f;
|
||||
break;
|
||||
case OutputDeviceTransformType_4000Nits:
|
||||
higherDynamicRangeInStops = 12.f;
|
||||
break;
|
||||
default:
|
||||
AZ_Assert(false, "Invalid output device transform type.");
|
||||
break;
|
||||
}
|
||||
|
||||
void AcesDisplayMapperFeatureProcessor::Simulate(const FeatureProcessor::SimulatePacket& packet)
|
||||
displayMapperParameters->m_cinemaLimits[0] = MIDDLE_GREY * exp2(lowerDynamicRangeInStops);
|
||||
displayMapperParameters->m_cinemaLimits[1] = MIDDLE_GREY * exp2(higherDynamicRangeInStops);
|
||||
displayMapperParameters->m_acesSplineParams = GetAcesODTParameters(odtType);
|
||||
displayMapperParameters->m_OutputDisplayTransformFlags = AlterSurround | ApplyDesaturation | ApplyCATD60toD65;
|
||||
displayMapperParameters->m_OutputDisplayTransformMode = PerceptualQuantizer;
|
||||
ColorConvertionMatrixType colorMatrixType = XYZ_To_Bt2020;
|
||||
displayMapperParameters->m_XYZtoDisplayPrimaries = GetColorConvertionMatrix(colorMatrixType);
|
||||
|
||||
// Surround gamma value is from the dim surround gamma from the ACES reference transforms.
|
||||
// https://github.com/ampas/aces-dev/blob/master/transforms/ctl/lib/ACESlib.ODT_Common.ctl
|
||||
displayMapperParameters->m_surroundGamma = 0.9811f;
|
||||
displayMapperParameters->m_gamma = 1.0f; // gamma not used with perceptual quantizer, but just set to 1.0 anyways
|
||||
}
|
||||
|
||||
OutputDeviceTransformType AcesDisplayMapperFeatureProcessor::GetOutputDeviceTransformType(RHI::Format bufferFormat)
|
||||
{
|
||||
OutputDeviceTransformType outputDeviceTransformType = OutputDeviceTransformType_48Nits;
|
||||
if (bufferFormat == RHI::Format::R8G8B8A8_UNORM ||
|
||||
bufferFormat == RHI::Format::B8G8R8A8_UNORM)
|
||||
{
|
||||
AZ_TRACE_METHOD();
|
||||
AZ_UNUSED(packet);
|
||||
outputDeviceTransformType = OutputDeviceTransformType_48Nits;
|
||||
}
|
||||
else if (bufferFormat == RHI::Format::R10G10B10A2_UNORM)
|
||||
{
|
||||
outputDeviceTransformType = OutputDeviceTransformType_1000Nits;
|
||||
}
|
||||
else
|
||||
{
|
||||
AZ_Assert(false, "Not yet supported.");
|
||||
// To work normally on unsupported environment, initialize the display parameters by OutputDeviceTransformType_48Nits.
|
||||
outputDeviceTransformType = OutputDeviceTransformType_48Nits;
|
||||
}
|
||||
return outputDeviceTransformType;
|
||||
}
|
||||
|
||||
void AcesDisplayMapperFeatureProcessor::GetAcesDisplayMapperParameters(DisplayMapperParameters* displayMapperParameters, OutputDeviceTransformType odtType)
|
||||
{
|
||||
switch (odtType)
|
||||
{
|
||||
case OutputDeviceTransformType_48Nits:
|
||||
ApplyLdrOdtParameters(displayMapperParameters);
|
||||
break;
|
||||
case OutputDeviceTransformType_1000Nits:
|
||||
case OutputDeviceTransformType_2000Nits:
|
||||
case OutputDeviceTransformType_4000Nits:
|
||||
ApplyHdrOdtParameters(displayMapperParameters, odtType);
|
||||
break;
|
||||
default:
|
||||
AZ_Assert(false, "This ODT type[%d] is not supported.", odtType);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void AcesDisplayMapperFeatureProcessor::GetOwnedLut(DisplayMapperLut& displayMapperLut, const AZ::Name& lutName)
|
||||
{
|
||||
auto it = m_ownedLuts.find(lutName);
|
||||
if (it == m_ownedLuts.end())
|
||||
{
|
||||
InitializeLutImage(lutName);
|
||||
it = m_ownedLuts.find(lutName);
|
||||
AZ_Assert(it != m_ownedLuts.end(), "AcesDisplayMapperFeatureProcessor unable to create LUT %s", lutName.GetCStr());
|
||||
}
|
||||
displayMapperLut = it->second;
|
||||
}
|
||||
|
||||
void AcesDisplayMapperFeatureProcessor::GetDisplayMapperLut(DisplayMapperLut& displayMapperLut)
|
||||
{
|
||||
const AZ::Name acesLutName("AcesLutImage");
|
||||
auto it = m_ownedLuts.find(acesLutName);
|
||||
if (it == m_ownedLuts.end())
|
||||
{
|
||||
InitializeLutImage(acesLutName);
|
||||
|
||||
it = m_ownedLuts.find(acesLutName);
|
||||
AZ_Assert(it != m_ownedLuts.end(), "AcesDisplayMapperFeatureProcessor unable to create ACES LUT image");
|
||||
}
|
||||
displayMapperLut = it->second;
|
||||
}
|
||||
|
||||
void AcesDisplayMapperFeatureProcessor::GetLutFromAssetLocation(DisplayMapperAssetLut& displayMapperAssetLut, const AZStd::string& assetPath)
|
||||
{
|
||||
Data::AssetId assetId = RPI::AssetUtils::GetAssetIdForProductPath(assetPath.c_str(), RPI::AssetUtils::TraceLevel::Error);
|
||||
GetLutFromAssetId(displayMapperAssetLut, assetId);
|
||||
}
|
||||
|
||||
void AcesDisplayMapperFeatureProcessor::GetLutFromAssetId(DisplayMapperAssetLut& displayMapperAssetLut, const AZ::Data::AssetId assetId)
|
||||
{
|
||||
if (!assetId.IsValid())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
void AcesDisplayMapperFeatureProcessor::Render([[maybe_unused]] const FeatureProcessor::RenderPacket& packet)
|
||||
// Check first if this already exists
|
||||
auto it = m_assetLuts.find(assetId.ToString<AZStd::string>());
|
||||
if (it != m_assetLuts.end())
|
||||
{
|
||||
displayMapperAssetLut = it->second;
|
||||
return;
|
||||
}
|
||||
|
||||
void AcesDisplayMapperFeatureProcessor::ApplyLdrOdtParameters(DisplayMapperParameters* displayMapperParameters)
|
||||
// Read the lut which is a .3dl file embedded within an azasset file.
|
||||
Data::Asset<RPI::AnyAsset> asset = RPI::AssetUtils::LoadAssetById<RPI::AnyAsset>(assetId, RPI::AssetUtils::TraceLevel::Error);
|
||||
const LookupTableAsset* lutAsset = RPI::GetDataFromAnyAsset<LookupTableAsset>(asset);
|
||||
|
||||
if (lutAsset == nullptr)
|
||||
{
|
||||
AZ_Assert(displayMapperParameters != nullptr, "The pOutParameters must not to be null pointer.");
|
||||
if (displayMapperParameters == nullptr)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// These values in the ODT parameter are taken from the reference ACES transform.
|
||||
//
|
||||
// The original ACES references.
|
||||
// Common:
|
||||
// https://github.com/ampas/aces-dev/blob/master/transforms/ctl/lib/ACESlib.ODT_Common.ctl
|
||||
// For sRGB:
|
||||
// https://github.com/ampas/aces-dev/tree/master/transforms/ctl/odt/sRGB
|
||||
displayMapperParameters->m_cinemaLimits[0] = 0.02f;
|
||||
displayMapperParameters->m_cinemaLimits[1] = 48.0f;
|
||||
displayMapperParameters->m_acesSplineParams = GetAcesODTParameters(OutputDeviceTransformType_48Nits);
|
||||
displayMapperParameters->m_OutputDisplayTransformFlags = AlterSurround | ApplyDesaturation | ApplyCATD60toD65;
|
||||
displayMapperParameters->m_OutputDisplayTransformMode = Srgb;
|
||||
ColorConvertionMatrixType colorMatrixType = XYZ_To_Rec709;
|
||||
switch (displayMapperParameters->m_OutputDisplayTransformMode)
|
||||
{
|
||||
case Srgb:
|
||||
colorMatrixType = XYZ_To_Rec709;
|
||||
break;
|
||||
case PerceptualQuantizer:
|
||||
case Ldr:
|
||||
colorMatrixType = XYZ_To_Bt2020;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
displayMapperParameters->m_XYZtoDisplayPrimaries = GetColorConvertionMatrix(colorMatrixType);
|
||||
|
||||
displayMapperParameters->m_surroundGamma = 0.9811f;
|
||||
displayMapperParameters->m_gamma = 2.2f;
|
||||
AZ_Error("AcesDisplayMapperFeatureProcessor", false, "Unable to read LUT from asset.");
|
||||
asset.Release();
|
||||
return;
|
||||
}
|
||||
|
||||
void AcesDisplayMapperFeatureProcessor::ApplyHdrOdtParameters(DisplayMapperParameters* displayMapperParameters, const OutputDeviceTransformType& odtType)
|
||||
// The first row of numbers in a 3dl file is a number of vertices that partition the space from [0,..1023]
|
||||
// This assumes that the vertices are evenly spaced apart. Non-uniform spacing is supported by the format,
|
||||
// but haven't been encountered yet.
|
||||
const size_t lutSize = lutAsset->m_intervals.size();
|
||||
|
||||
if (lutSize == 0)
|
||||
{
|
||||
AZ_Assert(displayMapperParameters != nullptr, "The pOutParameters must not to be null pointer.");
|
||||
if (displayMapperParameters == nullptr)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Dynamic range limit values taken from NVIDIA HDR sample.
|
||||
// These values represent and low and high end of the dynamic range in terms of stops from middle grey (0.18)
|
||||
float lowerDynamicRangeInStops = -12.f;
|
||||
float higherDynamicRangeInStops = 10.f;
|
||||
const float MIDDLE_GREY = 0.18f;
|
||||
|
||||
switch (odtType)
|
||||
{
|
||||
case OutputDeviceTransformType_1000Nits:
|
||||
higherDynamicRangeInStops = 10.f;
|
||||
break;
|
||||
case OutputDeviceTransformType_2000Nits:
|
||||
higherDynamicRangeInStops = 11.f;
|
||||
break;
|
||||
case OutputDeviceTransformType_4000Nits:
|
||||
higherDynamicRangeInStops = 12.f;
|
||||
break;
|
||||
default:
|
||||
AZ_Assert(false, "Invalid output device transform type.");
|
||||
break;
|
||||
}
|
||||
|
||||
displayMapperParameters->m_cinemaLimits[0] = MIDDLE_GREY * exp2(lowerDynamicRangeInStops);
|
||||
displayMapperParameters->m_cinemaLimits[1] = MIDDLE_GREY * exp2(higherDynamicRangeInStops);
|
||||
displayMapperParameters->m_acesSplineParams = GetAcesODTParameters(odtType);
|
||||
displayMapperParameters->m_OutputDisplayTransformFlags = AlterSurround | ApplyDesaturation | ApplyCATD60toD65;
|
||||
displayMapperParameters->m_OutputDisplayTransformMode = PerceptualQuantizer;
|
||||
ColorConvertionMatrixType colorMatrixType = XYZ_To_Bt2020;
|
||||
displayMapperParameters->m_XYZtoDisplayPrimaries = GetColorConvertionMatrix(colorMatrixType);
|
||||
|
||||
// Surround gamma value is from the dim surround gamma from the ACES reference transforms.
|
||||
// https://github.com/ampas/aces-dev/blob/master/transforms/ctl/lib/ACESlib.ODT_Common.ctl
|
||||
displayMapperParameters->m_surroundGamma = 0.9811f;
|
||||
displayMapperParameters->m_gamma = 1.0f; // gamma not used with perceptual quantizer, but just set to 1.0 anyways
|
||||
AZ_Error("AcesDisplayMapperFeatureProcessor", false, "Lut asset has invalid size.");
|
||||
asset.Release();
|
||||
return;
|
||||
}
|
||||
|
||||
OutputDeviceTransformType AcesDisplayMapperFeatureProcessor::GetOutputDeviceTransformType(RHI::Format bufferFormat)
|
||||
// Create a buffer of half floats from the LUT and use it to initialize a 3d texture.
|
||||
|
||||
const size_t kChannels = 4;
|
||||
const size_t kChannelBytes = 2;
|
||||
const size_t bytesPerRow = lutSize * kChannels * kChannelBytes;
|
||||
const size_t bytesPerSlice = bytesPerRow * lutSize;
|
||||
|
||||
AZStd::vector<uint16_t> u16Buffer;
|
||||
const size_t bufferSize = lutSize * lutSize * lutSize * kChannels;
|
||||
u16Buffer.resize(bufferSize);
|
||||
|
||||
for (size_t slice = 0; slice < lutSize; slice++)
|
||||
{
|
||||
OutputDeviceTransformType outputDeviceTransformType = OutputDeviceTransformType_48Nits;
|
||||
if (bufferFormat == RHI::Format::R8G8B8A8_UNORM ||
|
||||
bufferFormat == RHI::Format::B8G8R8A8_UNORM)
|
||||
for (size_t column = 0; column < lutSize; column++)
|
||||
{
|
||||
outputDeviceTransformType = OutputDeviceTransformType_48Nits;
|
||||
}
|
||||
else if (bufferFormat == RHI::Format::R10G10B10A2_UNORM)
|
||||
{
|
||||
outputDeviceTransformType = OutputDeviceTransformType_1000Nits;
|
||||
}
|
||||
else
|
||||
{
|
||||
AZ_Assert(false, "Not yet supported.");
|
||||
// To work normally on unsupported environment, initialize the display parameters by OutputDeviceTransformType_48Nits.
|
||||
outputDeviceTransformType = OutputDeviceTransformType_48Nits;
|
||||
}
|
||||
return outputDeviceTransformType;
|
||||
}
|
||||
|
||||
void AcesDisplayMapperFeatureProcessor::GetAcesDisplayMapperParameters(DisplayMapperParameters* displayMapperParameters, OutputDeviceTransformType odtType)
|
||||
{
|
||||
switch (odtType)
|
||||
{
|
||||
case OutputDeviceTransformType_48Nits:
|
||||
ApplyLdrOdtParameters(displayMapperParameters);
|
||||
break;
|
||||
case OutputDeviceTransformType_1000Nits:
|
||||
case OutputDeviceTransformType_2000Nits:
|
||||
case OutputDeviceTransformType_4000Nits:
|
||||
ApplyHdrOdtParameters(displayMapperParameters, odtType);
|
||||
break;
|
||||
default:
|
||||
AZ_Assert(false, "This ODT type[%d] is not supported.", odtType);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void AcesDisplayMapperFeatureProcessor::GetOwnedLut(DisplayMapperLut& displayMapperLut, const AZ::Name& lutName)
|
||||
{
|
||||
auto it = m_ownedLuts.find(lutName);
|
||||
if (it == m_ownedLuts.end())
|
||||
{
|
||||
InitializeLutImage(lutName);
|
||||
it = m_ownedLuts.find(lutName);
|
||||
AZ_Assert(it != m_ownedLuts.end(), "AcesDisplayMapperFeatureProcessor unable to create LUT %s", lutName.GetCStr());
|
||||
}
|
||||
displayMapperLut = it->second;
|
||||
}
|
||||
|
||||
void AcesDisplayMapperFeatureProcessor::GetDisplayMapperLut(DisplayMapperLut& displayMapperLut)
|
||||
{
|
||||
const AZ::Name acesLutName("AcesLutImage");
|
||||
auto it = m_ownedLuts.find(acesLutName);
|
||||
if (it == m_ownedLuts.end())
|
||||
{
|
||||
InitializeLutImage(acesLutName);
|
||||
|
||||
it = m_ownedLuts.find(acesLutName);
|
||||
AZ_Assert(it != m_ownedLuts.end(), "AcesDisplayMapperFeatureProcessor unable to create ACES LUT image");
|
||||
}
|
||||
displayMapperLut = it->second;
|
||||
}
|
||||
|
||||
void AcesDisplayMapperFeatureProcessor::GetLutFromAssetLocation(DisplayMapperAssetLut& displayMapperAssetLut, const AZStd::string& assetPath)
|
||||
{
|
||||
Data::AssetId assetId = RPI::AssetUtils::GetAssetIdForProductPath(assetPath.c_str(), RPI::AssetUtils::TraceLevel::Error);
|
||||
GetLutFromAssetId(displayMapperAssetLut, assetId);
|
||||
}
|
||||
|
||||
void AcesDisplayMapperFeatureProcessor::GetLutFromAssetId(DisplayMapperAssetLut& displayMapperAssetLut, const AZ::Data::AssetId assetId)
|
||||
{
|
||||
if (!assetId.IsValid())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Check first if this already exists
|
||||
auto it = m_assetLuts.find(assetId.ToString<AZStd::string>());
|
||||
if (it != m_assetLuts.end())
|
||||
{
|
||||
displayMapperAssetLut = it->second;
|
||||
return;
|
||||
}
|
||||
|
||||
// Read the lut which is a .3dl file embedded within an azasset file.
|
||||
Data::Asset<RPI::AnyAsset> asset = RPI::AssetUtils::LoadAssetById<RPI::AnyAsset>(assetId, RPI::AssetUtils::TraceLevel::Error);
|
||||
const LookupTableAsset* lutAsset = RPI::GetDataFromAnyAsset<LookupTableAsset>(asset);
|
||||
|
||||
if (lutAsset == nullptr)
|
||||
{
|
||||
AZ_Error("AcesDisplayMapperFeatureProcessor", false, "Unable to read LUT from asset.");
|
||||
asset.Release();
|
||||
return;
|
||||
}
|
||||
|
||||
// The first row of numbers in a 3dl file is a number of vertices that partition the space from [0,..1023]
|
||||
// This assumes that the vertices are evenly spaced apart. Non-uniform spacing is supported by the format,
|
||||
// but haven't been encountered yet.
|
||||
uint32_t lutSize = static_cast<uint32_t>(lutAsset->m_intervals.size());
|
||||
|
||||
if (lutSize == 0)
|
||||
{
|
||||
AZ_Error("AcesDisplayMapperFeatureProcessor", false, "Lut asset has invalid size.");
|
||||
asset.Release();
|
||||
return;
|
||||
}
|
||||
|
||||
// The vertices in the file are given as a positive integer value in [0,..4095] and need to be normalized
|
||||
// and stored into a linear unaligned buffer used to initialize the streaming image.
|
||||
const float normalizeValue = 4095.0f;
|
||||
const int kChannels = 4;
|
||||
const int kChannelBytes = 2;
|
||||
int bytesPerRow = lutSize * kChannels * kChannelBytes;
|
||||
int bytesPerSlice = bytesPerRow * lutSize;
|
||||
|
||||
AZStd::vector<uint16_t> u16Buffer;
|
||||
size_t bufferSize = (bytesPerSlice * lutSize) / sizeof(uint16_t);
|
||||
u16Buffer.resize(bufferSize);
|
||||
uint16_t* data = u16Buffer.data();
|
||||
for (int slice = 0; slice < (int)lutSize; slice++)
|
||||
{
|
||||
for (int column = 0; column < (int)lutSize; column++)
|
||||
for (size_t row = 0; row < lutSize; row++)
|
||||
{
|
||||
for (int row = 0; row < (int)lutSize; row++)
|
||||
{
|
||||
// Index in the LUT texture data
|
||||
int idx = (column * kChannels) +
|
||||
(bytesPerRow * row / sizeof(uint16_t)) +
|
||||
((bytesPerSlice * slice) / sizeof(uint16_t));
|
||||
// Index in the LUT texture data
|
||||
size_t idx = (column * kChannels) +
|
||||
((bytesPerRow * row) / kChannelBytes) +
|
||||
((bytesPerSlice * slice) / kChannelBytes);
|
||||
|
||||
// Vertices the .3dl file are listed first by increasing slice, then row, and finally column coordinate
|
||||
// This corresponds to blue, green, and red channels, respectively.
|
||||
int assetIdx = slice + lutSize * row + (lutSize * lutSize * column);
|
||||
// Vertices the .3dl file are listed first by increasing slice, then row, and finally column coordinate
|
||||
// This corresponds to blue, green, and red channels, respectively.
|
||||
size_t assetIdx = slice + lutSize * row + (lutSize * lutSize * column);
|
||||
|
||||
AZ::u64 red = lutAsset->m_values[assetIdx * 3 + 0];
|
||||
AZ::u64 green = lutAsset->m_values[assetIdx * 3 + 1];
|
||||
AZ::u64 blue = lutAsset->m_values[assetIdx * 3 + 2];
|
||||
data[idx + 0] = ConvertFloatToHalf(static_cast<float>(red) / normalizeValue);
|
||||
data[idx + 1] = ConvertFloatToHalf(static_cast<float>(green) / normalizeValue);
|
||||
data[idx + 2] = ConvertFloatToHalf(static_cast<float>(blue) / normalizeValue);
|
||||
data[idx + 3] = 0x3b00; // 1.0 in half
|
||||
}
|
||||
AZ::u64 red = lutAsset->m_values[assetIdx * 3 + 0];
|
||||
AZ::u64 green = lutAsset->m_values[assetIdx * 3 + 1];
|
||||
AZ::u64 blue = lutAsset->m_values[assetIdx * 3 + 2];
|
||||
|
||||
// The vertices in the file are given as a positive integer value in [0,..4095] and need to be normalized
|
||||
constexpr float NormalizeValue = 4095.0f;
|
||||
|
||||
u16Buffer[idx + 0] = ConvertFloatToHalf(static_cast<float>(red) / NormalizeValue);
|
||||
u16Buffer[idx + 1] = ConvertFloatToHalf(static_cast<float>(green) / NormalizeValue);
|
||||
u16Buffer[idx + 2] = ConvertFloatToHalf(static_cast<float>(blue) / NormalizeValue);
|
||||
u16Buffer[idx + 3] = 0x3b00; // 1.0 in half
|
||||
}
|
||||
}
|
||||
|
||||
asset.Release();
|
||||
|
||||
Data::Instance<RPI::StreamingImagePool> streamingImagePool = RPI::ImageSystemInterface::Get()->GetSystemStreamingPool();
|
||||
|
||||
RHI::Size imageSize;
|
||||
imageSize.m_width = static_cast<uint32_t>(lutSize);
|
||||
imageSize.m_height = static_cast<uint32_t>(lutSize);
|
||||
imageSize.m_depth = static_cast<uint32_t>(lutSize);
|
||||
size_t imageDataSize = bytesPerSlice * lutSize;
|
||||
|
||||
Data::Instance<RPI::StreamingImage> lutStreamingImage = RPI::StreamingImage::CreateFromCpuData(
|
||||
*streamingImagePool, RHI::ImageDimension::Image3D, imageSize, LutFormat, data, imageDataSize);
|
||||
|
||||
AZ_Error("AcesDisplayMapperFeatureProcessor", lutStreamingImage, "Failed to initialize the lut assetId %s.", assetId.ToString<AZStd::string>().c_str());
|
||||
|
||||
DisplayMapperAssetLut assetLut;
|
||||
assetLut.m_lutStreamingImage = lutStreamingImage;
|
||||
|
||||
// Add to the list of LUT asset resources
|
||||
m_assetLuts.insert(AZStd::pair<AZStd::string, DisplayMapperAssetLut>(assetId.ToString<AZStd::string>(), assetLut));
|
||||
displayMapperAssetLut = assetLut;
|
||||
}
|
||||
|
||||
void AcesDisplayMapperFeatureProcessor::InitializeImagePool()
|
||||
asset.Release();
|
||||
|
||||
Data::Instance<RPI::StreamingImagePool> streamingImagePool = RPI::ImageSystemInterface::Get()->GetSystemStreamingPool();
|
||||
|
||||
RHI::Size imageSize;
|
||||
imageSize.m_width = static_cast<uint32_t>(lutSize);
|
||||
imageSize.m_height = static_cast<uint32_t>(lutSize);
|
||||
imageSize.m_depth = static_cast<uint32_t>(lutSize);
|
||||
size_t imageDataSize = bytesPerSlice * lutSize;
|
||||
|
||||
Data::Instance<RPI::StreamingImage> lutStreamingImage = RPI::StreamingImage::CreateFromCpuData(
|
||||
*streamingImagePool, RHI::ImageDimension::Image3D, imageSize, LutFormat, u16Buffer.data(), imageDataSize);
|
||||
|
||||
AZ_Error("AcesDisplayMapperFeatureProcessor", lutStreamingImage, "Failed to initialize the lut assetId %s.", assetId.ToString<AZStd::string>().c_str());
|
||||
|
||||
DisplayMapperAssetLut assetLut;
|
||||
assetLut.m_lutStreamingImage = lutStreamingImage;
|
||||
|
||||
// Add to the list of LUT asset resources
|
||||
m_assetLuts.insert(AZStd::pair<AZStd::string, DisplayMapperAssetLut>(assetId.ToString<AZStd::string>(), assetLut));
|
||||
displayMapperAssetLut = assetLut;
|
||||
}
|
||||
|
||||
void AcesDisplayMapperFeatureProcessor::InitializeImagePool()
|
||||
{
|
||||
AZ::RHI::Factory& factory = RHI::Factory::Get();
|
||||
m_displayMapperImagePool = factory.CreateImagePool();
|
||||
m_displayMapperImagePool->SetName(Name("DisplayMapperImagePool"));
|
||||
|
||||
RHI::ImagePoolDescriptor imagePoolDesc = {};
|
||||
imagePoolDesc.m_bindFlags = RHI::ImageBindFlags::ShaderReadWrite;
|
||||
imagePoolDesc.m_budgetInBytes = ImagePoolBudget;
|
||||
|
||||
RHI::Device* device = RHI::RHISystemInterface::Get()->GetDevice();
|
||||
RHI::ResultCode resultCode = m_displayMapperImagePool->Init(*device, imagePoolDesc);
|
||||
if (resultCode != RHI::ResultCode::Success)
|
||||
{
|
||||
AZ::RHI::Factory& factory = RHI::Factory::Get();
|
||||
m_displayMapperImagePool = factory.CreateImagePool();
|
||||
m_displayMapperImagePool->SetName(Name("DisplayMapperImagePool"));
|
||||
|
||||
RHI::ImagePoolDescriptor imagePoolDesc = {};
|
||||
imagePoolDesc.m_bindFlags = RHI::ImageBindFlags::ShaderReadWrite;
|
||||
imagePoolDesc.m_budgetInBytes = ImagePoolBudget;
|
||||
|
||||
RHI::Device* device = RHI::RHISystemInterface::Get()->GetDevice();
|
||||
RHI::ResultCode resultCode = m_displayMapperImagePool->Init(*device, imagePoolDesc);
|
||||
if (resultCode != RHI::ResultCode::Success)
|
||||
{
|
||||
AZ_Error("AcesDisplayMapperFeatureProcessor", false, "Failed to initialize image pool.");
|
||||
return;
|
||||
}
|
||||
AZ_Error("AcesDisplayMapperFeatureProcessor", false, "Failed to initialize image pool.");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void AcesDisplayMapperFeatureProcessor::InitializeLutImage(const AZ::Name& lutName)
|
||||
void AcesDisplayMapperFeatureProcessor::InitializeLutImage(const AZ::Name& lutName)
|
||||
{
|
||||
if (!m_displayMapperImagePool)
|
||||
{
|
||||
if (!m_displayMapperImagePool)
|
||||
{
|
||||
InitializeImagePool();
|
||||
}
|
||||
|
||||
DisplayMapperLut lutResource;
|
||||
lutResource.m_lutImage = RHI::Factory::Get().CreateImage();
|
||||
lutResource.m_lutImage->SetName(lutName);
|
||||
|
||||
RHI::ImageInitRequest imageRequest;
|
||||
imageRequest.m_image = lutResource.m_lutImage.get();
|
||||
imageRequest.m_descriptor = RHI::ImageDescriptor::Create3D(RHI::ImageBindFlags::ShaderReadWrite, LutSize, LutSize, LutSize, LutFormat);
|
||||
RHI::ResultCode resultCode = m_displayMapperImagePool->InitImage(imageRequest);
|
||||
|
||||
if (resultCode != RHI::ResultCode::Success)
|
||||
{
|
||||
AZ_Error("AcesDisplayMapperFeatureProcessor", false, "Failed to initialize LUT image.");
|
||||
return;
|
||||
}
|
||||
|
||||
lutResource.m_lutImageViewDescriptor = RHI::ImageViewDescriptor::Create(LutFormat, 0, 0);
|
||||
lutResource.m_lutImageView = lutResource.m_lutImage->GetImageView(lutResource.m_lutImageViewDescriptor);
|
||||
if (!lutResource.m_lutImageView.get())
|
||||
{
|
||||
AZ_Error("AcesDisplayMapperFeatureProcessor", false, "Failed to initialize LUT image view.");
|
||||
return;
|
||||
}
|
||||
|
||||
// Add to the list of lut resources
|
||||
lutResource.m_lutImageView->SetName(lutName);
|
||||
m_ownedLuts[lutName] = lutResource;
|
||||
InitializeImagePool();
|
||||
}
|
||||
|
||||
ShaperParams AcesDisplayMapperFeatureProcessor::GetShaperParameters(ShaperPresetType shaperPreset)
|
||||
DisplayMapperLut lutResource;
|
||||
lutResource.m_lutImage = RHI::Factory::Get().CreateImage();
|
||||
lutResource.m_lutImage->SetName(lutName);
|
||||
|
||||
RHI::ImageInitRequest imageRequest;
|
||||
imageRequest.m_image = lutResource.m_lutImage.get();
|
||||
static const int LutSize = 32;
|
||||
imageRequest.m_descriptor = RHI::ImageDescriptor::Create3D(RHI::ImageBindFlags::ShaderReadWrite, LutSize, LutSize, LutSize, LutFormat);
|
||||
RHI::ResultCode resultCode = m_displayMapperImagePool->InitImage(imageRequest);
|
||||
|
||||
if (resultCode != RHI::ResultCode::Success)
|
||||
{
|
||||
// Default is a linear shaper with bias 0.0 and scale 1.0. That is, fx = x*1.0 + 0.0
|
||||
ShaperParams shaperParams = { ShaperType::Linear, 0.0, 1.f };
|
||||
OutputDeviceTransformType outputDeviceTransformType = OutputDeviceTransformType::NumOutputDeviceTransformTypes;
|
||||
switch (shaperPreset)
|
||||
{
|
||||
case ShaperPresetType::None:
|
||||
break;
|
||||
case ShaperPresetType::Log2_48_nits:
|
||||
outputDeviceTransformType = OutputDeviceTransformType::OutputDeviceTransformType_48Nits;
|
||||
break;
|
||||
case ShaperPresetType::Log2_1000_nits:
|
||||
outputDeviceTransformType = OutputDeviceTransformType::OutputDeviceTransformType_1000Nits;
|
||||
break;
|
||||
case ShaperPresetType::Log2_2000_nits:
|
||||
outputDeviceTransformType = OutputDeviceTransformType::OutputDeviceTransformType_2000Nits;
|
||||
break;
|
||||
case ShaperPresetType::Log2_4000_nits:
|
||||
outputDeviceTransformType = OutputDeviceTransformType::OutputDeviceTransformType_4000Nits;
|
||||
break;
|
||||
default:
|
||||
AZ_Error("DisplayMapperPass", false, "Invalid shaper preset type.");
|
||||
break;
|
||||
}
|
||||
if (outputDeviceTransformType < OutputDeviceTransformType::NumOutputDeviceTransformTypes)
|
||||
{
|
||||
shaperParams = GetAcesShaperParameters(outputDeviceTransformType);
|
||||
}
|
||||
return shaperParams;
|
||||
AZ_Error("AcesDisplayMapperFeatureProcessor", false, "Failed to initialize LUT image.");
|
||||
return;
|
||||
}
|
||||
|
||||
void AcesDisplayMapperFeatureProcessor::GetDefaultDisplayMapperConfiguration(DisplayMapperConfigurationDescriptor& config)
|
||||
lutResource.m_lutImageViewDescriptor = RHI::ImageViewDescriptor::Create(LutFormat, 0, 0);
|
||||
lutResource.m_lutImageView = lutResource.m_lutImage->GetImageView(lutResource.m_lutImageViewDescriptor);
|
||||
if (!lutResource.m_lutImageView.get())
|
||||
{
|
||||
// Default configuration is ACES with LDR color grading LUT disabled.
|
||||
config.m_operationType = DisplayMapperOperationType::Aces;
|
||||
config.m_ldrGradingLutEnabled = false;
|
||||
config.m_ldrColorGradingLut.Release();
|
||||
AZ_Error("AcesDisplayMapperFeatureProcessor", false, "Failed to initialize LUT image view.");
|
||||
return;
|
||||
}
|
||||
|
||||
void AcesDisplayMapperFeatureProcessor::RegisterDisplayMapperConfiguration(const DisplayMapperConfigurationDescriptor& config)
|
||||
{
|
||||
m_displayMapperConfiguration = config;
|
||||
}
|
||||
// Add to the list of lut resources
|
||||
lutResource.m_lutImageView->SetName(lutName);
|
||||
m_ownedLuts[lutName] = lutResource;
|
||||
}
|
||||
|
||||
DisplayMapperConfigurationDescriptor AcesDisplayMapperFeatureProcessor::GetDisplayMapperConfiguration()
|
||||
ShaperParams AcesDisplayMapperFeatureProcessor::GetShaperParameters(ShaperPresetType shaperPreset, float customMinEv, float customMaxEv)
|
||||
{
|
||||
// Default is a linear shaper with bias 0.0 and scale 1.0. That is, fx = x*1.0 + 0.0
|
||||
ShaperParams shaperParams = { ShaperType::Linear, 0.0, 1.f };
|
||||
switch (shaperPreset)
|
||||
{
|
||||
return m_displayMapperConfiguration;
|
||||
case ShaperPresetType::None:
|
||||
break;
|
||||
case ShaperPresetType::Log2_48Nits:
|
||||
shaperParams = GetAcesShaperParameters(OutputDeviceTransformType::OutputDeviceTransformType_48Nits);
|
||||
break;
|
||||
case ShaperPresetType::Log2_1000Nits:
|
||||
shaperParams = GetAcesShaperParameters(OutputDeviceTransformType::OutputDeviceTransformType_1000Nits);
|
||||
break;
|
||||
case ShaperPresetType::Log2_2000Nits:
|
||||
shaperParams = GetAcesShaperParameters(OutputDeviceTransformType::OutputDeviceTransformType_2000Nits);
|
||||
break;
|
||||
case ShaperPresetType::Log2_4000Nits:
|
||||
shaperParams = GetAcesShaperParameters(OutputDeviceTransformType::OutputDeviceTransformType_4000Nits);
|
||||
break;
|
||||
case ShaperPresetType::LinearCustomRange:
|
||||
{
|
||||
// Map the range min exposure - max exposure to 0-1. Convert EV values to linear values here to avoid that work in the shader.
|
||||
// Shader equation becomes (x - bias) / scale;
|
||||
constexpr float MediumGray = 0.18f;
|
||||
const float minValue = MediumGray * powf(2, customMinEv);
|
||||
const float maxValue = MediumGray * powf(2, customMaxEv);
|
||||
shaperParams.m_type = ShaperType::Linear;
|
||||
shaperParams.m_scale = 1.0f / (maxValue - minValue);
|
||||
shaperParams.m_bias = -minValue * shaperParams.m_scale;
|
||||
break;
|
||||
}
|
||||
} // namespace Render
|
||||
} // namespace AZ
|
||||
case ShaperPresetType::Log2CustomRange:
|
||||
shaperParams = GetLog2ShaperParameters(customMinEv, customMaxEv);
|
||||
break;
|
||||
case ShaperPresetType::PqSmpteSt2084:
|
||||
shaperParams.m_type = ShaperType::PqSmpteSt2084;
|
||||
break;
|
||||
default:
|
||||
AZ_Error("DisplayMapperPass", false, "Invalid shaper preset type.");
|
||||
break;
|
||||
}
|
||||
return shaperParams;
|
||||
}
|
||||
|
||||
void AcesDisplayMapperFeatureProcessor::GetDefaultDisplayMapperConfiguration(DisplayMapperConfigurationDescriptor& config)
|
||||
{
|
||||
// Default configuration is ACES with LDR color grading LUT disabled.
|
||||
config.m_operationType = DisplayMapperOperationType::Aces;
|
||||
config.m_ldrGradingLutEnabled = false;
|
||||
config.m_ldrColorGradingLut.Release();
|
||||
}
|
||||
|
||||
void AcesDisplayMapperFeatureProcessor::RegisterDisplayMapperConfiguration(const DisplayMapperConfigurationDescriptor& config)
|
||||
{
|
||||
m_displayMapperConfiguration = config;
|
||||
}
|
||||
|
||||
DisplayMapperConfigurationDescriptor AcesDisplayMapperFeatureProcessor::GetDisplayMapperConfiguration()
|
||||
{
|
||||
return m_displayMapperConfiguration;
|
||||
}
|
||||
} // namespace AZ::Render
|
||||
|
||||
Reference in New Issue
Block a user