The Dual-Path Rendering Architecture
HorangPlayer has a unique rendering architecture that adapts based on what you're doing. No filters? You get the fastest possible path. Adjusting brightness? The full pipeline kicks in seamlessly.
Two Backends, Two Render Paths
mpv Backend (Default)
The mpv backend renders through OpenGL using CAOpenGLLayer. mpv handles everything internally — YUV to RGB conversion, scaling, subtitle rendering — in a single GPU pass via GLSL shaders.
This is the same approach used by IINA, and it's incredibly efficient. mpv's renderer is highly optimized with:
- Automatic BT.601/BT.709/BT.2020 color space detection
- Hardware-accelerated decoding via VideoToolbox
- Native HDR tone mapping
- Built-in ASS subtitle rendering
AVFoundation Backend (Metal Path)
When using the AVFoundation backend (for PiP or Apple-specific features), we use a Metal-based dual-pass architecture:
Fast Path — When no filters are active and content is SDR:
- AVFoundation outputs NV12 (YCbCr) pixel buffers — the native format from VideoToolbox
CVMetalTextureCachecreates zero-copy Metal textures via IOSurface- A custom Metal fragment shader converts NV12 → RGB directly on the GPU
- The result goes straight to
CAMetalLayer
No CIContext, no CIImage, no intermediate copies. This is as fast as it gets.
Full Path — When filters are active or HDR content is detected:
CIImagewraps the pixel buffer with automatic color space detection- A
CIFilterchain applies brightness → contrast → saturation → sharpen → deband CIContextrenders directly to the Metal drawable- HDR content uses
rgba16Floatpixel format with EDR support
The NV12 Optimization
This deserves special attention. VideoToolbox — Apple's hardware decoder — natively outputs video in NV12 format (YCbCr 4:2:0 bi-planar). Many players request BGRA output, which forces the CPU to convert every single pixel.
For 4K 60fps video, that conversion burns ~1.9 GB/s of CPU bandwidth:
3840 × 2160 × 4 bytes × 60fps ≈ 1.9 GB/sBy accepting NV12 and converting on the GPU with a Metal shader, we eliminate this entirely. The BT.709 YCbCr → RGB conversion in our fragment shader runs in microseconds on the GPU.
Metal Shader Details
Our NV12 fragment shader performs the standard BT.709 video-range conversion:
Y = (Y - 16/255) × (255/219)
Cb = (UV.x - 128/255) × (255/224)
Cr = (UV.y - 128/255) × (255/224)
R = Y + 1.5748 × Cr
G = Y - 0.1873 × Cb - 0.4681 × Cr
B = Y + 1.8556 × CbThis runs per-pixel on the GPU in parallel, making it essentially free compared to CPU conversion.
Render Thread Isolation
All rendering — whether OpenGL (mpv) or Metal (AVFoundation) — happens on a dedicated render thread:
- mpv:
mpvGLQueuewith.userInteractiveQoS - Metal:
renderQueuewith.userInteractiveQoS and semaphore-based frame gating
The main thread handles UI updates only. This ensures the interface remains responsive even during heavy playback — a lesson learned from IINA, which found that main-thread rendering caused side panel animations to stutter.
The Result
This architecture means HorangPlayer automatically uses the most efficient rendering path available. Most of the time, you get the raw speed of mpv's optimized OpenGL renderer. When you need Apple-specific features, Metal takes over with zero-copy NV12 processing. And when you apply filters, the CIContext pipeline handles it seamlessly.