From a02014ec19ded441d5f1bee914cd0585d32ca6c3 Mon Sep 17 00:00:00 2001 From: enricobuehler Date: Wed, 12 Aug 2026 18:02:15 +0200 Subject: [PATCH 1/4] fix(pf-vkdecode): treat an over-declared stream level as a clamp, not a refusal MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A 2026-08-12 field report (RTX 5060 client): every HEVC session demoted to D3D11VA with 81 "outside device caps: stream level (Std code point 12) above the device's maxLevelIdc (H.265 Std level 11)" refusals — the host's AMF encoder stamps general_level_idc 6.2 (the codec maximum) on a 4K120 stream that needs 5.2, and NVIDIA's driver caps H.265 decode at 6.1. The hardware decodes the actual stream trivially; only the declaration was oversized. AV1 passed the same gate, which is why "native-vulkan runs only with AV1". The declared level is a claim, and the stream's real demands are enforced where they are physical facts — coded extent and DPB depth, both checked at session build. So the up-front level gate (H.264 + H.265) now warns once and proceeds, and every SPS/VPS handed to the Vulkan parameters object has its level clamped to the device ceiling (a set above maxLevelIdc is invalid usage). AV1's gate is untouched: its code space is the bitstream's own and no over-declaration has been seen in the field. Verified on .173 (RTX 4090, driver 610.88): HEVC and AV1 both decode on the native Vulkan rung at 60 fps against an NVENC host; unit tests pin the clamp (lowers, only lowers, mutates the driver-visible block in place). --- crates/pf-vkdecode/src/decoder.rs | 34 ++++++++++----- crates/pf-vkdecode/src/decoder_h265.rs | 36 +++++++++++----- crates/pf-vkdecode/src/params.rs | 31 ++++++++++++++ crates/pf-vkdecode/src/params_h265.rs | 59 ++++++++++++++++++++++++++ crates/pf-vkdecode/src/session.rs | 12 +++++- crates/pf-vkdecode/src/session_h265.rs | 22 ++++++++-- 6 files changed, 168 insertions(+), 26 deletions(-) diff --git a/crates/pf-vkdecode/src/decoder.rs b/crates/pf-vkdecode/src/decoder.rs index d5fafc0b..bf0854b8 100644 --- a/crates/pf-vkdecode/src/decoder.rs +++ b/crates/pf-vkdecode/src/decoder.rs @@ -46,6 +46,7 @@ use pf_bitstream::h264::PlanError; use pf_bitstream::h264::PlanWarning; use tracing::debug; use tracing::trace; +use tracing::warn; use crate::caps::derive_caps; use crate::caps::query_h264_caps; @@ -685,6 +686,9 @@ pub struct VkH264Decoder { /// Session generation: bumped on every rebuild, stamped into frames. generation: u64, device_lost: bool, + /// The over-declared-level warning has fired (once per decoder — the condition + /// is a property of the stream's SPS, so repeating it per AU is noise). + level_clamp_warned: bool, } impl VkH264Decoder { @@ -723,6 +727,7 @@ impl VkH264Decoder { decoded: 0, generation: 0, device_lost: false, + level_clamp_warned: false, }) } @@ -1365,18 +1370,26 @@ impl VkH264Decoder { unsafe { query_h264_caps(&self.dev, std_profile) }.map_err(VkDecodeError::from)?; self.caps = Some((std_profile, derive_caps(&raw)?)); } - // The level gate: a stream above the device's maxLevelIdc is refused up - // front (within one codec the Std code points ascend with the level, so - // the comparison is numeric), never submitted on a hope. The ceiling came - // from an H.264 caps query, so it is compared against an H.264 code point - // — the pairing MaxLevelIdc's tag exists to keep honest. + // The declared level vs the device ceiling: a DECLARED level above + // `maxLevelIdc` is NOT a refusal — encoders over-claim levels in the wild + // (the H.265 twin carries the field evidence: AMF stamps the codec + // maximum). The stream's REAL demands are enforced where they are + // physical facts — coded extent and DPB depth, checked in + // `rebuild_state` — and the session's parameter sets are clamped to the + // ceiling (`SessionConfig::max_level_idc`) so the driver is never handed + // a level above its caps. The comparison stays within one codec's Std + // code space (`MaxLevelIdc`'s tag carries that argument). let caps_max_level = self.caps.as_ref().expect("queried above").1.max_level_idc; let stream_level = level_to_std(plan.picture.level_idc); - if stream_level > caps_max_level.code_point() { - return Err(VkDecodeError::Unsupported(format!( - "stream level (Std code point {stream_level}) above the device's \ - maxLevelIdc ({caps_max_level})" - ))); + if stream_level > caps_max_level.code_point() && !self.level_clamp_warned { + self.level_clamp_warned = true; + warn!( + stream_level, + ceiling = %caps_max_level, + "stream declares an H.264 level above the device ceiling — the \ + declared level is advisory (over-declared by some encoders); \ + proceeding with the parameter sets clamped to the ceiling" + ); } let coded = vk::Extent2D { width: plan.picture.coded_width, @@ -1488,6 +1501,7 @@ impl VkH264Decoder { max_dpb_slots: required_slots, max_active_references: (required_slots - 1).min(caps.max_active_references), std_profile_idc: std_profile, + max_level_idc: caps.max_level_idc.code_point(), }; let mut pool_plan = plan_pools(caps, required_slots); // TEST-ONLY readback hook: the GPU parity test (tests/gpu_parity.rs) diff --git a/crates/pf-vkdecode/src/decoder_h265.rs b/crates/pf-vkdecode/src/decoder_h265.rs index 893dde8d..a3713085 100644 --- a/crates/pf-vkdecode/src/decoder_h265.rs +++ b/crates/pf-vkdecode/src/decoder_h265.rs @@ -57,6 +57,7 @@ use pf_bitstream::h265::PlanError; use pf_bitstream::h265::PlanWarning; use tracing::debug; use tracing::trace; +use tracing::warn; use crate::caps::DecodeCaps; use crate::caps::DecodeProfile; @@ -219,6 +220,9 @@ pub struct VkH265Decoder { /// Recovery owed after a failed AU whose planning had already advanced /// ([`RecoveryLatch`] docs for the whole argument). recovery: RecoveryLatch, + /// The over-declared-level warning has fired (once per decoder — the condition + /// is a property of the stream's SPS, so repeating it per AU is noise). + level_clamp_warned: bool, } impl VkH265Decoder { @@ -266,6 +270,7 @@ impl VkH265Decoder { generation: 0, device_lost: false, recovery: RecoveryLatch::default(), + level_clamp_warned: false, }) } @@ -1004,18 +1009,28 @@ impl VkH265Decoder { let raw = unsafe { query_h265_caps(&self.dev, key) }.map_err(VkDecodeError::from)?; self.caps = Some((key, derive_caps_h265(&raw, wanted)?)); } - // The level gate: a stream above the device's maxLevelIdc is refused up - // front (within one codec the Std code points ascend with the level, so - // the comparison is numeric), never submitted on a hope. The ceiling came - // from an H.265 caps query, so it is compared against an H.265 code point - // — the pairing MaxLevelIdc's tag exists to keep honest. + // The declared level vs the device ceiling: a DECLARED level above + // `maxLevelIdc` is NOT a refusal. The level in an SPS is a claim, and + // encoders over-claim in the wild — AMF stamps 6.2 (the codec maximum) + // on 4K120 streams that need 5.2, which on an RTX 5060 (ceiling 6.1) + // demoted every HEVC session to D3D11VA (2026-08-12 field report). The + // stream's REAL demands are enforced where they are physical facts: + // coded extent and DPB depth, checked in `rebuild_state`. The session's + // parameter sets are clamped to the ceiling (`SessionConfigH265:: + // max_level_idc`) so the driver is never handed a level above its caps, + // and the comparison stays within one codec's Std code space + // (`MaxLevelIdc`'s tag carries that argument). let caps_max_level = self.caps.as_ref().expect("queried above").1.max_level_idc; let stream_level = level_to_std_h265(plan.picture.level_idc); - if stream_level > caps_max_level.code_point() { - return Err(VkDecodeError::Unsupported(format!( - "stream level (Std code point {stream_level}) above the device's \ - maxLevelIdc ({caps_max_level})" - ))); + if stream_level > caps_max_level.code_point() && !self.level_clamp_warned { + self.level_clamp_warned = true; + warn!( + stream_level, + ceiling = %caps_max_level, + "stream declares an H.265 level above the device ceiling — the \ + declared level is advisory (over-declared by some encoders); \ + proceeding with the parameter sets clamped to the ceiling" + ); } let coded = vk::Extent2D { width: plan.picture.coded_width, @@ -1108,6 +1123,7 @@ impl VkH265Decoder { max_dpb_slots: required_slots, max_active_references: (required_slots - 1).min(caps.max_active_references), profile: key, + max_level_idc: caps.max_level_idc.code_point(), }; let mut pool_plan = plan_pools(caps, required_slots); // TEST-ONLY readback hook, exactly as the H.264 decoder's: the parity diff --git a/crates/pf-vkdecode/src/params.rs b/crates/pf-vkdecode/src/params.rs index 522ab0ab..28bbd8c6 100644 --- a/crates/pf-vkdecode/src/params.rs +++ b/crates/pf-vkdecode/src/params.rs @@ -115,6 +115,17 @@ impl OwnedStdSps { pub fn std(&self) -> &hh::StdVideoH264SequenceParameterSet { &self.std } + + /// Lower `level_idc` to `max` when the stream declares a higher one. The + /// declared level is a claim encoders over-state in the wild, and a set above + /// the device's `maxLevelIdc` is invalid usage; the stream's real demands are + /// enforced by the session's coded extent and DPB depth. The "no mutation" + /// contract above is about a LIVE object's blocks — this runs before handover. + pub(crate) fn clamp_level(&mut self, max: hh::StdVideoH264LevelIdc) { + if self.std.level_idc > max { + self.std.level_idc = max; + } + } } /// The converted PPS plus the scaling-list allocation its `pScalingLists` targets. @@ -831,4 +842,24 @@ mod tests { ParamsError::InvalidWeightedBipredIdc(3) ); } + + /// The over-declared-level clamp ([`OwnedStdSps::clamp_level`]): lowering + /// writes the ceiling into the Std SPS; a ceiling at or above the declared + /// level changes nothing. + #[test] + fn clamp_level_lowers_and_only_lowers() { + let sps = full_sps(); + let declared = level_to_std(sps.level_idc); + + let mut owned = sps_to_std(&sps).unwrap(); + assert_eq!(owned.std().level_idc, declared); + // A ceiling above the declared level is a no-op. + owned.clamp_level(hh::StdVideoH264LevelIdc_STD_VIDEO_H264_LEVEL_IDC_6_2); + assert_eq!(owned.std().level_idc, declared); + // A ceiling below it is written through. + let ceiling = hh::StdVideoH264LevelIdc_STD_VIDEO_H264_LEVEL_IDC_3_1; + assert!(ceiling < declared, "fixture declares above 3.1"); + owned.clamp_level(ceiling); + assert_eq!(owned.std().level_idc, ceiling); + } } diff --git a/crates/pf-vkdecode/src/params_h265.rs b/crates/pf-vkdecode/src/params_h265.rs index d96ecc24..45c3db91 100644 --- a/crates/pf-vkdecode/src/params_h265.rs +++ b/crates/pf-vkdecode/src/params_h265.rs @@ -202,6 +202,19 @@ impl OwnedStdH265Vps { pub fn std(&self) -> &hh::StdVideoH265VideoParameterSet { &self.std } + + /// Lower the profile/tier/level block's `general_level_idc` to `max` when the + /// stream declares a higher one. The declared level is a CLAIM, and encoders + /// over-claim in the wild (AMF stamps 6.2 — the codec maximum — on streams that + /// need 5.2); handing the driver a level above its `maxLevelIdc` is invalid + /// usage, while the stream's real demands are enforced by the session's coded + /// extent and DPB depth. The "no mutation" ownership contract is about blocks a + /// LIVE parameters object points at; this runs before the set is handed over. + pub(crate) fn clamp_level(&mut self, max: hh::StdVideoH265LevelIdc) { + if self._ptl_backing.general_level_idc > max { + self._ptl_backing.general_level_idc = max; + } + } } /// The converted SPS plus the heap allocations its embedded pointers target. @@ -229,6 +242,14 @@ impl OwnedStdH265Sps { pub fn std(&self) -> &hh::StdVideoH265SequenceParameterSet { &self.std } + + /// Lower `general_level_idc` to the device ceiling — [`OwnedStdH265Vps::clamp_level`] + /// carries the argument. + pub(crate) fn clamp_level(&mut self, max: hh::StdVideoH265LevelIdc) { + if self._ptl_backing.general_level_idc > max { + self._ptl_backing.general_level_idc = max; + } + } } /// The converted PPS plus the scaling-list allocation its `pScalingLists` @@ -2000,4 +2021,42 @@ mod tests { "the vector opens with VPS + SPS + PPS" ); } + + /// The over-declared-level clamp (the AMF 6.2-on-everything field case): + /// lowering writes the ceiling into the PTL backing the driver will read; + /// a ceiling at or above the declared level changes nothing. + #[test] + fn clamp_level_lowers_the_ptl_and_only_lowers() { + let sps = full_sps(); + let declared = level_to_std(sps.profile_tier_level.general_level_idc); + + let mut owned = sps_to_std_h265(&sps).unwrap(); + // SAFETY: pProfileTierLevel targets `owned`'s boxed backing. + assert_eq!( + unsafe { (*owned.std().pProfileTierLevel).general_level_idc }, + declared + ); + // A ceiling above the declared level is a no-op. + owned.clamp_level(hh::StdVideoH265LevelIdc_STD_VIDEO_H265_LEVEL_IDC_6_2); + // SAFETY: as above. + assert_eq!( + unsafe { (*owned.std().pProfileTierLevel).general_level_idc }, + declared + ); + // A ceiling below it is written through — and the pointer still targets + // the wrapper's own backing (the clamp mutates in place, never re-points). + let ceiling = hh::StdVideoH265LevelIdc_STD_VIDEO_H265_LEVEL_IDC_3_1; + assert!(ceiling < declared, "fixture declares above 3.1"); + owned.clamp_level(ceiling); + // SAFETY: as above. + assert_eq!( + unsafe { (*owned.std().pProfileTierLevel).general_level_idc }, + ceiling + ); + + let mut owned_vps = fallback_vps_from_sps(&sps).unwrap(); + owned_vps.clamp_level(ceiling); + // SAFETY: as above, the VPS wrapper's own backing. + assert!(unsafe { (*owned_vps.std().pProfileTierLevel).general_level_idc } <= ceiling); + } } diff --git a/crates/pf-vkdecode/src/session.rs b/crates/pf-vkdecode/src/session.rs index 773222ad..c88f2421 100644 --- a/crates/pf-vkdecode/src/session.rs +++ b/crates/pf-vkdecode/src/session.rs @@ -168,6 +168,10 @@ pub struct SessionConfig { /// The Std profile the session was created against (a profile change is a /// renegotiation too). pub std_profile_idc: hh::StdVideoH264ProfileIdc, + /// The device's `maxLevelIdc` for this profile (Std code point). Every SPS + /// handed to the parameters object has its declared level clamped to this — + /// see `SessionConfigH265::max_level_idc` for the whole argument. + pub max_level_idc: hh::StdVideoH264LevelIdc, } /// Session creation/parameter failures the decoder maps into its error type. @@ -593,11 +597,14 @@ impl VideoSession { match action { ParamsAction::Current => Ok(()), ParamsAction::Add { add_sps, add_pps } => { - let owned_sps = if add_sps { + let mut owned_sps = if add_sps { Some(sps_to_std(sps)?) } else { None }; + if let Some(s) = owned_sps.as_mut() { + s.clamp_level(self.config.max_level_idc); + } let owned_pps = if add_pps { Some(pps_to_std(pps)?) } else { @@ -643,7 +650,8 @@ impl VideoSession { pps_id = pps.pic_parameter_set_id, "recreating session parameters (content change or capacity)" ); - let owned_sps = sps_to_std(sps)?; + let mut owned_sps = sps_to_std(sps)?; + owned_sps.clamp_level(self.config.max_level_idc); let owned_pps = pps_to_std(pps)?; // SAFETY: fn contract — live device + live session. The wrappers // are MOVED IN and come back owned by the fresh object, so they diff --git a/crates/pf-vkdecode/src/session_h265.rs b/crates/pf-vkdecode/src/session_h265.rs index 345af934..e9227a39 100644 --- a/crates/pf-vkdecode/src/session_h265.rs +++ b/crates/pf-vkdecode/src/session_h265.rs @@ -260,6 +260,12 @@ pub struct SessionConfigH265 { /// format / bit depths, all four of which a stream can renegotiate (an SPS /// switching Main→Main 10 mid-stream is a session rebuild, not an update). pub profile: H265ProfileKey, + /// The device's `maxLevelIdc` for this profile (Std code point). Every VPS/SPS + /// handed to the parameters object has its declared level clamped to this — + /// over-declared levels are common (AMF stamps 6.2 on 4K streams) and a set + /// above the ceiling is invalid usage, while the stream's real demands are + /// already enforced by `max_coded_extent` / `max_dpb_slots`. + pub max_level_idc: hh::StdVideoH265LevelIdc, } /// A live parameters object **and every Std parameter set it was given**, in one @@ -525,12 +531,18 @@ impl VideoSessionH265 { } => { // Every owned wrapper below stays alive until after the update // call: the Std structs embed pointers into their heap blocks. - let owned_vps = if add_vps { Some(vps.to_std()?) } else { None }; - let owned_sps = if add_sps { + let mut owned_vps = if add_vps { Some(vps.to_std()?) } else { None }; + let mut owned_sps = if add_sps { Some(sps_to_std_h265(sps)?) } else { None }; + if let Some(v) = owned_vps.as_mut() { + v.clamp_level(self.config.max_level_idc); + } + if let Some(s) = owned_sps.as_mut() { + s.clamp_level(self.config.max_level_idc); + } let owned_pps = if add_pps { Some(pps_to_std_h265(pps)?) } else { @@ -582,8 +594,10 @@ impl VideoSessionH265 { pps_id = pps.pic_parameter_set_id, "recreating H.265 session parameters (content change or capacity)" ); - let owned_vps = vps.to_std()?; - let owned_sps = sps_to_std_h265(sps)?; + let mut owned_vps = vps.to_std()?; + let mut owned_sps = sps_to_std_h265(sps)?; + owned_vps.clamp_level(self.config.max_level_idc); + owned_sps.clamp_level(self.config.max_level_idc); let owned_pps = pps_to_std_h265(pps)?; // SAFETY: fn contract — live device + live session. The wrappers // are MOVED IN and come back owned by the fresh object, so they -- 2.54.0 From 55a3d8b919396afe0869ad08ac27566dd8f4af12 Mon Sep 17 00:00:00 2001 From: enricobuehler Date: Wed, 12 Aug 2026 18:47:45 +0200 Subject: [PATCH 2/4] fix(clients/session): the edition-2024 session bin cannot build on Windows MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The half of the #177 fallout #180's follow-up could not reach: WP20 wrapped the session bin's single-threaded-startup env writes in the `unsafe {}` blocks edition 2024 requires — under `#![forbid(unsafe_code)]`, which no inner attribute can override, so `punktfunk-client-session` fails with two hard errors on every Windows leg (main push runs 17615/17616 red at Build; verified on .173). Same resolution as #180 gave the GTK shell: `forbid` becomes `deny`, and the three documented SAFETY sites carry the localized `#[allow(unsafe_code)]` pf-update models. --- clients/session/src/main.rs | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/clients/session/src/main.rs b/clients/session/src/main.rs index 1a23d3fb..26d50dca 100644 --- a/clients/session/src/main.rs +++ b/clients/session/src/main.rs @@ -13,7 +13,11 @@ //! the first presented frame, `stats:` lines per 1 s window, one `{"error": …}` / //! `{"ended": …}` JSON line on the way out. Logs go to stderr. Exit codes: 0 clean end, //! 2 connect failed, 3 trust rejected / pairing required, 4 presenter init failed. -#![forbid(unsafe_code)] +// `deny`, not `forbid`: edition 2024 makes `std::env::set_var`/`remove_var` unsafe (WP20 — +// the env-mutation class made visible), and this bin's three single-threaded-startup env +// writes carry documented SAFETY comments under localized `#[allow(unsafe_code)]` (the +// pf-update idiom). A `forbid` cannot be overridden at those sites and refuses the file. +#![deny(unsafe_code)] #[cfg(all(any(target_os = "linux", windows), feature = "ui"))] mod console; @@ -533,6 +537,7 @@ mod session_main { /// initialises, so a call placed after them leaves the triage tool describing a device /// that cannot decode while the streaming path decodes on it. #[cfg(target_os = "linux")] + #[allow(unsafe_code)] // the two SAFETY-commented single-threaded-startup env writes below fn enable_radv_video_decode() { const TOKEN: &str = "video_decode"; match std::env::var("RADV_PERFTEST") { @@ -840,7 +845,10 @@ mod session_main { // SAFETY: still the single-threaded startup stretch of `run()` — the // early-exit probes above return out of the process, and everything that // spawns threads (the session, the console, SDL) only starts below. - unsafe { std::env::set_var(var, value) }; + #[allow(unsafe_code)] + unsafe { + std::env::set_var(var, value) + }; } } } @@ -856,7 +864,10 @@ mod session_main { tracing::info!(var, value = %v, "clearing Steam's SDL device filter"); // SAFETY: as the settings block above — single-threaded startup, before SDL // (the reader of these variables) or any other thread exists. - unsafe { std::env::remove_var(var) }; + #[allow(unsafe_code)] + unsafe { + std::env::remove_var(var) + }; } } -- 2.54.0 From 44fa12a29840c571b3aa4220d9dc910429192da3 Mon Sep 17 00:00:00 2001 From: enricobuehler Date: Wed, 12 Aug 2026 18:24:03 +0200 Subject: [PATCH 3/4] test(pf-vkdecode): bind the PTL level reads so the SAFETY comments precede their unsafe blocks (clippy::undocumented_unsafe_blocks counts nothing inside macro arguments) --- crates/pf-vkdecode/src/params_h265.rs | 21 ++++++++------------- 1 file changed, 8 insertions(+), 13 deletions(-) diff --git a/crates/pf-vkdecode/src/params_h265.rs b/crates/pf-vkdecode/src/params_h265.rs index 45c3db91..d02226cc 100644 --- a/crates/pf-vkdecode/src/params_h265.rs +++ b/crates/pf-vkdecode/src/params_h265.rs @@ -2032,31 +2032,26 @@ mod tests { let mut owned = sps_to_std_h265(&sps).unwrap(); // SAFETY: pProfileTierLevel targets `owned`'s boxed backing. - assert_eq!( - unsafe { (*owned.std().pProfileTierLevel).general_level_idc }, - declared - ); + let level = unsafe { (*owned.std().pProfileTierLevel).general_level_idc }; + assert_eq!(level, declared); // A ceiling above the declared level is a no-op. owned.clamp_level(hh::StdVideoH265LevelIdc_STD_VIDEO_H265_LEVEL_IDC_6_2); // SAFETY: as above. - assert_eq!( - unsafe { (*owned.std().pProfileTierLevel).general_level_idc }, - declared - ); + let level = unsafe { (*owned.std().pProfileTierLevel).general_level_idc }; + assert_eq!(level, declared); // A ceiling below it is written through — and the pointer still targets // the wrapper's own backing (the clamp mutates in place, never re-points). let ceiling = hh::StdVideoH265LevelIdc_STD_VIDEO_H265_LEVEL_IDC_3_1; assert!(ceiling < declared, "fixture declares above 3.1"); owned.clamp_level(ceiling); // SAFETY: as above. - assert_eq!( - unsafe { (*owned.std().pProfileTierLevel).general_level_idc }, - ceiling - ); + let level = unsafe { (*owned.std().pProfileTierLevel).general_level_idc }; + assert_eq!(level, ceiling); let mut owned_vps = fallback_vps_from_sps(&sps).unwrap(); owned_vps.clamp_level(ceiling); // SAFETY: as above, the VPS wrapper's own backing. - assert!(unsafe { (*owned_vps.std().pProfileTierLevel).general_level_idc } <= ceiling); + let vps_level = unsafe { (*owned_vps.std().pProfileTierLevel).general_level_idc }; + assert!(vps_level <= ceiling); } } -- 2.54.0 From faefbae830a8e8d1d1595dc317f2b49ffc19bb6f Mon Sep 17 00:00:00 2001 From: enricobuehler Date: Wed, 12 Aug 2026 18:25:35 +0200 Subject: [PATCH 4/4] =?UTF-8?q?fix(pf-presenter):=20spell=20MAKEINTRESOURC?= =?UTF-8?q?E(1)=20as=20ptr::without=5Fprovenance=20=E2=80=94=20clippy=201.?= =?UTF-8?q?96's=20manual=5Fdangling=5Fptr=20reads=20the=20integer-ordinal?= =?UTF-8?q?=20cast=20as=20a=20dangling=20pointer=20and=20fails=20the=20Win?= =?UTF-8?q?dows=20-D=20warnings=20gate=20(masked=20on=20main=20by=20the=20?= =?UTF-8?q?client=20bins=20failing=20to=20build=20first)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- crates/pf-presenter/src/win32.rs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/crates/pf-presenter/src/win32.rs b/crates/pf-presenter/src/win32.rs index d017f0cb..c7bdc149 100644 --- a/crates/pf-presenter/src/win32.rs +++ b/crates/pf-presenter/src/win32.rs @@ -63,7 +63,17 @@ pub(crate) fn stamp_window_icon(window: &sdl3::video::Window) { let module = GetModuleHandleW(std::ptr::null()); for (which, metric) in [(ICON_SMALL, SM_CXSMICON), (ICON_BIG, SM_CXICON)] { let px = GetSystemMetrics(metric); - let icon = LoadImageW(module, 1 as *const u16, IMAGE_ICON, px, px, LR_DEFAULTCOLOR); + // MAKEINTRESOURCE(1): an integer resource ordinal smuggled through the name + // pointer, never dereferenced — `without_provenance` says exactly that (and + // `1 as *const u16` reads as a dangling pointer to clippy 1.96). + let icon = LoadImageW( + module, + std::ptr::without_provenance(1), + IMAGE_ICON, + px, + px, + LR_DEFAULTCOLOR, + ); if !icon.is_null() { SendMessageW(hwnd, WM_SETICON, which as WPARAM, icon as LPARAM); } -- 2.54.0