Repository navigation
Export: stream frames through a shared CPU/GPU work queue, convert to YUV 4:2:0 on the GPU, and tune to the hardware - #898
Conversation
2288fa8 to
49efcba
Compare
|
Tested #688 + #898 together on an 8 GB NVIDIA card on Windows, as asked in #879. Short version: every frame stays on the GPU, VRAM stays flat, output matches the CPU, and the 4K auto rule is now far too conservative for 8 GB cards. Setup
Results (H.264, 144 frames,
For comparison, the stock CPU path took about 2.4 s per 4K frame on this machine (~6 min for the comp). GPU vs CPU pixels (6 frames at 4K, The 4K auto rule
With #688 in the build, the measured peak for that frame size was +2.69 GB, about a quarter of the estimate, and all 144 frames rendered on the GPU. So on 8 GB cards the auto rule currently sends 4K exports to the CPU when they fit. It may be worth recalibrating the ~1,332 B/pixel figure once #688 lands (or checking whether texture reuse is on). One data point only; content-dependent, of course. Merging the two PRs (in case it saves someone time): #898 conflicts with #688 in Measured on my machine with the help of Claude (an AI assistant), which wrote the test scripts; all numbers come from this PC. |
|
Follow-up on the 4K estimate: I re-measured Every run:
So with #688 the per-pixel cost is about 2–5× below 1,332, but it also varies about 2.5× with content, so a single constant can't be both safe and tight:
Measured on my machine with the help of Claude (an AI assistant), which wrote the test scripts; all numbers come from this PC. |
|
@SakithaSamarathunga33 thank you for testing this so thoroughly on the RTX 5060. The real 4K comp, the GPU-vs-CPU PSNR check and the per-pixel re-measurement with reproducible The duplicate-field clash is fixed in 7c29ed9. #898's upload-budget code is now identical to #688's, and You're right about the 4K estimate. With #688 your numbers give about 692 B/px (Intro), 348 B/px (3D Showcase) and 260–274 B/px (your 4K comp), against the 1,332 B/px constant. They also vary about 2.5× with content, so no single constant is both safe and tight. Once #688 lands I'll revisit this in a follow-up: plan from the first frame's measured peak rather than a constant. Until then, |
With a GPU accelerator every frame of a batch rendered in parallel, each holding its own working textures, so a 30-frame 1080p batch could take the whole card (20+ GB on an RTX 3090) and run shared-memory GPUs out of memory. Cx::frames now renders GPU frames in consecutive chunks of 3 (EFFECTCRAFT_GPU_INFLIGHT overrides it), the same cap as the export scheduler in #898; output order and the CPU path are unchanged. Fixes #879
Three frames at once still filled an 8 GB card on the #879 project at 1080p (7.9 GB, the same as no cap); one frame peaked at 6.3 GB and two at 7.0 to 7.8 GB. One matches how the export scheduler in #898 renders image sequences (one GPU worker, synchronous readback). EFFECTCRAFT_GPU_INFLIGHT still raises it.
- The cap applied whenever an accelerator was attached, so Software Only projects (which render on the CPU) rendered one frame at a time. It now needs the project's GPU acceleration too, as #898 gates it. - Only the render is capped: each window of max(cap, threads) frames renders in chunks of the cap, then encodes in parallel, holding at most one window of images. - An invalid EFFECTCRAFT_GPU_INFLIGHT (0, negative, not a number, too large) logs a warning before using the default. - The default stays one frame: the GPU backend reports no free or total device memory portably, so it is not sized from VRAM.
…can convert an export frame to 8-bit YUV 4:2:0 with an asynchronous readback (storytold#897) Defaulted Accelerator hooks for the export: adapter_info, set_memory_budgets, comp_frame_yuv420 (declines by default, so the caller renders on the CPU) and poll_readbacks. gpu_flags carries the export's switches (resident precomps, unified memory) and the reason the accelerator last declined a frame on this thread, so the GPU crate reads no environment.
|
Thank you for this, @MatejKozlovsky! Thanks for the detailed tests (byte-identical YUV and scheduler output). We're holding it for a maintainer decision, and #1184 and #688 landed first, so it needs a rebase. Things to change:
If you are not on our Discord yet, please join us: https://discord.gg/artcraft |
…chronously through a staging ring (storytold#897) A compute kernel does the export's to_rgba8_over + rgba_to_yuv420 (byte-identical to the CPU, tested), so a 4K frame reads back 12 MB of 8-bit 4:2:0 instead of 133 MB of RGBA f32, through the context's bounded readbacks. The context reports its adapter and takes upload and pool budgets (set_upload_budget / set_pool_budget; defaults unchanged); resident precomps follow the export's switch (on by default).
…ue with a bounded reorder buffer, adaptive CPU workers and hardware-adaptive tuning, instead of handing the GPU a batch of frames at once (storytold#897) The batch-parallel export sent a whole batch (up to 16 frames) to the GPU at once; the frames filled VRAM and fell back to the CPU without a word. A tuned plan now keeps gpu-frames on the GPU, adds CPU workers only while measured throughput improves (and never ones slow enough to stall the in-order encoder), and logs why the GPU declined a frame. H.264/HEVC/AV1/WebM take the GPU's YUV 4:2:0 frames, read back asynchronously so the GPU worker records the next frame during the copy. Every option is set independently: CLI > EFFECTCRAFT_EXPORT_<OPTION> > config file > auto. Auto reads the GPU (VRAM, vendor, unified memory) and CPU (threads, performance/efficiency cores, RAM). The tuned pipeline is automatic only on NVIDIA discrete and Apple GPUs; other vendors keep the stock behaviour unless enabled. The RAM budget caps the CPU path's batch. CPU-only exports are unchanged. See crates/export/TUNING.md.
… its clusters follow the render batches (storytold#897)
… for more, and the GPU worker runs at normal priority (drops thread-priority and gpu-priority / gpu-ideal-cpu: off by default, no measured gain) (storytold#897)
…FECTCRAFT_EXPORT_* variables and the tuning config file, and passes them in; the export library spawns no processes and reads no system files (storytold#897)
7c29ed9 to
5aa3db0
Compare
|
Thanks @echelon, rebased onto current main (with #688 and #1184) and addressed all three points:
One more change: the scheduler now keeps 1 frame in flight per GPU worker by default. With more, a frame that runs out of GPU memory is retried, and the retry plus a few frames after it don't render exactly like a clean run. With 1 in flight, 3 runs are byte-identical and it's still faster than main. #1212 overlaps with this PR's budgeting ( Tests: |
|
Wrong layers after an absorbed GPU out-of-memory: fix proposed (3 small commits on top of 5aa3db0) What broke. On a test PC (GTX 1070 8 GB), a GPU export of a 40-layer effects comp at half resolution rendered the wrong layers. Some layers appeared several times and others were missing: about 217k px differ on frame 0, by up to 255 levels. That frame was already rendered on the CPU after the GPU declined it. Why. With this PR, an uncaptured OOM inside an armed frame no longer retires the device; the frame is declined if
Why only on cards that really run out. The path is taken only on real allocation failures. On a 24 GB GPU every variant is byte-identical to Fix.
Tests.
Possible follow-ups, not in this patch:
|
|
Closing this one. It grew way too big and mixes too many things. I'm splitting it up:
The OOM absorption bit isn't coming back in this form. It shared state across frames in flight, which is racy. That should get one proper recovery design instead. The scheduler/tuning part may come back later as its own smaller PR, without the env vars, with the knobs on the CLI. Thanks for all the review here, it shaped the split. |
Closes #897. This branch merges cleanly onto current main and does not depend on #688. It works with or without #688; with #688 (GPU-resident effect stacks, texture reuse) it gets most of the 4K gain measured below.
GpuContext::set_upload_budget(default unchanged) is also added by #688. Its code here (theupload_budgetfield and initialiser,set_upload_budget/upload_budget, and the budget check and one-time "upload cache full" warning inupload_buf) is written line for line as in #688, so git applies it once in either merge order. Merging #688 into this branch, or this branch into #688, both on top of main, gives the same tree with no conflicts and no duplicate field.On the test PC (RTX 3090), a 3 s 4K comp exports with
--gpuin 49.7 s instead of 201.9 s on main (median of 3 runs each).What this PR does
crates/export/src/stream.rs,pipeline.rs). Main sends a batch of up to 16 frames to the GPU at once. This PR hasgpu-framesGPU workers and adaptive CPU workers claim frames from a shared queue, and a bounded reorder buffer feeds the in-order encoder. A CPU worker is added only while measured throughput improves and never when a CPU frame is slow enough to stall the encoder. Every GPU decline is logged with its reason.crates/gpu/src/yuv.rs,shaders/yuv.wgsl). A compute kernel does the export'sto_rgba8_overandrgba_to_yuv420, so a 4K frame reads back 12 MB instead of 133 MB of RGBA f32. The copy goes through the context's bounded readbacks and an 8-buffer staging ring, and the GPU worker records the next frame while it runs. H.264 only; other formats take RGBA as before.crates/export/src/tuning.rs,hw.rs,TUNING.md). Each option resolves independently: CLI, thenEFFECTCRAFT_EXPORT_<OPTION>, then a--tuning-configfile, then auto. Auto reads VRAM, vendor, unified memory, core kinds and RAM, andrender --print-tuningprints every value with its source and reason. The tuned GPU path is automatic only on NVIDIA discrete and Apple GPUs. AMD, Intel, integrated and software adapters keep the stock path unless the user opts in with--backend gpuor individual options. VRAM and RAM are detected withoutunsafe(nvidia-smi, CIM on Windows, sysfs/procfs on Linux, sysctl on macOS). When a verified GPU's VRAM is not reported, auto keeps the stock path. No hardware encoder.LayerCache::default(), 1 GiB. This PR sets it to 15% of RAM, at most 4096 MB (cache-mb). This changes CPU-only exports too; see "Why CPU-only 4K is faster".crates/gpu/src/readback.rs,lib.rs,stream.rs). wgpu reports a failed allocation to the device's uncaptured-error handler. While an export frame records, that error now declines only that frame (reasonout of memory) instead of retiring the device. The GPU worker waits for its frames in flight, keeps one fewer in flight (at least 1, logged once) and retries on the GPU. A frame that fails again renders on the CPU. Outside an export frame, for example in the viewer or the app's preview, any GPU error still retires the context as on main.crates/render). Defaulted trait methods (adapter_info,set_memory_budgets,comp_frame_yuv420,poll_readbacks) andgpu_flags, so the GPU crate reads no environment.There are four commits, one change each: render hooks, GPU YUV and readback, export scheduler and tuning, CLI flags. The PR exposes no inert options. #688's
texture-reuseandresident-fxswitches are left out because without #688 they control nothing. They come back as a small follow-up after #688: twoGpuFlagsfields, twoOverrides/ExportTuningfields with their rules and--print-tuningrows (next toprecomp-resident), twoTUNING.mdrows, two CLI flags and #688's two checks incrates/gpu.Behaviour changes outside
--gpu--gpuand an audio track, the streamed H.264 path muxes audio after every frame instead of after every batch. The MP4 differs in layout from a CPU export; the video and the decoded audio are identical (tests/scheduler.rs).Tests
yuv::tests::yuv420_matches_cpu: the GPU 4:2:0 conversion is byte-identical to the CPU path.stream::tests: in-order output with out-of-order workers, the buffer bound, CPU-worker adaptation.tests/scheduler.rs: with a mock accelerator that renders or declines every frame, a scheduled export matches the CPU export byte for byte in H.264, ProRes, WebM, GIF, PNG and EXR without audio, and in video and decoded audio with an audio track.tuning::tests: precedence, vendor gating, unified memory, the RAM cap, and the calibrated estimate (8 GB at 4K picks the CPU with the warning, 24 GB at 4K plans 3 in flight, 8 GB at 1080p plans 3).readback::oom_tests: a failed allocation and the validation errors it causes decline the frame without retiring the device; a validation error on its own still retires it; outside an export frame every error retires it.stream::inflight_tests: out of memory lowers frames in flight once, never below 1, and retries on the GPU.cargo fmt --all --check,cargo clippy --workspace --all-targets -- -D warnings,cargo test --release -p effectcraft-export -p effectcraft-gpu -p effectcraft-render(GPU tests on real hardware),effectcraft-ui-egui --test ui_live_previewandcargo xtask wasmpass.tests_auto::auto_backend_keeps_fast_accelerators(main code, not touched here) failed once under full-suite load and passed 3 of 3 alone.Measured
All runs export the same comp (
Bench60F, 3 s, 90 frames, H.264) with noRAYON_NUM_THREADSoverride. 1080p is the same comp at--resolution half. GPU utilisation and VRAM come fromnvidia-smievery 250 ms and include other processes. "Output vs CPU" compares with main's CPU render.Test PC: Ryzen 7 5800X (16 threads), RTX 3090 24 GB, 64 GB RAM, Windows 10
Median of 3 runs per row, range in brackets. Runs were alternated between main and this PR. The third round used builds on 67434e9 (main moved by a CI-only change); its outputs are MD5-identical to the earlier rounds.
--gpu--gpuThe 1080p main rows are from an earlier session (same binary).
--gpuand 3.5x faster than main on the CPU. Main--gpurenders every 4K frame on the CPU without logging it: its output is MD5-identical to the CPU render.Why CPU-only 4K is faster
On the test PC, CPU-only 4K took 171.9 s on main (171.6-175.2) and 117.8 s with this PR (117.3-118.7), with MD5-identical output. With
--cache-mb 1024, main's cache size, this PR took 173.1 s (172.4-173.2; 3 runs, alternated with the others). The whole gain is the layer cache: at 4K a cached layer buffer is about 133 MB, so 1 GiB holds about 7 and 4 GB about 30, and static layers stop re-rendering. The scheduler is not involved; a CPU-only export keeps main's parallel batches (16 frames on this machine). The laptop shows the same effect (466.7 s on main, 318.6 s with this PR; one run each).Test laptop: i7-7700HQ (4C/8T), GTX 1070 8 GB, 32 GB RAM, Windows 10, NVIDIA 582.78
One run per row.
--gpu--gpu--gpu--gpu. The 318.6 s 4K run is faster than main's CPU run because of the larger layer cache.Test Mac: Apple M4, 16 GB
1080p only, one run per row, with an earlier build stacked on #688 (this PR alone has not been measured on the Mac). Main CPU 18 s; main
--gpu16 s; this PR + #688--gpu6 s with all 90 frames on the GPU and the same PSNR against the CPU as main--gpu(49.7 dB). With #688 stacked, the M4 also rendered 4K on the GPU.Box without a hardware GPU (llvmpipe)
Bench4K0.5 s at half resolution, one run each: main--gpuwas killed by the OOM killer after 16.8 s; this PR with--gpu --backend gpucompleted in 14.9 s with no declines. llvmpipe rasterises on the CPU, so this shows bounded memory, not speed.