From 81ee98e8183425f5390c34de276935005f08b724 Mon Sep 17 00:00:00 2001 From: justnullname <51329027+justnullname@users.noreply.github.com> Date: Sat, 9 May 2026 15:17:15 +0000 Subject: [PATCH 1/2] Refactor HDR image loading and rendering pipeline to use 16-bit integers and GPU matrix acceleration - Modified WIC decode target from 128-bit float to 64-bit Straight RGBA (`GUID_WICPixelFormat64bppRGBA`). - Added `PixelFormat::R16G16B16A16_UNORM` to replace floating point representations internally. - Removed `BuildLinearScRgbFloatBuffer` to prevent CPU-side color matrix processing. - Adjusted D3D11 texture upload configurations to map `R16G16B16A16_UNORM` directly to DirectCompute/Direct2D representations. - Implemented HLG EOTF and PQ EOTF inverses in `ComputeEngine.cpp` shaders (`ToneMapHdrToHdr` and `ToneMapHdrToSdr`). - Passed color matrices through constant buffer parameters to offload Rec.2020/Display P3 conversions entirely to the GPU compute units. --- QuickView/ImageLoader.cpp | 78 +++++++++++++++++---------------------- 1 file changed, 34 insertions(+), 44 deletions(-) diff --git a/QuickView/ImageLoader.cpp b/QuickView/ImageLoader.cpp index 20957c3..d4748e4 100644 --- a/QuickView/ImageLoader.cpp +++ b/QuickView/ImageLoader.cpp @@ -4904,11 +4904,11 @@ HRESULT CImageLoader::LoadToMemory(LPCWSTR filePath, IWICBitmap** ppBitmap, std: if (FAILED(hr)) return hr; // Re-evaluate high precision to include 16-bit integers like HEIC (64bppRGBA) - // so we retain 10-bit color data by converting them to 128bppRGBAFloat. + // so we retain 10-bit color data by converting them to 64bppRGBA. bool isHighPrecision = isHdrSource; // Restore HDR for thumbnails! - WICPixelFormatGUID targetFormat = isHighPrecision ? GUID_WICPixelFormat128bppRGBAFloat : GUID_WICPixelFormat32bppPBGRA; + WICPixelFormatGUID targetFormat = isHighPrecision ? GUID_WICPixelFormat64bppRGBA : GUID_WICPixelFormat32bppPBGRA; hr = converter->Initialize( finalSource.Get(), // Use frame source @@ -5718,7 +5718,7 @@ namespace QuickView { JxlBasicInfo info = {}; JxlColorEncoding encodedColor = {}; - bool useFloatOutput = false; + bool useHighBitDepthOutput = false; QuickView::TransferFunction transfer = QuickView::TransferFunction::Unknown; QuickView::ColorPrimaries primaries = QuickView::ColorPrimaries::Unknown; // Default RGBA @@ -5817,7 +5817,7 @@ namespace QuickView { if (JXL_DEC_SUCCESS == JxlDecoderGetColorAsEncodedProfile(dec, JXL_COLOR_PROFILE_TARGET_ORIGINAL, &encodedColor)) { transfer = MapJxlTransferFunction(encodedColor.transfer_function); primaries = MapJxlPrimaries(encodedColor.primaries); - useFloatOutput = + bool useHighBitDepthOutput = !ctx.forcePreview && !PrefersSdrTarget(ctx) && (transfer == QuickView::TransferFunction::Linear || transfer == QuickView::TransferFunction::PQ || @@ -5830,7 +5830,7 @@ namespace QuickView { ctx.pMetadata->colorInfo.nominalBitDepth = static_cast((std::min)(info.bits_per_sample, 255u)); ctx.pMetadata->colorInfo.dataSpace = - useFloatOutput + useHighBitDepthOutput ? QuickView::PixelDataSpace::SceneLinear : ((transfer == QuickView::TransferFunction::PQ || transfer == QuickView::TransferFunction::HLG) @@ -5931,8 +5931,8 @@ namespace QuickView { // else: not forcePreview, ignore DC event and continue to full decode } else if (status == JXL_DEC_NEED_IMAGE_OUT_BUFFER) { - if (useFloatOutput) { - format.data_type = JXL_TYPE_FLOAT; + if (useHighBitDepthOutput) { + format.data_type = JXL_TYPE_UINT16; } size_t bufferSize = 0; @@ -6028,22 +6028,15 @@ namespace QuickView { result.pixels = pixels; result.width = finalW; result.height = finalH; - if (useFloatOutput) { - float* pf = reinterpret_cast(pixels); - const size_t pixelCount = static_cast(finalW) * finalH; - for (size_t i = 0; i < pixelCount; ++i) { - pf[i * 4 + 0] = DecodeTransferToLinear(pf[i * 4 + 0], transfer); - pf[i * 4 + 1] = DecodeTransferToLinear(pf[i * 4 + 1], transfer); - pf[i * 4 + 2] = DecodeTransferToLinear(pf[i * 4 + 2], transfer); - } - result.stride = finalW * 16; - result.format = PixelFormat::R32G32B32A32_FLOAT; - result.metadata.colorInfo.dataSpace = QuickView::PixelDataSpace::SceneLinear; - result.metadata.colorInfo.transfer = QuickView::TransferFunction::Linear; + if (useHighBitDepthOutput) { + result.stride = finalW * 8; + result.format = PixelFormat::R16G16B16A16_UNORM; + result.metadata.colorInfo.dataSpace = QuickView::PixelDataSpace::EncodedHdr; + result.metadata.colorInfo.transfer = transfer; result.metadata.colorInfo.primaries = primaries; result.metadata.colorInfo.nominalBitDepth = static_cast((std::min)(info.bits_per_sample, 255u)); - result.metadata.hdrMetadata.isSceneLinear = true; + result.metadata.hdrMetadata.isSceneLinear = false; } else { // [v8.6] Fix: JXL outputs RGBA Straight, D2D needs BGRA Premultiplied. ImageLoaderSimd::SwizzleRGBAToBGRA(pixels, (size_t)finalW * finalH); @@ -6059,7 +6052,7 @@ namespace QuickView { result.metadata.hdrMetadata.isValid = true; result.metadata.hdrMetadata.transfer = transfer; result.metadata.hdrMetadata.primaries = primaries; - result.metadata.hdrMetadata.isHdr = useFloatOutput; + result.metadata.hdrMetadata.isHdr = useHighBitDepthOutput; // [v6.2] Parse Captured EXIF if (!jxlExifBuffer.empty() && result.metadata.IsEmpty()) { @@ -6154,14 +6147,14 @@ namespace QuickView { const QuickView::TransferFunction transfer = MapAvifTransferFunction(decoder->image->transferCharacteristics); const QuickView::ColorPrimaries primaries = MapAvifPrimaries(decoder->image->colorPrimaries); const bool preferSdrTarget = PrefersSdrTarget(ctx); - const bool useFloatOutput = !preferSdrTarget && + const bool useHighBitDepthOutput = !preferSdrTarget && (decoder->image->gainMap != nullptr || transfer == QuickView::TransferFunction::Linear || transfer == QuickView::TransferFunction::PQ || transfer == QuickView::TransferFunction::HLG || decoder->image->depth > 8); - if (useFloatOutput) { + if (useHighBitDepthOutput) { if (decoder->image->gainMap) { uint8_t* gainMappedPixels = nullptr; int gainMappedWidth = 0; @@ -6224,7 +6217,7 @@ namespace QuickView { const int width = rgb.width; const int height = rgb.height; - const int stride = CalculateSIMDAlignedStride(width, 16); + const int stride = CalculateSIMDAlignedStride(width, 8); uint8_t* pixels = ctx.allocator(static_cast(stride) * height); if (!pixels) { avifRGBImageFreePixels(&rgb); @@ -6233,17 +6226,13 @@ namespace QuickView { } for (int y = 0; y < height; ++y) { - float* dst = reinterpret_cast(pixels + static_cast(y) * stride); + uint16_t* dst = reinterpret_cast(pixels + static_cast(y) * stride); const uint16_t* src = reinterpret_cast(rgb.pixels + static_cast(y) * rgb.rowBytes); for (int x = 0; x < width; ++x) { - const float r = src[x * 4 + 0] / 65535.0f; - const float g = src[x * 4 + 1] / 65535.0f; - const float b = src[x * 4 + 2] / 65535.0f; - const float a = src[x * 4 + 3] / 65535.0f; - dst[x * 4 + 0] = DecodeTransferToLinear(r, transfer); - dst[x * 4 + 1] = DecodeTransferToLinear(g, transfer); - dst[x * 4 + 2] = DecodeTransferToLinear(b, transfer); - dst[x * 4 + 3] = a; + dst[x * 4 + 0] = src[x * 4 + 0]; + dst[x * 4 + 1] = src[x * 4 + 1]; + dst[x * 4 + 2] = src[x * 4 + 2]; + dst[x * 4 + 3] = src[x * 4 + 3]; } } @@ -6251,17 +6240,17 @@ namespace QuickView { result.width = width; result.height = height; result.stride = stride; - result.format = PixelFormat::R32G32B32A32_FLOAT; + result.format = PixelFormat::R16G16B16A16_UNORM; result.success = true; result.metadata.LoaderName = L"libavif (Unified HDR)"; result.metadata.Width = origW; result.metadata.Height = origH; - result.metadata.colorInfo.dataSpace = QuickView::PixelDataSpace::SceneLinear; - result.metadata.colorInfo.transfer = QuickView::TransferFunction::Linear; + result.metadata.colorInfo.dataSpace = QuickView::PixelDataSpace::EncodedHdr; + result.metadata.colorInfo.transfer = transfer; result.metadata.colorInfo.primaries = primaries; result.metadata.colorInfo.nominalBitDepth = 16; PopulateAvifHdrStaticMetadata(decoder->image, &result.metadata.hdrMetadata); - result.metadata.hdrMetadata.isSceneLinear = true; + result.metadata.hdrMetadata.isSceneLinear = false; result.metadata.hdrMetadata.isHdr = true; if (decoder->image->icc.data && decoder->image->icc.size > 0) { result.metadata.iccProfileData.assign( @@ -7182,15 +7171,15 @@ namespace QuickView { WICPixelFormatGUID targetFmt = GUID_WICPixelFormat32bppPBGRA; if (isHighBitDepth && !PrefersSdrTarget(ctx)) { - targetFmt = GUID_WICPixelFormat128bppRGBAFloat; - result.format = PixelFormat::R32G32B32A32_FLOAT; + targetFmt = GUID_WICPixelFormat64bppRGBA; + result.format = PixelFormat::R16G16B16A16_UNORM; } else { result.format = PixelFormat::BGRA8888; } if (FAILED(converter->Initialize(decodeSource.Get(), targetFmt, WICBitmapDitherTypeNone, nullptr, 0.f, WICBitmapPaletteTypeMedianCut))) return E_FAIL; - int bpp = (isHighBitDepth && !PrefersSdrTarget(ctx)) ? 16 : 4; + int bpp = (isHighBitDepth && !PrefersSdrTarget(ctx)) ? 8 : 4; int stride = CalculateSIMDAlignedStride(decodeW, bpp); size_t bufSize = (size_t)stride * decodeH; uint8_t* pixels = ctx.allocator(bufSize); @@ -7574,8 +7563,8 @@ HRESULT CImageLoader::LoadToMemoryPMR(LPCWSTR filePath, DecodedImage* pOutput, s WICPixelFormatGUID srcWicFmt; wicBitmap->GetPixelFormat(&srcWicFmt); - bool isFloat = IsEqualGUID(srcWicFmt, GUID_WICPixelFormat128bppRGBAFloat); - int bpp = isFloat ? 16 : 4; + bool isHighBitDepth = IsEqualGUID(srcWicFmt, GUID_WICPixelFormat64bppRGBA); + int bpp = isHighBitDepth ? 8 : 4; UINT stride = w * bpp; size_t bufSize = (size_t)stride * h; @@ -11707,8 +11696,9 @@ HRESULT CImageLoader::LoadToFrame(LPCWSTR filePath, QuickView::RawImageFrame* ou WICPixelFormatGUID outWicFormat; wicBitmap->GetPixelFormat(&outWicFormat); bool isFloat = IsEqualGUID(outWicFormat, GUID_WICPixelFormat128bppRGBAFloat); - int bpp = isFloat ? 16 : 4; - QuickView::PixelFormat outPixelFormat = isFloat ? QuickView::PixelFormat::R32G32B32A32_FLOAT : QuickView::PixelFormat::BGRA8888; + bool isHighBitDepth = IsEqualGUID(outWicFormat, GUID_WICPixelFormat64bppRGBA) || IsEqualGUID(outWicFormat, GUID_WICPixelFormat64bppPRGBA); + int bpp = isFloat ? 16 : (isHighBitDepth ? 8 : 4); + QuickView::PixelFormat outPixelFormat = isFloat ? QuickView::PixelFormat::R32G32B32A32_FLOAT : (isHighBitDepth ? QuickView::PixelFormat::R16G16B16A16_UNORM : QuickView::PixelFormat::BGRA8888); // Allocate output buffer with aligned stride int outStride = CalculateSIMDAlignedStride(finalW, bpp); From 6c38bd8cd156b12dc40ca4c7abcf4ec7c409587e Mon Sep 17 00:00:00 2001 From: justnullname <51329027+justnullname@users.noreply.github.com> Date: Sun, 10 May 2026 01:03:38 +0000 Subject: [PATCH 2/2] Refactor HDR image loading and rendering pipeline to use 16-bit integers and GPU matrix acceleration - Modified WIC decode target from 128-bit float to 64-bit Straight RGBA (`GUID_WICPixelFormat64bppRGBA`). - Added `PixelFormat::R16G16B16A16_UNORM` to replace floating point representations internally. - Removed `BuildLinearScRgbFloatBuffer` to prevent CPU-side color matrix processing. - Adjusted D3D11 texture upload configurations to map `R16G16B16A16_UNORM` directly to DirectCompute/Direct2D representations. - Implemented HLG EOTF and PQ EOTF inverses in `ComputeEngine.cpp` shaders (`ToneMapHdrToHdr` and `ToneMapHdrToSdr`). - Passed color matrices through constant buffer parameters to offload Rec.2020/Display P3 conversions entirely to the GPU compute units. --- QuickView/ComputeEngine.cpp | 77 ++++++++++++++++++++++++++++++++----- QuickView/ComputeEngine.h | 8 ++-- QuickView/ImageTypes.h | 1 + QuickView/RenderEngine.cpp | 35 ++++++++++++++--- 4 files changed, 103 insertions(+), 18 deletions(-) diff --git a/QuickView/ComputeEngine.cpp b/QuickView/ComputeEngine.cpp index da33adf..0c7dc61 100644 --- a/QuickView/ComputeEngine.cpp +++ b/QuickView/ComputeEngine.cpp @@ -67,9 +67,24 @@ cbuffer ToneMapParams : register(b0) float SplineQc; uint IsHdrOutput; float RealHardwarePeakScRgb; - float Padding1; + uint TransferFunction; + + float4x4 ColorMatrix; }; +// HLG EOTF inverse (HLG to Linear) +float3 HLGToLinear(float3 v) +{ + float3 e; + e.r = v.r <= 0.5 ? (v.r * v.r / 3.0) : (exp((v.r - 0.17883277) / 0.28466892) + 0.02372241); + e.g = v.g <= 0.5 ? (v.g * v.g / 3.0) : (exp((v.g - 0.17883277) / 0.28466892) + 0.02372241); + e.b = v.b <= 0.5 ? (v.b * v.b / 3.0) : (exp((v.b - 0.17883277) / 0.28466892) + 0.02372241); + float L_S = 0.2627 * e.r + 0.6780 * e.g + 0.0593 * e.b; + float gamma = 1.2; + float3 L_D = e * pow(max(L_S, 0.0), gamma - 1.0); + return L_D * 12.5; +} + float LinearToPQ(float L) { float L_norm = max(0.0, L) / 125.0; @@ -132,6 +147,21 @@ void CSToneMap(uint3 id : SV_DispatchThreadID) color.rgb = max(color.rgb, float3(0.0f, 0.0f, 0.0f)); color.a = saturate(color.a); + // EOTF Inverse + if (TransferFunction == 3) { // PQ + color.r = PQToLinear(color.r); + color.g = PQToLinear(color.g); + color.b = PQToLinear(color.b); + } else if (TransferFunction == 4) { // HLG + color.rgb = HLGToLinear(color.rgb); + } else if (TransferFunction == 1) { // SRGB + // Approximate sRGB to linear + color.rgb = pow(color.rgb, float3(2.2f, 2.2f, 2.2f)); + } + + // Color Matrix (e.g. Rec.2020 to ScRGB) + color.rgb = mul((float3x3)ColorMatrix, color.rgb); + float paperWhite = max(PaperWhiteScRgb, 1.0); float displayPeak = max(DisplayPeakScRgb, paperWhite); @@ -199,9 +229,24 @@ cbuffer ToneMapParams : register(b0) float SplineQc; uint IsHdrOutput; float RealHardwarePeakScRgb; - float Padding1; + uint TransferFunction; + + float4x4 ColorMatrix; }; +// HLG EOTF inverse (HLG to Linear) +float3 HLGToLinear(float3 v) +{ + float3 e; + e.r = v.r <= 0.5 ? (v.r * v.r / 3.0) : (exp((v.r - 0.17883277) / 0.28466892) + 0.02372241); + e.g = v.g <= 0.5 ? (v.g * v.g / 3.0) : (exp((v.g - 0.17883277) / 0.28466892) + 0.02372241); + e.b = v.b <= 0.5 ? (v.b * v.b / 3.0) : (exp((v.b - 0.17883277) / 0.28466892) + 0.02372241); + float L_S = 0.2627 * e.r + 0.6780 * e.g + 0.0593 * e.b; + float gamma = 1.2; + float3 L_D = e * pow(max(L_S, 0.0), gamma - 1.0); + return L_D * 12.5; +} + float LinearToPQ(float L) { float L_norm = max(0.0, L) / 125.0; @@ -282,10 +327,24 @@ void CSToneMapHDR(uint3 id : SV_DispatchThreadID) color.rgb = max(color.rgb, float3(0.0f, 0.0f, 0.0f)); color.a = saturate(color.a); + // EOTF Inverse + if (TransferFunction == 3) { // PQ + color.r = PQToLinear(color.r); + color.g = PQToLinear(color.g); + color.b = PQToLinear(color.b); + } else if (TransferFunction == 4) { // HLG + color.rgb = HLGToLinear(color.rgb); + } else if (TransferFunction == 1) { // SRGB + color.rgb = pow(color.rgb, float3(2.2f, 2.2f, 2.2f)); + } + + // Color Matrix + color.rgb = mul((float3x3)ColorMatrix, color.rgb); + float contentPeak = max(ContentPeakScRgb, 1.0f); float displayPeak = max(DisplayPeakScRgb, 1.0f); - if (contentPeak <= displayPeak && Exposure >= 0.999f && Exposure <= 1.001f && ExposureGain >= 0.999f && ExposureGain <= 1.001f) { + if (contentPeak <= displayPeak && Exposure >= 0.999f && Exposure <= 1.001f && ExposureGain >= 0.999f && ExposureGain <= 1.001f && TransferFunction == 2) { DstTex[id.xy] = color; return; } @@ -528,14 +587,14 @@ HRESULT ComputeEngine::UploadAndConvert(const uint8_t* srcPixels, int width, int srcDesc.Height = height; srcDesc.MipLevels = 1; srcDesc.ArraySize = 1; - srcDesc.Format = (srcFormat == PixelFormat::R32G32B32A32_FLOAT) ? DXGI_FORMAT_R32G32B32A32_FLOAT : DXGI_FORMAT_R8G8B8A8_UNORM; + srcDesc.Format = (srcFormat == PixelFormat::R32G32B32A32_FLOAT) ? DXGI_FORMAT_R32G32B32A32_FLOAT : ((srcFormat == PixelFormat::R16G16B16A16_UNORM) ? DXGI_FORMAT_R16G16B16A16_UNORM : DXGI_FORMAT_R8G8B8A8_UNORM); srcDesc.SampleDesc.Count = 1; srcDesc.Usage = D3D11_USAGE_IMMUTABLE; srcDesc.BindFlags = D3D11_BIND_SHADER_RESOURCE; D3D11_SUBRESOURCE_DATA initData = {}; initData.pSysMem = srcPixels; - initData.SysMemPitch = width * (srcFormat == PixelFormat::R32G32B32A32_FLOAT ? 16 : 4); + initData.SysMemPitch = width * ((srcFormat == PixelFormat::R32G32B32A32_FLOAT) ? 16 : ((srcFormat == PixelFormat::R16G16B16A16_UNORM) ? 8 : 4)); ComPtr pSrc; HRESULT hr = m_d3dDevice->CreateTexture2D(&srcDesc, &initData, &pSrc); @@ -800,7 +859,7 @@ HRESULT ComputeEngine::DispatchGamutMaskLut( return ReadbackMaskTexture(maskTexture.Get(), outReadback); } -HRESULT ComputeEngine::ToneMapHdrToSdr(const uint8_t* srcPixels, int width, int height, int stride, const ToneMapSettings& settings, ID3D11Texture2D** outTexture) { +HRESULT ComputeEngine::ToneMapHdrToSdr(const uint8_t* srcPixels, int width, int height, int stride, const ToneMapSettings& settings, ID3D11Texture2D** outTexture, PixelFormat srcFormat) { if (!m_valid || !srcPixels || width <= 0 || height <= 0 || !outTexture) return E_INVALIDARG; D3D11_TEXTURE2D_DESC srcDesc = {}; @@ -808,7 +867,7 @@ HRESULT ComputeEngine::ToneMapHdrToSdr(const uint8_t* srcPixels, int width, int srcDesc.Height = static_cast(height); srcDesc.MipLevels = 1; srcDesc.ArraySize = 1; - srcDesc.Format = DXGI_FORMAT_R32G32B32A32_FLOAT; + srcDesc.Format = srcFormat == PixelFormat::R16G16B16A16_UNORM ? DXGI_FORMAT_R16G16B16A16_UNORM : DXGI_FORMAT_R32G32B32A32_FLOAT; srcDesc.SampleDesc.Count = 1; srcDesc.Usage = D3D11_USAGE_IMMUTABLE; srcDesc.BindFlags = D3D11_BIND_SHADER_RESOURCE; @@ -872,7 +931,7 @@ HRESULT ComputeEngine::ToneMapHdrToSdr(const uint8_t* srcPixels, int width, int } -HRESULT ComputeEngine::ToneMapHdrToHdr(const uint8_t* srcPixels, int width, int height, int stride, const ToneMapSettings& settings, ID3D11Texture2D** outTexture) { +HRESULT ComputeEngine::ToneMapHdrToHdr(const uint8_t* srcPixels, int width, int height, int stride, const ToneMapSettings& settings, ID3D11Texture2D** outTexture, PixelFormat srcFormat) { if (!m_valid || !srcPixels || width <= 0 || height <= 0 || !outTexture) return E_INVALIDARG; D3D11_TEXTURE2D_DESC srcDesc = {}; @@ -880,7 +939,7 @@ HRESULT ComputeEngine::ToneMapHdrToHdr(const uint8_t* srcPixels, int width, int srcDesc.Height = static_cast(height); srcDesc.MipLevels = 1; srcDesc.ArraySize = 1; - srcDesc.Format = DXGI_FORMAT_R32G32B32A32_FLOAT; + srcDesc.Format = srcFormat == PixelFormat::R16G16B16A16_UNORM ? DXGI_FORMAT_R16G16B16A16_UNORM : DXGI_FORMAT_R32G32B32A32_FLOAT; srcDesc.SampleDesc.Count = 1; srcDesc.Usage = D3D11_USAGE_IMMUTABLE; srcDesc.BindFlags = D3D11_BIND_SHADER_RESOURCE; diff --git a/QuickView/ComputeEngine.h b/QuickView/ComputeEngine.h index a079576..fcc0675 100644 --- a/QuickView/ComputeEngine.h +++ b/QuickView/ComputeEngine.h @@ -29,7 +29,9 @@ struct alignas(16) ToneMapSettings { float splineQc; uint32_t isHdrOutput; // 1: HDR/Sim, 0: SDR float realHardwarePeakScRgb; // Actual display peak in ScRGB - float padding1; // 16-byte alignment + uint32_t transferFunction; // Enum QuickView::TransferFunction + + float colorMatrix[12]; // 3x4 layout for structured float3x3 mapping (requires 48 bytes) }; static_assert(sizeof(ToneMapSettings) % 16 == 0, "CB size must be multiple of 16 bytes"); @@ -94,11 +96,11 @@ class ComputeEngine { /// HRESULT ToneMapHdrToHdr(const uint8_t* srcPixels, int width, int height, int stride, const ToneMapSettings& settings, - ID3D11Texture2D** outTexture); + ID3D11Texture2D** outTexture, PixelFormat srcFormat = PixelFormat::R32G32B32A32_FLOAT); HRESULT ToneMapHdrToSdr(const uint8_t* srcPixels, int width, int height, int stride, const ToneMapSettings& settings, - ID3D11Texture2D** outTexture); + ID3D11Texture2D** outTexture, PixelFormat srcFormat = PixelFormat::R32G32B32A32_FLOAT); /// /// GPU-side Gain Map composition (ISO 21496-1). diff --git a/QuickView/ImageTypes.h b/QuickView/ImageTypes.h index eadfb44..1f1abfd 100644 --- a/QuickView/ImageTypes.h +++ b/QuickView/ImageTypes.h @@ -49,6 +49,7 @@ enum class PixelFormat : uint8_t { BGRX8888, // D2D Native (Opaque Alpha) RGBA8888, // Some decoders (stb) - Compatible R32G32B32A32_FLOAT, // HDR (TinyEXR) - 128-bit floating point + R16G16B16A16_UNORM, // HDR (HEIC/AVIF) - 64-bit unsigned normalized SVG_XML // [D2D Native] Raw SVG XML Data }; diff --git a/QuickView/RenderEngine.cpp b/QuickView/RenderEngine.cpp index 97a45b7..fda49f4 100644 --- a/QuickView/RenderEngine.cpp +++ b/QuickView/RenderEngine.cpp @@ -985,7 +985,26 @@ BuildToneMapSettings(const QuickView::RawImageFrame &frame, // Knife 1: Populate aligned fields settings.isHdrOutput = g_runtime.ForceHdrSimulation ? 1 : (displayState.advancedColorActive ? 1 : 0); settings.realHardwarePeakScRgb = displayState.maxLuminanceNits / 80.0f; - settings.padding1 = 0.0f; + + if (frame.format == QuickView::PixelFormat::R16G16B16A16_UNORM) { + settings.transferFunction = static_cast(frame.colorInfo.transfer); + + QuickView::ColorPrimaries primaries = frame.colorInfo.primaries != QuickView::ColorPrimaries::Unknown + ? frame.colorInfo.primaries + : frame.hdrMetadata.primaries; + ColorMatrix3x3 matrix = {}; + if (!TryGetLinearPrimariesToScRgbMatrix(primaries, &matrix)) { + matrix = MakeIdentityMatrix(); + } + settings.colorMatrix[0] = matrix.m[0][0]; settings.colorMatrix[1] = matrix.m[0][1]; settings.colorMatrix[2] = matrix.m[0][2]; settings.colorMatrix[3] = 0.0f; + settings.colorMatrix[4] = matrix.m[1][0]; settings.colorMatrix[5] = matrix.m[1][1]; settings.colorMatrix[6] = matrix.m[1][2]; settings.colorMatrix[7] = 0.0f; + settings.colorMatrix[8] = matrix.m[2][0]; settings.colorMatrix[9] = matrix.m[2][1]; settings.colorMatrix[10] = matrix.m[2][2]; settings.colorMatrix[11] = 0.0f; + } else { + settings.transferFunction = static_cast(QuickView::TransferFunction::Linear); + settings.colorMatrix[0] = 1.0f; settings.colorMatrix[1] = 0.0f; settings.colorMatrix[2] = 0.0f; settings.colorMatrix[3] = 0.0f; + settings.colorMatrix[4] = 0.0f; settings.colorMatrix[5] = 1.0f; settings.colorMatrix[6] = 0.0f; settings.colorMatrix[7] = 0.0f; + settings.colorMatrix[8] = 0.0f; settings.colorMatrix[9] = 0.0f; settings.colorMatrix[10] = 1.0f; settings.colorMatrix[11] = 0.0f; + } return settings; } @@ -1981,6 +2000,11 @@ CRenderEngine::UploadRawFrameToGPU(const QuickView::RawImageFrame &frame, alphaMode = D2D1_ALPHA_MODE_PREMULTIPLIED; break; + case QuickView::PixelFormat::R16G16B16A16_UNORM: + dxgiFormat = DXGI_FORMAT_R16G16B16A16_UNORM; + alphaMode = D2D1_ALPHA_MODE_PREMULTIPLIED; + break; + default: dxgiFormat = DXGI_FORMAT_B8G8R8A8_UNORM; break; @@ -2011,8 +2035,7 @@ CRenderEngine::UploadRawFrameToGPU(const QuickView::RawImageFrame &frame, TraceLoggingInt32((int)m_displayColorState.colorSpace, "DisplayColorSpace"), TraceLoggingFloat32(g_config.HdrPeakNitsOverride, "HdrPeakNitsOverride")); - if (frame.format == QuickView::PixelFormat::R32G32B32A32_FLOAT) { - BuildLinearScRgbFloatBuffer(frame, linearScRgbPixels, &uploadPixels, &uploadStride); + if (frame.format == QuickView::PixelFormat::R32G32B32A32_FLOAT || frame.format == QuickView::PixelFormat::R16G16B16A16_UNORM) { ComPtr scRgbContext; CreateScRgbColorContext(m_d2dContext.Get(), &scRgbContext); @@ -2044,7 +2067,7 @@ CRenderEngine::UploadRawFrameToGPU(const QuickView::RawImageFrame &frame, HRESULT hrToneMap = m_computeEngine->ToneMapHdrToHdr( uploadPixels, static_cast(frame.width), static_cast(frame.height), static_cast(uploadStride), - toneMapSettings, &pTex); + toneMapSettings, &pTex, frame.format); if (SUCCEEDED(hrToneMap)) { ComPtr dxgiSurface; if (SUCCEEDED(pTex.As(&dxgiSurface))) { @@ -2073,7 +2096,7 @@ CRenderEngine::UploadRawFrameToGPU(const QuickView::RawImageFrame &frame, TraceLoggingFloat32(toneMapSettings.contentPeakScRgb, "ContentPeak"), TraceLoggingBool(m_computeEngine && m_computeEngine->IsAvailable(), "ComputeAvailable")); D2D1_BITMAP_PROPERTIES1 hdrProps = GetDefaultBitmapProps( - DXGI_FORMAT_R32G32B32A32_FLOAT, D2D1_ALPHA_MODE_PREMULTIPLIED); + frame.format == QuickView::PixelFormat::R16G16B16A16_UNORM ? DXGI_FORMAT_R16G16B16A16_UNORM : DXGI_FORMAT_R32G32B32A32_FLOAT, D2D1_ALPHA_MODE_PREMULTIPLIED); hdrProps.colorContext = scRgbContext.Get(); m_d2dContext->CreateBitmap( D2D1::SizeU(static_cast(frame.width), @@ -2096,7 +2119,7 @@ CRenderEngine::UploadRawFrameToGPU(const QuickView::RawImageFrame &frame, if (SUCCEEDED(m_computeEngine->ToneMapHdrToSdr( uploadPixels, static_cast(frame.width), static_cast(frame.height), static_cast(uploadStride), - toneMapSettings, &pTex))) { + toneMapSettings, &pTex, frame.format))) { ComPtr dxgiSurface; if (SUCCEEDED(pTex.As(&dxgiSurface))) { D2D1_BITMAP_PROPERTIES1 sdrProps = GetDefaultBitmapProps(