Skip to content

Add HDR10 GPU rendering on Windows11, HiDPI zoom-box fix, distortion playback, and 16-bit PNG suppor#671

Open
supernova-jpg wants to merge 1 commit into
IENT:developfrom
supernova-jpg:feature/hdr-10bit-display
Open

Add HDR10 GPU rendering on Windows11, HiDPI zoom-box fix, distortion playback, and 16-bit PNG suppor#671
supernova-jpg wants to merge 1 commit into
IENT:developfrom
supernova-jpg:feature/hdr-10bit-display

Conversation

@supernova-jpg

Copy link
Copy Markdown

Summary

This PR extends YUView with a GPU-accelerated HDR10 viewing pipeline for 10-bit YUV sources, built on Qt 6 RHI (D3D12 on Windows). It also includes several complementary improvements developed alongside the HDR work:

  • HDR transfer functions: PQ (HDR10), HLG, and Linear (scRGB) for 10-bit analytical viewing — including a 10-bit SDR / pass-through path without PQ/HLG tone mapping
  • Raw YUV frame caching to bypass CPU YUV→RGB during HDR playback
  • HiDPI-aware zoom compensation so canvas layout and zoom box magnification stay consistent across Windows display scaling (100% / 125% / 150% / 200%)
  • First-level / second-level distortion playback controls for subjective quality assessment
  • 16-bit PNG/BMP loading with full sample precision preserved
  • YUV pipeline refactor (YUVDataManager, YUVColorConverter, etc.) as the foundation for GPU upload and per-pixel tools

Motivation

YUView is widely used for codec development and subjective quality analysis of 10-bit BT.2020 content. The existing CPU QPainter path quantizes to 8-bit SDR and cannot drive a native HDR10 display. This PR adds a parallel HDR overlay window that:

  1. Keeps the existing SDR UI and tools intact
  2. Uploads raw 10-bit YUV directly to the GPU
  3. Applies PQ / HLG / Linear transfer functions in fragment shaders
  4. Preserves overlay tools (grid, zoom box, split view, pixel values) in the HDR path

HDR Rendering

Supported render modes (QSettings: HDRMode)

Mode UI label Transfer function Exposure controls Use case
0 PQ (HDR10) SMPTE ST.2084 PQ EOTF 350–10,000 nits (log slider) Standard HDR10 BT.2020 content
1 HLG ARIB STD-B67 (shader OETF⁻¹ + OOTF) 350–1,000 nits Broadcast HLG content
2 Linear (scRGB) None — linear light pass-through Disabled 10-bit SDR / analytical viewing: inspect linear BT.2020 without tone mapping; 1.0 scRGB = 80 nits reference white

All modes share a unified RGBA16F (scRGB linear light) internal rendering path and an HDR10 swap chain (R10G10B10A2 + PQ color space on Windows).

Shader pipeline (hdr_rhi_yuv_fragment.frag)

YUV (10-bit planar / P010) → BT.2020 YCbCr→R'G'B' matrix
  → [PQ EOTF | HLG decode | Linear skip]
  → exposure scaling (PQ/HLG only): display_luminance *= userNits / peakNits
  → BT.2020 → sRGB gamut + scRGB nits scaling
  → RGBA16F output → HDR10 presentation

Supported YUV input formats (HDRTextureUploader):

  • YUV420p10le, YUV422p10le, YUV444p10le (planar)
  • P010le (semi-planar; MSB-aligned samples normalized to internal 10-bit layout)

Performance optimizations

  • Raw YUV cache (videoHandler::CacheMode::RawYUV): when Enable10BitDisplay is on and source is 10-bit planar, frames are cached as raw YUV (~25% smaller than RGBA for 4:2:0) instead of CPU-converted QImage
  • Zero-copy GPU upload: updateHDRFrameYUVMove() moves frame buffers into the upload path, avoiding ~50 MB memcpy per 4K frame (~50 ms saved)
  • Lazy settings cache: atomic m_enable10BitDisplayCached avoids per-frame QSettings disk I/O

UI controls (videoHandlerYUV.ui)

Control Setting key Behavior
Enable HDR display Enable10BitDisplay Master toggle; requires app restart (16-bit QSurfaceFormat set at startup in yuviewapp.cpp)
HDR mode combo HDRMode PQ / HLG / Linear; restart required
Tone mapping slider HDRExposureNits Log-spaced exposure target in nits; live update when HDR is active
Restart now Relaunches app to apply HDR surface format changes

HDR activates only when all of the following are true:

  • User enabled Enable10BitDisplay (and restarted)
  • Source is 10-bit YUV (isHDR10Candidate())
  • Windows DXGI reports HDR-capable display

HDR detection & fallback

  • HDRDetectionWorker runs DXGI enumeration off the GUI thread
  • Multi-monitor aware: matches display by name; debounced re-check on window move/resize
  • On HDR loss (e.g. dragging to SDR monitor): emits signalDisplayHDRSupportChangedMainWindow auto-disables HDR
  • On detection failure: clears setting, shows error, auto-restarts in SDR mode after 2 s

Architecture

New module under YUViewLib/src/video/hdr/:

Component Responsibility
HDRRenderingManager Singleton orchestrator: detection → window creation → SplitViewWidget attach → frame forwarding
HDR_RhiVideoWindow QWindow + QRhi render surface; overlay state; tryInitialize() for delayed expose handling
HDR_RhiVideoWindow_Overlays.cpp Grid, zoom box, split line, loading spinner, pixel values — rendered in HDR color space
HDRRhiPipeline RHI backend (D3D12 preferred on Windows), swap chain, shader pipelines, texture upload
HDRColorConversion BT.601/709/2020 limited/full range matrices
HDRTextureUploader YUV buffer parsing and GPU upload configuration
HDRDetection / HDRDetectionWorker Windows DXGI HDR capability probe

Integration with SplitViewWidget

  • HDR window embedded via QWidget::createWindowContainer() as a full-widget overlay
  • Overlay is mouse-transparent; wheel events forwarded to parent for zoom
  • paintEvent: when HDR active, pushes overlay state to HDR_RhiVideoWindow and skips SDR base draw to prevent double rendering
  • Chicken-and-egg init fix: calls updateHDRVisibilityForSelection() immediately after embedding (not only on widgetInitialized)
  • Scheduled tryInitialize() at 50 / 150 / 300 ms as fallback for delayed expose events

HiDPI Zoom Box & Canvas Consistency

Problem

On Windows with 125%/150%/200% display scaling, Qt reports devicePixelRatio() > 1. Without compensation, the same logical zoom factor maps to different pixel densities, causing:

  • Mismatched canvas size vs. video pixels
  • Zoom box magnification drifting relative to the underlying frame
  • HDR overlay transform diverging from SDR painter

Solution (splitViewWidget)

Introduces dual zoom semantics:

Variable Formula Used for
zoomFactor (raw) User zoom steps UI label, zoom-to-fit target, zoom-box hide threshold
effectiveZoomFactor() zoomFactor / devicePixelRatio Item drawing, HDR projection matrix (m_parentZoom)

devicePixelRatioForCurrentScreen() resolves DPR via cascade:
windowHandle()screen() → primary screen → logicalDpiX() / 96

Applied in:

  • paintEvent, setMoveOffset, zoomToFitInternal (physical widget size = logical × DPR)
  • HDR_RhiVideoWindow::updateTransform(renderZoom, offset, displayZoom) — render uses DPI-compensated zoom; overlay label shows raw zoom

Zoom box behavior (SDR + HDR)

  • Samples a 5×5 source region, magnified 32× into a 160×160 px preview window
  • Hidden when effectiveZoom >= 32 (already at pixel level)
  • HDR optimization: lazy m_overlayPatchProvider calls videoHandlerYUV::getCurrentFramePatchAsImage(region) — converts only the 5×5 patch via YUVPixelRenderer, not the full frame
  • Y/U/V readout: m_overlayYuvPixelProvidergetYuvPixelValueAt() for the info panel
  • HDR overlay grid lines use 1/DPR snapping for crisp 1-px lines on HiDPI displays

Distortion Playback (First-level / Second-level)

Extracted into DistortionPlaybackController (YUViewLib/src/video/yuv/).

Button Playback rate Purpose
First-level 30 FPS Fast subjective sweep — catch brief artifacts, blocking, motion issues
Second-level 1.5 FPS Slow frame-by-frame inspection — fine structural distortion

Workflow:

  1. Click level button → green active style, view forced to 1× zoom on all splitViewWidget instances
  2. Captures current frame as revert point; enables Repeat One loop mode
  3. Second click → stop, pause, reset repeat mode
  4. Revert buttons seek back to captured frame
  5. Stall detection: auto-stops after 3 timer ticks with no frame advance

UI located in videoHandlerYUV.ui (pushButtonFirstLevel, pushButtonSecondLevel, revertButton_L1/L2).

Note: ORI reference-file comparison (findOriFile(), toggleOriComparison()) is scaffolded in videoHandlerYUV_UI.cpp but loading is marked TODO.


16-bit PNG / BMP Support

Implemented in FrameHandler.cpp:

Function Description
load16BitPng() Custom PNG parser (IHDR + IDAT inflate/unfilter) → QImage::Format_RGBA64 or Format_Grayscale16
load16BitBmp() 48/64-bit uncompressed BMP → Format_RGBA64
loadImagePreservingHighBitDepth() Entry point: tries 16-bit loaders for .png/.bmp, falls back to QImageReader
getImageRgbSample() Returns native 16-bit samples for pixel-value overlay
configureHighBitDepthRendering() One-time OpenGL format check for QPainter high bit-depth path

User-visible effect: image sequences and single frames retain full 16-bit sample precision (not clamped to 8-bit). Pixel-value overlay shows extended range. HDR window accepts QImage::Format_RGBA16FPx4 for RGB upload.


YUV Pipeline Refactor

Monolithic videoHandlerYUV.cpp logic split into focused modules:

Module Role
YUVDataManager Raw buffer lifecycle, bytes/frame, shouldUseRawYUVCache() policy
YUVColorConverter YUV→RGB conversion API
YUVPixelRenderer Per-pixel readout + zoomed value drawing
YUVDifferenceCalculator Frame diff, MSE/PSNR/SSIM
YUVConversionCore/Planes/RGB.h Hot-path math decomposition
videoHandlerYUV_UI.cpp UI slot logic extracted from handler
DistortionPlaybackController Distortion playback timing

Enables both the HDR GPU path and efficient zoom-box / pixel-value tools without full-frame CPU conversion.


Other Notable Changes

  • Video cache thread safety: VideoCache / LoadingWorker guards against races when dragging files during 4K 10-bit buffer loading
  • Qt model fixes: PacketItemModel uses valid index ranges (avoids dataChanged() warnings)
  • PlaylistTreeWidget: viewport()->update() instead of invalid dataChanged() indices
  • Shader build: YUViewLib.pro adds qsb compile rule for .vert/.frag.qsb (requires Qt shadertools)
  • Build deps: gui-private (QRhi), Windows libs dxgi, user32, ole32

Platform Support

Platform HDR rendering HDR detection Fallback
Windows 10/11 Full (D3D12 + DXGI HDR10 swap chain) DXGI async probe Auto-restart to SDR on failure
Linux RHI backends present (Vulkan/OpenGL) Not implemented (isHDRSupported = false) Graceful SDR
macOS RHI Metal backend present Not implemented Graceful SDR

Requirements: Qt 6.4+ (6.6+ recommended for public QRhi API), shadertools for shader compilation.


Settings Reference

Key Type Default Description
Enable10BitDisplay bool false Master HDR / 10-bit startup toggle
HDRMode int 0 0=PQ, 1=HLG, 2=Linear
HDRExposureNits int 1000 Exposure target (live when HDR on)
ToneMapTargetNits int 1000 Legacy fallback for exposure

Test Plan

Build

  • Windows: Qt 6.6+ with shadertools; MSVC build succeeds; qsb shaders generated
  • Linux/macOS: project compiles with HDR code paths disabled at runtime

HDR core

  • Enable HDR Display, restart; open 10-bit YUV420 BT.2020 file
  • Verify PQ mode on HDR monitor — highlights beyond SDR white visible
  • Switch to HLG — correct gamma/brightness relative to PQ
  • Switch to Linear (scRGB) — no tone mapping; exposure slider disabled; linear pass-through
  • Adjust exposure slider — live brightness change in PQ/HLG
  • Non-HDR monitor → graceful fallback message + SDR restart

HiDPI / Zoom box

  • Test at Windows scaling 100%, 125%, 150%, 200%
  • At same logical zoom, video pixel mapping identical across scaling levels
  • Zoom box 32× magnification aligned with cursor pixel in both SDR and HDR
  • Zoom box Y/U/V readout matches pixel under cursor

Distortion playback

  • First-level: 30 FPS loop, revert works, second click stops
  • Second-level: ~1.5 FPS loop, revert works
  • View resets to 1× on activation

16-bit images

  • Load 16-bit PNG sequence — pixel values show >255 range
  • Load 16-bit BMP — no banding to 8-bit

Regression

  • 8-bit YUV/RGB playback unchanged in SDR mode
  • Seek, frame cache, split view, comparison view, statistics overlay
  • Drag new file while caching 4K 10-bit — no crash
  • Multi-monitor: move window SDR↔HDR — debounced detection, no black overlay

Known Limitations & Future Work

  • HDR detection is Windows-only; Linux/macOS need platform-specific probes
  • getHDRRenderedImage() returns empty QImage (stub)
  • No unit tests for HDR color conversion / texture upload yet
  • ORI reference-file distortion comparison not fully wired
  • App restart required for HDR enable/disable (by design — avoids runtime GL format switching)
  • Happy to split into smaller PRs (YUV refactor → HDR core → UI/integration) if maintainers prefer

PR Scope Note

This branch also includes the YUV pipeline refactor and minor Qt warning fixes that were developed as prerequisites for the HDR GPU upload path. Non-HDR debug-macro cleanups in decoder/parser files are included only where they were part of the same development branch; happy to isolate further on request.

Introduce GPU-accelerated HDR10 rendering for 10-bit YUV sources using
Qt RHI (D3D12), with PQ/HLG tone mapping, raw YUV caching, and overlay
integration in SplitViewWidget. Includes YUV pipeline refactor and shader
build rules. HDR capability detection uses DXGI on Windows.

Co-authored-by: Cursor <cursoragent@cursor.com>
@supernova-jpg supernova-jpg force-pushed the feature/hdr-10bit-display branch from d1d7591 to d11ee24 Compare July 8, 2026 08:13
@supernova-jpg

supernova-jpg commented Jul 9, 2026

Copy link
Copy Markdown
Author

Hi @ChristianFeldmann,
I understand this is a massive PR with a lot of changes. To avoid putting any potential risks or instability onto the develop branch, would it be better to create a dedicated feature branch (e.g., feature/hdr-rendering) on the main repo? We could merge these changes there first for thorough testing.
Also, if you prefer, I can split out the independent fixes (like the HiDPI zoom-box fix and 16-bit PNG support) into smaller, separate PRs targeting develop so they can be reviewed and merged much faster.
Let me know what you think!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant