Skip to content

Commit c73158d

Browse files
committed
chore: improve live
1 parent 2854ab3 commit c73158d

4 files changed

Lines changed: 33 additions & 43 deletions

File tree

src/lib/common.sh

Lines changed: 0 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -414,22 +414,6 @@ _cudaupload_dmabuf_ok() {
414414
[ "$GS_CUDAUPLOAD_DMABUF" = 1 ]
415415
}
416416

417-
# True when GPU HUD compositing is usable for the live/demo composite: requested
418-
# (GS_GPU_COMPOSITOR=1), the active encoder is CUDA NVENC (so the
419-
# cudacompositor -> cudaconvertscale -> nvenc chain stays GPU-resident), and this
420-
# gst has cudacompositor. Moves the 1080p60 HUD alpha-blend off the 2 capture CPU
421-
# cores onto the GPU. Default OFF (opt-in); a miss => the software `compositor`.
422-
# Cached. Usage: _gpu_compositor_ok <codec>.
423-
_gpu_compositor_ok() {
424-
case "${GS_GPU_COMPOSITOR:-0}" in 1|on|true|yes) ;; *) return 1 ;; esac
425-
_active_encoder_is_cuda "${1:-h264}" || return 1
426-
if [ -z "${GS_CUDACOMP_OK:-}" ]; then
427-
if gst-inspect-1.0 cudacompositor >/dev/null 2>&1; then GS_CUDACOMP_OK=1; else GS_CUDACOMP_OK=0; fi
428-
export GS_CUDACOMP_OK
429-
fi
430-
[ "$GS_CUDACOMP_OK" = 1 ]
431-
}
432-
433417
# Emit the scale + colorspace-convert fragment that feeds the encoder.
434418
# When the active encoder is a CUDA NVENC element and cudaconvertscale is
435419
# present, the scale (e.g. 1440p->1080p) and RGBx->NV12 convert run on the

src/lib/stream.sh

Lines changed: 17 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -118,28 +118,17 @@ start_capture() {
118118
if [ -n "$hud_xid" ]; then
119119
log " composite: cs2 present-hook + HUD overlay (xid=$hud_xid, hud=${hud_fps}fps)"
120120
# sink_0 = cs2 (base), sink_1 = HUD on top. The HUD ximagesrc MUST carry alpha
121-
# (BGRA) or it paints opaque over cs2.
122-
local cs2_src hud_src outchain
123-
if _gpu_compositor_ok "$codec"; then
124-
# GPU HUD blend: upload both legs to CUDA and let cudacompositor alpha-blend
125-
# on the GPU, then cudaconvertscale -> nvenc, all GPU-resident. Keeps the
126-
# 1080p60 blend off the 2 capture CPU cores (the software compositor's cost).
127-
# Opt-in (GS_GPU_COMPOSITOR=1); dies-on-spawn falls back to ximagesrc below.
128-
log " composite: GPU HUD blend (cudacompositor) — alpha-blend off the capture CPU"
129-
cs2_src="appsrc name=vksrc ! queue ! videorate ! video/x-raw,framerate=$fps/1 ! cudaupload ! comp.sink_0"
130-
hud_src="ximagesrc xid=$hud_xid use-damage=0 show-pointer=false ! video/x-raw,framerate=$hud_fps/1 ! videoconvert ! video/x-raw,format=BGRA ! cudaupload ! queue leaky=downstream max-size-buffers=2 ! comp.sink_1"
131-
outchain="cudacompositor name=comp background=black ! cudaconvertscale ! video/x-raw(memory:CUDAMemory),format=NV12,width=$out_w,height=$out_h,framerate=$fps/1 ! $enc ! $parse"
132-
else
133-
# Software compositor (default): CPU alpha-blend on the capture cores.
134-
cs2_src="appsrc name=vksrc ! queue ! videorate ! video/x-raw,framerate=$fps/1 ! comp.sink_0"
135-
# leaky=downstream: a slow/blocked HUD grab drops its own frames instead of
136-
# back-pressuring the compositor (which would stall the cs2 leg too).
137-
hud_src="ximagesrc xid=$hud_xid use-damage=0 show-pointer=false ! video/x-raw,framerate=$hud_fps/1 ! videoconvert ! video/x-raw,format=BGRA ! queue leaky=downstream max-size-buffers=2 ! comp.sink_1"
138-
# queue after the compositor = a thread boundary so the software blend and the
139-
# upload/encode run on separate cores (else they serialize on one). Bounded by
140-
# buffer count — raw frames are big, so the default byte cap would throttle.
141-
outchain="compositor name=comp background=black ! queue max-size-buffers=8 max-size-bytes=0 max-size-time=0 ! $convert ! $enc ! $parse"
142-
fi
121+
# (BGRA) or it paints opaque over cs2 — verify this on-node first. The software
122+
# compositor stays: LIVE-DIAG measured the capture at ~1.1 of its 2 cores, so the
123+
# blend is NOT CPU-bound and GPU compositing would buy nothing here.
124+
local cs2_src="appsrc name=vksrc ! queue ! videorate ! video/x-raw,framerate=$fps/1 ! comp.sink_0"
125+
# leaky=downstream: a slow/blocked HUD grab drops its own frames instead of
126+
# back-pressuring the compositor (which would stall the cs2 leg too).
127+
local hud_src="ximagesrc xid=$hud_xid use-damage=0 show-pointer=false ! video/x-raw,framerate=$hud_fps/1 ! videoconvert ! video/x-raw,format=BGRA ! queue leaky=downstream max-size-buffers=2 ! comp.sink_1"
128+
# queue after the compositor = a thread boundary so the software blend and the
129+
# upload/encode run on separate cores (else they serialize on one). Bounded by
130+
# buffer count — raw frames are big, so the default byte cap would throttle.
131+
local outchain="compositor name=comp background=black ! queue max-size-buffers=8 max-size-bytes=0 max-size-time=0 ! $convert ! $enc ! $parse"
143132
local pipeline
144133
if [ "$audio" = 1 ]; then
145134
pipeline="$outchain ! queue ! mux. \
@@ -153,6 +142,12 @@ $cs2_src \
153142
$hud_src"
154143
fi
155144
export VKCAP_FPS="$fps"
145+
# With LIVE-DIAG on, also have the consumer log presents/s — present-locked,
146+
# so presents/s == cs2's REAL render fps feeding the stream. This is the signal
147+
# LIVE-DIAG lacked: presents/s consistently ≥ fps ⇒ the stream is true CFR and
148+
# any "not 60" feel is the camera switching or viewer-side; presents/s dipping
149+
# below fps ⇒ cs2 isn't holding the rate under live load (videorate then dups).
150+
[ "${GS_LIVE_DIAG:-1}" = "1" ] && export VKCAP_DEBUG="${VKCAP_DEBUG:-1}"
156151
# Zero-copy is clip-only: the software `compositor` here blends in system
157152
# memory and can't consume a device-local dmabuf. Force it off so a global
158153
# VKCAP_ZEROCOPY=1 can't push this path into the ximagesrc fallback.

src/spectator/constants.mjs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,13 @@ export const DIRECTOR_STICKY_PRIORITY_FLOOR = 140;
4848
// whiplashes between simultaneous events.
4949
export const DIRECTOR_MIN_DWELL_MS = 3_000;
5050

51+
// Death of the current target still cuts fast, but NOT instantly: snapping at
52+
// 0ms cuts into cs2's death-cam/PVS transition (a black frame on the new POV, made
53+
// worse by GSI's ~10Hz alive-state lagging cs2's render tick) and reads as frantic.
54+
// A short grace lets the kill register + the new view settle, then cut. Also linger
55+
// a beat on the kill, which is natural broadcast feel. Tune/zero via env if needed.
56+
export const DIRECTOR_DEATH_GRACE_MS = 350;
57+
5158
// Max time on one target before the camera is forced off — keeps the
5259
// view moving when nothing else is more interesting.
5360
export const DIRECTOR_DWELL_MAX_MS = {

src/spectator/director/index.mjs

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import process from "node:process";
22

33
import {
44
DIRECTOR_MIN_DWELL_MS,
5+
DIRECTOR_DEATH_GRACE_MS,
56
DIRECTOR_TICK_MS,
67
KEY_AUTODIRECTOR_OFF,
78
} from "../constants.mjs";
@@ -59,14 +60,17 @@ export async function directorTick() {
5960
: null;
6061
if (cur && cur.steamId === pick.target.steamId && cur.alive) return;
6162

62-
// Death of the current target is the only thing that bypasses the
63-
// minimum dwell. Event bonuses (AWP kill, damage dealer, upset)
64-
// already bumped priority — they wait their turn here.
63+
// Death of the current target cuts fast but NOT instantly — a short grace
64+
// (vs the full dwell) lets cs2's death-cam/PVS transition settle so we don't
65+
// cut into a black frame, and avoids whiplash on multi-kill exchanges. Event
66+
// bonuses (AWP kill, damage dealer, upset) already bumped priority — they wait
67+
// the full dwell.
6568
const sinceLast = nowMs - directorState.lastSwitchMs;
66-
if (!pick.forcedByDeath && sinceLast < DIRECTOR_MIN_DWELL_MS) {
69+
const dwellFloor = pick.forcedByDeath ? DIRECTOR_DEATH_GRACE_MS : DIRECTOR_MIN_DWELL_MS;
70+
if (sinceLast < dwellFloor) {
6771
logSkip(
6872
"skip: min-dwell",
69-
`${sinceLast}ms < ${DIRECTOR_MIN_DWELL_MS}ms (want=${pick.target.name ?? pick.target.steamId} slot=${pick.target.slot})`,
73+
`${sinceLast}ms < ${dwellFloor}ms (want=${pick.target.name ?? pick.target.steamId} slot=${pick.target.slot}${pick.forcedByDeath ? " death-grace" : ""})`,
7074
);
7175
return;
7276
}

0 commit comments

Comments
 (0)