Skip to content

Export: convert H.264 frames to YUV 4:2:0 on the GPU - #1631

Open
MatejKozlovsky wants to merge 1 commit into
storytold:mainfrom
MatejKozlovsky:gpu-yuv-convert
Open

MatejKozlovsky wants to merge 1 commit into
storytold:mainfrom
MatejKozlovsky:gpu-yuv-convert

Conversation

@MatejKozlovsky

@MatejKozlovsky MatejKozlovsky commented Oct 16, 2026 •

Copy link
Copy Markdown
Contributor

YUV part of #898, split out.

With the GPU on, H.264 export reads every frame back as RGBA f32 and converts to 4:2:0 on the CPU. At 4K that's ~133 MB per frame back for ~12 MB of planes. This does the conversion in a small compute kernel and reads back only the planes.

  • New Accelerator::comp_frame_yuv420, defaults to None. The GPU compositor implements it.
  • Export asks for planes only when nothing happens after the render (progressive, project bit depth, no crop/resize, output size). Otherwise, or on None, it's the old RGBA path. Deferred readbacks (browser worker) keep their path. Sizes the kernel can't do (width not a multiple of 8, odd height) are rejected before rendering.
  • rgba_to_yuv420 moved from export to effectcraft_raster::yuv so export and the GPU tests share it.

GPU output is within one level of the CPU conversion. llvmpipe matches exactly; on an RTX 3090 (Vulkan) ~0.25% of bytes come out one level off, probably FMA contraction / divide precision in the driver. Tests require exact on software adapters and allow ±1 on a small fraction elsewhere.

Tests:

  • yuv420_matches_cpu (noise, NaN/inf, out-of-range, a few sizes)
  • rounding_ties_match_cpu (every Y tie, plus U/V ties)
  • comp_frame_yuv420_is_the_cpu_conversion_of_comp_frame, odd_sized_comps_fall_back_to_the_cpu, unsupported_sizes_are_not_converted
  • export/tests/h264_gpu_yuv.rs (fake accelerator, checks the wiring, fallback and deferred path)

GPU tests follow the crate's usual tests::gpu() pattern and skip without an adapter.

Tested on llvmpipe and an RTX 3090 (Vulkan). DX12 on that card fails to create the context on main already (classic3d: Validation Error), separate issue. fmt/clippy clean; only the two known main failures (tests_fx_pixel2::stylize, tests_fx_stylize::posterize_thresholds_strobe_vignette).

Async version is #1632, on top of this.

Accelerator::comp_frame_yuv420 (default None) renders a top-level comp frame and
converts it over the background to the H.264 export's 8-bit BT.709 limited-range
4:2:0, every byte within one level of the CPU conversion (the GPU kernel follows
it operation for operation, including NaN and half-away-from-zero ties, but a
driver may contract its arithmetic). A 4K frame then reads back 12 MB of planes
instead of 133 MB of RGBA f32.

The H.264 export asks the accelerator for planes when a frame needs nothing after
the render (progressive, project depth, no crop/resize, rendered at the output
size) and otherwise renders and converts on the CPU as before. Deferred
readbacks (a browser worker) keep the pass-based path. Sizes the kernel can't
take (width not a multiple of 8, odd height) are declined before any GPU work.

rgba_to_yuv420 moves to effectcraft_raster::yuv so the export and the GPU tests
share one reference. The GPU tests are #[ignore]d and fail without an adapter
instead of passing untested.
@00xJS

00xJS commented Oct 18, 2026

Copy link
Copy Markdown

Tested on AMD: RX 6900 XT, Windows 11, Adrenalin 32.0.21045.5002, Vulkan, EFFECTCRAFT_REQUIRE_GPU=1, head dd400143.

All 6 yuv tests pass. The ±1 rate is about the same as on your 3090:

check bytes off by one max
comp frame (comp_frame_yuv420_is_the_cpu_conversion_of_comp_frame) 22 of 8640 (0.25 %) 1
noise 264×150 / 8×2 / 1928×6 0 / 0 / 0 0
Y ties 20 of 3072 (0.65 %) 1
chroma ties 62 of 3072 (2.0 %) 1

Odd sizes fall back to the CPU, and unsupported sizes are declined as expected. Whole effectcraft-gpu suite: 215 passed, and the only 3 failures also fail on main (#1605). effectcraft-export --test h264_gpu_yuv: 3 passed. Not tested on DX12, since main can't start the compositor there without #1571.


Generated with Claude Code

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants