mirror of
https://github.com/rustdesk/rustdesk.git
synced 2026-11-06 00:39:16 +00:00
Hdr tonemap (#16033)
* 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) * 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 * scrap: only apply the SDR white level to outputs DXGI reports as HDR Review follow-up: - An FP16 desktop is not necessarily HDR. Since Windows 11 22H2 an Advanced Color (WCG) SDR display is composed in FP16 scRGB too, and there 1.0 is the display's reference white, not 80 nits, so the SDRWhiteLevel scaling does not apply (Microsoft: "Reference white applies only to HDR displays"). Query IDXGIOutput6::GetDesc1 and treat the output as HDR only for DXGI_COLOR_SPACE_RGB_FULL_G2084_NONE_P2020, the documented Win32 check; a non-HDR FP16 frame now gets just the scRGB -> sRGB transfer. Without IDXGIOutput6 (before Windows 10 1803) FP16 can only mean HDR. - The SDR white level is now Option: a failed query or a reported 0 is "unknown" on both the initial and the refresh path, an HDR output with an unknown level logs a warning and keeps the 80-nit assumption until the periodic re-query succeeds. DISPLAYCONFIG_DEVICE_INFO_GET_SDR_WHITE_LEVEL needs Windows 10 1709, not 1703 as an earlier message said. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RLb1dNdhFqUQExJPANjo1F * scrap: re-read the Advanced Color state while capturing, gate FP16 on IDXGIOutput6 Review follow-up: - The HDR/WCG decision was taken once when the conversion was created. HDR can be switched on or off, and a WCG desktop can turn into an HDR one, without the duplication being invalidated, so the choice between "divide by the SDR white level" and "no scaling" could go stale. The once-a-second refresh now re-reads it: it keeps an IDXGIFactory1, and when IsCurrent reports FALSE it creates a fresh factory and re-finds the output by GDI name, since a stale factory's outputs keep stale descriptions (this is the procedure the GetDesc1 docs require). On a change it switches modes, re-reads the white level and updates the shader constant; an unreadable level keeps the last known one. - Float frames are only requested when IDXGIOutput6 exists, as Microsoft's duplication sample does. That interface is also what tells HDR from WCG, so there is no longer a state where FP16 is requested without being able to interpret it. IDXGIOutput6 dates from Windows 10 1703, not 1803 as the previous commit said; 1803 added IDXGIFactory6. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RLb1dNdhFqUQExJPANjo1F * scrap: replace the DXGI factory and output together when re-reading HDR state Review follow-up: when the fresh factory did not find the output by name, the refresh kept the new, current factory next to the old output, and because IsCurrent then reported TRUE it never enumerated again, so the stale output could stay forever. enumerate_output6 now returns both or neither, and the refresh only replaces the pair together; a null factory forces another enumeration on the next refresh while the old output is still read in the meantime. The constructor gets the same guarantee for free, since a failed lookup leaves the factory null there too. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RLb1dNdhFqUQExJPANjo1F * scrap: keep enumerate_output6's both-or-neither promise when IDXGIOutput6 is missing Review nit: a matched output whose IDXGIOutput6 query fails returned the fresh factory next to a null output, which let the constructor pair a current factory with the capturer's fallback output. Return neither in that case so the next refresh enumerates again. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RLb1dNdhFqUQExJPANjo1F * scrap: log DuplicateOutput1 failures before fallback Signed-off-by: 21pages <sunboeasy@gmail.com> * scrap: avoid null duplication after HDR fallback failure Signed-off-by: 21pages <sunboeasy@gmail.com> * scrap: load the D3D compiler securely from System32 Signed-off-by: 21pages <sunboeasy@gmail.com> * scrap: release the frame through the state machine when abandoning the tone-map master's FrameState machine is left at Acquired by the raw ReleaseFrame(), so the next release_frame() hits DXGI_ERROR_INVALID_CALL on the fresh duplication and the capture loop falls back to GDI -- the outcome the fallback exists to avoid. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> --------- Signed-off-by: 21pages <sunboeasy@gmail.com> Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com> Co-authored-by: 21pages <sunboeasy@gmail.com>
This commit is contained in:
3 files changed
+750
-5
No files matched your search
@@ -0,0 +1,629 @@
|
||||
//! 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 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.
|
||||
//!
|
||||
//! Windows 11 22H2 also composes Advanced Color SDR (WCG) desktops in FP16,
|
||||
//! but there 1.0 is the display's reference white rather than 80 nits and no
|
||||
//! SDR white level applies. IDXGIOutput6 tells the two apart, and for a
|
||||
//! non-HDR output the pass only applies the scRGB -> sRGB transfer.
|
||||
//!
|
||||
//! 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, OnceLock},
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
use winapi::{
|
||||
ctypes::c_void,
|
||||
shared::{
|
||||
basetsd::SIZE_T,
|
||||
dxgi::{CreateDXGIFactory1, IDXGIFactory1, IID_IDXGIFactory1, DXGI_OUTPUT_DESC},
|
||||
dxgi1_2::IDXGIOutput1,
|
||||
dxgi1_6::{IDXGIOutput6, IID_IDXGIOutput6, DXGI_OUTPUT_DESC1},
|
||||
dxgiformat::DXGI_FORMAT_B8G8R8A8_UNORM,
|
||||
dxgitype::{DXGI_COLOR_SPACE_RGB_FULL_G2084_NONE_P2020, DXGI_SAMPLE_DESC},
|
||||
minwindef::{FALSE, LPCVOID, UINT, ULONG},
|
||||
ntdef::{LONG, LPCSTR, WCHAR},
|
||||
winerror::S_OK,
|
||||
},
|
||||
um::{
|
||||
d3d11::*,
|
||||
d3dcommon::{ID3DBlob, ID3DInclude, D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST, D3D_SHADER_MACRO},
|
||||
libloaderapi::{GetProcAddress, LoadLibraryExW, LOAD_LIBRARY_SEARCH_SYSTEM32},
|
||||
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 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);
|
||||
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);
|
||||
}";
|
||||
|
||||
const OUTPUT_STATE_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],
|
||||
// Advanced Color state is read from `output6`, which is re-enumerated from a
|
||||
// fresh factory whenever `factory` stops being current.
|
||||
factory: ComPtr<IDXGIFactory1>,
|
||||
output6: ComPtr<IDXGIOutput6>,
|
||||
is_hdr: bool,
|
||||
// DISPLAYCONFIG units (1000 == 80 nits == scRGB 1.0). `None` when it could
|
||||
// not be read, in which case 80 nits is assumed until it can.
|
||||
sdr_white_level: Option<u32>,
|
||||
queried_at: Instant,
|
||||
}
|
||||
|
||||
impl HdrToSdr {
|
||||
pub fn new(
|
||||
device: *mut ID3D11Device,
|
||||
context: *mut ID3D11DeviceContext,
|
||||
output: *mut IDXGIOutput1,
|
||||
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);
|
||||
|
||||
// Not found leaves the factory null, so the first refresh enumerates
|
||||
// again instead of trusting the capturer's possibly stale output.
|
||||
let (factory, mut output6) = enumerate_output6(device_name);
|
||||
if output6.is_null() {
|
||||
output6 = query_output6(output as *mut IUnknown);
|
||||
}
|
||||
// Float frames are only requested where IDXGIOutput6 exists, so an
|
||||
// unreadable description still comes from an HDR-capable stack.
|
||||
let is_hdr = output_is_hdr(output6.0).unwrap_or(true);
|
||||
let sdr_white_level = if is_hdr {
|
||||
query_sdr_white_level(device_name)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
if is_hdr && sdr_white_level.is_none() {
|
||||
log::warn!(
|
||||
"HDR output but the SDR white level cannot be read (needs Windows 10 1709+), \
|
||||
assuming 80 nits until it can"
|
||||
);
|
||||
}
|
||||
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!(
|
||||
"scRGB desktop conversion ready, hdr {is_hdr}, 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,
|
||||
factory,
|
||||
output6,
|
||||
is_hdr,
|
||||
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_output_state();
|
||||
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, ¶ms);
|
||||
(*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(())
|
||||
}
|
||||
|
||||
// Advanced Color state is dynamic: HDR can be switched on or off, or a WCG
|
||||
// desktop can turn into an HDR one, without the duplication being lost. An
|
||||
// output's description is a snapshot, so once the factory is no longer
|
||||
// current a new factory and output are needed to see the new state, as the
|
||||
// GetDesc1 docs require.
|
||||
unsafe fn refresh_output_state(&mut self) {
|
||||
if self.queried_at.elapsed() < OUTPUT_STATE_REFRESH {
|
||||
return;
|
||||
}
|
||||
self.queried_at = Instant::now();
|
||||
if self.factory.is_null() || (*self.factory.0).IsCurrent() == FALSE {
|
||||
let (factory, output6) = enumerate_output6(&self.device_name);
|
||||
if !output6.is_null() {
|
||||
self.factory = factory;
|
||||
self.output6 = output6;
|
||||
} else {
|
||||
// Keep reading the old output, but enumerate again next time.
|
||||
self.factory = ComPtr(ptr::null_mut());
|
||||
}
|
||||
}
|
||||
let is_hdr = output_is_hdr(self.output6.0).unwrap_or(self.is_hdr);
|
||||
let level = if is_hdr {
|
||||
query_sdr_white_level(&self.device_name)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
// A transiently unreadable level keeps the last known one.
|
||||
if is_hdr == self.is_hdr && (level.is_none() || level == self.sdr_white_level) {
|
||||
return;
|
||||
}
|
||||
log::info!(
|
||||
"output changed: hdr {} -> {is_hdr}, sdr white level {:?} -> {level:?}",
|
||||
self.is_hdr,
|
||||
self.sdr_white_level
|
||||
);
|
||||
if is_hdr && level.is_none() {
|
||||
log::warn!(
|
||||
"HDR output but the SDR white level cannot be read, assuming 80 nits until it can"
|
||||
);
|
||||
}
|
||||
self.is_hdr = is_hdr;
|
||||
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,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// `None` is either a non-HDR (WCG) output, where 1.0 already is the display's
|
||||
// reference white, or an HDR output whose level is unknown; both use 1.0.
|
||||
fn params_data(sdr_white_level: Option<u32>) -> [f32; 4] {
|
||||
[
|
||||
1000.0 / sdr_white_level.unwrap_or(1000) as f32,
|
||||
0.0,
|
||||
0.0,
|
||||
0.0,
|
||||
]
|
||||
}
|
||||
|
||||
// FP16 desktop composition means either HDR (scene-referred, 1.0 == 80 nits)
|
||||
// or, since Windows 11 22H2, Advanced Color SDR (display-referred), and only
|
||||
// IDXGIOutput6 (Windows 10 1703) tells them apart. `None` when it cannot be
|
||||
// read right now.
|
||||
unsafe fn output_is_hdr(output6: *mut IDXGIOutput6) -> Option<bool> {
|
||||
if output6.is_null() {
|
||||
return None;
|
||||
}
|
||||
let mut desc: DXGI_OUTPUT_DESC1 = mem::zeroed();
|
||||
if (*output6).GetDesc1(&mut desc) != S_OK {
|
||||
return None;
|
||||
}
|
||||
Some(desc.ColorSpace == DXGI_COLOR_SPACE_RGB_FULL_G2084_NONE_P2020)
|
||||
}
|
||||
|
||||
unsafe fn query_output6(object: *mut IUnknown) -> ComPtr<IDXGIOutput6> {
|
||||
let mut output6: *mut IDXGIOutput6 = ptr::null_mut();
|
||||
if !object.is_null() {
|
||||
(*object).QueryInterface(
|
||||
&IID_IDXGIOutput6,
|
||||
&mut output6 as *mut *mut _ as *mut *mut _,
|
||||
);
|
||||
}
|
||||
ComPtr(output6)
|
||||
}
|
||||
|
||||
// A fresh factory sees the current display configuration; the output is found
|
||||
// by its GDI name because the outputs of a stale factory keep stale descriptions.
|
||||
// Returns both or neither: a non-null factory guarantees the output came from
|
||||
// its topology, so a caller that sees a null factory knows to enumerate again.
|
||||
unsafe fn enumerate_output6(
|
||||
device_name: &[WCHAR; 32],
|
||||
) -> (ComPtr<IDXGIFactory1>, ComPtr<IDXGIOutput6>) {
|
||||
let mut factory: *mut c_void = ptr::null_mut();
|
||||
if CreateDXGIFactory1(&IID_IDXGIFactory1, &mut factory) != S_OK {
|
||||
return (ComPtr(ptr::null_mut()), ComPtr(ptr::null_mut()));
|
||||
}
|
||||
let factory = ComPtr(factory as *mut IDXGIFactory1);
|
||||
let mut adapter_index = 0;
|
||||
loop {
|
||||
let mut adapter = ptr::null_mut();
|
||||
if (*factory.0).EnumAdapters1(adapter_index, &mut adapter) != S_OK {
|
||||
break;
|
||||
}
|
||||
let adapter = ComPtr(adapter);
|
||||
adapter_index += 1;
|
||||
let mut output_index = 0;
|
||||
loop {
|
||||
let mut output = ptr::null_mut();
|
||||
if (*adapter.0).EnumOutputs(output_index, &mut output) != S_OK {
|
||||
break;
|
||||
}
|
||||
let output = ComPtr(output);
|
||||
output_index += 1;
|
||||
let mut desc: DXGI_OUTPUT_DESC = mem::zeroed();
|
||||
if (*output.0).GetDesc(&mut desc) == S_OK && wide_eq(&desc.DeviceName, device_name) {
|
||||
let output6 = query_output6(output.0 as *mut IUnknown);
|
||||
if output6.is_null() {
|
||||
return (ComPtr(ptr::null_mut()), ComPtr(ptr::null_mut()));
|
||||
}
|
||||
return (factory, output6);
|
||||
}
|
||||
}
|
||||
}
|
||||
(ComPtr(ptr::null_mut()), ComPtr(ptr::null_mut()))
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
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 = LoadLibraryExW(name.as_ptr(), ptr::null_mut(), LOAD_LIBRARY_SEARCH_SYSTEM32);
|
||||
if module.is_null() {
|
||||
return Err("d3dcompiler_47.dll not available".into());
|
||||
}
|
||||
let f = GetProcAddress(module, b"D3DCompile\0".as_ptr() as _);
|
||||
if f.is_null() {
|
||||
return Err("D3DCompile not exported".into());
|
||||
}
|
||||
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(io::Error::new(
|
||||
io::ErrorKind::Unsupported,
|
||||
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). `None`
|
||||
/// when the query fails (before Windows 10 1709) or reports 0.
|
||||
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 && white.SDRWhiteLevel != 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)]
|
||||
}
|
||||
+120
-4
@@ -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_6::*,
|
||||
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::*,
|
||||
@@ -66,6 +68,7 @@ pub struct Capturer {
|
||||
adapter_desc1: DXGI_ADAPTER_DESC1,
|
||||
rotate: Rotate,
|
||||
frame_state: FrameState,
|
||||
hdr: Option<hdr::HdrToSdr>,
|
||||
}
|
||||
|
||||
impl Capturer {
|
||||
@@ -113,7 +116,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");
|
||||
@@ -169,7 +172,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,
|
||||
@@ -183,9 +187,104 @@ impl Capturer {
|
||||
adapter_desc1,
|
||||
rotate,
|
||||
frame_state: FrameState::Idle,
|
||||
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.
|
||||
// Only where IDXGIOutput6 (Windows 10 1703) exists, since that is what later
|
||||
// tells an HDR desktop from a WCG one; Microsoft's duplication sample gates
|
||||
// the float request the same way.
|
||||
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 output6: *mut IDXGIOutput6 = ptr::null_mut();
|
||||
(*display.inner.0).QueryInterface(
|
||||
&IID_IDXGIOutput6,
|
||||
&mut output6 as *mut *mut _ as *mut *mut _,
|
||||
);
|
||||
if !output6.is_null() {
|
||||
let output6 = ComPtr(output6);
|
||||
let formats = [DXGI_FORMAT_R16G16B16A16_FLOAT, DXGI_FORMAT_B8G8R8A8_UNORM];
|
||||
let hres = (*output6.0).DuplicateOutput1(
|
||||
device as *mut _,
|
||||
0,
|
||||
formats.len() as UINT,
|
||||
formats.as_ptr(),
|
||||
duplication,
|
||||
);
|
||||
if hres == S_OK {
|
||||
return hres;
|
||||
}
|
||||
hbb_common::log::warn!(
|
||||
"HDR DuplicateOutput1 failed: hr={:#x}, fallback=DuplicateOutput",
|
||||
hres as u32
|
||||
);
|
||||
}
|
||||
}
|
||||
(*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.inner.0,
|
||||
&self.display.desc.DeviceName,
|
||||
) {
|
||||
Ok(hdr) => self.hdr = Some(hdr),
|
||||
Err(err) => return self.abandon_tonemap(err),
|
||||
}
|
||||
}
|
||||
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). If re-duplication fails,
|
||||
// switch to GDI before returning. 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;
|
||||
if let Err(release_err) = self.release_frame() {
|
||||
hbb_common::log::warn!("DXGI frame cleanup failed while disabling HDR: {release_err}");
|
||||
}
|
||||
self.duplication = ComPtr(ptr::null_mut());
|
||||
let mut duplication = ptr::null_mut();
|
||||
let result = wrap_hresult(
|
||||
(*self.display.inner.0).DuplicateOutput(self.device.0 as *mut _, &mut duplication),
|
||||
);
|
||||
if let Err(err) = result {
|
||||
if self.set_gdi() {
|
||||
return Err(io::ErrorKind::WouldBlock.into());
|
||||
}
|
||||
return Err(err);
|
||||
}
|
||||
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,
|
||||
@@ -339,6 +438,9 @@ impl Capturer {
|
||||
}
|
||||
|
||||
unsafe fn load_frame(&mut self, timeout: UINT) -> io::Result<(*const u8, i32)> {
|
||||
if self.duplication.0.is_null() {
|
||||
return Err(io::ErrorKind::AddrNotAvailable.into());
|
||||
}
|
||||
let mut frame = ptr::null_mut();
|
||||
#[allow(invalid_value)]
|
||||
let mut info = mem::MaybeUninit::uninit().assume_init();
|
||||
@@ -377,6 +479,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;
|
||||
@@ -397,7 +505,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)
|
||||
}
|
||||
@@ -505,6 +613,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,
|
||||
|
||||
Reference in new issue
Block a user