diff --git a/crates/pf-capture/src/windows/dxgi.rs b/crates/pf-capture/src/windows/dxgi.rs index e0eefbbf..1a0cd1b9 100644 --- a/crates/pf-capture/src/windows/dxgi.rs +++ b/crates/pf-capture/src/windows/dxgi.rs @@ -387,11 +387,20 @@ pub(crate) struct HdrP010Converter { cbuf: ID3D11Buffer, } +// The three converters' methods below are SAFE fns. They were `unsafe fn` because their bodies are +// D3D11 FFI, which is not the same thing as having a caller contract: every parameter is a borrowed +// windows-rs COM wrapper (`&ID3D11Device`, `&ID3D11DeviceContext`, `&ID3D11Texture2D`, the views) or +// a plain `u32`/`bool`, each body builds its own descriptors from those, and every created interface +// owns its reference. There is nothing a caller can pass that makes them unsound, and the proofs the +// markers carried said exactly that — "`?`-checked D3D11 methods on the live `device` borrow" — which +// is a description of the body, not an obligation. `unsafe` now marks the FFI inside them, where the +// blocks and their proofs already were. `compile_shader` KEEPS its marker: it takes `PCSTR`, a raw +// pointer the caller must guarantee is a NUL-terminated literal. impl HdrP010Converter { /// `w`/`h` are the SOURCE dimensions this converter's chroma pass will sample, baked into the /// immutable constant buffer. Rebuild the converter if they change (the IDD capturer's /// `recreate_ring` already drops it). - pub(crate) unsafe fn new(device: &ID3D11Device, w: u32, h: u32) -> Result { + pub(crate) fn new(device: &ID3D11Device, w: u32, h: u32) -> Result { // SAFETY: every call is a `?`-checked D3D11 method on the live `device` borrow, over // fully-initialized stack descriptors and live `Option` out-params; `compile_shader` receives // `s!()` literals (its contract). Each created COM interface owns its own reference, and no @@ -458,7 +467,7 @@ impl HdrP010Converter { /// Called ONCE PER OUT-RING SLOT by the owner of the P010 textures, not per frame — see /// [`Self::convert`]. Fails when the driver rejects a planar RTV, which is the one hard /// requirement of this whole path (a D3D11.3+ runtime plus driver support). - pub(crate) unsafe fn plane_rtv( + pub(crate) fn plane_rtv( device: &ID3D11Device, dst: &ID3D11Texture2D, format: DXGI_FORMAT, @@ -498,7 +507,7 @@ impl HdrP010Converter { /// ring slot's keyed-mutex hold, i.e. time the DRIVER spent blocked on that slot. Both are /// lifetime-of-mode facts: the views belong to the out-ring slot (built in `ensure_out_ring`), /// the buffer to this converter, which is already rebuilt on every mode change. - pub(crate) unsafe fn convert( + pub(crate) fn convert( &self, ctx: &ID3D11DeviceContext, src_srv: &ID3D11ShaderResourceView, @@ -693,7 +702,7 @@ pub(crate) struct BgraToYuvPlanes { } impl BgraToYuvPlanes { - pub(crate) unsafe fn new(device: &ID3D11Device, hdr: bool, chroma444: bool) -> Result { + pub(crate) fn new(device: &ID3D11Device, hdr: bool, chroma444: bool) -> Result { // SAFETY: as `HdrP010Converter::new` — `?`-checked D3D11 shader creation on the live // `device` borrow, with `s!()` literals into `compile_shader` and live out-params. unsafe { @@ -731,7 +740,7 @@ impl BgraToYuvPlanes { /// texture) + `cbcr_rtv` (half- or full-res CbCr texture per the constructed mode). Two opaque /// passes; `w`/`h` are the full luma dims (even for 4:2:0). #[allow(clippy::too_many_arguments)] - pub(crate) unsafe fn convert( + pub(crate) fn convert( &self, ctx: &ID3D11DeviceContext, src_srv: &ID3D11ShaderResourceView, @@ -828,7 +837,7 @@ impl VideoConverter { /// linear (the HDR ring, used by a PyroWave session that tone-maps the HDR desktop down to the /// 8-bit wavelet stream). The output is always studio-range BT.709 NV12 — the P010/BT.2020 HDR /// path is [`HdrP010Converter`]'s job, never this one. - pub(crate) unsafe fn new( + pub(crate) fn new( device: &ID3D11Device, context: &ID3D11DeviceContext, width: u32, @@ -897,11 +906,7 @@ impl VideoConverter { /// Convert `input` (BGRA, or scRGB FP16 for a converter built with `scrgb_input`) → `output` /// (NV12, BT.709 studio-range — see the type doc: never P010) on the video engine. Views are /// created per call (cheap relative to the Blt) so the input texture can vary frame to frame. - pub(crate) unsafe fn convert( - &self, - input: &ID3D11Texture2D, - output: &ID3D11Texture2D, - ) -> Result<()> { + pub(crate) fn convert(&self, input: &ID3D11Texture2D, output: &ID3D11Texture2D) -> Result<()> { // SAFETY: both view creations are `?`-checked calls on `self.vdev` with fully-initialized // stack descriptors and live out-params. `stream.pInputSurface` is a `ManuallyDrop` of the // input view just created: `VideoProcessorBlt` only BORROWS it (a COM in-param never transfers diff --git a/crates/pf-capture/src/windows/idd_push.rs b/crates/pf-capture/src/windows/idd_push.rs index 54ff1de6..db39c2ac 100644 --- a/crates/pf-capture/src/windows/idd_push.rs +++ b/crates/pf-capture/src/windows/idd_push.rs @@ -648,11 +648,7 @@ impl IddPushCapturer { // nothing downstream could detect the mismatch, because every field it would compare against // had already been moved. Nothing below this line fails. let fmt = Self::ring_format_for(new_display_hdr); - // SAFETY: `create_ring_slots` is an `unsafe fn` (it makes D3D11/DXGI COM calls); we pass a live - // borrow of `self.device` (the capturer's own device, on which the slots are created) plus plain - // `u32`/`DXGI_FORMAT` values, and `?` propagates any failure before the slots are used. Every - // returned slot's texture + keyed mutex belongs to that same `self.device`. - let new_slots = unsafe { Self::create_ring_slots(&self.device, new_w, new_h, fmt)? }; + let new_slots = Self::create_ring_slots(&self.device, new_w, new_h, fmt)?; self.display_hdr = new_display_hdr; self.width = new_w; self.height = new_h; @@ -969,11 +965,11 @@ impl IddPushCapturer { /// mid-session mode swap. fn ensure_pyro_conv(&mut self) -> Result<()> { if self.pyro_conv.is_none() { - // SAFETY: `BgraToYuvPlanes::new` compiles D3D11 shaders on `self.device`; `?` propagates - // failure before it is stored. - self.pyro_conv = Some(unsafe { - BgraToYuvPlanes::new(&self.device, self.display_hdr, self.want_444)? - }); + self.pyro_conv = Some(BgraToYuvPlanes::new( + &self.device, + self.display_hdr, + self.want_444, + )?); } Ok(()) } @@ -984,22 +980,22 @@ impl IddPushCapturer { fn ensure_converter(&mut self) -> Result<()> { if self.display_hdr { if self.hdr_p010_conv.is_none() { - // SAFETY: `HdrP010Converter::new` is `unsafe` (it compiles D3D11 shaders + creates - // resources); we pass a live borrow of `self.device`, the device the converter's resources - // belong to, and `?` propagates any failure before the converter is stored. - self.hdr_p010_conv = - Some(unsafe { HdrP010Converter::new(&self.device, self.width, self.height)? }); + self.hdr_p010_conv = Some(HdrP010Converter::new( + &self.device, + self.width, + self.height, + )?); } } else if self.want_444 { // Full-chroma passthrough — no conversion resources to build. } else if self.video_conv.is_none() { - // SAFETY: `VideoConverter::new` is `unsafe` (it sets up the D3D11 VIDEO processor); we pass live - // borrows of `self.device` + its immediate `self.context` (single-threaded, this thread) plus - // plain `u32` dimensions, and `?` propagates any failure before it is stored. The converter's - // resources belong to that same device/context. - self.video_conv = Some(unsafe { - VideoConverter::new(&self.device, &self.context, self.width, self.height, false)? - }); + self.video_conv = Some(VideoConverter::new( + &self.device, + &self.context, + self.width, + self.height, + false, + )?); } Ok(()) } diff --git a/crates/pf-capture/src/windows/idd_push/cursor_blend.rs b/crates/pf-capture/src/windows/idd_push/cursor_blend.rs index 77b35b01..cf389496 100644 --- a/crates/pf-capture/src/windows/idd_push/cursor_blend.rs +++ b/crates/pf-capture/src/windows/idd_push/cursor_blend.rs @@ -56,7 +56,7 @@ pub(super) struct CursorBlendPass { } impl CursorBlendPass { - pub(super) unsafe fn new(device: &ID3D11Device) -> Result { + pub(super) fn new(device: &ID3D11Device) -> Result { // SAFETY: `?`-checked D3D11 resource creation on the live `device` borrow, over // fully-initialized stack descriptors and live out-params; `compile_shader` receives `s!()` // literals (its contract). @@ -116,11 +116,7 @@ impl CursorBlendPass { } /// Upload `ov`'s bitmap if its serial is new; reuse the cached SRV otherwise. - unsafe fn ensure_shape( - &mut self, - device: &ID3D11Device, - ov: &pf_frame::CursorOverlay, - ) -> Result<()> { + fn ensure_shape(&mut self, device: &ID3D11Device, ov: &pf_frame::CursorOverlay) -> Result<()> { // SAFETY: `CreateTexture2D`/`CreateShaderResourceView` are `?`-checked calls on the live // `device` borrow. `init.pSysMem` points into `ov.rgba`, which the length check above proves // holds at least `ov.w * ov.h * 4` bytes for the declared `SysMemPitch = ov.w * 4`, and which @@ -170,7 +166,7 @@ impl CursorBlendPass { /// composition) — linearize and scale to the target's SDR white. The quad is placed purely /// via the viewport (the fullscreen-triangle VS fills whatever viewport is set), clipped by /// the target automatically. - pub(super) unsafe fn blend( + pub(super) fn blend( &mut self, device: &ID3D11Device, ctx: &ID3D11DeviceContext, diff --git a/crates/pf-capture/src/windows/idd_push/open.rs b/crates/pf-capture/src/windows/idd_push/open.rs index a8ed1470..6103bd44 100644 --- a/crates/pf-capture/src/windows/idd_push/open.rs +++ b/crates/pf-capture/src/windows/idd_push/open.rs @@ -85,7 +85,7 @@ impl IddPushCapturer { /// to the display's composition format — FP16 in HDR, BGRA in SDR). Each is shared through an /// UNNAMED NT handle (nothing to open by name — the sealed channel); the driver reaches it only via /// the duplicate the [`ChannelBroker`] sends after the ring is published. - pub(super) unsafe fn create_ring_slots( + pub(super) fn create_ring_slots( device: &ID3D11Device, w: u32, h: u32,