Skip to content

Commit 4fdb206

Browse files
committed
test(trickplay): add disposable onscreen RGB and planar YUV benchmark
1 parent 2bd7c72 commit 4fdb206

11 files changed

Lines changed: 2630 additions & 0 deletions

File tree

‎CMakeLists.txt‎

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -340,7 +340,14 @@ qt_add_executable(spool
340340
src/main.cpp
341341
src/app/GraphicsStartup.cpp
342342
src/app/GraphicsStartup.h
343+
src/diagnostics/TrickplayBenchmark.h
344+
src/diagnostics/TrickplayBenchmark.cpp
345+
src/diagnostics/TrickplayBenchmarkItem.cpp
343346
)
347+
# Reuse the same SIMD JPEG library as Qt; the experiment needs no second codec.
348+
find_package(JPEG REQUIRED)
349+
find_package(Qt6 REQUIRED COMPONENTS GuiPrivate)
350+
target_link_libraries(spool PRIVATE JPEG::JPEG Qt6::GuiPrivate)
344351

345352
find_package(Python3 3.11 REQUIRED COMPONENTS Interpreter)
346353
include(cmake/ProviderBundle.cmake)
@@ -612,6 +619,23 @@ qt_add_shaders(spool "hdr_ui_shaders"
612619
BASE "${CMAKE_CURRENT_SOURCE_DIR}/qml/shell/shaders"
613620
FILES qml/shell/shaders/sdr-ui-to-scrgb.frag
614621
)
622+
623+
qt_add_shaders(spool "trickplay_benchmark_shaders"
624+
PREFIX "/bench/shaders"
625+
BASE "${CMAKE_CURRENT_SOURCE_DIR}/src/diagnostics/shaders"
626+
BATCHABLE
627+
GLSL "100 es,120,150"
628+
FILES
629+
src/diagnostics/shaders/preview.vert
630+
src/diagnostics/shaders/rgb.frag
631+
src/diagnostics/shaders/yuv.frag
632+
)
633+
set_source_files_properties(tests/fixtures/trickplay-p95.json PROPERTIES QT_RESOURCE_ALIAS fixture.json)
634+
qt_add_resources(spool "trickplay_benchmark_fixture"
635+
PREFIX "/bench"
636+
BASE "${CMAKE_CURRENT_SOURCE_DIR}/tests/fixtures"
637+
FILES tests/fixtures/trickplay-p95.jpg tests/fixtures/trickplay-p95.json
638+
)
615639
if(NOT SPOOL_WEBOS)
616640
qt_target_qml_sources(spool
617641
QML_FILES

‎README.md‎

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
# Spool
22

3+
**Disposable experiment branch:** `experiment/trickplay-yuv-benchmark` launches
4+
the onscreen RGB/YUV benchmark automatically. It is not intended for merging.
5+
Use `--regular-app` to enter the retained normal application.
6+
37
- libmpv: We've forked this and made it compatible in the directory above. keep libmpv behind a thin PlayerController / PlaybackController facade and do not let Jellyfin/network/UI code know about mpv internals.
48
- Qt6.11
59
HTTP / REST: QNetworkAccessManager as the base, QRestAccessManager on top, plus QNetworkRequestFactory for shared base URL / headers / auth boilerplate. Use Qt’s JSON types (QJsonDocument, QJsonObject, QJsonArray) end to end. In Qt 6.11, QRestAccessManager is the REST-focused wrapper over QNetworkAccessManager, and Qt OpenAPI generates Qt HTTP clients using Qt Network APIs such as QRestAccessManager.
@@ -169,6 +173,70 @@ the player; it does not close Spool. `stop` also returns to the application.
169173
Bindings which manipulate volume, mute or speed update the corresponding Spool
170174
controls. Closing Spool continues to use its normal application shutdown path.
171175

176+
## Disposable trickplay RGB/YUV benchmark
177+
178+
The bundled private fixture is the nearest-rank 95th percentile by compressed
179+
JPEG size among 15,603 sheets inventoried from Jellyfin's trickplay directory:
180+
1,548,147 bytes, 3200×1800, 10×10 thumbnails, 8-bit 4:2:0. The exact SHA-256,
181+
source-relative path, population and selection rule are in
182+
`tests/fixtures/trickplay-p95.json`. This is not a claim of 95th-percentile
183+
decode latency. Do not publish the fixture with this temporary branch.
184+
185+
The three cases are the current production Qt RGB32 decoder, direct libjpeg
186+
BGRA decoding, and direct libjpeg planar YUV420 decoding. All use the same
187+
single-quad Qt Quick renderer, thumbnail geometry and fixed tile trace.
188+
Direct RGB batches scanlines; YUV writes directly into MCU-padded planes and
189+
uses actual one-byte GPU textures. GPU textures are reused for replacement
190+
sheets. Crop-only frames never re-upload pixels. Both paths are opaque,
191+
single-pass draws into the SDR window; no intermediate RGB framebuffer exists.
192+
193+
Each case runs three complete warm-ups, then three measured runs in a shuffled,
194+
logged order. Every run contains 12 decode/upload replacements and 120
195+
crop-only frames. The seed is logged. Default launch leaves the final result
196+
onscreen; `--bench-auto-exit` makes desktop runs finite:
197+
198+
```sh
199+
nix develop .#native -c cmake --build build/linux-release/app --target spool
200+
nix develop .#native -c env SPOOL_RENDER_API=vulkan \
201+
build/linux-release/app/spool --bench-auto-exit --bench-seed 20261005 \
202+
--bench-log local-docs/trickplay-yuv-benchmark/optimized-vulkan.jsonl
203+
nix develop .#native -c env SPOOL_RENDER_API=opengl \
204+
build/linux-release/app/spool --bench-auto-exit --bench-seed 20261005 \
205+
--bench-log local-docs/trickplay-yuv-benchmark/optimized-opengl.jsonl
206+
```
207+
208+
`--bench-fresh-textures` restores the initial fresh-resource experiment.
209+
`--bench-screenshot-dir PATH` chooses the diagnostic image directory.
210+
Without an explicit log path, JSONL goes into the platform's persistent data
211+
directory; the path is printed and shown onscreen. Every raw record also goes
212+
to stdout with a `BENCH` prefix.
213+
214+
Logs retain warm-ups and measured records, wall/thread-CPU decode stages,
215+
allocation sizes, queue/handoff times, synchronization, material updates,
216+
per-plane upload enqueue, command recording and selection-to-Qt-swap latency.
217+
Qt does not expose a truthful allocation/codec stage split, so those Qt fields
218+
are null. GPU upload enqueue is not GPU execution time. Vulkan GPU events are
219+
attributed to their originating request using Qt's frame-slot timestamp
220+
lifecycle and include the whole Qt frame, uploads and drawing. Other backends
221+
retain explicitly unattributed delayed GPU diagnostics; isolated GPU
222+
upload/draw timings are always null.
223+
224+
Process RSS/anonymous memory has both a fixed pre-fixture baseline and
225+
per-run baselines. An independent requested-1 ms sampler records approximate
226+
run peaks; cumulative VmHWM remains separately labeled. Replacement decoding
227+
can overlap the old and new CPU buffers. Decoded bytes, logical texture bytes
228+
and Vulkan/D3D12 allocator counters are separate: none are a physical VRAM
229+
measurement, and they must not be added blindly to RSS. Allocator caches and
230+
driver/code pages can remain resident across runs.
231+
232+
After timing, the app reads back two thumbnails per case, including a
233+
same-pointer crop change with `uploaded=false`, and verifies real rendered
234+
pixels against CPU-decoded fixture crops. Screenshots and comparisons are
235+
excluded from every performance metric. Treat results as valid only when the
236+
final `complete` record reports both independent visual checks successful.
237+
The early `fresh-vulkan.jsonl` run predates that check and is invalid because
238+
its missing batchable shader variant produced blank previews.
239+
172240
## Performance benchmarks
173241

174242
GitHub Actions reports performance regressions as warnings, not build failures:

0 commit comments

Comments
 (0)