refactor(host/W6.1): extract GPU vendor/adapter detection into the pf-gpu leaf crate
Fourth de-coupling for the host crate carve (plan §W6.1 leaf). gpu.rs (inventory, selection preference, active-session accounting — deps only pf-host-config + pf-paths, no subsystem refs) moves to a new pf-gpu leaf so pf-encode/pf-capture/pf-vdisplay can consult the selected GPU without an orchestrator edge. ~50 crate::gpu:: sites repoint to pf_gpu::; the ~30 pub(crate) items become pub (crate API). assign_ids gets a macOS-only allow(dead_code) (used only by the Linux/Windows enumerate arms). Verified: Linux (home-worker-5) clippy -p pf-gpu -p punktfunk-host --all-targets -D warnings + pf-gpu tests (12 pass); Windows (192.168.1.158) clippy --features nvenc,amf-qsv --all-targets green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Generated
+15
@@ -2808,6 +2808,20 @@ dependencies = [
|
|||||||
"pkg-config",
|
"pkg-config",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pf-gpu"
|
||||||
|
version = "0.12.0"
|
||||||
|
dependencies = [
|
||||||
|
"anyhow",
|
||||||
|
"pf-host-config",
|
||||||
|
"pf-paths",
|
||||||
|
"serde",
|
||||||
|
"serde_json",
|
||||||
|
"tempfile",
|
||||||
|
"tracing",
|
||||||
|
"windows 0.62.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pf-host-config"
|
name = "pf-host-config"
|
||||||
version = "0.12.0"
|
version = "0.12.0"
|
||||||
@@ -3133,6 +3147,7 @@ dependencies = [
|
|||||||
"opus",
|
"opus",
|
||||||
"parking_lot",
|
"parking_lot",
|
||||||
"pf-driver-proto",
|
"pf-driver-proto",
|
||||||
|
"pf-gpu",
|
||||||
"pf-host-config",
|
"pf-host-config",
|
||||||
"pf-paths",
|
"pf-paths",
|
||||||
"pipewire",
|
"pipewire",
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ members = [
|
|||||||
"crates/pf-driver-proto",
|
"crates/pf-driver-proto",
|
||||||
"crates/pf-paths",
|
"crates/pf-paths",
|
||||||
"crates/pf-host-config",
|
"crates/pf-host-config",
|
||||||
|
"crates/pf-gpu",
|
||||||
"crates/pyrowave-sys",
|
"crates/pyrowave-sys",
|
||||||
"clients/probe",
|
"clients/probe",
|
||||||
"clients/linux",
|
"clients/linux",
|
||||||
|
|||||||
@@ -0,0 +1,28 @@
|
|||||||
|
# GPU vendor/adapter detection + selection + the live-session record, extracted into a leaf crate so
|
||||||
|
# every subsystem crate (pf-encode, pf-capture, pf-vdisplay) can consult the selected GPU WITHOUT
|
||||||
|
# depending on the orchestrator (plan §W6). Self-contained: reads config + writes the pref store.
|
||||||
|
[package]
|
||||||
|
name = "pf-gpu"
|
||||||
|
version = "0.12.0"
|
||||||
|
edition = "2021"
|
||||||
|
license = "MIT OR Apache-2.0"
|
||||||
|
description = "punktfunk host GPU vendor/adapter enumeration, selection preference, and active-session accounting."
|
||||||
|
publish = false
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
pf-host-config = { path = "../pf-host-config" }
|
||||||
|
pf-paths = { path = "../pf-paths" }
|
||||||
|
anyhow = "1"
|
||||||
|
tracing = "0.1"
|
||||||
|
serde = { version = "1", features = ["derive"] }
|
||||||
|
serde_json = "1"
|
||||||
|
|
||||||
|
[target.'cfg(windows)'.dependencies]
|
||||||
|
windows = { version = "0.62", features = [
|
||||||
|
"Win32_Foundation",
|
||||||
|
"Win32_Graphics_Dxgi",
|
||||||
|
"Win32_Graphics_Dxgi_Common",
|
||||||
|
] }
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
tempfile = "3"
|
||||||
@@ -28,14 +28,14 @@ use std::path::PathBuf;
|
|||||||
use std::sync::{Mutex, OnceLock};
|
use std::sync::{Mutex, OnceLock};
|
||||||
|
|
||||||
/// PCI vendor ids of the GPU vendors the encode backends know (NVENC / AMF / QSV, VAAPI on Linux).
|
/// PCI vendor ids of the GPU vendors the encode backends know (NVENC / AMF / QSV, VAAPI on Linux).
|
||||||
pub(crate) const VENDOR_NVIDIA: u32 = 0x10DE;
|
pub const VENDOR_NVIDIA: u32 = 0x10DE;
|
||||||
pub(crate) const VENDOR_AMD: u32 = 0x1002;
|
pub const VENDOR_AMD: u32 = 0x1002;
|
||||||
pub(crate) const VENDOR_INTEL: u32 = 0x8086;
|
pub const VENDOR_INTEL: u32 = 0x8086;
|
||||||
|
|
||||||
/// Platform handle of an enumerated GPU — how the pipeline actually addresses it. Not part of the
|
/// Platform handle of an enumerated GPU — how the pipeline actually addresses it. Not part of the
|
||||||
/// stable identity (Windows LUIDs are per-boot; a render node can renumber across kernel updates).
|
/// stable identity (Windows LUIDs are per-boot; a render node can renumber across kernel updates).
|
||||||
#[derive(Clone, Debug, Default, PartialEq, Eq)]
|
#[derive(Clone, Debug, Default, PartialEq, Eq)]
|
||||||
pub(crate) struct GpuHandle {
|
pub struct GpuHandle {
|
||||||
/// DXGI `AdapterLuid` of this adapter (this boot only).
|
/// DXGI `AdapterLuid` of this adapter (this boot only).
|
||||||
#[cfg(target_os = "windows")]
|
#[cfg(target_os = "windows")]
|
||||||
pub luid_low: u32,
|
pub luid_low: u32,
|
||||||
@@ -48,7 +48,7 @@ pub(crate) struct GpuHandle {
|
|||||||
|
|
||||||
/// One hardware GPU as enumerated on this host.
|
/// One hardware GPU as enumerated on this host.
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub(crate) struct GpuInfo {
|
pub struct GpuInfo {
|
||||||
/// Stable identifier for the API/UI: `"{vendor:04x}-{device:04x}-{occurrence}"`. Occurrence
|
/// Stable identifier for the API/UI: `"{vendor:04x}-{device:04x}-{occurrence}"`. Occurrence
|
||||||
/// disambiguates identical cards (two of the same model) by enumeration order among their
|
/// disambiguates identical cards (two of the same model) by enumeration order among their
|
||||||
/// twins — the best available tiebreaker (PCI order), imperfect but honest.
|
/// twins — the best available tiebreaker (PCI order), imperfect but honest.
|
||||||
@@ -65,7 +65,7 @@ pub(crate) struct GpuInfo {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Lowercase vendor tag for the API (`nvidia` / `amd` / `intel` / `other`).
|
/// Lowercase vendor tag for the API (`nvidia` / `amd` / `intel` / `other`).
|
||||||
pub(crate) fn vendor_tag(vendor_id: u32) -> &'static str {
|
pub fn vendor_tag(vendor_id: u32) -> &'static str {
|
||||||
match vendor_id {
|
match vendor_id {
|
||||||
VENDOR_NVIDIA => "nvidia",
|
VENDOR_NVIDIA => "nvidia",
|
||||||
VENDOR_AMD => "amd",
|
VENDOR_AMD => "amd",
|
||||||
@@ -93,6 +93,9 @@ impl GpuInfo {
|
|||||||
/// Assign the stable `id` + `occurrence` fields after enumeration (occurrence = index among
|
/// Assign the stable `id` + `occurrence` fields after enumeration (occurrence = index among
|
||||||
/// same-(vendor,device) twins, in inventory order — Windows sorts the inventory by LUID first so
|
/// same-(vendor,device) twins, in inventory order — Windows sorts the inventory by LUID first so
|
||||||
/// twin numbering is stable for the boot, see [`enumerate`]).
|
/// twin numbering is stable for the boot, see [`enumerate`]).
|
||||||
|
// Called only by the Linux/Windows `enumerate()` arms; the stub `enumerate()` on other targets
|
||||||
|
// (macOS dev host) doesn't, so it's dead there.
|
||||||
|
#[cfg_attr(not(any(target_os = "linux", target_os = "windows")), allow(dead_code))]
|
||||||
fn assign_ids(gpus: &mut [GpuInfo]) {
|
fn assign_ids(gpus: &mut [GpuInfo]) {
|
||||||
for i in 0..gpus.len() {
|
for i in 0..gpus.len() {
|
||||||
let occ = gpus[..i]
|
let occ = gpus[..i]
|
||||||
@@ -127,7 +130,7 @@ mod adapter_type {
|
|||||||
|
|
||||||
/// True when these bits describe an adapter that can never be the render/encode GPU:
|
/// True when these bits describe an adapter that can never be the render/encode GPU:
|
||||||
/// indirect-display, software, or anything without render support.
|
/// indirect-display, software, or anything without render support.
|
||||||
pub(crate) fn hidden(bits: u32) -> bool {
|
pub fn hidden(bits: u32) -> bool {
|
||||||
bits & INDIRECT_DISPLAY_DEVICE != 0
|
bits & INDIRECT_DISPLAY_DEVICE != 0
|
||||||
|| bits & SOFTWARE_DEVICE != 0
|
|| bits & SOFTWARE_DEVICE != 0
|
||||||
|| bits & RENDER_SUPPORTED == 0
|
|| bits & RENDER_SUPPORTED == 0
|
||||||
@@ -172,7 +175,7 @@ mod kmt {
|
|||||||
|
|
||||||
/// The `D3DKMT_ADAPTERTYPE` bits for the adapter with this LUID, `None` when the kernel
|
/// The `D3DKMT_ADAPTERTYPE` bits for the adapter with this LUID, `None` when the kernel
|
||||||
/// query fails (callers fail open — better a listed twin than a hidden real GPU).
|
/// query fails (callers fail open — better a listed twin than a hidden real GPU).
|
||||||
pub(crate) fn adapter_type_bits(luid_low: u32, luid_high: i32) -> Option<u32> {
|
pub fn adapter_type_bits(luid_low: u32, luid_high: i32) -> Option<u32> {
|
||||||
// SAFETY: every pointer handed to the three D3DKMT calls addresses a stack local that
|
// SAFETY: every pointer handed to the three D3DKMT calls addresses a stack local that
|
||||||
// outlives the call; NTSTATUS >= 0 is success. The kernel handle is closed on every
|
// outlives the call; NTSTATUS >= 0 is success. The kernel handle is closed on every
|
||||||
// path that opened it, including a failed query.
|
// path that opened it, including a failed query.
|
||||||
@@ -208,7 +211,7 @@ mod kmt {
|
|||||||
/// Other platforms (the macOS dev/test host build): empty — the endpoints still exist, they just
|
/// Other platforms (the macOS dev/test host build): empty — the endpoints still exist, they just
|
||||||
/// report no GPUs.
|
/// report no GPUs.
|
||||||
#[cfg(target_os = "windows")]
|
#[cfg(target_os = "windows")]
|
||||||
pub(crate) fn enumerate() -> Vec<GpuInfo> {
|
pub fn enumerate() -> Vec<GpuInfo> {
|
||||||
use windows::Win32::Graphics::Dxgi::{
|
use windows::Win32::Graphics::Dxgi::{
|
||||||
CreateDXGIFactory1, IDXGIFactory1, DXGI_ADAPTER_FLAG_SOFTWARE,
|
CreateDXGIFactory1, IDXGIFactory1, DXGI_ADAPTER_FLAG_SOFTWARE,
|
||||||
};
|
};
|
||||||
@@ -281,7 +284,7 @@ pub(crate) fn enumerate() -> Vec<GpuInfo> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(target_os = "linux")]
|
#[cfg(target_os = "linux")]
|
||||||
pub(crate) fn enumerate() -> Vec<GpuInfo> {
|
pub fn enumerate() -> Vec<GpuInfo> {
|
||||||
let mut nodes: Vec<String> = std::fs::read_dir("/dev/dri")
|
let mut nodes: Vec<String> = std::fs::read_dir("/dev/dri")
|
||||||
.map(|rd| {
|
.map(|rd| {
|
||||||
rd.filter_map(|e| e.ok())
|
rd.filter_map(|e| e.ok())
|
||||||
@@ -331,7 +334,7 @@ pub(crate) fn enumerate() -> Vec<GpuInfo> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(not(any(target_os = "windows", target_os = "linux")))]
|
#[cfg(not(any(target_os = "windows", target_os = "linux")))]
|
||||||
pub(crate) fn enumerate() -> Vec<GpuInfo> {
|
pub fn enumerate() -> Vec<GpuInfo> {
|
||||||
Vec::new()
|
Vec::new()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -343,7 +346,7 @@ pub(crate) fn enumerate() -> Vec<GpuInfo> {
|
|||||||
/// `Manual` (an explicit GPU chosen in the web console).
|
/// `Manual` (an explicit GPU chosen in the web console).
|
||||||
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
|
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
#[serde(rename_all = "snake_case")]
|
#[serde(rename_all = "snake_case")]
|
||||||
pub(crate) enum GpuMode {
|
pub enum GpuMode {
|
||||||
#[default]
|
#[default]
|
||||||
Auto,
|
Auto,
|
||||||
Manual,
|
Manual,
|
||||||
@@ -351,7 +354,7 @@ pub(crate) enum GpuMode {
|
|||||||
|
|
||||||
/// Stable identity of the manually preferred GPU (see [`GpuInfo::id`] for why not LUID/index).
|
/// Stable identity of the manually preferred GPU (see [`GpuInfo::id`] for why not LUID/index).
|
||||||
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
pub(crate) struct PreferredGpu {
|
pub struct PreferredGpu {
|
||||||
pub vendor_id: u32,
|
pub vendor_id: u32,
|
||||||
pub device_id: u32,
|
pub device_id: u32,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
@@ -364,7 +367,7 @@ pub(crate) struct PreferredGpu {
|
|||||||
|
|
||||||
/// The persisted GPU preference (`<config>/gpu-settings.json`).
|
/// The persisted GPU preference (`<config>/gpu-settings.json`).
|
||||||
#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
|
#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
pub(crate) struct GpuPreference {
|
pub struct GpuPreference {
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub mode: GpuMode,
|
pub mode: GpuMode,
|
||||||
/// `Some` when `mode == Manual` (kept when switching back to Auto so the console can offer
|
/// `Some` when `mode == Manual` (kept when switching back to Auto so the console can offer
|
||||||
@@ -376,7 +379,7 @@ pub(crate) struct GpuPreference {
|
|||||||
/// The preference store: in-memory current value + its JSON file. Mirrors `native_pairing`'s
|
/// The preference store: in-memory current value + its JSON file. Mirrors `native_pairing`'s
|
||||||
/// persistence discipline (private dir, secret-file temp write + atomic rename, in-memory
|
/// persistence discipline (private dir, secret-file temp write + atomic rename, in-memory
|
||||||
/// rollback if the disk write fails).
|
/// rollback if the disk write fails).
|
||||||
pub(crate) struct GpuPrefStore {
|
pub struct GpuPrefStore {
|
||||||
path: PathBuf,
|
path: PathBuf,
|
||||||
cur: Mutex<GpuPreference>,
|
cur: Mutex<GpuPreference>,
|
||||||
}
|
}
|
||||||
@@ -422,7 +425,7 @@ impl GpuPrefStore {
|
|||||||
/// The process-wide preference store (config-dir file), loaded once on first access — the same
|
/// The process-wide preference store (config-dir file), loaded once on first access — the same
|
||||||
/// global-accessor shape as [`pf_host_config::config`], because selection happens deep inside
|
/// global-accessor shape as [`pf_host_config::config`], because selection happens deep inside
|
||||||
/// capture/encode setup where no app state is threaded.
|
/// capture/encode setup where no app state is threaded.
|
||||||
pub(crate) fn prefs() -> &'static GpuPrefStore {
|
pub fn prefs() -> &'static GpuPrefStore {
|
||||||
static STORE: OnceLock<GpuPrefStore> = OnceLock::new();
|
static STORE: OnceLock<GpuPrefStore> = OnceLock::new();
|
||||||
STORE.get_or_init(|| GpuPrefStore::load_from(pf_paths::config_dir().join("gpu-settings.json")))
|
STORE.get_or_init(|| GpuPrefStore::load_from(pf_paths::config_dir().join("gpu-settings.json")))
|
||||||
}
|
}
|
||||||
@@ -433,7 +436,7 @@ pub(crate) fn prefs() -> &'static GpuPrefStore {
|
|||||||
|
|
||||||
/// Why a GPU was selected — surfaced by the mgmt API so the console can explain the decision.
|
/// Why a GPU was selected — surfaced by the mgmt API so the console can explain the decision.
|
||||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||||
pub(crate) enum PickSource {
|
pub enum PickSource {
|
||||||
/// The operator's manual preference matched a present GPU.
|
/// The operator's manual preference matched a present GPU.
|
||||||
Preference,
|
Preference,
|
||||||
/// `PUNKTFUNK_RENDER_ADAPTER` substring matched.
|
/// `PUNKTFUNK_RENDER_ADAPTER` substring matched.
|
||||||
@@ -458,7 +461,7 @@ impl PickSource {
|
|||||||
|
|
||||||
/// A resolved selection: the GPU the next session's pipeline will be created on, and why.
|
/// A resolved selection: the GPU the next session's pipeline will be created on, and why.
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub(crate) struct SelectedGpu {
|
pub struct SelectedGpu {
|
||||||
pub info: GpuInfo,
|
pub info: GpuInfo,
|
||||||
pub source: PickSource,
|
pub source: PickSource,
|
||||||
}
|
}
|
||||||
@@ -466,7 +469,7 @@ pub(crate) struct SelectedGpu {
|
|||||||
/// Find the manually preferred GPU in the inventory. Match order: exact stable identity
|
/// Find the manually preferred GPU in the inventory. Match order: exact stable identity
|
||||||
/// (vendor, device, occurrence) → same model (vendor, device; a twin renumbered) → exact name
|
/// (vendor, device, occurrence) → same model (vendor, device; a twin renumbered) → exact name
|
||||||
/// (ids changed across a driver/firmware quirk but the marketing name survived).
|
/// (ids changed across a driver/firmware quirk but the marketing name survived).
|
||||||
pub(crate) fn find_preferred(gpus: &[GpuInfo], want: &PreferredGpu) -> Option<usize> {
|
pub fn find_preferred(gpus: &[GpuInfo], want: &PreferredGpu) -> Option<usize> {
|
||||||
gpus.iter()
|
gpus.iter()
|
||||||
.position(|g| {
|
.position(|g| {
|
||||||
g.vendor_id == want.vendor_id
|
g.vendor_id == want.vendor_id
|
||||||
@@ -489,7 +492,7 @@ pub(crate) fn find_preferred(gpus: &[GpuInfo], want: &PreferredGpu) -> Option<us
|
|||||||
/// the index into `gpus` plus the reason. `None` only when `gpus` is empty. A set-but-unmatched
|
/// the index into `gpus` plus the reason. `None` only when `gpus` is empty. A set-but-unmatched
|
||||||
/// env substring falls through to max-VRAM (same outcome as env unset — deliberately more robust
|
/// env substring falls through to max-VRAM (same outcome as env unset — deliberately more robust
|
||||||
/// than the old `resolve_render_adapter_luid`, which returned *no* adapter on a stale substring).
|
/// than the old `resolve_render_adapter_luid`, which returned *no* adapter on a stale substring).
|
||||||
pub(crate) fn pick(
|
pub fn pick(
|
||||||
gpus: &[GpuInfo],
|
gpus: &[GpuInfo],
|
||||||
pref: &GpuPreference,
|
pref: &GpuPreference,
|
||||||
env_substr: Option<&str>,
|
env_substr: Option<&str>,
|
||||||
@@ -532,7 +535,7 @@ pub(crate) fn pick(
|
|||||||
/// the encoder-vendor dispatch both consume, so capture, encode, and the advertisement agree by
|
/// the encoder-vendor dispatch both consume, so capture, encode, and the advertisement agree by
|
||||||
/// construction. Pure query — callers log (this runs per serverinfo poll).
|
/// construction. Pure query — callers log (this runs per serverinfo poll).
|
||||||
#[cfg(target_os = "windows")]
|
#[cfg(target_os = "windows")]
|
||||||
pub(crate) fn selected_gpu() -> Option<SelectedGpu> {
|
pub fn selected_gpu() -> Option<SelectedGpu> {
|
||||||
let gpus = enumerate();
|
let gpus = enumerate();
|
||||||
let pref = prefs().get();
|
let pref = prefs().get();
|
||||||
let env = pf_host_config::config()
|
let env = pf_host_config::config()
|
||||||
@@ -551,7 +554,7 @@ pub(crate) fn selected_gpu() -> Option<SelectedGpu> {
|
|||||||
/// owns the VAAPI render node. (The *authoritative* Linux switches stay in `encode::open_video` /
|
/// owns the VAAPI render node. (The *authoritative* Linux switches stay in `encode::open_video` /
|
||||||
/// [`linux_render_node`] — this is the console's view of them.)
|
/// [`linux_render_node`] — this is the console's view of them.)
|
||||||
#[cfg(target_os = "linux")]
|
#[cfg(target_os = "linux")]
|
||||||
pub(crate) fn selected_gpu() -> Option<SelectedGpu> {
|
pub fn selected_gpu() -> Option<SelectedGpu> {
|
||||||
let gpus = enumerate();
|
let gpus = enumerate();
|
||||||
let pref = prefs().get();
|
let pref = prefs().get();
|
||||||
let mut preference_missing = false;
|
let mut preference_missing = false;
|
||||||
@@ -593,14 +596,14 @@ pub(crate) fn selected_gpu() -> Option<SelectedGpu> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(not(any(target_os = "windows", target_os = "linux")))]
|
#[cfg(not(any(target_os = "windows", target_os = "linux")))]
|
||||||
pub(crate) fn selected_gpu() -> Option<SelectedGpu> {
|
pub fn selected_gpu() -> Option<SelectedGpu> {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The manually preferred GPU, only when `mode == Manual` **and** it is currently present.
|
/// The manually preferred GPU, only when `mode == Manual` **and** it is currently present.
|
||||||
/// The Linux encode dispatch consults this (auto mode keeps today's NVIDIA-presence behavior
|
/// The Linux encode dispatch consults this (auto mode keeps today's NVIDIA-presence behavior
|
||||||
/// exactly).
|
/// exactly).
|
||||||
pub(crate) fn manual_selection() -> Option<GpuInfo> {
|
pub fn manual_selection() -> Option<GpuInfo> {
|
||||||
let pref = prefs().get();
|
let pref = prefs().get();
|
||||||
if pref.mode != GpuMode::Manual {
|
if pref.mode != GpuMode::Manual {
|
||||||
return None;
|
return None;
|
||||||
@@ -614,7 +617,7 @@ pub(crate) fn manual_selection() -> Option<GpuInfo> {
|
|||||||
/// The VAAPI/DRM render node for this host: matched manual preference > `PUNKTFUNK_RENDER_NODE`
|
/// The VAAPI/DRM render node for this host: matched manual preference > `PUNKTFUNK_RENDER_NODE`
|
||||||
/// (a deliberate live env read — see `config.rs` module docs) > `/dev/dri/renderD128`.
|
/// (a deliberate live env read — see `config.rs` module docs) > `/dev/dri/renderD128`.
|
||||||
#[cfg(target_os = "linux")]
|
#[cfg(target_os = "linux")]
|
||||||
pub(crate) fn linux_render_node() -> PathBuf {
|
pub fn linux_render_node() -> PathBuf {
|
||||||
if let Some(g) = manual_selection() {
|
if let Some(g) = manual_selection() {
|
||||||
if let Some(node) = g.handle.render_node {
|
if let Some(node) = g.handle.render_node {
|
||||||
return node;
|
return node;
|
||||||
@@ -637,7 +640,7 @@ fn linux_nvidia_present() -> bool {
|
|||||||
/// A cache key that changes whenever the *selection* changes (preference edits included), for the
|
/// A cache key that changes whenever the *selection* changes (preference edits included), for the
|
||||||
/// per-GPU probe caches (`can_encode_444`, `windows_codec_support`) that were process-lifetime
|
/// per-GPU probe caches (`can_encode_444`, `windows_codec_support`) that were process-lifetime
|
||||||
/// `OnceLock`s back when selection was env-only.
|
/// `OnceLock`s back when selection was env-only.
|
||||||
pub(crate) fn selection_key() -> String {
|
pub fn selection_key() -> String {
|
||||||
match selected_gpu() {
|
match selected_gpu() {
|
||||||
Some(sel) => {
|
Some(sel) => {
|
||||||
#[cfg(target_os = "windows")]
|
#[cfg(target_os = "windows")]
|
||||||
@@ -662,7 +665,7 @@ pub(crate) fn selection_key() -> String {
|
|||||||
|
|
||||||
/// What a live session encodes on — the console's "currently used GPU".
|
/// What a live session encodes on — the console's "currently used GPU".
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub(crate) struct ActiveGpu {
|
pub struct ActiveGpu {
|
||||||
/// Stable id of the GPU ([`GpuInfo::id`]; empty for the CPU/software path) so a UI can match
|
/// Stable id of the GPU ([`GpuInfo::id`]; empty for the CPU/software path) so a UI can match
|
||||||
/// it against the inventory.
|
/// it against the inventory.
|
||||||
pub id: String,
|
pub id: String,
|
||||||
@@ -682,7 +685,7 @@ static ACTIVE: Mutex<Option<ActiveState>> = Mutex::new(None);
|
|||||||
/// RAII marker for one live encode session; dropping it decrements the session count. Held by the
|
/// RAII marker for one live encode session; dropping it decrements the session count. Held by the
|
||||||
/// encoder wrapper `open_video` returns, so the count is correct by construction (every successful
|
/// encoder wrapper `open_video` returns, so the count is correct by construction (every successful
|
||||||
/// open is paired with a drop).
|
/// open is paired with a drop).
|
||||||
pub(crate) struct ActiveSession(());
|
pub struct ActiveSession(());
|
||||||
|
|
||||||
impl Drop for ActiveSession {
|
impl Drop for ActiveSession {
|
||||||
fn drop(&mut self) {
|
fn drop(&mut self) {
|
||||||
@@ -696,7 +699,7 @@ impl Drop for ActiveSession {
|
|||||||
/// Record a session opening on `gpu`. Concurrent sessions share one GPU (the Windows pipeline is
|
/// Record a session opening on `gpu`. Concurrent sessions share one GPU (the Windows pipeline is
|
||||||
/// single-GPU by construction; Linux sessions share the selection), so the latest record wins and
|
/// single-GPU by construction; Linux sessions share the selection), so the latest record wins and
|
||||||
/// a counter tracks liveness.
|
/// a counter tracks liveness.
|
||||||
pub(crate) fn session_begin(gpu: ActiveGpu) -> ActiveSession {
|
pub fn session_begin(gpu: ActiveGpu) -> ActiveSession {
|
||||||
let mut st = ACTIVE.lock().unwrap_or_else(|e| e.into_inner());
|
let mut st = ACTIVE.lock().unwrap_or_else(|e| e.into_inner());
|
||||||
let sessions = st.as_ref().map(|s| s.sessions).unwrap_or(0) + 1;
|
let sessions = st.as_ref().map(|s| s.sessions).unwrap_or(0) + 1;
|
||||||
*st = Some(ActiveState { gpu, sessions });
|
*st = Some(ActiveState { gpu, sessions });
|
||||||
@@ -705,7 +708,7 @@ pub(crate) fn session_begin(gpu: ActiveGpu) -> ActiveSession {
|
|||||||
|
|
||||||
/// The GPU live sessions encode on + how many sessions hold it. `Some` with `sessions == 0` means
|
/// The GPU live sessions encode on + how many sessions hold it. `Some` with `sessions == 0` means
|
||||||
/// "last used, idle now" — the mgmt API distinguishes the two.
|
/// "last used, idle now" — the mgmt API distinguishes the two.
|
||||||
pub(crate) fn active() -> Option<(ActiveGpu, u32)> {
|
pub fn active() -> Option<(ActiveGpu, u32)> {
|
||||||
ACTIVE
|
ACTIVE
|
||||||
.lock()
|
.lock()
|
||||||
.unwrap_or_else(|e| e.into_inner())
|
.unwrap_or_else(|e| e.into_inner())
|
||||||
@@ -14,6 +14,8 @@ punktfunk-core = { path = "../punktfunk-core", features = ["quic"] }
|
|||||||
pf-paths = { path = "../pf-paths" }
|
pf-paths = { path = "../pf-paths" }
|
||||||
# Process-wide host config global, extracted to a leaf crate (plan §W6).
|
# Process-wide host config global, extracted to a leaf crate (plan §W6).
|
||||||
pf-host-config = { path = "../pf-host-config" }
|
pf-host-config = { path = "../pf-host-config" }
|
||||||
|
# GPU vendor/adapter detection + selection, extracted to a leaf crate (plan §W6).
|
||||||
|
pf-gpu = { path = "../pf-gpu" }
|
||||||
# M3 native control plane (the `punktfunk/1` QUIC handshake; data plane stays native-thread UDP).
|
# M3 native control plane (the `punktfunk/1` QUIC handshake; data plane stays native-thread UDP).
|
||||||
quinn = "0.11"
|
quinn = "0.11"
|
||||||
anyhow = "1"
|
anyhow = "1"
|
||||||
|
|||||||
@@ -128,24 +128,24 @@ pub fn open_video(
|
|||||||
// mirroring its dispatch, which went stale the moment a backend gained an internal fallback
|
// mirroring its dispatch, which went stale the moment a backend gained an internal fallback
|
||||||
// (the default-on Vulkan Video path falls back to VAAPI on a failed open, and a dispatch
|
// (the default-on Vulkan Video path falls back to VAAPI on a failed open, and a dispatch
|
||||||
// mirror would report "vaapi" for every Vulkan session or vice versa). The GPU identity is the
|
// mirror would report "vaapi" for every Vulkan session or vice versa). The GPU identity is the
|
||||||
// same selection the capturer was created on ([`crate::gpu::selected_gpu`]). Dropping the
|
// same selection the capturer was created on ([`pf_gpu::selected_gpu`]). Dropping the
|
||||||
// returned encoder ends the record, so the live count is correct by construction.
|
// returned encoder ends the record, so the live count is correct by construction.
|
||||||
let gpu = if backend == "software" {
|
let gpu = if backend == "software" {
|
||||||
crate::gpu::ActiveGpu {
|
pf_gpu::ActiveGpu {
|
||||||
id: String::new(),
|
id: String::new(),
|
||||||
name: "CPU (openh264)".into(),
|
name: "CPU (openh264)".into(),
|
||||||
vendor_id: 0,
|
vendor_id: 0,
|
||||||
backend,
|
backend,
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
match crate::gpu::selected_gpu() {
|
match pf_gpu::selected_gpu() {
|
||||||
Some(sel) => crate::gpu::ActiveGpu {
|
Some(sel) => pf_gpu::ActiveGpu {
|
||||||
id: sel.info.id,
|
id: sel.info.id,
|
||||||
name: sel.info.name,
|
name: sel.info.name,
|
||||||
vendor_id: sel.info.vendor_id,
|
vendor_id: sel.info.vendor_id,
|
||||||
backend,
|
backend,
|
||||||
},
|
},
|
||||||
None => crate::gpu::ActiveGpu {
|
None => pf_gpu::ActiveGpu {
|
||||||
id: String::new(),
|
id: String::new(),
|
||||||
name: "GPU".into(),
|
name: "GPU".into(),
|
||||||
vendor_id: 0,
|
vendor_id: 0,
|
||||||
@@ -155,7 +155,7 @@ pub fn open_video(
|
|||||||
};
|
};
|
||||||
Ok(Box::new(TrackedEncoder {
|
Ok(Box::new(TrackedEncoder {
|
||||||
inner,
|
inner,
|
||||||
_session: crate::gpu::session_begin(gpu),
|
_session: pf_gpu::session_begin(gpu),
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -163,7 +163,7 @@ pub fn open_video(
|
|||||||
/// otherwise.
|
/// otherwise.
|
||||||
struct TrackedEncoder {
|
struct TrackedEncoder {
|
||||||
inner: Box<dyn Encoder>,
|
inner: Box<dyn Encoder>,
|
||||||
_session: crate::gpu::ActiveSession,
|
_session: pf_gpu::ActiveSession,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Encoder for TrackedEncoder {
|
impl Encoder for TrackedEncoder {
|
||||||
@@ -406,11 +406,11 @@ fn open_video_backend(
|
|||||||
// explicit PUNKTFUNK_ENCODER contradicts the GPU the pipeline sits on (e.g. `nvenc` forced
|
// explicit PUNKTFUNK_ENCODER contradicts the GPU the pipeline sits on (e.g. `nvenc` forced
|
||||||
// while the web-console preference pins the Intel iGPU) — the open below will then fail on
|
// while the web-console preference pins the Intel iGPU) — the open below will then fail on
|
||||||
// a wrong-vendor device; say why up front instead of leaving an opaque encoder error.
|
// a wrong-vendor device; say why up front instead of leaving an opaque encoder error.
|
||||||
if let Some(sel) = crate::gpu::selected_gpu() {
|
if let Some(sel) = pf_gpu::selected_gpu() {
|
||||||
let mismatched = match backend {
|
let mismatched = match backend {
|
||||||
WindowsBackend::Nvenc => sel.info.vendor_id != crate::gpu::VENDOR_NVIDIA,
|
WindowsBackend::Nvenc => sel.info.vendor_id != pf_gpu::VENDOR_NVIDIA,
|
||||||
WindowsBackend::Amf => sel.info.vendor_id != crate::gpu::VENDOR_AMD,
|
WindowsBackend::Amf => sel.info.vendor_id != pf_gpu::VENDOR_AMD,
|
||||||
WindowsBackend::Qsv => sel.info.vendor_id != crate::gpu::VENDOR_INTEL,
|
WindowsBackend::Qsv => sel.info.vendor_id != pf_gpu::VENDOR_INTEL,
|
||||||
WindowsBackend::Software => false,
|
WindowsBackend::Software => false,
|
||||||
};
|
};
|
||||||
if mismatched {
|
if mismatched {
|
||||||
@@ -680,14 +680,14 @@ fn nvidia_present() -> bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// The `auto` Linux backend decision, shared by [`open_video`] and [`linux_zero_copy_is_vaapi`]:
|
/// The `auto` Linux backend decision, shared by [`open_video`] and [`linux_zero_copy_is_vaapi`]:
|
||||||
/// a manual web-console GPU preference (when that GPU is present — [`crate::gpu::manual_selection`])
|
/// a manual web-console GPU preference (when that GPU is present — [`pf_gpu::manual_selection`])
|
||||||
/// picks its vendor's backend — AMD/Intel → VAAPI on that GPU's render node, NVIDIA → NVENC (still
|
/// picks its vendor's backend — AMD/Intel → VAAPI on that GPU's render node, NVIDIA → NVENC (still
|
||||||
/// requiring the proprietary driver's device nodes; a nouveau NVIDIA GPU can't NVENC) — otherwise
|
/// requiring the proprietary driver's device nodes; a nouveau NVIDIA GPU can't NVENC) — otherwise
|
||||||
/// today's NVIDIA-presence probe, unchanged.
|
/// today's NVIDIA-presence probe, unchanged.
|
||||||
#[cfg(target_os = "linux")]
|
#[cfg(target_os = "linux")]
|
||||||
fn linux_auto_is_vaapi() -> bool {
|
fn linux_auto_is_vaapi() -> bool {
|
||||||
if let Some(g) = crate::gpu::manual_selection() {
|
if let Some(g) = pf_gpu::manual_selection() {
|
||||||
if g.vendor_id == crate::gpu::VENDOR_NVIDIA {
|
if g.vendor_id == pf_gpu::VENDOR_NVIDIA {
|
||||||
return !nvidia_present();
|
return !nvidia_present();
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
@@ -788,7 +788,7 @@ pub fn can_encode_444(codec: Codec) -> bool {
|
|||||||
// Cached per selected GPU (was a process-lifetime OnceLock): a web-console preference change
|
// Cached per selected GPU (was a process-lifetime OnceLock): a web-console preference change
|
||||||
// re-probes on the newly selected adapter before the next Welcome.
|
// re-probes on the newly selected adapter before the next Welcome.
|
||||||
static CACHE: OnceLock<Mutex<HashMap<String, bool>>> = OnceLock::new();
|
static CACHE: OnceLock<Mutex<HashMap<String, bool>>> = OnceLock::new();
|
||||||
let key = crate::gpu::selection_key();
|
let key = pf_gpu::selection_key();
|
||||||
let cache = CACHE.get_or_init(|| Mutex::new(HashMap::new()));
|
let cache = CACHE.get_or_init(|| Mutex::new(HashMap::new()));
|
||||||
if let Some(v) = cache.lock().unwrap().get(&key) {
|
if let Some(v) = cache.lock().unwrap().get(&key) {
|
||||||
return *v;
|
return *v;
|
||||||
@@ -870,7 +870,7 @@ pub fn can_encode_10bit(codec: Codec) -> bool {
|
|||||||
// Cached per (selected GPU, codec) — a web-console preference change re-probes on the newly
|
// Cached per (selected GPU, codec) — a web-console preference change re-probes on the newly
|
||||||
// selected adapter before the next Welcome, mirroring `can_encode_444`.
|
// selected adapter before the next Welcome, mirroring `can_encode_444`.
|
||||||
static CACHE: OnceLock<Mutex<HashMap<(String, &'static str), bool>>> = OnceLock::new();
|
static CACHE: OnceLock<Mutex<HashMap<(String, &'static str), bool>>> = OnceLock::new();
|
||||||
let key = (crate::gpu::selection_key(), codec.label());
|
let key = (pf_gpu::selection_key(), codec.label());
|
||||||
let cache = CACHE.get_or_init(|| Mutex::new(HashMap::new()));
|
let cache = CACHE.get_or_init(|| Mutex::new(HashMap::new()));
|
||||||
if let Some(v) = cache.lock().unwrap().get(&key) {
|
if let Some(v) = cache.lock().unwrap().get(&key) {
|
||||||
return *v;
|
return *v;
|
||||||
@@ -1007,7 +1007,7 @@ pub fn windows_backend_is_probed() -> bool {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Detect the encode-GPU vendor from the **selected render adapter** ([`crate::gpu::selected_gpu`]:
|
/// Detect the encode-GPU vendor from the **selected render adapter** ([`pf_gpu::selected_gpu`]:
|
||||||
/// web-console preference > `PUNKTFUNK_RENDER_ADAPTER` > max VRAM) — the same adapter the capture
|
/// web-console preference > `PUNKTFUNK_RENDER_ADAPTER` > max VRAM) — the same adapter the capture
|
||||||
/// ring and the IddCx render pin sit on, so the encoder backend can never disagree with where the
|
/// ring and the IddCx render pin sit on, so the encoder backend can never disagree with where the
|
||||||
/// captured frames live. The old first-DXGI-adapter scan did exactly that on hybrid boxes: adapter
|
/// captured frames live. The old first-DXGI-adapter scan did exactly that on hybrid boxes: adapter
|
||||||
@@ -1019,18 +1019,15 @@ pub fn windows_backend_is_probed() -> bool {
|
|||||||
fn windows_gpu_vendor() -> Option<GpuVendor> {
|
fn windows_gpu_vendor() -> Option<GpuVendor> {
|
||||||
fn by_id(vendor_id: u32) -> Option<GpuVendor> {
|
fn by_id(vendor_id: u32) -> Option<GpuVendor> {
|
||||||
match vendor_id {
|
match vendor_id {
|
||||||
crate::gpu::VENDOR_NVIDIA => Some(GpuVendor::Nvidia),
|
pf_gpu::VENDOR_NVIDIA => Some(GpuVendor::Nvidia),
|
||||||
crate::gpu::VENDOR_AMD => Some(GpuVendor::Amd),
|
pf_gpu::VENDOR_AMD => Some(GpuVendor::Amd),
|
||||||
crate::gpu::VENDOR_INTEL => Some(GpuVendor::Intel),
|
pf_gpu::VENDOR_INTEL => Some(GpuVendor::Intel),
|
||||||
_ => None,
|
_ => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let sel = crate::gpu::selected_gpu()?;
|
let sel = pf_gpu::selected_gpu()?;
|
||||||
by_id(sel.info.vendor_id).or_else(|| {
|
by_id(sel.info.vendor_id)
|
||||||
crate::gpu::enumerate()
|
.or_else(|| pf_gpu::enumerate().iter().find_map(|g| by_id(g.vendor_id)))
|
||||||
.iter()
|
|
||||||
.find_map(|g| by_id(g.vendor_id))
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Probe the active Windows AMF/QSV backend for its encodable codecs (cached **per (backend,
|
/// Probe the active Windows AMF/QSV backend for its encodable codecs (cached **per (backend,
|
||||||
@@ -1050,7 +1047,7 @@ pub fn windows_codec_support() -> CodecSupport {
|
|||||||
use std::sync::{Mutex, OnceLock};
|
use std::sync::{Mutex, OnceLock};
|
||||||
static CACHE: OnceLock<Mutex<HashMap<String, CodecSupport>>> = OnceLock::new();
|
static CACHE: OnceLock<Mutex<HashMap<String, CodecSupport>>> = OnceLock::new();
|
||||||
let backend = windows_resolved_backend();
|
let backend = windows_resolved_backend();
|
||||||
let key = format!("{backend:?}:{}", crate::gpu::selection_key());
|
let key = format!("{backend:?}:{}", pf_gpu::selection_key());
|
||||||
let cache = CACHE.get_or_init(|| Mutex::new(HashMap::new()));
|
let cache = CACHE.get_or_init(|| Mutex::new(HashMap::new()));
|
||||||
if let Some(c) = cache.lock().unwrap().get(&key) {
|
if let Some(c) = cache.lock().unwrap().get(&key) {
|
||||||
return *c;
|
return *c;
|
||||||
|
|||||||
@@ -44,13 +44,11 @@ const fn fourcc(a: u8, b: u8, c: u8, d: u8) -> u32 {
|
|||||||
(a as u32) | ((b as u32) << 8) | ((c as u32) << 16) | ((d as u32) << 24)
|
(a as u32) | ((b as u32) << 8) | ((c as u32) << 16) | ((d as u32) << 24)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The render node a VAAPI/DRM device should open, from [`crate::gpu::linux_render_node`]: a
|
/// The render node a VAAPI/DRM device should open, from [`pf_gpu::linux_render_node`]: a
|
||||||
/// matched web-console GPU preference pins it, else `PUNKTFUNK_RENDER_NODE`, else the single-GPU
|
/// matched web-console GPU preference pins it, else `PUNKTFUNK_RENDER_NODE`, else the single-GPU
|
||||||
/// default.
|
/// default.
|
||||||
fn render_node() -> CString {
|
fn render_node() -> CString {
|
||||||
let p = crate::gpu::linux_render_node()
|
let p = pf_gpu::linux_render_node().to_string_lossy().into_owned();
|
||||||
.to_string_lossy()
|
|
||||||
.into_owned();
|
|
||||||
CString::new(p).unwrap_or_else(|_| CString::new("/dev/dri/renderD128").unwrap())
|
CString::new(p).unwrap_or_else(|_| CString::new("/dev/dri/renderD128").unwrap())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -44,7 +44,6 @@ mod drm_sync;
|
|||||||
mod encode;
|
mod encode;
|
||||||
mod events;
|
mod events;
|
||||||
mod gamestream;
|
mod gamestream;
|
||||||
mod gpu;
|
|
||||||
#[cfg(target_os = "linux")]
|
#[cfg(target_os = "linux")]
|
||||||
#[path = "linux/gpuclocks.rs"]
|
#[path = "linux/gpuclocks.rs"]
|
||||||
mod gpuclocks;
|
mod gpuclocks;
|
||||||
|
|||||||
@@ -86,11 +86,11 @@ pub(crate) struct SetGpuPreference {
|
|||||||
|
|
||||||
/// Build the [`GpuState`] snapshot (shared by the GET and the PUT's response).
|
/// Build the [`GpuState`] snapshot (shared by the GET and the PUT's response).
|
||||||
pub(crate) fn gpu_state() -> GpuState {
|
pub(crate) fn gpu_state() -> GpuState {
|
||||||
let gpus = crate::gpu::enumerate();
|
let gpus = pf_gpu::enumerate();
|
||||||
let pref = crate::gpu::prefs().get();
|
let pref = pf_gpu::prefs().get();
|
||||||
let (preferred_id, preferred_name, preferred_available) = match &pref.gpu {
|
let (preferred_id, preferred_name, preferred_available) = match &pref.gpu {
|
||||||
Some(want) => {
|
Some(want) => {
|
||||||
let found = crate::gpu::find_preferred(&gpus, want);
|
let found = pf_gpu::find_preferred(&gpus, want);
|
||||||
let id = match found {
|
let id = match found {
|
||||||
// Canonical: the present GPU's id (identity may have matched loosely).
|
// Canonical: the present GPU's id (identity may have matched loosely).
|
||||||
Some(i) => gpus[i].id.clone(),
|
Some(i) => gpus[i].id.clone(),
|
||||||
@@ -107,15 +107,15 @@ pub(crate) fn gpu_state() -> GpuState {
|
|||||||
}
|
}
|
||||||
None => (None, None, false),
|
None => (None, None, false),
|
||||||
};
|
};
|
||||||
let selected = crate::gpu::selected_gpu().map(|sel| ApiSelectedGpu {
|
let selected = pf_gpu::selected_gpu().map(|sel| ApiSelectedGpu {
|
||||||
vendor: sel.info.vendor_tag().into(),
|
vendor: sel.info.vendor_tag().into(),
|
||||||
id: sel.info.id,
|
id: sel.info.id,
|
||||||
name: sel.info.name,
|
name: sel.info.name,
|
||||||
source: sel.source.tag().into(),
|
source: sel.source.tag().into(),
|
||||||
});
|
});
|
||||||
let active = crate::gpu::active().and_then(|(g, sessions)| {
|
let active = pf_gpu::active().and_then(|(g, sessions)| {
|
||||||
(sessions > 0).then(|| ApiActiveGpu {
|
(sessions > 0).then(|| ApiActiveGpu {
|
||||||
vendor: crate::gpu::vendor_tag(g.vendor_id).into(),
|
vendor: pf_gpu::vendor_tag(g.vendor_id).into(),
|
||||||
id: g.id,
|
id: g.id,
|
||||||
name: g.name,
|
name: g.name,
|
||||||
backend: g.backend.into(),
|
backend: g.backend.into(),
|
||||||
@@ -133,8 +133,8 @@ pub(crate) fn gpu_state() -> GpuState {
|
|||||||
})
|
})
|
||||||
.collect(),
|
.collect(),
|
||||||
mode: match pref.mode {
|
mode: match pref.mode {
|
||||||
crate::gpu::GpuMode::Auto => "auto".into(),
|
pf_gpu::GpuMode::Auto => "auto".into(),
|
||||||
crate::gpu::GpuMode::Manual => "manual".into(),
|
pf_gpu::GpuMode::Manual => "manual".into(),
|
||||||
},
|
},
|
||||||
preferred_id,
|
preferred_id,
|
||||||
preferred_name,
|
preferred_name,
|
||||||
@@ -189,8 +189,8 @@ pub(crate) async fn set_gpu_preference(ApiJson(req): ApiJson<SetGpuPreference>)
|
|||||||
let pref = match req.mode.to_ascii_lowercase().as_str() {
|
let pref = match req.mode.to_ascii_lowercase().as_str() {
|
||||||
"auto" => {
|
"auto" => {
|
||||||
// Keep the stored manual pick so the console can offer switching back to it.
|
// Keep the stored manual pick so the console can offer switching back to it.
|
||||||
let mut p = crate::gpu::prefs().get();
|
let mut p = pf_gpu::prefs().get();
|
||||||
p.mode = crate::gpu::GpuMode::Auto;
|
p.mode = pf_gpu::GpuMode::Auto;
|
||||||
p
|
p
|
||||||
}
|
}
|
||||||
"manual" => {
|
"manual" => {
|
||||||
@@ -202,15 +202,15 @@ pub(crate) async fn set_gpu_preference(ApiJson(req): ApiJson<SetGpuPreference>)
|
|||||||
else {
|
else {
|
||||||
return api_error(StatusCode::BAD_REQUEST, "mode `manual` requires `gpu_id`");
|
return api_error(StatusCode::BAD_REQUEST, "mode `manual` requires `gpu_id`");
|
||||||
};
|
};
|
||||||
let Some(g) = crate::gpu::enumerate().into_iter().find(|g| g.id == id) else {
|
let Some(g) = pf_gpu::enumerate().into_iter().find(|g| g.id == id) else {
|
||||||
return api_error(
|
return api_error(
|
||||||
StatusCode::BAD_REQUEST,
|
StatusCode::BAD_REQUEST,
|
||||||
"gpu_id does not match a present GPU (see GET /gpus)",
|
"gpu_id does not match a present GPU (see GET /gpus)",
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
crate::gpu::GpuPreference {
|
pf_gpu::GpuPreference {
|
||||||
mode: crate::gpu::GpuMode::Manual,
|
mode: pf_gpu::GpuMode::Manual,
|
||||||
gpu: Some(crate::gpu::PreferredGpu {
|
gpu: Some(pf_gpu::PreferredGpu {
|
||||||
vendor_id: g.vendor_id,
|
vendor_id: g.vendor_id,
|
||||||
device_id: g.device_id,
|
device_id: g.device_id,
|
||||||
occurrence: g.occurrence,
|
occurrence: g.occurrence,
|
||||||
@@ -225,7 +225,7 @@ pub(crate) async fn set_gpu_preference(ApiJson(req): ApiJson<SetGpuPreference>)
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
if let Err(e) = crate::gpu::prefs().set(pref) {
|
if let Err(e) = pf_gpu::prefs().set(pref) {
|
||||||
return api_error(
|
return api_error(
|
||||||
StatusCode::INTERNAL_SERVER_ERROR,
|
StatusCode::INTERNAL_SERVER_ERROR,
|
||||||
&format!("persist GPU preference: {e:#}"),
|
&format!("persist GPU preference: {e:#}"),
|
||||||
|
|||||||
@@ -408,7 +408,7 @@ pub fn preset_fields(preset: Preset) -> Option<EffectivePolicy> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// The persisted policy store: the loaded file value (or `None` when no file exists) behind its
|
/// The persisted policy store: the loaded file value (or `None` when no file exists) behind its
|
||||||
/// JSON path. Mirrors [`crate::gpu::GpuPrefStore`] — private dir, temp-write + atomic rename,
|
/// JSON path. Mirrors [`pf_gpu::GpuPrefStore`] — private dir, temp-write + atomic rename,
|
||||||
/// in-memory rollback if the disk write fails.
|
/// in-memory rollback if the disk write fails.
|
||||||
pub struct DisplayPolicyStore {
|
pub struct DisplayPolicyStore {
|
||||||
path: PathBuf,
|
path: PathBuf,
|
||||||
@@ -492,7 +492,7 @@ impl DisplayPolicyStore {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// The process-wide display-policy store (config-dir file), loaded once on first access — the same
|
/// The process-wide display-policy store (config-dir file), loaded once on first access — the same
|
||||||
/// global-accessor shape as [`crate::gpu::prefs`], because display setup happens deep in the
|
/// global-accessor shape as [`pf_gpu::prefs`], because display setup happens deep in the
|
||||||
/// capture/vdisplay path where no app state is threaded.
|
/// capture/vdisplay path where no app state is threaded.
|
||||||
pub fn prefs() -> &'static DisplayPolicyStore {
|
pub fn prefs() -> &'static DisplayPolicyStore {
|
||||||
static STORE: OnceLock<DisplayPolicyStore> = OnceLock::new();
|
static STORE: OnceLock<DisplayPolicyStore> = OnceLock::new();
|
||||||
|
|||||||
@@ -1462,7 +1462,7 @@ fn resolve_render_pin() -> Option<LUID> {
|
|||||||
/// not be compared with render-GPU picks.
|
/// not be compared with render-GPU picks.
|
||||||
fn warn_if_pick_moved(mon: &Monitor) {
|
fn warn_if_pick_moved(mon: &Monitor) {
|
||||||
let Some(pin) = mon.render_pin else { return };
|
let Some(pin) = mon.render_pin else { return };
|
||||||
let Some(sel) = crate::gpu::selected_gpu() else {
|
let Some(sel) = pf_gpu::selected_gpu() else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
let pick = sel.info.luid();
|
let pick = sel.info.luid();
|
||||||
|
|||||||
@@ -16,11 +16,11 @@ use windows::Win32::Foundation::LUID;
|
|||||||
|
|
||||||
/// Pick the render GPU LUID the pipeline is created on: the IDD-push capturer's shared-texture
|
/// Pick the render GPU LUID the pipeline is created on: the IDD-push capturer's shared-texture
|
||||||
/// ring, the IddCx SET_RENDER_ADAPTER pin, and (via the captured frame's device) NVENC/AMF/QSV all
|
/// ring, the IddCx SET_RENDER_ADAPTER pin, and (via the captured frame's device) NVENC/AMF/QSV all
|
||||||
/// follow this one decision — see [`crate::gpu::selected_gpu`] for the precedence. A configured
|
/// follow this one decision — see [`pf_gpu::selected_gpu`] for the precedence. A configured
|
||||||
/// preference that doesn't match a present GPU falls back to auto selection (with a warning)
|
/// preference that doesn't match a present GPU falls back to auto selection (with a warning)
|
||||||
/// rather than returning `None`, so a stale preference never stops the host from streaming.
|
/// rather than returning `None`, so a stale preference never stops the host from streaming.
|
||||||
pub(crate) fn resolve_render_adapter_luid() -> Option<LUID> {
|
pub(crate) fn resolve_render_adapter_luid() -> Option<LUID> {
|
||||||
match crate::gpu::selected_gpu() {
|
match pf_gpu::selected_gpu() {
|
||||||
Some(sel) => {
|
Some(sel) => {
|
||||||
tracing::info!(
|
tracing::info!(
|
||||||
adapter = sel.info.name,
|
adapter = sel.info.name,
|
||||||
@@ -28,7 +28,7 @@ pub(crate) fn resolve_render_adapter_luid() -> Option<LUID> {
|
|||||||
source = sel.source.tag(),
|
source = sel.source.tag(),
|
||||||
"render adapter selected"
|
"render adapter selected"
|
||||||
);
|
);
|
||||||
if sel.source == crate::gpu::PickSource::PreferenceMissing {
|
if sel.source == pf_gpu::PickSource::PreferenceMissing {
|
||||||
tracing::warn!(
|
tracing::warn!(
|
||||||
"the preferred GPU is not present — auto-selected the adapter above \
|
"the preferred GPU is not present — auto-selected the adapter above \
|
||||||
(fix or clear the preference in the web console)"
|
(fix or clear the preference in the web console)"
|
||||||
|
|||||||
Reference in New Issue
Block a user