feat(host,web): multi-GPU selection — GPU inventory + preference API, web-console GPU card
apple / swift (push) Successful in 1m9s
ci / rust (push) Successful in 1m50s
ci / web (push) Successful in 56s
ci / docs-site (push) Successful in 57s
decky / build-publish (push) Successful in 11s
android / android (push) Successful in 3m13s
apple / screenshots (push) Successful in 5m32s
deb / build-publish (push) Successful in 3m15s
docker / build-push (--build-arg FEDORA_VERSION=44, ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm) (push) Successful in 5s
windows-host / package (push) Successful in 7m35s
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Successful in 31s
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 4s
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 4s
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 4s
ci / bench (push) Successful in 4m53s
rpm / build-publish (bazzite, punktfunk-fedora-rpm) (push) Successful in 9m58s
rpm / build-publish (fedora-44, punktfunk-fedora44-rpm) (push) Successful in 9m54s
docker / deploy-docs (push) Successful in 18s
apple / swift (push) Successful in 1m9s
ci / rust (push) Successful in 1m50s
ci / web (push) Successful in 56s
ci / docs-site (push) Successful in 57s
decky / build-publish (push) Successful in 11s
android / android (push) Successful in 3m13s
apple / screenshots (push) Successful in 5m32s
deb / build-publish (push) Successful in 3m15s
docker / build-push (--build-arg FEDORA_VERSION=44, ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm) (push) Successful in 5s
windows-host / package (push) Successful in 7m35s
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Successful in 31s
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 4s
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 4s
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 4s
ci / bench (push) Successful in 4m53s
rpm / build-publish (bazzite, punktfunk-fedora-rpm) (push) Successful in 9m58s
rpm / build-publish (fedora-44, punktfunk-fedora44-rpm) (push) Successful in 9m54s
docker / deploy-docs (push) Successful in 18s
- new crate::gpu (compiled on all platforms so the OpenAPI doc stays platform-independent): DXGI / sysfs GPU inventory with reboot-stable ids (PCI vendor:device + occurrence — LUIDs are per-boot), persisted auto/manual preference (<config>/gpu-settings.json, atomic temp+rename with in-memory rollback), one selection with precedence console preference > PUNKTFUNK_RENDER_ADAPTER > max VRAM and graceful fallback when the preferred GPU is absent, plus a live "in use" record (RAII session guard wrapped around every encoder open_video returns) - fix: windows_gpu_vendor derived the encoder backend from DXGI adapter 0 instead of the selected render adapter — on a hybrid box (e.g. Intel iGPU at index 0 + NVIDIA dGPU) the backend could disagree with the GPU the capture ring / IddCx render pin sit on. The NVENC 4:4:4 probe now also runs on the selected adapter (was: OS default), the codec/4:4:4 probe caches are keyed per selected GPU (were process-lifetime OnceLocks), and an explicit PUNKTFUNK_ENCODER conflicting with the selected GPU's vendor warns up front - mgmt API: GET /api/v1/gpus (inventory + mode + preferred + next-session selection with reason + in-use GPU/backend/session-count) and PUT /api/v1/gpus/preference (validates mode/gpu_id before writing); openapi.json regenerated; the vdisplay render pin now also engages for a console preference (not just the env pin) - web console: GPU card on the Host page — list with vendor + VRAM, Automatic / Prefer controls, Preferred / Next session / "In use · backend" badges, missing-preferred-GPU warning and env-pin note; en + de messages - Linux: a matched manual preference picks the VAAPI render node and the NVENC-vs-VAAPI auto choice; auto mode is exactly the previous behavior Validated live on the hybrid laptop (RTX 3500 Ada + Intel Arc Pro, which enumerates twice — the occurrence ids disambiguate): enumerate, prefer, bad-id 400, restart persistence, auto-restore keeping the stored pick. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -138,6 +138,100 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/v1/gpus": {
|
||||
"get": {
|
||||
"tags": [
|
||||
"gpu"
|
||||
],
|
||||
"summary": "GPU inventory and selection",
|
||||
"description": "Lists the host's hardware GPUs, the persisted auto/manual preference, the GPU the next session\nwill use (and why), and the GPU live sessions encode on right now.",
|
||||
"operationId": "listGpus",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "GPU inventory + selection state",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/GpuState"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
"description": "Missing or invalid bearer token",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/ApiError"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/v1/gpus/preference": {
|
||||
"put": {
|
||||
"tags": [
|
||||
"gpu"
|
||||
],
|
||||
"summary": "Set the GPU preference",
|
||||
"description": "`auto` restores automatic selection (`PUNKTFUNK_RENDER_ADAPTER` pin, else max dedicated VRAM);\n`manual` pins capture + encode to the given GPU. Persisted across restarts; applies to the\n**next** session (a running session keeps its GPU). If the preferred GPU is absent at session\nstart the host falls back to automatic selection rather than failing.",
|
||||
"operationId": "setGpuPreference",
|
||||
"requestBody": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/SetGpuPreference"
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": true
|
||||
},
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Preference stored; the new selection state",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/GpuState"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "Unknown mode, or `gpu_id` missing / not a listed GPU",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/ApiError"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
"description": "Missing or invalid bearer token",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/ApiError"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "Preference could not be persisted",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/ApiError"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/v1/health": {
|
||||
"get": {
|
||||
"tags": [
|
||||
@@ -1373,6 +1467,40 @@
|
||||
},
|
||||
"components": {
|
||||
"schemas": {
|
||||
"ApiActiveGpu": {
|
||||
"type": "object",
|
||||
"description": "The GPU live sessions are encoding on right now.",
|
||||
"required": [
|
||||
"id",
|
||||
"name",
|
||||
"vendor",
|
||||
"backend",
|
||||
"sessions"
|
||||
],
|
||||
"properties": {
|
||||
"backend": {
|
||||
"type": "string",
|
||||
"description": "The encode backend in use (`nvenc` | `amf` | `qsv` | `vaapi` | `software`)."
|
||||
},
|
||||
"id": {
|
||||
"type": "string",
|
||||
"description": "Stable id matching an entry of `gpus` (empty for the CPU/software encoder)."
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"sessions": {
|
||||
"type": "integer",
|
||||
"format": "int32",
|
||||
"description": "Number of live encode sessions on it.",
|
||||
"minimum": 0
|
||||
},
|
||||
"vendor": {
|
||||
"type": "string",
|
||||
"description": "`nvidia` | `amd` | `intel` | `other`."
|
||||
}
|
||||
}
|
||||
},
|
||||
"ApiCodec": {
|
||||
"type": "string",
|
||||
"description": "Video codec identifier.",
|
||||
@@ -1394,6 +1522,64 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"ApiGpu": {
|
||||
"type": "object",
|
||||
"description": "One hardware GPU on the host (software/WARP adapters are never listed).",
|
||||
"required": [
|
||||
"id",
|
||||
"name",
|
||||
"vendor",
|
||||
"vram_mb"
|
||||
],
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string",
|
||||
"description": "Stable identifier (`vendorid-deviceid-occurrence`, hex PCI ids) — pass to `setGpuPreference`.\nStable across reboots and driver updates, unlike an adapter index or LUID.",
|
||||
"example": "10de-2c05-0"
|
||||
},
|
||||
"name": {
|
||||
"type": "string",
|
||||
"description": "Adapter/marketing name.",
|
||||
"example": "NVIDIA GeForce RTX 5070 Ti"
|
||||
},
|
||||
"vendor": {
|
||||
"type": "string",
|
||||
"description": "`nvidia` | `amd` | `intel` | `other`."
|
||||
},
|
||||
"vram_mb": {
|
||||
"type": "integer",
|
||||
"format": "int64",
|
||||
"description": "Dedicated VRAM in MiB (0 where the platform doesn't expose it).",
|
||||
"minimum": 0
|
||||
}
|
||||
}
|
||||
},
|
||||
"ApiSelectedGpu": {
|
||||
"type": "object",
|
||||
"description": "The GPU the **next** session's pipeline will be created on, and why. (A preference change\napplies to the next session; a running session keeps the GPU it opened on.)",
|
||||
"required": [
|
||||
"id",
|
||||
"name",
|
||||
"vendor",
|
||||
"source"
|
||||
],
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"source": {
|
||||
"type": "string",
|
||||
"description": "Why this GPU was selected: `preference` (the manual choice), `env`\n(`PUNKTFUNK_RENDER_ADAPTER`), `auto` (max dedicated VRAM / platform default), or\n`preference_missing` (a manual choice is set but that GPU is absent — auto-selected\ninstead so the host keeps streaming)."
|
||||
},
|
||||
"vendor": {
|
||||
"type": "string",
|
||||
"description": "`nvidia` | `amd` | `intel` | `other`."
|
||||
}
|
||||
}
|
||||
},
|
||||
"ApprovePending": {
|
||||
"type": "object",
|
||||
"description": "Approve-pending-device request body. Send `{}` to keep the device's own name.",
|
||||
@@ -1671,6 +1857,75 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"GpuState": {
|
||||
"type": "object",
|
||||
"description": "Full GPU-selection state for the console: inventory, the persisted preference, what the next\nsession will use, and what is in use right now.",
|
||||
"required": [
|
||||
"gpus",
|
||||
"mode",
|
||||
"preferred_available"
|
||||
],
|
||||
"properties": {
|
||||
"active": {
|
||||
"oneOf": [
|
||||
{
|
||||
"type": "null"
|
||||
},
|
||||
{
|
||||
"$ref": "#/components/schemas/ApiActiveGpu",
|
||||
"description": "The GPU live sessions use right now (absent while nothing is streaming)."
|
||||
}
|
||||
]
|
||||
},
|
||||
"env_override": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
],
|
||||
"description": "`PUNKTFUNK_RENDER_ADAPTER` (the host.env pin), when set — it applies while `mode` is\n`auto`; a manual preference overrides it."
|
||||
},
|
||||
"gpus": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/ApiGpu"
|
||||
},
|
||||
"description": "The host's hardware GPUs."
|
||||
},
|
||||
"mode": {
|
||||
"type": "string",
|
||||
"description": "`auto` or `manual`."
|
||||
},
|
||||
"preferred_available": {
|
||||
"type": "boolean",
|
||||
"description": "Whether the preferred GPU is currently present."
|
||||
},
|
||||
"preferred_id": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
],
|
||||
"description": "The manually preferred GPU's stable id, when one is stored (kept while `mode` is `auto` so\na console can offer returning to it). May reference a GPU that is currently absent."
|
||||
},
|
||||
"preferred_name": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
],
|
||||
"description": "The stored name of the preferred GPU (a usable label even when it is absent)."
|
||||
},
|
||||
"selected": {
|
||||
"oneOf": [
|
||||
{
|
||||
"type": "null"
|
||||
},
|
||||
{
|
||||
"$ref": "#/components/schemas/ApiSelectedGpu",
|
||||
"description": "The GPU the next session will use."
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"Health": {
|
||||
"type": "object",
|
||||
"description": "Liveness + version probe.",
|
||||
@@ -2047,6 +2302,28 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"SetGpuPreference": {
|
||||
"type": "object",
|
||||
"description": "Request body for `setGpuPreference`.",
|
||||
"required": [
|
||||
"mode"
|
||||
],
|
||||
"properties": {
|
||||
"gpu_id": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
],
|
||||
"description": "Required when `mode` is `manual`: the stable `id` of a currently listed GPU\n(see `listGpus`).",
|
||||
"example": "10de-2c05-0"
|
||||
},
|
||||
"mode": {
|
||||
"type": "string",
|
||||
"description": "`auto` (env pin, else max dedicated VRAM — the default) or `manual`.",
|
||||
"example": "manual"
|
||||
}
|
||||
}
|
||||
},
|
||||
"StageTiming": {
|
||||
"type": "object",
|
||||
"description": "One pipeline stage's latency in an aggregation window (microseconds).",
|
||||
@@ -2267,6 +2544,10 @@
|
||||
"name": "host",
|
||||
"description": "Host identity, capabilities, and liveness"
|
||||
},
|
||||
{
|
||||
"name": "gpu",
|
||||
"description": "GPU inventory and selection: list the host's GPUs, choose automatic or a preferred GPU, see the one in use"
|
||||
},
|
||||
{
|
||||
"name": "clients",
|
||||
"description": "Paired Moonlight client management"
|
||||
|
||||
Reference in New Issue
Block a user