feat(encode/pyrowave): log which GPU pyrowave picked — the selection stays put, by decision
ci / web (push) Successful in 55s
ci / docs-site (push) Successful in 1m12s
apple / swift (push) Successful in 5m20s
ci / rust (push) Failing after 5m55s
android / android (push) Failing after 5m59s
deb / build-publish (push) Failing after 5m1s
docker / build-push (--build-arg FEDORA_VERSION=44, ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm) (push) Successful in 10s
decky / build-publish (push) Successful in 17s
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Successful in 8s
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 8s
docker / build-push (ci, ci/rust-ci-noble.Dockerfile, punktfunk-rust-ci-noble) (push) Successful in 10s
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 9s
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 10s
ci / bench (push) Successful in 7m17s
ci / rust-arm64 (push) Successful in 9m40s
docker / deploy-docs (push) Successful in 26s
docker / build-push-arm64cross (push) Successful in 4m9s
deb / build-publish-host (push) Successful in 10m32s
arch / build-publish (push) Failing after 12m34s
rpm / build-publish (43, bazzite, punktfunk-fedora-rpm) (push) Failing after 5m58s
rpm / build-publish (44, fedora-44, punktfunk-fedora44-rpm) (push) Failing after 5m50s
deb / build-publish-client-arm64 (push) Failing after 7m10s
windows-host / package (push) Successful in 17m27s
apple / screenshots (push) Successful in 23m26s
ci / web (push) Successful in 55s
ci / docs-site (push) Successful in 1m12s
apple / swift (push) Successful in 5m20s
ci / rust (push) Failing after 5m55s
android / android (push) Failing after 5m59s
deb / build-publish (push) Failing after 5m1s
docker / build-push (--build-arg FEDORA_VERSION=44, ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm) (push) Successful in 10s
decky / build-publish (push) Successful in 17s
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Successful in 8s
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 8s
docker / build-push (ci, ci/rust-ci-noble.Dockerfile, punktfunk-rust-ci-noble) (push) Successful in 10s
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 9s
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 10s
ci / bench (push) Successful in 7m17s
ci / rust-arm64 (push) Successful in 9m40s
docker / deploy-docs (push) Successful in 26s
docker / build-push-arm64cross (push) Successful in 4m9s
deb / build-publish-host (push) Successful in 10m32s
arch / build-publish (push) Failing after 12m34s
rpm / build-publish (43, bazzite, punktfunk-fedora-rpm) (push) Failing after 5m58s
rpm / build-publish (44, fedora-44, punktfunk-fedora44-rpm) (push) Failing after 5m50s
deb / build-publish-client-arm64 (push) Failing after 7m10s
windows-host / package (push) Successful in 17m27s
apple / screenshots (push) Successful in 23m26s
WP4.5's device-selection half, closed as the observability intermediate after TWO selection designs died in adversarial review: - Attempt #1 (d26bcf05, withdrawn): match pf_gpu::selected_gpu(). Its Linux auto arm answers "the NVIDIA GPU" whenever /dev/nvidiactl exists, moving the encoder off the iGPU that can import the compositor's dmabufs on an Intel-compositor + NVIDIA-present laptop — import failures feed the process-wide raw-dmabuf latch, which never un-latches. - Attempt #2 (this session, withdrawn before commit): anchor on the PUNKTFUNK_RENDER_NODE-else-renderD128 node via VK_EXT_physical_device_drm. Render minors are driver-BIND-ORDER artifacts, not display topology: on the common AMD-iGPU + NVIDIA-display desktop, in-tree amdgpu binds before out-of-tree nvidia, so the anchor deterministically picks the idle iGPU while the compositor allocates on NVIDIA — the same latch, opposite polarity, behind a success-looking log. The correct oracle is evidence of which device ALLOCATED the capture buffers — producer identity from the capture negotiation, threaded per session into this open. Until that plumbing exists, selection stays first-usable, both call sites still share one selector (pure over the device list, so capture_modifiers and open_inner cannot diverge — including across an in-place resize's re-open, which does not renegotiate capture), and the open logs ONE greppable line: picked vendor/device, the anchor node and its owner (DRM render major/minor, VK_EXT_pci_bus_info fallback), and the console's selected GPU. A wrong-device session on a multi-GPU host used to be completely invisible; a field report can now show it. No WARN arm on purpose: the wrong-pick direction inverts between the laptop and desktop topologies, so a mismatch is not evidence of a wrong pick, and a warning that fires forever on healthy hosts teaches people to ignore warnings. Decision recorded against the audit's framing: manual console GPU selection stays unhonored by pyrowave on Linux (the Windows twin honors it) — honoring console-mutable state without per-session threading is what made attempt #1 unsafe. Verified on the 780M: the line resolves all three identities (1002:15bf x3, DRM-props match live); full pyrowave on-glass suite green; selection behaviour byte-for-byte unchanged. WP4.5 (device half). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -75,22 +75,9 @@ pub(crate) fn capture_modifiers(fourcc: u32) -> Vec<u64> {
|
|||||||
) else {
|
) else {
|
||||||
return Vec::new();
|
return Vec::new();
|
||||||
};
|
};
|
||||||
// Same device selection as `open_inner`: the first real GPU with graphics+compute.
|
// The SAME device selection `open_inner` uses — these modifiers are what the capturer
|
||||||
let pd = instance
|
// will allocate against, so the two must never diverge (see `select_physical_device`).
|
||||||
.enumerate_physical_devices()
|
let pd = select_physical_device(&instance).ok().map(|p| p.pd);
|
||||||
.unwrap_or_default()
|
|
||||||
.into_iter()
|
|
||||||
.find(|&pd| {
|
|
||||||
instance.get_physical_device_properties(pd).device_type
|
|
||||||
!= vk::PhysicalDeviceType::CPU
|
|
||||||
&& instance
|
|
||||||
.get_physical_device_queue_family_properties(pd)
|
|
||||||
.iter()
|
|
||||||
.any(|q| {
|
|
||||||
q.queue_flags
|
|
||||||
.contains(vk::QueueFlags::GRAPHICS | vk::QueueFlags::COMPUTE)
|
|
||||||
})
|
|
||||||
});
|
|
||||||
let mods = pd
|
let mods = pd
|
||||||
.map(|pd| {
|
.map(|pd| {
|
||||||
let mut list = vk::DrmFormatModifierPropertiesListEXT::default();
|
let mut list = vk::DrmFormatModifierPropertiesListEXT::default();
|
||||||
@@ -119,6 +106,198 @@ pub(crate) fn capture_modifiers(fourcc: u32) -> Vec<u64> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The render node whose owner the WP4.5 observability line names alongside the picked device:
|
||||||
|
/// `PUNKTFUNK_RENDER_NODE` (the house DRM-node override) else `/dev/dri/renderD128`.
|
||||||
|
/// **Log-only** — see [`select_physical_device`] for why no oracle, this one included, is
|
||||||
|
/// allowed to CHANGE the selection.
|
||||||
|
fn capture_anchor_node() -> std::path::PathBuf {
|
||||||
|
std::env::var("PUNKTFUNK_RENDER_NODE")
|
||||||
|
.ok()
|
||||||
|
.map(|s| s.trim().to_string())
|
||||||
|
.filter(|s| !s.is_empty())
|
||||||
|
.map(std::path::PathBuf::from)
|
||||||
|
.unwrap_or_else(|| std::path::PathBuf::from("/dev/dri/renderD128"))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// `(major, minor)` of a device node, split the way `VkPhysicalDeviceDrmPropertiesEXT` reports
|
||||||
|
/// its render node (glibc `gnu_dev_major`/`gnu_dev_minor` encoding).
|
||||||
|
fn node_rdev(path: &std::path::Path) -> Option<(i64, i64)> {
|
||||||
|
use std::os::unix::fs::MetadataExt;
|
||||||
|
let rdev = std::fs::metadata(path).ok()?.rdev();
|
||||||
|
let major = ((rdev >> 8) & 0xfff) | ((rdev >> 32) & !0xfffu64);
|
||||||
|
let minor = (rdev & 0xff) | ((rdev >> 12) & !0xffu64);
|
||||||
|
Some((major as i64, minor as i64))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The node's PCI address `(domain, bus, device, function)` from sysfs — the
|
||||||
|
/// `VK_EXT_pci_bus_info` fallback for drivers without `VK_EXT_physical_device_drm`.
|
||||||
|
fn node_pci_address(path: &std::path::Path) -> Option<(u32, u32, u32, u32)> {
|
||||||
|
let node = path.file_name()?.to_str()?;
|
||||||
|
let dev = std::fs::canonicalize(format!("/sys/class/drm/{node}/device")).ok()?;
|
||||||
|
let addr = dev.file_name()?.to_str()?; // e.g. "0000:01:00.0"
|
||||||
|
let (rest, func) = addr.rsplit_once('.')?;
|
||||||
|
let mut parts = rest.split(':');
|
||||||
|
let domain = u32::from_str_radix(parts.next()?, 16).ok()?;
|
||||||
|
let bus = u32::from_str_radix(parts.next()?, 16).ok()?;
|
||||||
|
let device = u32::from_str_radix(parts.next()?, 16).ok()?;
|
||||||
|
Some((domain, bus, device, u32::from_str_radix(func, 16).ok()?))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The Vulkan features pyrowave's encoder documents as required (pyrowave.h): shaderInt16,
|
||||||
|
/// storageBuffer8BitAccess, timeline semaphores, subgroup size control (1.3 core);
|
||||||
|
/// shaderFloat16 is optional. Checked AFTER selection, as it always was — folding it into the
|
||||||
|
/// selection predicate was considered for WP4.5 and rejected: a fall-through to a non-display
|
||||||
|
/// GPU can strand the session on a device that cannot import the capturer's buffers, which
|
||||||
|
/// feeds the process-wide raw-dmabuf latch, while the hard `bail!` is an immediate,
|
||||||
|
/// diagnosable, latch-free failure the session layer renegotiates around.
|
||||||
|
///
|
||||||
|
/// # Safety
|
||||||
|
/// `instance` must be live; issues only physical-device feature queries.
|
||||||
|
unsafe fn missing_features(instance: &ash::Instance, pd: vk::PhysicalDevice) -> Vec<&'static str> {
|
||||||
|
let mut have12 = vk::PhysicalDeviceVulkan12Features::default();
|
||||||
|
let mut have13 = vk::PhysicalDeviceVulkan13Features::default();
|
||||||
|
let mut have2 = vk::PhysicalDeviceFeatures2::default()
|
||||||
|
.push_next(&mut have12)
|
||||||
|
.push_next(&mut have13);
|
||||||
|
instance.get_physical_device_features2(pd, &mut have2);
|
||||||
|
[
|
||||||
|
(have2.features.shader_int16 == vk::TRUE, "shaderInt16"),
|
||||||
|
(
|
||||||
|
have12.storage_buffer8_bit_access == vk::TRUE,
|
||||||
|
"storageBuffer8BitAccess",
|
||||||
|
),
|
||||||
|
(have12.timeline_semaphore == vk::TRUE, "timelineSemaphore"),
|
||||||
|
(
|
||||||
|
have13.subgroup_size_control == vk::TRUE,
|
||||||
|
"subgroupSizeControl",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
have13.compute_full_subgroups == vk::TRUE,
|
||||||
|
"computeFullSubgroups",
|
||||||
|
),
|
||||||
|
(have13.synchronization2 == vk::TRUE, "synchronization2"),
|
||||||
|
]
|
||||||
|
.iter()
|
||||||
|
.filter(|(ok, _)| !ok)
|
||||||
|
.map(|(_, n)| *n)
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Does `pd` own the anchor render node? `VK_EXT_physical_device_drm`'s render major/minor is
|
||||||
|
/// the primary identity (unique per GPU — it disambiguates twin-model GPUs that
|
||||||
|
/// `(vendor, device)` cannot); `VK_EXT_pci_bus_info` against the node's sysfs PCI address is the
|
||||||
|
/// fallback. A device advertising neither extension never matches.
|
||||||
|
///
|
||||||
|
/// # Safety
|
||||||
|
/// `instance` must be live; issues only physical-device property queries.
|
||||||
|
unsafe fn device_owns_node(
|
||||||
|
instance: &ash::Instance,
|
||||||
|
pd: vk::PhysicalDevice,
|
||||||
|
rdev: Option<(i64, i64)>,
|
||||||
|
pci: Option<(u32, u32, u32, u32)>,
|
||||||
|
) -> bool {
|
||||||
|
let exts = instance
|
||||||
|
.enumerate_device_extension_properties(pd)
|
||||||
|
.unwrap_or_default();
|
||||||
|
let has_ext =
|
||||||
|
|name: &std::ffi::CStr| exts.iter().any(|e| e.extension_name_as_c_str() == Ok(name));
|
||||||
|
if let Some((major, minor)) = rdev {
|
||||||
|
if has_ext(ash::ext::physical_device_drm::NAME) {
|
||||||
|
let mut drm = vk::PhysicalDeviceDrmPropertiesEXT::default();
|
||||||
|
let mut p2 = vk::PhysicalDeviceProperties2::default().push_next(&mut drm);
|
||||||
|
instance.get_physical_device_properties2(pd, &mut p2);
|
||||||
|
return drm.has_render == vk::TRUE
|
||||||
|
&& drm.render_major == major
|
||||||
|
&& drm.render_minor == minor;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Some((domain, bus, device, function)) = pci {
|
||||||
|
if has_ext(ash::ext::pci_bus_info::NAME) {
|
||||||
|
let mut pcip = vk::PhysicalDevicePCIBusInfoPropertiesEXT::default();
|
||||||
|
let mut p2 = vk::PhysicalDeviceProperties2::default().push_next(&mut pcip);
|
||||||
|
instance.get_physical_device_properties2(pd, &mut p2);
|
||||||
|
return pcip.pci_domain == domain
|
||||||
|
&& pcip.pci_bus == bus
|
||||||
|
&& pcip.pci_device == device
|
||||||
|
&& pcip.pci_function == function;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The physical device this backend will run on.
|
||||||
|
struct PickedDevice {
|
||||||
|
pd: vk::PhysicalDevice,
|
||||||
|
/// Index of a graphics+compute queue family on `pd` (pyrowave requires a graphics-capable
|
||||||
|
/// queue in the device create info; the CSC + codec run on it).
|
||||||
|
family: u32,
|
||||||
|
vendor_id: u32,
|
||||||
|
device_id: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Pick pyrowave's physical device: the first non-CPU Vulkan device with a graphics+compute
|
||||||
|
/// family — **the pre-WP4.5 behaviour, kept by decision after two withdrawn attempts to
|
||||||
|
/// "fix" it**. What this function must never become:
|
||||||
|
///
|
||||||
|
/// - **Not `pf_gpu::selected_gpu()`** (withdrawn attempt #1): its Linux auto arm answers "the
|
||||||
|
/// NVIDIA GPU" whenever `/dev/nvidiactl` exists, regardless of which GPU the capture pipeline
|
||||||
|
/// runs on. On an Intel-compositor + NVIDIA-present laptop that moves the encoder onto the
|
||||||
|
/// one GPU that CANNOT import the compositor's dmabufs → five same-device rebuilds → the
|
||||||
|
/// process-wide raw-dmabuf latch degrades every later session to CPU capture, permanently.
|
||||||
|
/// - **Not the `/dev/dri/renderD128` render node** (withdrawn attempt #2, this session): render
|
||||||
|
/// minors are driver-BIND-ORDER artifacts, not display topology. On the common
|
||||||
|
/// AMD-iGPU + NVIDIA-display desktop (in-tree amdgpu binds before out-of-tree nvidia),
|
||||||
|
/// renderD128 is the idle iGPU while the compositor allocates on NVIDIA — anchoring there
|
||||||
|
/// deterministically picks a device that cannot import the capturer's buffers and trips the
|
||||||
|
/// same latch, with a success-looking log. The loader's first-device order is an
|
||||||
|
/// ICD-manifest lottery, but on that desktop class it usually lands on the NVIDIA device —
|
||||||
|
/// i.e. the status quo is accidentally right exactly where the anchor is deterministically
|
||||||
|
/// wrong.
|
||||||
|
///
|
||||||
|
/// The only correct oracle is *evidence of which device allocated the capture buffers* —
|
||||||
|
/// producer identity from the actual capture negotiation, threaded per session into this open.
|
||||||
|
/// That is real plumbing (capture and encode negotiate in different crates today) and belongs
|
||||||
|
/// to a change that can be measured on a hybrid rig; until then the selection stays put and
|
||||||
|
/// `open_inner` logs the picked device beside the house's two guesses so a field report can
|
||||||
|
/// finally SHOW a wrong-device topology instead of leaving it invisible.
|
||||||
|
///
|
||||||
|
/// ⚠ Shared by [`capture_modifiers`] and `open_inner` on purpose, and it must stay that way:
|
||||||
|
/// `capture_modifiers` advertises the DRM modifiers the CAPTURER then allocates for. If the two
|
||||||
|
/// disagreed about the device, capture would hand the encoder buffers it cannot import. Both
|
||||||
|
/// call sites being pure functions of the (process-stable) Vulkan device list is what makes the
|
||||||
|
/// agreement hold across an in-place resize's encoder re-open, which does NOT renegotiate
|
||||||
|
/// capture.
|
||||||
|
///
|
||||||
|
/// # Safety
|
||||||
|
/// `instance` must be live; only physical-device property/queue queries are issued against it.
|
||||||
|
unsafe fn select_physical_device(instance: &ash::Instance) -> Result<PickedDevice> {
|
||||||
|
for pd in instance.enumerate_physical_devices()? {
|
||||||
|
let props = instance.get_physical_device_properties(pd);
|
||||||
|
if props.device_type == vk::PhysicalDeviceType::CPU {
|
||||||
|
continue; // skip llvmpipe
|
||||||
|
}
|
||||||
|
let Some(family) = instance
|
||||||
|
.get_physical_device_queue_family_properties(pd)
|
||||||
|
.iter()
|
||||||
|
.position(|q| {
|
||||||
|
q.queue_flags
|
||||||
|
.contains(vk::QueueFlags::GRAPHICS | vk::QueueFlags::COMPUTE)
|
||||||
|
})
|
||||||
|
else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
return Ok(PickedDevice {
|
||||||
|
pd,
|
||||||
|
family: family as u32,
|
||||||
|
vendor_id: props.vendor_id,
|
||||||
|
device_id: props.device_id,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
Err(anyhow::anyhow!(
|
||||||
|
"no Vulkan GPU with a graphics+compute queue"
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
fn pw_check(r: pw::pyrowave_result, what: &str) -> Result<()> {
|
fn pw_check(r: pw::pyrowave_result, what: &str) -> Result<()> {
|
||||||
if r == pw::pyrowave_result_PYROWAVE_SUCCESS {
|
if r == pw::pyrowave_result_PYROWAVE_SUCCESS {
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -292,62 +471,65 @@ impl PyroWaveEncoder {
|
|||||||
// SAFETY: plain physical-device queries on the live instance just created, and a
|
// SAFETY: plain physical-device queries on the live instance just created, and a
|
||||||
// `create_device` whose create-infos are pinned in `hold` for the call's duration.
|
// `create_device` whose create-infos are pinned in `hold` for the call's duration.
|
||||||
let selected = (|| unsafe {
|
let selected = (|| unsafe {
|
||||||
// Pick the first real GPU with a graphics+compute family (pyrowave requires a
|
// The SAME selector `capture_modifiers` uses, so the two can never disagree about
|
||||||
// graphics-capable queue in the device create info; the CSC + codec run on it).
|
// the device (see `select_physical_device` — including why the selection itself is
|
||||||
let (pd, family) = {
|
// deliberately unchanged by WP4.5).
|
||||||
let mut found = None;
|
let picked = select_physical_device(&instance)?;
|
||||||
for pd in instance.enumerate_physical_devices()? {
|
let (pd, family) = (picked.pd, picked.family);
|
||||||
let props = instance.get_physical_device_properties(pd);
|
// WP4.5, observability half (log-only BY DECISION — two selection "fixes" were
|
||||||
if props.device_type == vk::PhysicalDeviceType::CPU {
|
// withdrawn after review; the rationale lives on `select_physical_device`): one
|
||||||
continue; // skip llvmpipe
|
// greppable line naming the picked device beside the house's two guesses at the
|
||||||
}
|
// right one. On a multi-GPU host a wrong-device session used to be completely
|
||||||
let fam = instance
|
// invisible — a field report showed only downstream import failures. NO arm of this
|
||||||
.get_physical_device_queue_family_properties(pd)
|
// is a WARN on purpose: a mismatch between these fields is not evidence of a wrong
|
||||||
.iter()
|
// pick (on an AMD-iGPU + NVIDIA-display desktop the loader's first device is right
|
||||||
.position(|q| {
|
// and renderD128 is wrong; on the hybrid laptop it is the reverse), and a warning
|
||||||
q.queue_flags
|
// that fires forever on healthy hosts only teaches people to ignore warnings.
|
||||||
.contains(vk::QueueFlags::GRAPHICS | vk::QueueFlags::COMPUTE)
|
let anchor = capture_anchor_node();
|
||||||
});
|
let anchor_owner = {
|
||||||
if let Some(f) = fam {
|
let rdev = node_rdev(&anchor);
|
||||||
found = Some((pd, f as u32));
|
let pci = node_pci_address(&anchor);
|
||||||
break;
|
if rdev.is_none() && pci.is_none() {
|
||||||
}
|
"unresolved".to_string()
|
||||||
|
} else {
|
||||||
|
instance
|
||||||
|
.enumerate_physical_devices()
|
||||||
|
.unwrap_or_default()
|
||||||
|
.into_iter()
|
||||||
|
.find(|&o| device_owns_node(&instance, o, rdev, pci))
|
||||||
|
.map(|o| {
|
||||||
|
let p = instance.get_physical_device_properties(o);
|
||||||
|
format!("{:04x}:{:04x}", p.vendor_id, p.device_id)
|
||||||
|
})
|
||||||
|
.unwrap_or_else(|| "unmatched".to_string())
|
||||||
}
|
}
|
||||||
found.context("no Vulkan GPU with a graphics+compute queue")?
|
|
||||||
};
|
};
|
||||||
|
let selected_gpu = pf_gpu::selected_gpu()
|
||||||
|
.map(|s| format!("{:04x}:{:04x}", s.info.vendor_id, s.info.device_id))
|
||||||
|
.unwrap_or_else(|| "none".to_string());
|
||||||
|
tracing::info!(
|
||||||
|
vendor_id = format_args!("{:04x}", picked.vendor_id),
|
||||||
|
device_id = format_args!("{:04x}", picked.device_id),
|
||||||
|
anchor = %anchor.display(),
|
||||||
|
anchor_owner = %anchor_owner,
|
||||||
|
selected_gpu = %selected_gpu,
|
||||||
|
"pyrowave: encoding on the first usable Vulkan GPU (a wrong-device report on a \
|
||||||
|
multi-GPU host needs these fields)"
|
||||||
|
);
|
||||||
|
|
||||||
// Feature gate — pyrowave's documented encoder requirements (pyrowave.h): shaderInt16,
|
// Feature gate — pyrowave's documented encoder requirements; the re-query below
|
||||||
// storageBuffer8BitAccess, subgroup size control (1.3 core); shaderFloat16 is optional.
|
// reads back the optionals mirrored into the device create-info (shaderFloat16,
|
||||||
|
// vulkanMemoryModel, maintenance4).
|
||||||
|
let missing = missing_features(&instance, pd);
|
||||||
|
if !missing.is_empty() {
|
||||||
|
bail!("GPU lacks pyrowave-required Vulkan features: {missing:?}");
|
||||||
|
}
|
||||||
let mut have12 = vk::PhysicalDeviceVulkan12Features::default();
|
let mut have12 = vk::PhysicalDeviceVulkan12Features::default();
|
||||||
let mut have13 = vk::PhysicalDeviceVulkan13Features::default();
|
let mut have13 = vk::PhysicalDeviceVulkan13Features::default();
|
||||||
let mut have2 = vk::PhysicalDeviceFeatures2::default()
|
let mut have2 = vk::PhysicalDeviceFeatures2::default()
|
||||||
.push_next(&mut have12)
|
.push_next(&mut have12)
|
||||||
.push_next(&mut have13);
|
.push_next(&mut have13);
|
||||||
instance.get_physical_device_features2(pd, &mut have2);
|
instance.get_physical_device_features2(pd, &mut have2);
|
||||||
let missing: Vec<&str> = [
|
|
||||||
(have2.features.shader_int16 == vk::TRUE, "shaderInt16"),
|
|
||||||
(
|
|
||||||
have12.storage_buffer8_bit_access == vk::TRUE,
|
|
||||||
"storageBuffer8BitAccess",
|
|
||||||
),
|
|
||||||
(have12.timeline_semaphore == vk::TRUE, "timelineSemaphore"),
|
|
||||||
(
|
|
||||||
have13.subgroup_size_control == vk::TRUE,
|
|
||||||
"subgroupSizeControl",
|
|
||||||
),
|
|
||||||
(
|
|
||||||
have13.compute_full_subgroups == vk::TRUE,
|
|
||||||
"computeFullSubgroups",
|
|
||||||
),
|
|
||||||
(have13.synchronization2 == vk::TRUE, "synchronization2"),
|
|
||||||
]
|
|
||||||
.iter()
|
|
||||||
.filter(|(ok, _)| !ok)
|
|
||||||
.map(|(_, n)| *n)
|
|
||||||
.collect();
|
|
||||||
if !missing.is_empty() {
|
|
||||||
bail!("GPU lacks pyrowave-required Vulkan features: {missing:?}");
|
|
||||||
}
|
|
||||||
|
|
||||||
hold._feat2.features.shader_int16 = vk::TRUE;
|
hold._feat2.features.shader_int16 = vk::TRUE;
|
||||||
hold._v12.storage_buffer8_bit_access = vk::TRUE;
|
hold._v12.storage_buffer8_bit_access = vk::TRUE;
|
||||||
|
|||||||
Reference in New Issue
Block a user