scrap: tone-map HDR desktops to SDR in the Windows capturer

With HDR enabled, Windows composes the desktop as linear scRGB in
R16G16B16A16_FLOAT and places SDR white at the user's "SDR content
brightness" (DISPLAYCONFIG_SDR_WHITE_LEVEL / 1000, e.g. 2.5 for 200
nits) rather than at 1.0. The legacy IDXGIOutput1::DuplicateOutput we
used always converts that surface to BGRA8, and it does so by clipping,
so everything above ~40% linear brightness lands on white. That is the
washed-out / overexposed / high-contrast picture reported for HDR hosts
in #5368, #9951, #12707 and discussion #7652.

Ask for the desktop with IDXGIOutput5::DuplicateOutput1 and the format
list [R16G16B16A16_FLOAT, B8G8R8A8_UNORM]. An SDR desktop still yields
BGRA8 and takes the unchanged path. A float frame is converted on the
GPU by a small pixel shader: divide by the SDR white level, clamp, apply
the sRGB transfer. SDR content round-trips exactly, HDR highlights clip
at white, the same result the local user sees. The white level is read
per output through DisplayConfigGetDeviceInfo and refreshed once a
second, so dragging the Windows slider tracks remotely. The converted
BGRA8 texture feeds both the staging-copy (CPU) path and the vram
(hwcodec texture) path; rotation is unchanged. Toggling HDR mid-session
invalidates the duplication as before, and the recreated capturer
re-detects the format.

The shaders are compiled at runtime from a dynamically loaded
d3dcompiler_47.dll so no new import is added to the binary. If the DLL,
the compile or any D3D object creation fails, a process-wide flag makes
later capturers fall back to the legacy DuplicateOutput, i.e. today's
behaviour.

This deliberately stops at SDR on the controlled side, with no option
and no protocol change, which is how OBS, Sunshine (client without HDR)
and macOS screen capture handle it. Real HDR pass-through would need
10-bit capture, Main10/AV1-10 encoders in the hwcodec fork, colour
metadata on the wire and, decisively, an HDR output path on the
controller: Flutter's desktop external textures are BGRA8888/RGBA8888
only on every platform, so nothing would be visible. When that changes
it should follow the Sunshine/Moonlight pattern: an hdr capability bit
advertised by the controller behind an explicit user toggle, negotiated
like i444.

Type-checked against x86_64-pc-windows-msvc (with and without the vram
feature); not yet run on an HDR machine, which needs Windows 10 1703+
with HDR on.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RLb1dNdhFqUQExJPANjo1F
(cherry picked from commit ca3c2f8ac7d1549a40855411b0539a69c82d7223)
This commit is contained in:
rustdesk
2026-09-02 11:50:53 +08:00
parent f28ac38ccf
commit a7df138fc7
2 changed files with 541 additions and 4 deletions

464
libs/scrap/src/dxgi/hdr.rs Normal file
View File

@@ -0,0 +1,464 @@
//! HDR desktop -> SDR frame tone-map for Desktop Duplication.
//!
//! 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.
//!
//! The conversion is automatic and stays on the controlled side on purpose:
//! the controller renders through Flutter external textures, which are 8-bit
//! on every desktop platform, so there is nothing to gain from sending HDR.
//! Real HDR pass-through, if the renderer ever supports it, should follow the
//! Sunshine/Moonlight pattern instead: an `hdr` capability bit advertised by
//! the controller behind an explicit user toggle, negotiated like i444.
use super::ComPtr;
use hbb_common::log;
use std::{
io, mem, ptr,
sync::atomic::AtomicBool,
time::{Duration, Instant},
};
use winapi::{
shared::{
basetsd::SIZE_T,
dxgiformat::DXGI_FORMAT_B8G8R8A8_UNORM,
dxgitype::DXGI_SAMPLE_DESC,
minwindef::{LPCVOID, UINT, ULONG},
ntdef::{LONG, LPCSTR, WCHAR},
winerror::S_OK,
},
um::{
d3d11::*,
d3dcommon::{ID3DBlob, ID3DInclude, D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST, D3D_SHADER_MACRO},
libloaderapi::{GetProcAddress, LoadLibraryW},
unknwnbase::IUnknown,
wingdi::{
DISPLAYCONFIG_DEVICE_INFO_GET_SOURCE_NAME, DISPLAYCONFIG_DEVICE_INFO_HEADER,
DISPLAYCONFIG_MODE_INFO, DISPLAYCONFIG_PATH_INFO, DISPLAYCONFIG_SOURCE_DEVICE_NAME,
DISPLAYCONFIG_TOPOLOGY_ID,
},
winnt::HRESULT,
},
};
/// 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.
pub static UNAVAILABLE: AtomicBool = AtomicBool::new(false);
const VS_SRC: &str = "\
float4 main(uint id : SV_VertexID) : SV_Position {
float2 uv = float2((id << 1) & 2, id & 2);
return float4(uv * float2(2.0, -2.0) + float2(-1.0, 1.0), 0.0, 1.0);
}";
const PS_SRC: &str = "\
Texture2D<float4> src : register(t0);
cbuffer Params : register(b0) { float inv_sdr_white; float3 pad; };
float4 main(float4 pos : SV_Position) : SV_Target {
float3 lin = saturate(src.Load(int3(pos.xy, 0)).rgb * inv_sdr_white);
float3 lo = lin * 12.92;
float3 hi = 1.055 * pow(lin, 1.0 / 2.4) - 0.055;
return float4(lerp(hi, lo, step(lin, 0.0031308)), 1.0);
}";
/// DISPLAYCONFIG units: 1000 == 80 nits == scRGB 1.0.
const DEFAULT_SDR_WHITE_LEVEL: u32 = 1000;
const SDR_WHITE_LEVEL_REFRESH: Duration = Duration::from_secs(1);
pub struct HdrToSdr {
device: ComPtr<ID3D11Device>,
context: ComPtr<ID3D11DeviceContext>,
vs: ComPtr<ID3D11VertexShader>,
ps: ComPtr<ID3D11PixelShader>,
params: ComPtr<ID3D11Buffer>,
target: ComPtr<ID3D11Texture2D>,
rtv: ComPtr<ID3D11RenderTargetView>,
srv: ComPtr<ID3D11ShaderResourceView>,
// Texture `srv` was created for. The view keeps it alive, so the address
// cannot be recycled behind our back.
srv_source: *mut ID3D11Texture2D,
width: u32,
height: u32,
device_name: [WCHAR; 32],
sdr_white_level: u32,
queried_at: Instant,
}
impl HdrToSdr {
pub fn new(
device: *mut ID3D11Device,
context: *mut ID3D11DeviceContext,
device_name: &[WCHAR; 32],
) -> io::Result<Self> {
unsafe {
if device.is_null() || context.is_null() {
return Err(other("no d3d11 device"));
}
(*device).AddRef();
let device = ComPtr(device);
(*context).AddRef();
let context = ComPtr(context);
let compile = load_d3d_compile()?;
let vs_code = compile_shader(compile, VS_SRC, b"vs_4_0\0")?;
let ps_code = compile_shader(compile, PS_SRC, b"ps_4_0\0")?;
let mut vs = ptr::null_mut();
check(
(*device.0).CreateVertexShader(
(*vs_code.0).GetBufferPointer(),
(*vs_code.0).GetBufferSize(),
ptr::null_mut(),
&mut vs,
),
"CreateVertexShader",
)?;
let vs = ComPtr(vs);
let mut ps = ptr::null_mut();
check(
(*device.0).CreatePixelShader(
(*ps_code.0).GetBufferPointer(),
(*ps_code.0).GetBufferSize(),
ptr::null_mut(),
&mut ps,
),
"CreatePixelShader",
)?;
let ps = ComPtr(ps);
let sdr_white_level =
query_sdr_white_level(device_name).unwrap_or(DEFAULT_SDR_WHITE_LEVEL);
let init = params_data(sdr_white_level);
let desc = D3D11_BUFFER_DESC {
ByteWidth: mem::size_of_val(&init) as _,
Usage: D3D11_USAGE_DEFAULT,
BindFlags: D3D11_BIND_CONSTANT_BUFFER,
CPUAccessFlags: 0,
MiscFlags: 0,
StructureByteStride: 0,
};
let data = D3D11_SUBRESOURCE_DATA {
pSysMem: init.as_ptr() as _,
SysMemPitch: 0,
SysMemSlicePitch: 0,
};
let mut params = ptr::null_mut();
check(
(*device.0).CreateBuffer(&desc, &data, &mut params),
"CreateBuffer",
)?;
let params = ComPtr(params);
log::info!("HDR tone-map ready, sdr white level {sdr_white_level}");
Ok(Self {
device,
context,
vs,
ps,
params,
target: ComPtr(ptr::null_mut()),
rtv: ComPtr(ptr::null_mut()),
srv: ComPtr(ptr::null_mut()),
srv_source: ptr::null_mut(),
width: 0,
height: 0,
device_name: *device_name,
sdr_white_level,
queried_at: Instant::now(),
})
}
}
/// Renders `source` (R16G16B16A16_FLOAT) into an owned B8G8R8A8_UNORM
/// texture of the same size and returns it. The texture stays valid until
/// the next call.
pub fn convert(
&mut self,
source: *mut ID3D11Texture2D,
desc: &D3D11_TEXTURE2D_DESC,
) -> io::Result<*mut ID3D11Texture2D> {
unsafe {
self.refresh_sdr_white_level();
self.ensure_target(desc.Width, desc.Height)?;
self.ensure_source_view(source)?;
let ctx = self.context.0;
let rtv = self.rtv.0;
let srv = self.srv.0;
let params = self.params.0;
let viewport = D3D11_VIEWPORT {
TopLeftX: 0.0,
TopLeftY: 0.0,
Width: self.width as f32,
Height: self.height as f32,
MinDepth: 0.0,
MaxDepth: 1.0,
};
(*ctx).OMSetRenderTargets(1, &rtv, ptr::null_mut());
(*ctx).OMSetBlendState(ptr::null_mut(), &[0.0; 4], 0xffff_ffff);
(*ctx).OMSetDepthStencilState(ptr::null_mut(), 0);
(*ctx).RSSetState(ptr::null_mut());
(*ctx).RSSetViewports(1, &viewport);
(*ctx).IASetInputLayout(ptr::null_mut());
(*ctx).IASetPrimitiveTopology(D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
(*ctx).VSSetShader(self.vs.0, ptr::null(), 0);
(*ctx).PSSetShader(self.ps.0, ptr::null(), 0);
(*ctx).PSSetConstantBuffers(0, 1, &params);
(*ctx).PSSetShaderResources(0, 1, &srv);
(*ctx).Draw(3, 0);
// Unbind so the next frame's copy and the encoder never see the
// target as a live render target or the desktop image as a bound
// shader input.
let no_srv: *mut ID3D11ShaderResourceView = ptr::null_mut();
(*ctx).PSSetShaderResources(0, 1, &no_srv);
(*ctx).OMSetRenderTargets(0, ptr::null(), ptr::null_mut());
Ok(self.target.0)
}
}
unsafe fn ensure_target(&mut self, width: u32, height: u32) -> io::Result<()> {
if !self.target.is_null() && self.width == width && self.height == height {
return Ok(());
}
let desc = D3D11_TEXTURE2D_DESC {
Width: width,
Height: height,
MipLevels: 1,
ArraySize: 1,
Format: DXGI_FORMAT_B8G8R8A8_UNORM,
SampleDesc: DXGI_SAMPLE_DESC {
Count: 1,
Quality: 0,
},
Usage: D3D11_USAGE_DEFAULT,
BindFlags: D3D11_BIND_RENDER_TARGET | D3D11_BIND_SHADER_RESOURCE,
CPUAccessFlags: 0,
MiscFlags: D3D11_RESOURCE_MISC_SHARED,
};
let mut target = ptr::null_mut();
check(
(*self.device.0).CreateTexture2D(&desc, ptr::null(), &mut target),
"CreateTexture2D",
)?;
let target = ComPtr(target);
let mut rtv = ptr::null_mut();
check(
(*self.device.0).CreateRenderTargetView(target.0 as *mut _, ptr::null(), &mut rtv),
"CreateRenderTargetView",
)?;
self.rtv = ComPtr(rtv);
self.target = target;
self.width = width;
self.height = height;
Ok(())
}
unsafe fn ensure_source_view(&mut self, source: *mut ID3D11Texture2D) -> io::Result<()> {
if !self.srv.is_null() && self.srv_source == source {
return Ok(());
}
let mut srv = ptr::null_mut();
check(
(*self.device.0).CreateShaderResourceView(source as *mut _, ptr::null(), &mut srv),
"CreateShaderResourceView",
)?;
self.srv = ComPtr(srv);
self.srv_source = source;
Ok(())
}
unsafe fn refresh_sdr_white_level(&mut self) {
if self.queried_at.elapsed() < SDR_WHITE_LEVEL_REFRESH {
return;
}
self.queried_at = Instant::now();
let Some(level) = query_sdr_white_level(&self.device_name) else {
return;
};
if level == self.sdr_white_level || level == 0 {
return;
}
log::info!(
"sdr white level changed {} -> {level}",
self.sdr_white_level
);
self.sdr_white_level = level;
let data = params_data(level);
(*self.context.0).UpdateSubresource(
self.params.0 as *mut _,
0,
ptr::null(),
data.as_ptr() as _,
0,
0,
);
}
}
fn params_data(sdr_white_level: u32) -> [f32; 4] {
[1000.0 / sdr_white_level.max(1) as f32, 0.0, 0.0, 0.0]
}
fn other(msg: impl Into<String>) -> io::Error {
io::Error::new(io::ErrorKind::Other, msg.into())
}
fn check(hr: HRESULT, what: &str) -> io::Result<()> {
if hr == S_OK {
Ok(())
} else {
Err(other(format!("{what} failed: {hr:#x}")))
}
}
// D3DCompile(pSrcData, SrcDataSize, pSourceName, pDefines, pInclude,
// pEntrypoint, pTarget, Flags1, Flags2, ppCode, ppErrorMsgs)
type D3DCompileFn = unsafe extern "system" fn(
LPCVOID,
SIZE_T,
LPCSTR,
*const D3D_SHADER_MACRO,
*mut ID3DInclude,
LPCSTR,
LPCSTR,
UINT,
UINT,
*mut *mut ID3DBlob,
*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> {
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"));
}
let f = GetProcAddress(module, b"D3DCompile\0".as_ptr() as _);
if f.is_null() {
return Err(other("D3DCompile not exported"));
}
Ok(mem::transmute::<_, D3DCompileFn>(f))
}
unsafe fn compile_shader(
compile: D3DCompileFn,
src: &str,
target: &[u8],
) -> io::Result<ComPtr<ID3DBlob>> {
let mut code = ptr::null_mut();
let mut errors = ptr::null_mut();
let hr = compile(
src.as_ptr() as _,
src.len(),
ptr::null(),
ptr::null(),
ptr::null_mut(),
b"main\0".as_ptr() as _,
target.as_ptr() as _,
0,
0,
&mut code,
&mut errors,
);
let errors = ComPtr(errors);
if hr != S_OK || code.is_null() {
let msg = if errors.is_null() {
String::new()
} else {
let bytes = std::slice::from_raw_parts(
(*errors.0).GetBufferPointer() as *const u8,
(*errors.0).GetBufferSize(),
);
String::from_utf8_lossy(bytes).into_owned()
};
if !code.is_null() {
(*(code as *mut IUnknown)).Release();
}
return Err(other(format!("D3DCompile failed: {hr:#x} {msg}")));
}
Ok(ComPtr(code))
}
const DISPLAYCONFIG_DEVICE_INFO_GET_SDR_WHITE_LEVEL: u32 = 11;
const QDC_ONLY_ACTIVE_PATHS: u32 = 2;
#[repr(C)]
#[allow(non_snake_case)]
struct DISPLAYCONFIG_SDR_WHITE_LEVEL {
header: DISPLAYCONFIG_DEVICE_INFO_HEADER,
SDRWhiteLevel: ULONG,
}
#[link(name = "user32")]
extern "system" {
fn GetDisplayConfigBufferSizes(
flags: u32,
numPathArrayElements: *mut u32,
numModeInfoArrayElements: *mut u32,
) -> LONG;
fn QueryDisplayConfig(
flags: u32,
numPathArrayElements: *mut u32,
pathArray: *mut DISPLAYCONFIG_PATH_INFO,
numModeInfoArrayElements: *mut u32,
modeInfoArray: *mut DISPLAYCONFIG_MODE_INFO,
currentTopologyId: *mut DISPLAYCONFIG_TOPOLOGY_ID,
) -> LONG;
fn DisplayConfigGetDeviceInfo(requestPacket: *mut DISPLAYCONFIG_DEVICE_INFO_HEADER) -> LONG;
}
/// SDR white level of the output whose GDI name is `device_name`
/// (e.g. `\\.\DISPLAY1`), in DISPLAYCONFIG units (1000 == 80 nits).
fn query_sdr_white_level(device_name: &[WCHAR; 32]) -> Option<u32> {
unsafe {
let mut n_paths = 0u32;
let mut n_modes = 0u32;
if GetDisplayConfigBufferSizes(QDC_ONLY_ACTIVE_PATHS, &mut n_paths, &mut n_modes) != 0 {
return None;
}
let mut paths: Vec<DISPLAYCONFIG_PATH_INFO> = vec![mem::zeroed(); n_paths as usize];
let mut modes: Vec<DISPLAYCONFIG_MODE_INFO> = vec![mem::zeroed(); n_modes as usize];
if QueryDisplayConfig(
QDC_ONLY_ACTIVE_PATHS,
&mut n_paths,
paths.as_mut_ptr(),
&mut n_modes,
modes.as_mut_ptr(),
ptr::null_mut(),
) != 0
{
return None;
}
for path in &paths[..n_paths as usize] {
let mut source: DISPLAYCONFIG_SOURCE_DEVICE_NAME = mem::zeroed();
source.header._type = DISPLAYCONFIG_DEVICE_INFO_GET_SOURCE_NAME;
source.header.size = mem::size_of::<DISPLAYCONFIG_SOURCE_DEVICE_NAME>() as _;
source.header.adapterId = path.sourceInfo.adapterId;
source.header.id = path.sourceInfo.id;
if DisplayConfigGetDeviceInfo(&mut source.header) != 0
|| !wide_eq(&source.viewGdiDeviceName, device_name)
{
continue;
}
let mut white: DISPLAYCONFIG_SDR_WHITE_LEVEL = mem::zeroed();
white.header._type = DISPLAYCONFIG_DEVICE_INFO_GET_SDR_WHITE_LEVEL;
white.header.size = mem::size_of::<DISPLAYCONFIG_SDR_WHITE_LEVEL>() as _;
white.header.adapterId = path.targetInfo.adapterId;
white.header.id = path.targetInfo.id;
if DisplayConfigGetDeviceInfo(&mut white.header) == 0 {
return Some(white.SDRWhiteLevel);
}
}
None
}
}
fn wide_eq(a: &[WCHAR], b: &[WCHAR]) -> bool {
let end = |s: &[WCHAR]| s.iter().position(|&c| c == 0).unwrap_or(s.len());
a[..end(a)] == b[..end(b)]
}

View File

@@ -1,18 +1,20 @@
use std::{io, mem, ptr, slice};
pub mod gdi;
pub use gdi::CapturerGDI;
pub mod hdr;
pub mod mag;
use winapi::{
shared::{
dxgi::*,
dxgi1_2::*,
dxgi1_5::*,
dxgiformat::{DXGI_FORMAT_B8G8R8A8_UNORM, DXGI_FORMAT_R16G16B16A16_FLOAT},
dxgitype::*,
minwindef::{DWORD, FALSE, TRUE, UINT},
ntdef::LONG,
windef::{HMONITOR, RECT},
winerror::*,
// dxgiformat::{DXGI_FORMAT, DXGI_FORMAT_B8G8R8A8_UNORM, DXGI_FORMAT_420_OPAQUE},
},
um::{
d3d11::*, d3dcommon::D3D_DRIVER_TYPE_UNKNOWN, unknwnbase::IUnknown, wingdi::*,
@@ -58,6 +60,7 @@ pub struct Capturer {
output_texture: bool,
adapter_desc1: DXGI_ADAPTER_DESC1,
rotate: Rotate,
hdr: Option<hdr::HdrToSdr>,
}
impl Capturer {
@@ -105,7 +108,7 @@ impl Capturer {
}
} else {
res = wrap_hresult(unsafe {
let hres = (*display.inner.0).DuplicateOutput(device.0 as *mut _, &mut duplication);
let hres = Self::duplicate_output(&display, device.0, &mut duplication);
if hres != S_OK {
gdi_capturer = display.create_gdi();
println!("Fallback to GDI");
@@ -161,7 +164,8 @@ impl Capturer {
device,
context,
duplication: ComPtr(duplication),
fastlane: desc.DesktopImageInSystemMemory == TRUE,
fastlane: desc.DesktopImageInSystemMemory == TRUE
&& desc.ModeDesc.Format != DXGI_FORMAT_R16G16B16A16_FLOAT,
surface: ComPtr(ptr::null_mut()),
texture: ComPtr(ptr::null_mut()),
width: display.width() as usize,
@@ -174,9 +178,64 @@ impl Capturer {
output_texture: false,
adapter_desc1,
rotate,
hdr: None,
})
}
// Asks for the float desktop that HDR mode composes so it can be tone-mapped;
// the legacy call would hand back DXGI's clipped BGRA8 conversion instead.
unsafe fn duplicate_output(
display: &Display,
device: *mut ID3D11Device,
duplication: &mut *mut IDXGIOutputDuplication,
) -> HRESULT {
if !hdr::UNAVAILABLE.load(std::sync::atomic::Ordering::Relaxed) {
let mut output5: *mut IDXGIOutput5 = ptr::null_mut();
(*display.inner.0).QueryInterface(
&IID_IDXGIOutput5,
&mut output5 as *mut *mut _ as *mut *mut _,
);
if !output5.is_null() {
let output5 = ComPtr(output5);
let formats = [DXGI_FORMAT_R16G16B16A16_FLOAT, DXGI_FORMAT_B8G8R8A8_UNORM];
let hres = (*output5.0).DuplicateOutput1(
device as *mut _,
0,
formats.len() as UINT,
formats.as_ptr(),
duplication,
);
if hres == S_OK {
return hres;
}
}
}
(*display.inner.0).DuplicateOutput(device as *mut _, duplication)
}
unsafe fn tonemap(
&mut self,
source: *mut ID3D11Texture2D,
desc: &D3D11_TEXTURE2D_DESC,
) -> io::Result<*mut ID3D11Texture2D> {
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);
}
}
}
match self.hdr.as_mut() {
Some(hdr) => hdr.convert(source, desc),
None => Err(io::Error::new(io::ErrorKind::Other, "no tone-map")),
}
}
fn create_rotations(
device: *mut ID3D11Device,
context: *mut ID3D11DeviceContext,
@@ -365,6 +424,12 @@ impl Capturer {
let mut texture_desc = mem::MaybeUninit::uninit().assume_init();
(*texture.0).GetDesc(&mut texture_desc);
let mut source = texture.0;
if texture_desc.Format == DXGI_FORMAT_R16G16B16A16_FLOAT {
source = self.tonemap(texture.0, &texture_desc)?;
(*source).GetDesc(&mut texture_desc);
}
texture_desc.Usage = D3D11_USAGE_STAGING;
texture_desc.BindFlags = 0;
texture_desc.CPUAccessFlags = D3D11_CPU_ACCESS_READ;
@@ -385,7 +450,7 @@ impl Capturer {
&mut surface as *mut *mut _ as *mut *mut _,
);
(*self.context.0).CopyResource(readable.0 as *mut _, texture.0 as *mut _);
(*self.context.0).CopyResource(readable.0 as *mut _, source as *mut _);
Ok(surface)
}
@@ -492,6 +557,14 @@ impl Capturer {
let texture = ComPtr(texture);
self.texture = texture;
let mut frame_desc: D3D11_TEXTURE2D_DESC = mem::zeroed();
(*self.texture.0).GetDesc(&mut frame_desc);
if frame_desc.Format == DXGI_FORMAT_R16G16B16A16_FLOAT {
let converted = self.tonemap(self.texture.0, &frame_desc)?;
(*converted).AddRef();
self.texture = ComPtr(converted);
}
let mut final_texture = self.texture.0 as *mut c_void;
let mut rotation = match self.display.rotation() {
DXGI_MODE_ROTATION_ROTATE90 => 90,