mirror of
https://github.com/rustdesk/rustdesk.git
synced 2026-09-08 13:31:03 +03:00
scrap: stay on DXGI when the HDR conversion fails, load the compiler once
Review follow-up: - A tone-map failure surfaced as a capture error, and the capture loop answers any DXGI error by switching the capturer to GDI for the rest of its life. The capturer now drops the tone-map, re-creates the duplication with the legacy DuplicateOutput (DXGI's clipped BGRA8, the pre-HDR behaviour) and returns WouldBlock, so the session stays on DXGI and simply fetches the next frame. - Only failures that cannot succeed anywhere in the process (no d3dcompiler_47.dll, shaders that do not compile) set the global UNAVAILABLE flag; D3D object creation failures stay with the capturer that hit them, so another adapter or a recreated capturer tries again. - D3DCompile is resolved once through a OnceLock instead of a LoadLibrary per capturer that was never freed. - The module doc now states what this pass is: normalization of an HDR desktop to SDR, with everything above SDR white clipping, not a tone map, and why a roll-off is deliberately not applied. The earlier claim that clipping matches what the local user sees was wrong for HDR content on an HDR display. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RLb1dNdhFqUQExJPANjo1F
This commit is contained in:
@@ -1,12 +1,18 @@
|
||||
//! HDR desktop -> SDR frame tone-map for Desktop Duplication.
|
||||
//! HDR desktop -> SDR normalization for Desktop Duplication frames.
|
||||
//!
|
||||
//! With HDR enabled Windows composes the desktop as linear scRGB in
|
||||
//! R16G16B16A16_FLOAT, and SDR "white" sits at the user's SDR content
|
||||
//! brightness (DISPLAYCONFIG_SDR_WHITE_LEVEL) rather than at 1.0. The legacy
|
||||
//! DuplicateOutput converts that to BGRA8 by clipping, which is the washed-out
|
||||
//! picture reported for HDR hosts. This pass divides by the SDR white level,
|
||||
//! clamps, and applies the sRGB transfer, so SDR content round-trips exactly
|
||||
//! and HDR highlights clip at white.
|
||||
//! clamps, and applies the sRGB transfer, so SDR content comes out exactly as
|
||||
//! it would from an SDR desktop.
|
||||
//!
|
||||
//! It is a normalization, not a tone map: anything brighter than SDR white
|
||||
//! (HDR video, HDR games) clips to white on the SDR viewer, where the local
|
||||
//! HDR display would show it brighter than white. A roll-off would have to
|
||||
//! move SDR white below 1.0 to make headroom, trading the accuracy of the SDR
|
||||
//! content this pass exists for, so it is deliberately not done.
|
||||
//!
|
||||
//! The conversion is automatic and stays on the controlled side on purpose:
|
||||
//! the controller renders through Flutter external textures, which are 8-bit
|
||||
@@ -19,7 +25,7 @@ use super::ComPtr;
|
||||
use hbb_common::log;
|
||||
use std::{
|
||||
io, mem, ptr,
|
||||
sync::atomic::AtomicBool,
|
||||
sync::{atomic::AtomicBool, OnceLock},
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
use winapi::{
|
||||
@@ -45,10 +51,18 @@ use winapi::{
|
||||
},
|
||||
};
|
||||
|
||||
/// Set once the tone-map cannot be built on this machine (no d3dcompiler, shader
|
||||
/// creation failed, ...). The capturer then stops asking DXGI for float frames.
|
||||
/// Set once the tone-map can never work in this process (no d3dcompiler, the
|
||||
/// shaders do not compile). Capturers then stop asking DXGI for float frames.
|
||||
/// Device-specific failures are not recorded here; the capturer that hit one
|
||||
/// re-duplicates without the tone-map on its own.
|
||||
pub static UNAVAILABLE: AtomicBool = AtomicBool::new(false);
|
||||
|
||||
/// Failures no capturer on this machine can recover from, as opposed to
|
||||
/// device-specific ones that a recreated capturer may not hit again.
|
||||
pub fn is_permanent(err: &io::Error) -> bool {
|
||||
err.kind() == io::ErrorKind::Unsupported
|
||||
}
|
||||
|
||||
const VS_SRC: &str = "\
|
||||
float4 main(uint id : SV_VertexID) : SV_Position {
|
||||
float2 uv = float2((id << 1) & 2, id & 2);
|
||||
@@ -330,17 +344,26 @@ type D3DCompileFn = unsafe extern "system" fn(
|
||||
*mut *mut ID3DBlob,
|
||||
) -> HRESULT;
|
||||
|
||||
// Loaded on demand: the compiler DLL is only needed on HDR desktops, and an
|
||||
// import-time link would make every install depend on it.
|
||||
unsafe fn load_d3d_compile() -> io::Result<D3DCompileFn> {
|
||||
static D3D_COMPILE: OnceLock<Result<D3DCompileFn, String>> = OnceLock::new();
|
||||
|
||||
// Loaded once per process and kept: the compiler DLL is only needed on HDR
|
||||
// desktops, and an import-time link would make every install depend on it.
|
||||
fn load_d3d_compile() -> io::Result<D3DCompileFn> {
|
||||
D3D_COMPILE
|
||||
.get_or_init(|| unsafe { find_d3d_compile() })
|
||||
.clone()
|
||||
.map_err(|e| io::Error::new(io::ErrorKind::Unsupported, e))
|
||||
}
|
||||
|
||||
unsafe fn find_d3d_compile() -> Result<D3DCompileFn, String> {
|
||||
let name: Vec<u16> = "d3dcompiler_47.dll\0".encode_utf16().collect();
|
||||
let module = LoadLibraryW(name.as_ptr());
|
||||
if module.is_null() {
|
||||
return Err(other("d3dcompiler_47.dll not available"));
|
||||
return Err("d3dcompiler_47.dll not available".into());
|
||||
}
|
||||
let f = GetProcAddress(module, b"D3DCompile\0".as_ptr() as _);
|
||||
if f.is_null() {
|
||||
return Err(other("D3DCompile not exported"));
|
||||
return Err("D3DCompile not exported".into());
|
||||
}
|
||||
Ok(mem::transmute::<_, D3DCompileFn>(f))
|
||||
}
|
||||
@@ -379,7 +402,10 @@ unsafe fn compile_shader(
|
||||
if !code.is_null() {
|
||||
(*(code as *mut IUnknown)).Release();
|
||||
}
|
||||
return Err(other(format!("D3DCompile failed: {hr:#x} {msg}")));
|
||||
return Err(io::Error::new(
|
||||
io::ErrorKind::Unsupported,
|
||||
format!("D3DCompile failed: {hr:#x} {msg}"),
|
||||
));
|
||||
}
|
||||
Ok(ComPtr(code))
|
||||
}
|
||||
|
||||
@@ -221,21 +221,42 @@ impl Capturer {
|
||||
if self.hdr.is_none() {
|
||||
match hdr::HdrToSdr::new(self.device.0, self.context.0, &self.display.desc.DeviceName) {
|
||||
Ok(hdr) => self.hdr = Some(hdr),
|
||||
Err(err) => {
|
||||
hdr::UNAVAILABLE.store(true, std::sync::atomic::Ordering::Relaxed);
|
||||
hbb_common::log::error!(
|
||||
"HDR tone-map unavailable, next capturer falls back to clipped BGRA: {err}"
|
||||
);
|
||||
return Err(err);
|
||||
}
|
||||
Err(err) => return self.abandon_tonemap(err),
|
||||
}
|
||||
}
|
||||
match self.hdr.as_mut() {
|
||||
let converted = match self.hdr.as_mut() {
|
||||
Some(hdr) => hdr.convert(source, desc),
|
||||
None => Err(io::Error::new(io::ErrorKind::Other, "no tone-map")),
|
||||
};
|
||||
match converted {
|
||||
Ok(texture) => Ok(texture),
|
||||
Err(err) => self.abandon_tonemap(err),
|
||||
}
|
||||
}
|
||||
|
||||
// Drops the tone-map and re-duplicates the output the legacy way, so DXGI
|
||||
// hands over clipped BGRA8 (the pre-HDR behaviour) and the session stays on
|
||||
// DXGI instead of being switched to GDI by the capture loop, which treats
|
||||
// any other error that way. The caller sees WouldBlock and asks again.
|
||||
unsafe fn abandon_tonemap<T>(&mut self, err: io::Error) -> io::Result<T> {
|
||||
if hdr::is_permanent(&err) {
|
||||
hdr::UNAVAILABLE.store(true, std::sync::atomic::Ordering::Relaxed);
|
||||
}
|
||||
hbb_common::log::error!("HDR tone-map failed, re-duplicating without it: {err}");
|
||||
self.hdr = None;
|
||||
(*self.duplication.0).ReleaseFrame();
|
||||
self.duplication = ComPtr(ptr::null_mut());
|
||||
let mut duplication = ptr::null_mut();
|
||||
wrap_hresult(
|
||||
(*self.display.inner.0).DuplicateOutput(self.device.0 as *mut _, &mut duplication),
|
||||
)?;
|
||||
self.duplication = ComPtr(duplication);
|
||||
let mut desc: DXGI_OUTDUPL_DESC = mem::zeroed();
|
||||
(*duplication).GetDesc(&mut desc);
|
||||
self.fastlane = desc.DesktopImageInSystemMemory == TRUE;
|
||||
Err(io::ErrorKind::WouldBlock.into())
|
||||
}
|
||||
|
||||
fn create_rotations(
|
||||
device: *mut ID3D11Device,
|
||||
context: *mut ID3D11DeviceContext,
|
||||
|
||||
Reference in New Issue
Block a user