mirror of
https://github.com/libretro/RetroArch.git
synced 2026-11-08 08:10:30 -10:00
Compare commits
290
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
249ba6f2e3 | ||
|
|
59e6caa7bf | ||
|
|
7169fb191d | ||
|
|
37c2964a10 | ||
|
|
ec8e234dc3 | ||
|
|
f817e27bab | ||
|
|
704ba2c4a1 | ||
|
|
fafc841034 | ||
|
|
28912bfb19 | ||
|
|
19b2ac1e53 | ||
|
|
1215f526b9 | ||
|
|
8b084b8f02 | ||
|
|
a37d9e5025 | ||
|
|
7c5a1e511c | ||
|
|
5c91c6a297 | ||
|
|
e13540d39b | ||
|
|
cf5a318410 | ||
|
|
6bc9be5d67 | ||
|
|
4781239614 | ||
|
|
e3a48f3a16 | ||
|
|
14a0a9102f | ||
|
|
eefe17dd5b | ||
|
|
60de19825c | ||
|
|
c0657f9b9f | ||
|
|
67d9d2e704 | ||
|
|
1f5682f3bd | ||
|
|
eb2c94efd7 | ||
|
|
a7961703e6 | ||
|
|
0adb2302a9 | ||
|
|
63ea9b5ae0 | ||
|
|
f36c998b6c | ||
|
|
a58bb21a15 | ||
|
|
a9b70a16aa | ||
|
|
0a9d017609 | ||
|
|
a9a6b925a8 | ||
|
|
356d382c11 | ||
|
|
b2bd15fe7d | ||
|
|
12f968b9fc | ||
|
|
008b672bac | ||
|
|
4ef797f16d | ||
|
|
99c07ab0e0 | ||
|
|
553d650782 | ||
|
|
cd1963571c | ||
|
|
a2744181aa | ||
|
|
cd33fb9062 | ||
|
|
3af4ff288d | ||
|
|
a36a066959 | ||
|
|
4f07a54e5e | ||
|
|
23132fdf7d | ||
|
|
b90e83ac00 | ||
|
|
5e1a160d7f | ||
|
|
eb1c4c979e | ||
|
|
4beb10789e | ||
|
|
8de9161c6e | ||
|
|
8995fac08a | ||
|
|
3b1631163e | ||
|
|
c0e4be8251 | ||
|
|
4c3cf90d73 | ||
|
|
fae6ebc8eb | ||
|
|
ffd49befc6 | ||
|
|
bd33b2ab28 | ||
|
|
9f87fa089b | ||
|
|
a23020bbd5 | ||
|
|
ccac4dc5b3 | ||
|
|
58363d892a | ||
|
|
12659387f1 | ||
|
|
d2b5462b87 | ||
|
|
5668d9afc4 | ||
|
|
52c80ab0be | ||
|
|
6640500010 | ||
|
|
ea35cd2973 | ||
|
|
c5fe577bcf | ||
|
|
6394afc9c3 | ||
|
|
2cbd669641 | ||
|
|
8b3296ee7f | ||
|
|
35899e65e4 | ||
|
|
c4f0967df7 | ||
|
|
bcb51f41de | ||
|
|
dbfcccfc6d | ||
|
|
c473ac1470 | ||
|
|
e06385a2cc | ||
|
|
c29a5ef860 | ||
|
|
df2cfda5d8 | ||
|
|
cf0dd22df8 | ||
|
|
5460adac99 | ||
|
|
dacf2d7a9f | ||
|
|
f180af2c5a | ||
|
|
e3cc552c34 | ||
|
|
6c35eb1b64 | ||
|
|
781853a905 | ||
|
|
78cc0196d9 | ||
|
|
766a365f61 | ||
|
|
01a54a9fd2 | ||
|
|
d09da7b20e | ||
|
|
dd38c0dab3 | ||
|
|
2b12a040c2 | ||
|
|
6e28729120 | ||
|
|
b71ce6e61d | ||
|
|
f5acdc1722 | ||
|
|
a56e1b006a | ||
|
|
f901aa40d4 | ||
|
|
93b3114029 | ||
|
|
e0c48b473d | ||
|
|
33996131a1 | ||
|
|
44d4e9c5ac | ||
|
|
54b9271633 | ||
|
|
51dce116c6 | ||
|
|
2f7e664d88 | ||
|
|
599c35c247 | ||
|
|
731d95f52c | ||
|
|
823de371e8 | ||
|
|
8c5e4473cc | ||
|
|
bdad7a1acd | ||
|
|
ce6d74fc2d | ||
|
|
151a67d06d | ||
|
|
bfd3f884d0 | ||
|
|
d355ae237b | ||
|
|
e89a508e9a | ||
|
|
7e8587cbcf | ||
|
|
12d11e8322 | ||
|
|
e736b5aa75 | ||
|
|
7cce0be1a2 | ||
|
|
d2a2fdc37e | ||
|
|
ab554a3ab7 | ||
|
|
54c3407bb0 | ||
|
|
6695043ce9 | ||
|
|
d7182ccea7 | ||
|
|
6ddc16eff2 | ||
|
|
1dbb4ecb04 | ||
|
|
2dbc25edaf | ||
|
|
2f26f56198 | ||
|
|
5e5ba8b90c | ||
|
|
9a697765f8 | ||
|
|
89364ba943 | ||
|
|
37fb6dd13e | ||
|
|
6fa9e490f0 | ||
|
|
6a9886de64 | ||
|
|
ac35f9fa36 | ||
|
|
6a6b492626 | ||
|
|
b9392ebf46 | ||
|
|
20ebf595cd | ||
|
|
a4b20df274 | ||
|
|
7744736d83 | ||
|
|
ab25c98feb | ||
|
|
de524e63d9 | ||
|
|
4613d47bc0 | ||
|
|
f01298f246 | ||
|
|
8465e993b4 | ||
|
|
2c21231c86 | ||
|
|
616b915dc3 | ||
|
|
f8ee96bc96 | ||
|
|
266761837b | ||
|
|
e8e2ff3396 | ||
|
|
fe199993a3 | ||
|
|
035e1b0a53 | ||
|
|
be3e85bb5b | ||
|
|
95adc8c060 | ||
|
|
3cbfdbb91e | ||
|
|
f89b4a8fb7 | ||
|
|
8218c1923a | ||
|
|
ff7cef3da1 | ||
|
|
620405d736 | ||
|
|
1482dab102 | ||
|
|
b316823902 | ||
|
|
d89cec4bdf | ||
|
|
a93d2c4663 | ||
|
|
9a22d549c1 | ||
|
|
9fd92b3a68 | ||
|
|
a35a2b36da | ||
|
|
300be42cea | ||
|
|
f1ddffb95a | ||
|
|
1f53fc376c | ||
|
|
166835d878 | ||
|
|
a4ef3b5f4a | ||
|
|
6a2aa0b6e9 | ||
|
|
725afac1f5 | ||
|
|
5bb8d13270 | ||
|
|
1e1bc0d3e4 | ||
|
|
d74c08de2c | ||
|
|
6d418fd6a5 | ||
|
|
48c17098a6 | ||
|
|
4d9642f844 | ||
|
|
4891a5de59 | ||
|
|
e02fc1a467 | ||
|
|
ef4956ee82 | ||
|
|
ee5eed049b | ||
|
|
415735875e | ||
|
|
b8b6fca0b2 | ||
|
|
2891916ad7 | ||
|
|
eea612586b | ||
|
|
c7622782cc | ||
|
|
f036a9c764 | ||
|
|
c047f15028 | ||
|
|
f90e2de655 | ||
|
|
bda2fbea3f | ||
|
|
e20ba583e4 | ||
|
|
9c3e97f739 | ||
|
|
77ef1c7d95 | ||
|
|
0f0ee60703 | ||
|
|
bcc0d9bdcb | ||
|
|
cbbf6d2a43 | ||
|
|
1d862204fa | ||
|
|
e8733ab179 | ||
|
|
f2512de8b5 | ||
|
|
a8286d93fa | ||
|
|
19f37ff656 | ||
|
|
1758ffd469 | ||
|
|
4f7abb7734 | ||
|
|
dcfdc099d6 | ||
|
|
939c5ed088 | ||
|
|
bdb9f5a3ee | ||
|
|
d0bd247a9a | ||
|
|
c11aae0554 | ||
|
|
27c38530e2 | ||
|
|
bc1cdfa00d | ||
|
|
63f30c9569 | ||
|
|
a153d185ff | ||
|
|
6e1b2318c8 | ||
|
|
4874c400c7 | ||
|
|
b9198c8886 | ||
|
|
9d12250174 | ||
|
|
bfff7f90df | ||
|
|
42b32da45e | ||
|
|
87224b14b4 | ||
|
|
90c70f94fc | ||
|
|
61db8ae149 | ||
|
|
b75a7f373c | ||
|
|
fdd27c04ce | ||
|
|
d4932e8f25 | ||
|
|
3eff27b52c | ||
|
|
28221097e0 | ||
|
|
de28925f7b | ||
|
|
60834a9628 | ||
|
|
0a69266bc2 | ||
|
|
2fd1326943 | ||
|
|
22de713719 | ||
|
|
0beecd021c | ||
|
|
3ad02e2935 | ||
|
|
3076c7e936 | ||
|
|
b97e28a05b | ||
|
|
9eb993f011 | ||
|
|
d247c3b353 | ||
|
|
be479b7b6e | ||
|
|
727936f331 | ||
|
|
631d770472 | ||
|
|
54909c62d6 | ||
|
|
89a310a69a | ||
|
|
b0c79b4b01 | ||
|
|
389516d408 | ||
|
|
b9d5dd0460 | ||
|
|
29f05b9f4a | ||
|
|
026ec76bde | ||
|
|
9b12d82ef5 | ||
|
|
5ccfb36f1d | ||
|
|
a43188f642 | ||
|
|
8bff865216 | ||
|
|
344648123e | ||
|
|
e411036f18 | ||
|
|
010683ca31 | ||
|
|
0dfc3b8124 | ||
|
|
b1bf9ee6b1 | ||
|
|
8cd312ba2f | ||
|
|
3ae1b5ad3b | ||
|
|
746b5d3db2 | ||
|
|
dbcd658b9c | ||
|
|
94b995efc7 | ||
|
|
720fbf5056 | ||
|
|
e0ca7b3b0d | ||
|
|
202ec8e8f6 | ||
|
|
26069ee954 | ||
|
|
2bc182435e | ||
|
|
a83d3c997a | ||
|
|
ac2eb32e7a | ||
|
|
a0d7749922 | ||
|
|
fa43677c9a | ||
|
|
83579a04d8 | ||
|
|
ff1ceafe1d | ||
|
|
b77d37a65d | ||
|
|
954539d87e | ||
|
|
275eafab01 | ||
|
|
b3789ce352 | ||
|
|
f8444c1d98 | ||
|
|
256a5f48f9 | ||
|
|
82be334b42 | ||
|
|
e81d767dae | ||
|
|
6aa0e45313 | ||
|
|
11e76e7abf | ||
|
|
4f39c8a27a | ||
|
|
42f0cc2075 | ||
|
|
5c2db2becf |
No files matched your search
@@ -87,12 +87,17 @@ jobs:
|
||||
bin="$MEDIA_NDK/toolchains/llvm/prebuilt/linux-x86_64/bin"
|
||||
unity="$RUNNER_TEMP/android-record-unity.c"
|
||||
: > "$unity"
|
||||
for source in gfx/common/mediacodec_inventory.c record/record_mediacodec_encoder.c \
|
||||
for source in record/record_audio_tap.c record/drivers/record_mediacodec.c gfx/common/mediacodec_inventory.c record/record_mediacodec_encoder.c \
|
||||
record/record_mux_mediacodec.c record/record_mediacodec_worker.c \
|
||||
record/record_mediacodec_surface.c record/record_android_capture_images.c \
|
||||
record/record_android_capture_submit.c record/record_android_capture_gl.c \
|
||||
record/record_android_capture_submit.c record/record_android_capture_pipe.c \
|
||||
record/record_android_capture_pool.c record/record_android_capture_device.c record/record_android_capture_request.c record/record_android_capture_config.c record/record_android_capture_ops.c \
|
||||
record/record_android_capture_egl.c \
|
||||
record/record_android_capture_gl.c \
|
||||
record/record_android_capture_draw.c record/record_mediacodec_gpu_worker.c \
|
||||
record/record_mediacodec_file.c record/record_gpu_queue.c \
|
||||
record/record_mediacodec_file.c record/record_session_mediacodec.c \
|
||||
record/record_dispatch_mediacodec.c record/record_gpu_dispatch_mediacodec.c \
|
||||
record/record_gpu_queue.c \
|
||||
record/record_audio_queue.c libretro-common/queues/retro_spsc.c; do
|
||||
printf '#include "%s/%s"\n' "$PWD" "$source" >> "$unity"
|
||||
done
|
||||
@@ -104,10 +109,11 @@ jobs:
|
||||
CC="$bin/$target$api-clang $extra" python3 libretro-common/samples/formats/vp9/check_stack.py
|
||||
"$bin/$target$api-clang" $extra -O1 -std=c89 -pedantic -Wall -Wextra -Werror \
|
||||
-Wno-long-long -Wno-c99-extensions -Wframe-larger-than=2048 \
|
||||
-DANDROID -DHAVE_VULKAN -DHAVE_RECORD_MEDIACODEC -DHAVE_CONFIGFILE \
|
||||
-DHAVE_THREADS -DHAVE_DYLIB -Ilibretro-common/include -Igfx/include \
|
||||
-c "$unity" -o "$RUNNER_TEMP/record-$target-$api-$offset.o"
|
||||
if "$bin/llvm-nm" -u "$RUNNER_TEMP/record-$target-$api-$offset.o" | \
|
||||
grep -Eq ' U (AMedia|ANativeWindow|AHardwareBuffer|egl|gl[A-Z]|record_android_capture_|record_mediacodec_|record_mux_mediacodec_|record_audio_queue_|record_gpu_queue_|retro_spsc_|mediacodec_inventory_)'; then
|
||||
grep -Eq ' U (AMedia|ANativeWindow|AHardwareBuffer|egl|gl[A-Z]|record_android_capture_|record_mediacodec_|record_session_mediacodec_|record_dispatch_mediacodec_|record_gpu_dispatch_mediacodec_|record_mux_mediacodec_|record_audio_tap_|record_audio_queue_|record_gpu_queue_|retro_spsc_|mediacodec_inventory_)'; then
|
||||
echo "Unresolved Android recording dependency"
|
||||
exit 1
|
||||
fi
|
||||
@@ -208,3 +214,227 @@ jobs:
|
||||
run: make -C samples/gfx/mediacodec_session vulkan-gpu-check CC=${{ matrix.cc }} SANITIZER=
|
||||
- name: GLES recording SDR PQ and scRGB pixel oracle
|
||||
run: EGL_PLATFORM=surfaceless LIBGL_ALWAYS_SOFTWARE=1 CC=${{ matrix.cc }} python3 samples/record/mediacodec_encode/draw_pixels.py
|
||||
|
||||
jni-contract:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
cc: [gcc, clang]
|
||||
env:
|
||||
APT_PKGS: libvulkan-dev mesa-vulkan-drivers ffmpeg
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- name: Apt cache key
|
||||
id: apt
|
||||
run: sh .github/workflows/scripts/apt-cache-key.sh "$APT_PKGS"
|
||||
- name: Restore apt cache
|
||||
uses: actions/cache/restore@v4
|
||||
with:
|
||||
path: ~/.apt-cache
|
||||
key: ${{ steps.apt.outputs.key }}
|
||||
restore-keys: ${{ steps.apt.outputs.prefix }}
|
||||
- name: Install test dependencies
|
||||
timeout-minutes: 20
|
||||
run: sh .github/workflows/scripts/apt-install.sh $APT_PKGS
|
||||
- name: Save apt cache
|
||||
if: env.APT_CACHE_SAVE == '1'
|
||||
uses: actions/cache/save@v4
|
||||
with:
|
||||
path: ~/.apt-cache
|
||||
key: ${{ steps.apt.outputs.key }}
|
||||
- uses: actions/setup-java@v4
|
||||
with:
|
||||
distribution: temurin
|
||||
java-version: '21'
|
||||
- name: Native decoder session failure and Surface timestamp contracts
|
||||
run: make -C samples/gfx/mediacodec_session clean all check CC=${{ matrix.cc }} SANITIZER=address,undefined
|
||||
- name: Hardware startup, first-frame refusal and software playback recovery
|
||||
run: |
|
||||
for threads in 0 1; do
|
||||
make -C libretro-common/samples/formats/video_player clean all CC=${{ matrix.cc }} THREADS="$threads" SANITIZER=address,undefined
|
||||
libretro-common/samples/formats/video_player/video_player_test
|
||||
done
|
||||
- name: HEVC drain and software output after hardware admission refusal
|
||||
run: |
|
||||
make -C samples/gfx/d3d11_video_decode rh265_hw_stream_test hevc_color_stream_test CC=${{ matrix.cc }} SANITIZER=address,undefined
|
||||
(cd samples/gfx/d3d11_video_decode && ./rh265_hw_stream_test && ./hevc_color_stream_test)
|
||||
- name: Execute RGB shader crop and SDR PQ HLG pixel oracle on Vulkan software device
|
||||
run: make -C samples/gfx/mediacodec_session gpu-check CC=${{ matrix.cc }} SANITIZER=
|
||||
- name: HEVC Annex-B configuration and packet preservation
|
||||
run: make -C samples/gfx/mediacodec_hevc_packet clean all check parser-check CC=${{ matrix.cc }} SANITIZER=address,undefined
|
||||
ndk-compile:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- name: Use the Android project NDK version
|
||||
run: |
|
||||
set -eu
|
||||
version=$(sed -n 's/.*ndkVersion "\([^"]*\)".*/\1/p' pkg/android/phoenix/build.gradle)
|
||||
test -n "$version"
|
||||
"$ANDROID_HOME/cmdline-tools/latest/bin/sdkmanager" "ndk;$version"
|
||||
echo "MEDIA_NDK=$ANDROID_HOME/ndk/$version" >> "$GITHUB_ENV"
|
||||
- name: Strict C89, SDK function signatures and Android file-offset ABIs
|
||||
run: |
|
||||
set -euo pipefail
|
||||
bin="$MEDIA_NDK/toolchains/llvm/prebuilt/linux-x86_64/bin"
|
||||
sources=(mediacodec_inventory android_image_queue android_image_ndk android_frame mediacodec_session mediacodec_color mediacodec_frame_export mediacodec_hevc_packet mediacodec_hevc_decoder mediacodec_hevc_backend mediacodec_vp9_decoder mediacodec_vp9_backend mediacodec_av1_decoder mediacodec_av1_backend vulkan_android_image vulkan_android_sync vulkan_android_pipeline vulkan_android_submit vulkan_android_queue vulkan_android_cache_entry vulkan_android_cache vulkan_android_device)
|
||||
unity="$RUNNER_TEMP/android-media-unity.c"
|
||||
printf '#include "%s/gfx/common/mediacodec_hevc_registry.h"\n' "$PWD" > "$unity"
|
||||
printf '#include "%s/gfx/common/mediacodec_vp9_registry.h"\n' "$PWD" >> "$unity"
|
||||
printf '#include "%s/gfx/common/mediacodec_av1_registry.h"\n' "$PWD" >> "$unity"
|
||||
printf '#ifndef MEDIACODEC_HEVC_REGISTRY\n#error registry atomics unavailable\n#endif\n' >> "$unity"
|
||||
for source in "${sources[@]}"; do
|
||||
printf '#include "%s/gfx/common/%s.c"\n' "$PWD" "$source" >> "$unity"
|
||||
done
|
||||
printf '#include "%s/record/record_mediacodec_encoder.c"\n' "$PWD" >> "$unity"
|
||||
printf '#include "%s/record/record_mux_mediacodec.c"\n' "$PWD" >> "$unity"
|
||||
printf '#include "%s/record/record_mediacodec_worker.c"\n' "$PWD" >> "$unity"
|
||||
printf '#include "%s/record/record_mediacodec_surface.c"\n' "$PWD" >> "$unity"
|
||||
printf '#include "%s/record/record_audio_tap.c"\n' "$PWD" >> "$unity"
|
||||
printf '#include "%s/record/drivers/record_mediacodec.c"\n' "$PWD" >> "$unity"
|
||||
printf '#include "%s/record/record_android_capture_images.c"\n' "$PWD" >> "$unity"
|
||||
printf '#include "%s/record/record_android_capture_submit.c"\n' "$PWD" >> "$unity"
|
||||
printf '#include "%s/record/record_android_capture_pipe.c"\n' "$PWD" >> "$unity"
|
||||
printf '#include "%s/record/record_android_capture_config.c"\n' "$PWD" >> "$unity"
|
||||
printf '#include "%s/record/record_android_capture_request.c"\n' "$PWD" >> "$unity"
|
||||
printf '#include "%s/record/record_android_capture_device.c"\n' "$PWD" >> "$unity"
|
||||
printf '#include "%s/record/record_android_capture_pool.c"\n' "$PWD" >> "$unity"
|
||||
printf '#include "%s/record/record_android_capture_ops.c"\n' "$PWD" >> "$unity"
|
||||
printf '#include "%s/record/record_android_capture_egl.c"\n' "$PWD" >> "$unity"
|
||||
printf '#include "%s/record/record_android_capture_gl.c"\n' "$PWD" >> "$unity"
|
||||
printf '#include "%s/record/record_android_capture_draw.c"\n' "$PWD" >> "$unity"
|
||||
printf '#include "%s/record/record_mediacodec_gpu_worker.c"\n' "$PWD" >> "$unity"
|
||||
printf '#include "%s/record/record_mediacodec_file.c"\n' "$PWD" >> "$unity"
|
||||
printf '#include "%s/record/record_session_mediacodec.c"\n' "$PWD" >> "$unity"
|
||||
printf '#include "%s/record/record_dispatch_mediacodec.c"\n' "$PWD" >> "$unity"
|
||||
printf '#include "%s/record/record_gpu_dispatch_mediacodec.c"\n' "$PWD" >> "$unity"
|
||||
for target in aarch64-linux-android armv7a-linux-androideabi; do
|
||||
for api in 21 29; do
|
||||
for offset in default 64; do
|
||||
extra=()
|
||||
if [ "$offset" = 64 ]; then extra=(-D_FILE_OFFSET_BITS=64); fi
|
||||
for source in "${sources[@]}"; do
|
||||
object="$RUNNER_TEMP/$source-$target-$api-$offset.o"
|
||||
"$bin/$target$api-clang" \
|
||||
-std=c89 -pedantic -Wall -Wextra -Werror -Wno-long-long -Wno-c99-extensions \
|
||||
-DHAVE_THREADS -DHAVE_DYLIB -Wframe-larger-than=2048 -Ilibretro-common/include -Igfx/include \
|
||||
"${extra[@]}" -c "gfx/common/$source.c" -o "$object"
|
||||
if "$bin/llvm-nm" -u "$object" | grep -Eq ' U (AMedia|AImage|AHardwareBuffer|ANativeWindow_setFrameRateWithChangeStrategy)'; then
|
||||
echo "Unexpected direct media API dependency: $object"
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
for record_source in record_audio_tap record_mediacodec_encoder record_mux_mediacodec record_mediacodec_worker record_mediacodec_surface record_android_capture_images record_android_capture_submit record_android_capture_pipe record_android_capture_pool record_android_capture_device record_android_capture_request record_android_capture_config record_android_capture_gl record_android_capture_draw record_mediacodec_gpu_worker record_mediacodec_file record_session_mediacodec record_dispatch_mediacodec record_gpu_dispatch_mediacodec record_gpu_queue record_audio_queue; do
|
||||
"$bin/$target$api-clang" \
|
||||
-std=c89 -pedantic -Wall -Wextra -Werror -Wno-long-long -Wno-c99-extensions \
|
||||
-DHAVE_THREADS -DHAVE_DYLIB -Wframe-larger-than=2048 -Ilibretro-common/include -Igfx/include \
|
||||
"${extra[@]}" -c "record/$record_source.c" \
|
||||
-o "$RUNNER_TEMP/$record_source-$target-$api-$offset.o"
|
||||
done
|
||||
"$bin/$target$api-clang" \
|
||||
-std=c89 -pedantic -Wall -Wextra -Werror -Wno-long-long -Wno-c99-extensions \
|
||||
-DANDROID -DHAVE_VULKAN -DHAVE_RECORD_MEDIACODEC -DHAVE_CONFIGFILE \
|
||||
-DHAVE_THREADS -DHAVE_DYLIB -Wframe-larger-than=2048 -Ilibretro-common/include -Igfx/include \
|
||||
"${extra[@]}" -c "$unity" -o "$RUNNER_TEMP/unity-$target-$api-$offset.o"
|
||||
"$bin/$target$api-clang" \
|
||||
-std=c89 -pedantic -Wall -Wextra -Werror -Wno-long-long -Wno-c99-extensions \
|
||||
-DANDROID -DHAVE_VULKAN -DHAVE_RECORD_MEDIACODEC -DHAVE_CONFIGFILE \
|
||||
-DHAVE_WEBMPLAYER -DHAVE_THREADS -DHAVE_DYLIB -Wframe-larger-than=2048 \
|
||||
-Ilibretro-common/include -Igfx/include "${extra[@]}" \
|
||||
-c record/drivers/record_mediacodec.c \
|
||||
-o "$RUNNER_TEMP/record-history-$target-$api-$offset.o"
|
||||
for root_source in gfx/common/vulkan_common.c gfx/drivers/vulkan.c gfx/gfx_surface_player.c libretro-common/jni/rjni.c; do
|
||||
"$bin/$target$api-clang" \
|
||||
-DANDROID -DHAVE_MENU -DHAVE_GFX_WIDGETS \
|
||||
-DHAVE_VULKAN -DHAVE_RMP4 -DHAVE_RVP9 -DHAVE_RAV1 -DHAVE_DYLIB -DHAVE_THREADS \
|
||||
-Dinline=__inline__ -O1 -std=c89 \
|
||||
-Werror=declaration-after-statement -Werror=frame-larger-than -Wframe-larger-than=4096 \
|
||||
-I. -Ilibretro-common/include -Igfx/include "${extra[@]}" \
|
||||
-c "$root_source" -o "$RUNNER_TEMP/$(basename "$root_source")-$target-$api-$offset.o"
|
||||
done
|
||||
for variant in vp9-only av1-only no-codecs recording-only; do
|
||||
codec_flags=()
|
||||
case "$variant" in
|
||||
vp9-only) codec_flags=(-DHAVE_RVP9 -DHAVE_THREADS) ;;
|
||||
av1-only) codec_flags=(-DHAVE_RAV1 -DHAVE_THREADS) ;;
|
||||
no-codecs) codec_flags=(-DHAVE_THREADS) ;;
|
||||
recording-only) codec_flags=(-DHAVE_THREADS -DHAVE_CONFIGFILE -DHAVE_RECORD_MEDIACODEC) ;;
|
||||
esac
|
||||
for root_source in gfx/common/vulkan_common.c gfx/drivers/vulkan.c; do
|
||||
"$bin/$target$api-clang" \
|
||||
-DANDROID -DHAVE_MENU -DHAVE_GFX_WIDGETS \
|
||||
-DHAVE_VULKAN -DHAVE_DYLIB "${codec_flags[@]}" \
|
||||
-Dinline=__inline__ -O1 -std=c89 \
|
||||
-Werror=declaration-after-statement -Werror=frame-larger-than -Wframe-larger-than=4096 \
|
||||
-I. -Ilibretro-common/include -Igfx/include "${extra[@]}" \
|
||||
-c "$root_source" -o "$RUNNER_TEMP/$variant-$(basename "$root_source")-$target-$api-$offset.o"
|
||||
done
|
||||
done
|
||||
for player_source in libretro-common/formats/video_player.c libretro-common/formats/image_transfer.c libretro-common/formats/webm/rwebm_video.c libretro-common/formats/mp4/rmp4_video.c; do
|
||||
"$bin/$target$api-clang" \
|
||||
-DHAVE_RMP4 -DHAVE_RWEBM -DHAVE_RVP9 -DHAVE_RAV1 -DHAVE_THREADS \
|
||||
-Dinline=__inline__ -O1 -std=c89 -Wno-long-long -Wno-c99-extensions \
|
||||
-Werror=declaration-after-statement -Werror=frame-larger-than -Wframe-larger-than=2048 \
|
||||
-I. -Ilibretro-common/include "${extra[@]}" \
|
||||
-c "$player_source" -o "$RUNNER_TEMP/$(basename "$player_source")-$target-$api-$offset.o"
|
||||
done
|
||||
for parser_source in libretro-common/formats/h265/rh265.c libretro-common/formats/vp9/rvp9.c libretro-common/formats/av1/rav1_hw.c libretro-common/queues/retro_spsc.c; do
|
||||
"$bin/$target$api-clang" \
|
||||
-DHAVE_THREADS -O2 -std=c89 -pedantic -Wall -Wextra -Werror \
|
||||
-Wno-unused-parameter -Wno-long-long -Wno-c99-extensions -Wframe-larger-than=2048 -fstack-usage \
|
||||
-Ilibretro-common/include "${extra[@]}" -c "$parser_source" \
|
||||
-o "$RUNNER_TEMP/$(basename "$parser_source")-$target-$api-$offset.o"
|
||||
done
|
||||
"$bin/$target$api-clang" \
|
||||
-O2 -std=c89 -pedantic -Wall -Wextra -Werror \
|
||||
-Wno-unused-parameter -Wno-long-long -Wno-c99-extensions -Wframe-larger-than=2048 -fstack-usage \
|
||||
-Ilibretro-common/include "${extra[@]}" -c libretro-common/formats/vp9/rvp9.c \
|
||||
-o "$RUNNER_TEMP/threadless-rvp9.c-$target-$api-$offset.o"
|
||||
# Link AV1-only roots with exactly their selected native sources.
|
||||
av1_objects=()
|
||||
for source in mediacodec_inventory android_image_queue android_image_ndk android_frame mediacodec_session mediacodec_color mediacodec_frame_export mediacodec_av1_decoder mediacodec_av1_backend vulkan_android_image vulkan_android_sync vulkan_android_pipeline vulkan_android_submit vulkan_android_queue vulkan_android_cache_entry vulkan_android_cache vulkan_android_device; do
|
||||
av1_objects+=("$RUNNER_TEMP/$source-$target-$api-$offset.o")
|
||||
done
|
||||
"$bin/ld.lld" -r "${av1_objects[@]}" \
|
||||
"$RUNNER_TEMP/av1-only-vulkan_common.c-$target-$api-$offset.o" \
|
||||
"$RUNNER_TEMP/av1-only-vulkan.c-$target-$api-$offset.o" \
|
||||
"$RUNNER_TEMP/rjni.c-$target-$api-$offset.o" \
|
||||
"$RUNNER_TEMP/rav1_hw.c-$target-$api-$offset.o" \
|
||||
-o "$RUNNER_TEMP/av1-only-root-$target-$api-$offset.o"
|
||||
if "$bin/llvm-nm" -u "$RUNNER_TEMP/av1-only-root-$target-$api-$offset.o" | \
|
||||
grep -Eq ' U (rav1_|rvp9_|rh265_|rjni_env$|mediacodec_|android_frame_|android_image_|vulkan_android_)'; then
|
||||
echo "Unresolved private helper in AV1-only Android root"
|
||||
exit 1
|
||||
fi
|
||||
for variant in no-codecs; do
|
||||
if "$bin/llvm-nm" -u "$RUNNER_TEMP/$variant-vulkan.c-$target-$api-$offset.o" | \
|
||||
grep -Eq ' U (mediacodec_|rav1_(set|unset)_hw_backend)'; then
|
||||
echo "Unexpected native decoder dependency: $variant"
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
"$bin/ld.lld" -r \
|
||||
"$RUNNER_TEMP/unity-$target-$api-$offset.o" \
|
||||
"$RUNNER_TEMP/vulkan_common.c-$target-$api-$offset.o" \
|
||||
"$RUNNER_TEMP/vulkan.c-$target-$api-$offset.o" \
|
||||
"$RUNNER_TEMP/gfx_surface_player.c-$target-$api-$offset.o" \
|
||||
"$RUNNER_TEMP/rjni.c-$target-$api-$offset.o" \
|
||||
"$RUNNER_TEMP/video_player.c-$target-$api-$offset.o" \
|
||||
"$RUNNER_TEMP/image_transfer.c-$target-$api-$offset.o" \
|
||||
"$RUNNER_TEMP/rwebm_video.c-$target-$api-$offset.o" \
|
||||
"$RUNNER_TEMP/rmp4_video.c-$target-$api-$offset.o" \
|
||||
"$RUNNER_TEMP/rh265.c-$target-$api-$offset.o" \
|
||||
"$RUNNER_TEMP/rvp9.c-$target-$api-$offset.o" \
|
||||
"$RUNNER_TEMP/rav1_hw.c-$target-$api-$offset.o" \
|
||||
"$RUNNER_TEMP/record_audio_queue-$target-$api-$offset.o" \
|
||||
"$RUNNER_TEMP/record_gpu_queue-$target-$api-$offset.o" \
|
||||
"$RUNNER_TEMP/retro_spsc.c-$target-$api-$offset.o" \
|
||||
-o "$RUNNER_TEMP/media-root-$target-$api-$offset.o"
|
||||
if "$bin/llvm-nm" -u "$RUNNER_TEMP/media-root-$target-$api-$offset.o" | \
|
||||
grep -Eq ' U (record_android_capture_|record_mediacodec_|record_session_mediacodec_|record_dispatch_mediacodec_|record_gpu_dispatch_mediacodec_|record_mux_mediacodec_|record_audio_tap_|record_audio_queue_|record_gpu_queue_|retro_spsc_|rav1_|rvp9_|rjni_env$|mediacodec_|android_frame_|android_image_|vulkan_android_|video_player_(request_software|set_hw_fallback)|image_transfer_anim_stream_stop_hw|rwebm_video_stream_stop_hw)'; then
|
||||
echo "Unresolved private Android media helper in root link"
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
done
|
||||
done
|
||||
@@ -314,6 +314,7 @@ jobs:
|
||||
# VP9 reference-slot sizing, the hardware session's
|
||||
# metadata, retries and frame leases, and WebM dispatch
|
||||
# into it.
|
||||
rmp4_vp9_hw_stream_test
|
||||
rmp4_vp9_config_test
|
||||
rvp9_config_test
|
||||
rvp9_reference_test
|
||||
@@ -845,11 +846,12 @@ jobs:
|
||||
|
||||
TSAN_OPTIONS=halt_on_error=1 ./crc32_test
|
||||
|
||||
- name: Permanent inert prefix pages, refill and I/O boundaries
|
||||
- name: Prefix refill boundaries and sliding-window I/O
|
||||
run: |
|
||||
make -B -C libretro-common/samples/formats/data_transfer data_transfer_prefix_test COUNT_IO=1 SAMPLE_OBJDIR=obj-inert-strict CFLAGS='-Wall -pedantic -std=gnu99 -O2 -g1 -DDT_STRICT -DDT_COUNT_IO -DHAVE_MMAP -D_GNU_SOURCE -I../../../include'
|
||||
make -B -C libretro-common/samples/formats/data_transfer data_transfer_prefix_test data_transfer_window_test COUNT_IO=1 SAMPLE_OBJDIR=obj-inert-strict CFLAGS='-Wall -pedantic -std=gnu99 -O2 -g1 -DDT_STRICT -DDT_COUNT_IO -DHAVE_MMAP -D_GNU_SOURCE -I../../../include'
|
||||
cd libretro-common/samples/formats/data_transfer
|
||||
INERT_ONLY=1 ./data_transfer_prefix_test
|
||||
./data_transfer_window_test
|
||||
|
||||
- name: ccache statistics
|
||||
if: ${{ !cancelled() }}
|
||||
|
||||
@@ -62,6 +62,17 @@ jobs:
|
||||
python3 samples/menu/rgui_media/run.py --c89 --external --driver "$driver" samples/menu/rgui_media/fixture.png
|
||||
python3 samples/menu/rgui_media/run.py --c89 --browser-image --driver "$driver" samples/menu/rgui_media/fixture.png
|
||||
done
|
||||
- name: Natural MP4 folder playback and file controls across all menus
|
||||
run: |
|
||||
for driver in rgui materialui xmb ozone; do
|
||||
python3 samples/menu/rgui_media/run.py --c89 --continuous --fullscreen-settings --require-cadence --driver "$driver" samples/menu/rgui_media/fixture.mp4
|
||||
python3 samples/menu/rgui_media/run.py --c89 --continuous-error --driver "$driver" samples/menu/rgui_media/fixture.mp4
|
||||
done
|
||||
- name: Desktop refresh source lifecycle across all menus
|
||||
run: |
|
||||
for driver in rgui materialui xmb ozone; do
|
||||
python3 samples/menu/rgui_media/run.py --c89 --desktop-refresh --driver "$driver" samples/menu/rgui_media/fixture.mp4
|
||||
done
|
||||
- name: Generate WebM transport fixtures
|
||||
run: |
|
||||
ffmpeg -v error -y -f lavfi -i testsrc2=size=320x240:rate=30 -f lavfi -i sine=frequency=440:sample_rate=48000 -t 3 -c:v libvpx -deadline realtime -cpu-used 8 -g 30 -auto-alt-ref 0 -c:a libopus -shortest samples/menu/rgui_media/fixture-opus.webm
|
||||
@@ -72,3 +83,48 @@ jobs:
|
||||
python3 samples/menu/rgui_media/run.py --c89 --driver "$driver" samples/menu/rgui_media/fixture-opus.webm
|
||||
python3 samples/menu/rgui_media/run.py --c89 --driver "$driver" samples/menu/rgui_media/fixture-vorbis.webm
|
||||
done
|
||||
- name: Natural WebM folder playback across all menus
|
||||
run: |
|
||||
for driver in rgui materialui xmb ozone; do
|
||||
python3 samples/menu/rgui_media/run.py --c89 --continuous --fullscreen-settings --require-cadence --driver "$driver" samples/menu/rgui_media/fixture-vorbis.webm
|
||||
python3 samples/menu/rgui_media/run.py --c89 --continuous-error --driver "$driver" samples/menu/rgui_media/fixture-vorbis.webm
|
||||
done
|
||||
|
||||
window-toggle-media:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 20
|
||||
env:
|
||||
APT_PKGS: ffmpeg xvfb xauth libgl1-mesa-dev libx11-dev libxext-dev libxrandr-dev
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- name: Apt cache key
|
||||
id: apt
|
||||
run: sh .github/workflows/scripts/apt-cache-key.sh "$APT_PKGS"
|
||||
- name: Restore apt cache
|
||||
uses: actions/cache/restore@v4
|
||||
with:
|
||||
path: ~/.apt-cache
|
||||
key: ${{ steps.apt.outputs.key }}
|
||||
restore-keys: ${{ steps.apt.outputs.prefix }}
|
||||
- name: Install dependencies
|
||||
timeout-minutes: 10
|
||||
run: sh .github/workflows/scripts/apt-install.sh $APT_PKGS
|
||||
- name: Save apt cache
|
||||
if: env.APT_CACHE_SAVE == '1'
|
||||
uses: actions/cache/save@v4
|
||||
with:
|
||||
path: ~/.apt-cache
|
||||
key: ${{ steps.apt.outputs.key }}
|
||||
- name: Configure C89 GL frontend
|
||||
run: ./configure --disable-qt --disable-vulkan --disable-wayland --disable-ffmpeg --disable-cheevos --enable-webmplayer
|
||||
- name: Build shipping frontend
|
||||
run: make -j2 C89_BUILD=1
|
||||
- name: Generate moving VP8 fixture
|
||||
run: ffmpeg -v error -y -f lavfi -i "testsrc2=size=320x240:rate=30,hue=h=90*t" -t 3 -c:v libvpx -deadline realtime -cpu-used 8 -g 90 -auto-alt-ref 0 samples/menu/rgui_media/window-toggle.webm
|
||||
- name: Fullscreen window transitions retain movie pixels and seeking
|
||||
run: |
|
||||
for driver in rgui materialui xmb ozone; do
|
||||
for threaded in '' --threaded; do
|
||||
xvfb-run -a python3 samples/menu/rgui_media/run.py --c89 --window-toggle --video-driver gl --driver "$driver" $threaded samples/menu/rgui_media/window-toggle.webm
|
||||
done
|
||||
done
|
||||
@@ -20,6 +20,59 @@ concurrency:
|
||||
cancel-in-progress: ${{ github.ref != 'refs/heads/master' }}
|
||||
|
||||
jobs:
|
||||
vaapi-hevc-parameters:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 30
|
||||
strategy:
|
||||
matrix:
|
||||
cc: [gcc, clang]
|
||||
env:
|
||||
APT_PKGS: libva-dev libdrm-dev libvulkan-dev mesa-vulkan-drivers vulkan-validationlayers ffmpeg
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- name: Apt cache key
|
||||
id: apt
|
||||
run: sh .github/workflows/scripts/apt-cache-key.sh "$APT_PKGS"
|
||||
|
||||
- name: Restore apt cache
|
||||
uses: actions/cache/restore@v4
|
||||
with:
|
||||
path: ~/.apt-cache
|
||||
key: ${{ steps.apt.outputs.key }}
|
||||
restore-keys: ${{ steps.apt.outputs.prefix }}
|
||||
|
||||
- name: Install dependencies
|
||||
timeout-minutes: 15
|
||||
run: sh .github/workflows/scripts/apt-install.sh $APT_PKGS
|
||||
|
||||
- name: Save apt cache
|
||||
if: env.APT_CACHE_SAVE == '1'
|
||||
uses: actions/cache/save@v4
|
||||
with:
|
||||
path: ~/.apt-cache
|
||||
key: ${{ steps.apt.outputs.key }}
|
||||
|
||||
- name: Strict C89 HEVC buffer ABI and cold session ownership
|
||||
run: |
|
||||
make -C samples/gfx/vaapi_hevc clean check CC=${{ matrix.cc }} SANITIZER=address,undefined
|
||||
make -C samples/gfx/vaapi_vp9 clean check CC=${{ matrix.cc }} SANITIZER=address,undefined
|
||||
make -C samples/gfx/vaapi_av1 clean check CC=${{ matrix.cc }} SANITIZER=address,undefined
|
||||
make -C samples/gfx/vaapi_av1 trace-check CC=${{ matrix.cc }} SANITIZER=address,undefined
|
||||
make -C libretro-common/samples/formats/vp9 rvp9_hw_segment_test CC=${{ matrix.cc }} SANITIZER=address,undefined
|
||||
libretro-common/samples/formats/vp9/rvp9_hw_segment_test
|
||||
make -C libretro-common/samples/formats/h265 rh265_slice_info_test CC=${{ matrix.cc }} SANITIZER=address,undefined
|
||||
libretro-common/samples/formats/h265/rh265_slice_info_test
|
||||
- name: Execute sampled-plane conversion, foreign ownership and native pixel oracle
|
||||
run: ASAN_OPTIONS=detect_leaks=0 make -C samples/gfx/vaapi_hevc gpu-check CC=${{ matrix.cc }} SANITIZER=address,undefined
|
||||
- name: Opaque VA producer and renderer return publication under TSan
|
||||
if: matrix.cc == 'gcc'
|
||||
run: |
|
||||
make -C samples/gfx/vaapi_hevc tsan-check CC=gcc RUNNER="setarch $(uname -m) -R"
|
||||
make -C samples/gfx/vaapi_vp9 backend_tsan_test CC=gcc
|
||||
setarch "$(uname -m)" -R samples/gfx/vaapi_vp9/backend_tsan_test
|
||||
make -C samples/gfx/vaapi_av1 backend_tsan_test CC=gcc
|
||||
setarch "$(uname -m)" -R samples/gfx/vaapi_av1/backend_tsan_test
|
||||
|
||||
metal-frame-lease:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 10
|
||||
@@ -55,6 +108,8 @@ jobs:
|
||||
run: CC=clang python3 samples/gfx/metal_frame/run.py
|
||||
- name: VideoToolbox HEVC parameter ownership and session setup
|
||||
run: |
|
||||
make -C samples/gfx/videotoolbox_vp9 check SANITIZER=address,undefined
|
||||
make -B -C samples/gfx/videotoolbox_vp9 check CC=clang SANITIZER=address,undefined
|
||||
python3 samples/gfx/videotoolbox_hevc/codec_fixtures.py --verify
|
||||
python3 samples/gfx/videotoolbox_hevc/hdr_codec_fixtures.py --verify
|
||||
python3 samples/gfx/videotoolbox_hevc/sequence_fixtures.py --verify
|
||||
@@ -2428,6 +2483,13 @@ jobs:
|
||||
- name: MinGW strict C89 float math
|
||||
run: CC=x86_64-w64-mingw32-gcc WINEDEBUG=-all python3 samples/gfx/mingw_c89_math/run.py
|
||||
|
||||
- name: Vulkan recording command and queue acceptance
|
||||
run: |
|
||||
CFLAGS='-fsanitize=address,undefined -fno-omit-frame-pointer' python3 samples/gfx/vulkan_recording/run.py
|
||||
CC=x86_64-w64-mingw32-gcc RUNNER=wine python3 samples/gfx/vulkan_recording/run.py
|
||||
DEVICE_INFO_THREADS=1 CFLAGS='-fsanitize=address,undefined -fno-omit-frame-pointer' python3 samples/gfx/vulkan_recording/device_info.py
|
||||
CC=x86_64-w64-mingw32-gcc RUNNER=wine python3 samples/gfx/vulkan_recording/device_info.py
|
||||
|
||||
- name: Vulkan synchronous readback slot
|
||||
run: python3 samples/gfx/vulkan_readback_slot/run.py
|
||||
|
||||
|
||||
@@ -6,32 +6,56 @@ on:
|
||||
paths:
|
||||
- '.github/workflows/MacOS-videotoolbox-hevc.yml'
|
||||
- 'samples/gfx/videotoolbox_hevc/**'
|
||||
- 'samples/gfx/videotoolbox_vp9/**'
|
||||
- 'samples/gfx/videotoolbox_av1/**'
|
||||
- 'gfx/common/videotoolbox_av1*.h'
|
||||
- 'gfx/common/videotoolbox_vp9*.h'
|
||||
- 'gfx/common/vp9_color.h'
|
||||
- 'libretro-common/include/formats/rvp9*.h'
|
||||
- 'libretro-common/include/formats/rwebm*.h'
|
||||
- 'libretro-common/formats/vp9/**'
|
||||
- 'libretro-common/formats/webm/**'
|
||||
- 'samples/gfx/metal_frame/**'
|
||||
- 'gfx/common/videotoolbox_hevc*.h'
|
||||
- 'gfx/common/metal_frame*.h'
|
||||
- 'gfx/common/metal_hevc_driver.h'
|
||||
- 'gfx/common/metal_video_decoder_driver.h'
|
||||
- 'gfx/common/metal_decoder_snapshot.h'
|
||||
- 'gfx/drivers/metal.m'
|
||||
- 'gfx/drivers/metal.h'
|
||||
- 'gfx/drivers/metal_shaders/Shaders.metal'
|
||||
- 'gfx/video_driver.h'
|
||||
- 'gfx/video_defines.h'
|
||||
- 'libretro-common/formats/h265/**'
|
||||
- 'libretro-common/formats/video_player.c'
|
||||
- 'libretro-common/samples/formats/video_player/**'
|
||||
- 'libretro-common/rthreads/**'
|
||||
- 'libretro-common/include/**'
|
||||
pull_request:
|
||||
paths:
|
||||
- '.github/workflows/MacOS-videotoolbox-hevc.yml'
|
||||
- 'samples/gfx/videotoolbox_hevc/**'
|
||||
- 'samples/gfx/videotoolbox_vp9/**'
|
||||
- 'samples/gfx/videotoolbox_av1/**'
|
||||
- 'gfx/common/videotoolbox_av1*.h'
|
||||
- 'gfx/common/videotoolbox_vp9*.h'
|
||||
- 'gfx/common/vp9_color.h'
|
||||
- 'libretro-common/include/formats/rvp9*.h'
|
||||
- 'libretro-common/include/formats/rwebm*.h'
|
||||
- 'libretro-common/formats/vp9/**'
|
||||
- 'libretro-common/formats/webm/**'
|
||||
- 'samples/gfx/metal_frame/**'
|
||||
- 'gfx/common/videotoolbox_hevc*.h'
|
||||
- 'gfx/common/metal_frame*.h'
|
||||
- 'gfx/common/metal_hevc_driver.h'
|
||||
- 'gfx/common/metal_video_decoder_driver.h'
|
||||
- 'gfx/common/metal_decoder_snapshot.h'
|
||||
- 'gfx/drivers/metal.m'
|
||||
- 'gfx/drivers/metal.h'
|
||||
- 'gfx/drivers/metal_shaders/Shaders.metal'
|
||||
- 'gfx/video_driver.h'
|
||||
- 'gfx/video_defines.h'
|
||||
- 'libretro-common/formats/h265/**'
|
||||
- 'libretro-common/formats/video_player.c'
|
||||
- 'libretro-common/samples/formats/video_player/**'
|
||||
- 'libretro-common/rthreads/**'
|
||||
- 'libretro-common/include/**'
|
||||
workflow_dispatch:
|
||||
@@ -45,6 +69,44 @@ jobs:
|
||||
timeout-minutes: 10
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- name: Player cold hardware startup and cancellation
|
||||
run: |
|
||||
for threads in 0 1; do
|
||||
make -C libretro-common/samples/formats/video_player clean all THREADS="$threads" SANITIZER=address,undefined
|
||||
libretro-common/samples/formats/video_player/video_player_test
|
||||
done
|
||||
- name: VideoToolbox VP9 configuration conformance (no device claim)
|
||||
run: |
|
||||
make -C samples/gfx/videotoolbox_vp9 check CC=clang SANITIZER=address,undefined
|
||||
CC=clang python3 samples/gfx/videotoolbox_vp9/run.py --native
|
||||
- name: VideoToolbox VP9 native pixels, Metal playback and retained output
|
||||
run: |
|
||||
rc=0
|
||||
CC=clang python3 samples/gfx/videotoolbox_vp9/run.py --native --pixels --pipeline || rc=$?
|
||||
if [ "$rc" = 77 ]; then
|
||||
echo "::warning::VP9 hardware or Metal device unavailable; native VP9 playback acceptance remains incomplete"
|
||||
exit 0
|
||||
fi
|
||||
exit "$rc"
|
||||
- name: VideoToolbox AV1 configuration and ownership (no decoder device claim)
|
||||
run: |
|
||||
make -C samples/gfx/videotoolbox_av1 check CC=clang SANITIZER=address,undefined
|
||||
CC=clang python3 samples/gfx/videotoolbox_av1/run.py --native-format
|
||||
- name: VideoToolbox AV1 hardware pixels, keyframe seeks and retained images
|
||||
run: |
|
||||
if ! command -v ffmpeg >/dev/null ||
|
||||
! ffmpeg -hide_banner -encoders 2>/dev/null | grep ' libaom-av1 ' >/dev/null; then
|
||||
brew install ffmpeg-full
|
||||
export PATH="$(brew --prefix ffmpeg-full)/bin:$PATH"
|
||||
fi
|
||||
ffmpeg -hide_banner -encoders 2>/dev/null | grep ' libaom-av1 '
|
||||
rc=0
|
||||
CC=clang python3 samples/gfx/videotoolbox_av1/run.py --native-pixels --pipeline || rc=$?
|
||||
if [ "$rc" = 77 ]; then
|
||||
echo "::warning::AV1 hardware unavailable; native AV1 decode acceptance remains incomplete"
|
||||
exit 0
|
||||
fi
|
||||
exit "$rc"
|
||||
- name: VideoToolbox HEVC hardware decode and pixel ownership
|
||||
run: |
|
||||
rc=0
|
||||
|
||||
+106
@@ -250,6 +250,7 @@ OBJ += frontend/frontend_driver.o \
|
||||
record/drivers/record_wav.o \
|
||||
record/drivers/record_nvenc.o \
|
||||
record/drivers/record_metal.o \
|
||||
record/drivers/record_mediacodec.o \
|
||||
command.o \
|
||||
msg_hash.o \
|
||||
intl/msg_hash_us.o \
|
||||
@@ -343,6 +344,7 @@ OBJ += \
|
||||
gfx/gfx_surface_player.o \
|
||||
gfx/gfx_thumbnail.o \
|
||||
gfx/gfx_media_controls.o \
|
||||
gfx/gfx_media_display.o \
|
||||
gfx/gfx_media_player.o \
|
||||
gfx/gfx_media_cpu.o \
|
||||
gfx/gfx_anim_preview.o \
|
||||
@@ -1813,9 +1815,91 @@ ifeq ($(HAVE_VULKAN), 1)
|
||||
gfx/common/vulkan_common.o \
|
||||
$(LIBRETRO_COMM_DIR)/vulkan/vulkan_symbol_wrapper.o
|
||||
|
||||
ifeq ($(ANDROID), 1)
|
||||
ifeq ($(HAVE_THREADS), 1)
|
||||
OBJ += record/record_android_capture_device.o
|
||||
endif
|
||||
endif
|
||||
|
||||
# H.264/HEVC previews decoded on the GPU
|
||||
ifeq ($(HAVE_RMP4), 1)
|
||||
OBJ += gfx/common/vulkan_video_decode.o
|
||||
ifeq ($(HAVE_VAAPI), 1)
|
||||
DEFINES += -DHAVE_VAAPI
|
||||
DEF_FLAGS += $(VAAPI_DRM_CFLAGS)
|
||||
ifneq ($(ANDROID), 1)
|
||||
OBJ += gfx/common/vulkan_android_queue.o
|
||||
endif
|
||||
ifeq ($(HAVE_RVP9), 1)
|
||||
OBJ += gfx/common/vaapi_vp9_decoder.o \
|
||||
gfx/common/vaapi_vp9_owner.o \
|
||||
gfx/common/vaapi_vp9_backend.o \
|
||||
gfx/common/vaapi_vp9_pool.o \
|
||||
gfx/common/vaapi_vp9_submit.o \
|
||||
gfx/common/vaapi_vp9_export.o
|
||||
endif
|
||||
ifeq ($(HAVE_RAV1), 1)
|
||||
ifeq ($(HAVE_VAAPI_AV1), 1)
|
||||
DEFINES += -DHAVE_VAAPI_AV1
|
||||
OBJ += gfx/common/vaapi_av1_decoder.o \
|
||||
gfx/common/vaapi_av1_owner.o \
|
||||
gfx/common/vaapi_av1_backend.o \
|
||||
gfx/common/vaapi_av1_pool.o \
|
||||
gfx/common/vaapi_av1_submit.o \
|
||||
gfx/common/vaapi_av1_export.o
|
||||
endif
|
||||
endif
|
||||
OBJ += gfx/common/vaapi_frame.o \
|
||||
gfx/common/vaapi_hevc_decoder.o \
|
||||
gfx/common/vaapi_hevc_owner.o \
|
||||
gfx/common/vaapi_hevc_backend.o \
|
||||
gfx/common/vaapi_hevc_session.o \
|
||||
gfx/common/vaapi_hevc_runtime.o \
|
||||
gfx/common/vaapi_hevc_submit.o \
|
||||
gfx/common/vaapi_hevc_pool.o \
|
||||
gfx/common/vaapi_hevc_export.o \
|
||||
gfx/common/vulkan_prime_image.o \
|
||||
gfx/common/vaapi_hevc_vulkan_frame.o \
|
||||
gfx/common/vaapi_hevc_vulkan_submit.o \
|
||||
gfx/common/vaapi_hevc_vulkan_lease.o \
|
||||
gfx/common/vaapi_hevc_vulkan_resources.o \
|
||||
gfx/common/vaapi_hevc_vulkan_cache.o
|
||||
endif
|
||||
endif
|
||||
|
||||
ifneq ($(filter 1,$(HAVE_RMP4) $(HAVE_RVP9) $(HAVE_RAV1) $(HAVE_RECORD_MEDIACODEC)),)
|
||||
ifeq ($(ANDROID), 1)
|
||||
OBJ += gfx/common/vulkan_android_device.o \
|
||||
gfx/common/android_image_queue.o \
|
||||
gfx/common/android_frame.o \
|
||||
gfx/common/vulkan_android_image.o \
|
||||
gfx/common/vulkan_android_sync.o \
|
||||
gfx/common/vulkan_android_pipeline.o \
|
||||
gfx/common/vulkan_android_submit.o \
|
||||
gfx/common/vulkan_android_queue.o \
|
||||
gfx/common/vulkan_android_cache_entry.o \
|
||||
gfx/common/vulkan_android_cache.o
|
||||
ifeq ($(HAVE_THREADS), 1)
|
||||
OBJ += gfx/common/mediacodec_inventory.o \
|
||||
gfx/common/android_image_ndk.o \
|
||||
gfx/common/mediacodec_session.o \
|
||||
gfx/common/mediacodec_color.o \
|
||||
gfx/common/mediacodec_frame_export.o
|
||||
ifeq ($(HAVE_RMP4), 1)
|
||||
OBJ += gfx/common/mediacodec_hevc_packet.o \
|
||||
gfx/common/mediacodec_hevc_decoder.o \
|
||||
gfx/common/mediacodec_hevc_backend.o
|
||||
endif
|
||||
ifeq ($(HAVE_RVP9), 1)
|
||||
OBJ += gfx/common/mediacodec_vp9_decoder.o \
|
||||
gfx/common/mediacodec_vp9_backend.o
|
||||
endif
|
||||
ifeq ($(HAVE_RAV1), 1)
|
||||
OBJ += gfx/common/mediacodec_av1_decoder.o \
|
||||
gfx/common/mediacodec_av1_backend.o
|
||||
endif
|
||||
endif
|
||||
endif
|
||||
endif
|
||||
|
||||
ifeq ($(HAVE_SLANG), 1)
|
||||
@@ -3247,3 +3331,25 @@ ifneq ($(findstring Win32,$(OS)),)
|
||||
endif
|
||||
endif
|
||||
endif
|
||||
|
||||
# Explicit native-device acceptance build only; default registration stays off.
|
||||
ifeq ($(HAVE_RECORD_MEDIACODEC), 1)
|
||||
ifneq ($(ANDROID)$(HAVE_VULKAN)$(HAVE_THREADS)$(HAVE_CONFIGFILE), 1111)
|
||||
$(error MediaCodec recording requires Android, Vulkan, threads and config files)
|
||||
endif
|
||||
DEFINES += -DHAVE_RECORD_MEDIACODEC
|
||||
MEDIA_RECORD_OBJ := record/record_mediacodec_encoder.o \
|
||||
record/record_audio_tap.o \
|
||||
record/record_mux_mediacodec.o record/record_mediacodec_worker.o \
|
||||
record/record_mediacodec_surface.o record/record_mediacodec_file.o \
|
||||
record/record_session_mediacodec.o record/record_dispatch_mediacodec.o \
|
||||
record/record_gpu_dispatch_mediacodec.o record/record_mediacodec_gpu_worker.o \
|
||||
record/record_android_capture_config.o record/record_android_capture_request.o \
|
||||
record/record_android_capture_images.o record/record_android_capture_submit.o \
|
||||
record/record_android_capture_pipe.o record/record_android_capture_pool.o \
|
||||
record/record_android_capture_ops.o record/record_android_capture_egl.o \
|
||||
record/record_android_capture_gl.o record/record_android_capture_draw.o \
|
||||
record/record_gpu_queue.o record/record_audio_queue.o
|
||||
MEDIA_RECORD_OBJ := $(filter-out $(OBJ),$(MEDIA_RECORD_OBJ))
|
||||
OBJ += $(MEDIA_RECORD_OBJ)
|
||||
endif
|
||||
@@ -35,6 +35,9 @@
|
||||
#include "../defaults.h"
|
||||
#include "audio_stretch.h"
|
||||
#include "audio_speed_lpf.h"
|
||||
#if defined(HAVE_RECORD_MEDIACODEC) || defined(RECORD_AUDIO_TAP_TEST)
|
||||
#include "../record/record_audio_tap.h"
|
||||
#endif
|
||||
|
||||
#include <retro_assert.h>
|
||||
#include <string/stdstring.h>
|
||||
@@ -1253,6 +1256,14 @@ static bool audio_driver_deinit_internal(bool audio_enable)
|
||||
}
|
||||
audio_driver_kept.requested = false;
|
||||
|
||||
#if defined(HAVE_RECORD_MEDIACODEC) || defined(RECORD_AUDIO_TAP_TEST)
|
||||
/* The old producer is quiescent here, before its arenas/lock are freed. */
|
||||
if (audio_st->record_tap)
|
||||
{
|
||||
record_audio_tap_retire(audio_st->record_tap, true);
|
||||
audio_st->record_tap = NULL;
|
||||
}
|
||||
#endif
|
||||
audio_st->pipe_pending = NULL;
|
||||
audio_st->pipe_pending_bytes = 0;
|
||||
audio_driver_inline_free(audio_st);
|
||||
@@ -3122,6 +3133,11 @@ static void audio_driver_pause_track(audio_driver_state_t *audio_st,
|
||||
audio_st->pause_mute_frames -= (unsigned)n;
|
||||
}
|
||||
|
||||
#if defined(HAVE_RECORD_MEDIACODEC) || defined(RECORD_AUDIO_TAP_TEST)
|
||||
if (audio_st->record_tap)
|
||||
record_audio_tap_push(audio_st->record_tap, buf, num_frames,
|
||||
is_float, audio_st->out_rate);
|
||||
#endif
|
||||
audio_st->last_out[0] = audio_driver_pause_read(buf, is_float,
|
||||
((num_frames - 1) * 2) + 0);
|
||||
audio_st->last_out[1] = audio_driver_pause_read(buf, is_float,
|
||||
@@ -3269,6 +3285,9 @@ static void audio_driver_flush(audio_driver_state_t *audio_st,
|
||||
* fall through to the float resampler path below (which is exactly
|
||||
* where the redundant int16<->float round-trip is avoided). */
|
||||
if (audio->write_raw
|
||||
#if defined(HAVE_RECORD_MEDIACODEC) || defined(RECORD_AUDIO_TAP_TEST)
|
||||
&& !audio_st->record_tap
|
||||
#endif
|
||||
&& !audio_st->resampler_hq
|
||||
&& !is_float
|
||||
/* The resume ramp and the pause mute are applied on the last
|
||||
@@ -5306,6 +5325,64 @@ void audio_driver_state_unlock(void)
|
||||
#endif
|
||||
}
|
||||
|
||||
/* Explicit start/stop events only. Busy passes are retried, never waited on. */
|
||||
int audio_driver_record_tap_install(struct record_audio_tap *tap)
|
||||
{
|
||||
#if defined(HAVE_RECORD_MEDIACODEC) || defined(RECORD_AUDIO_TAP_TEST)
|
||||
audio_driver_state_t *s = &audio_driver_st;
|
||||
int result = -1;
|
||||
if (!tap)
|
||||
return -1;
|
||||
#ifdef HAVE_THREADS
|
||||
if (s->state_lock && !slock_try_lock(s->state_lock))
|
||||
return 0;
|
||||
#endif
|
||||
if (s->record_tap == tap)
|
||||
result = 1;
|
||||
else if (!s->record_tap && (AUDIO_FLAGS_GET(s) & AUDIO_FLAG_ACTIVE)
|
||||
&& s->out_rate == record_audio_tap_rate(tap)
|
||||
&& record_audio_tap_begin(tap))
|
||||
{
|
||||
s->record_tap = tap;
|
||||
result = 1;
|
||||
}
|
||||
#ifdef HAVE_THREADS
|
||||
if (s->state_lock)
|
||||
slock_unlock(s->state_lock);
|
||||
#endif
|
||||
return result;
|
||||
#else
|
||||
(void)tap;
|
||||
return -1;
|
||||
#endif
|
||||
}
|
||||
bool audio_driver_record_tap_remove(struct record_audio_tap *tap)
|
||||
{
|
||||
#if defined(HAVE_RECORD_MEDIACODEC) || defined(RECORD_AUDIO_TAP_TEST)
|
||||
audio_driver_state_t *s = &audio_driver_st;
|
||||
bool ready;
|
||||
if (!tap)
|
||||
return true;
|
||||
#ifdef HAVE_THREADS
|
||||
if (s->state_lock && !slock_try_lock(s->state_lock))
|
||||
return false;
|
||||
#endif
|
||||
if (s->record_tap == tap)
|
||||
{
|
||||
s->record_tap = NULL;
|
||||
record_audio_tap_retire(tap, false);
|
||||
}
|
||||
ready = record_audio_tap_end(tap, NULL);
|
||||
#ifdef HAVE_THREADS
|
||||
if (s->state_lock)
|
||||
slock_unlock(s->state_lock);
|
||||
#endif
|
||||
return ready;
|
||||
#else
|
||||
return tap == NULL;
|
||||
#endif
|
||||
}
|
||||
|
||||
/* Converts to the device's format if it needs it, and writes. The pause
|
||||
* tail reaches the device outside audio_driver_flush()'s own conversion. */
|
||||
static void audio_driver_write_float(audio_driver_state_t *audio_st,
|
||||
@@ -8689,6 +8766,14 @@ int64_t audio_driver_mixer_stream_byte_tell(unsigned i)
|
||||
return (int64_t)audio_mixer_voice_buffer_tell(voice);
|
||||
}
|
||||
|
||||
void audio_driver_mixer_stream_set_repeat(unsigned i, bool repeat)
|
||||
{
|
||||
if (i < AUDIO_MIXER_MAX_SYSTEM_STREAMS
|
||||
&& audio_driver_st.mixer_streams[i].state != AUDIO_STREAM_STATE_NONE)
|
||||
audio_mixer_voice_set_repeat(audio_driver_st.mixer_streams[i].voice,
|
||||
repeat);
|
||||
}
|
||||
|
||||
void audio_driver_mixer_stream_set_paused(unsigned i, bool paused)
|
||||
{
|
||||
if (i < AUDIO_MIXER_MAX_SYSTEM_STREAMS
|
||||
|
||||
@@ -484,6 +484,9 @@ typedef struct
|
||||
uint64_t free_samples_count;
|
||||
|
||||
struct audio_record_source *record_source;
|
||||
#if defined(HAVE_RECORD_MEDIACODEC) || defined(RECORD_AUDIO_TAP_TEST)
|
||||
struct record_audio_tap *record_tap;
|
||||
#endif
|
||||
struct string_list *devices_list;
|
||||
/* The driver whose device_list_new built devices_list, so that its
|
||||
* device_list_free is the one that releases it - not whichever
|
||||
@@ -1241,6 +1244,12 @@ void audio_driver_pipeline_wake(void);
|
||||
* the lock exists for the driver's lifetime and both are no-ops
|
||||
* without it.
|
||||
**/
|
||||
struct record_audio_tap;
|
||||
/* Cold main owner. Uses try-lock on the existing audio state serialization;
|
||||
* install: 1 installed, 0 retry, -1 refused. Removal proves producer quiescence
|
||||
* without waiting. No acquisition is added to an audio processing pass. */
|
||||
int audio_driver_record_tap_install(struct record_audio_tap *tap);
|
||||
bool audio_driver_record_tap_remove(struct record_audio_tap *tap);
|
||||
void audio_driver_state_lock(void);
|
||||
void audio_driver_state_unlock(void);
|
||||
|
||||
@@ -1361,6 +1370,7 @@ int64_t audio_driver_mixer_stream_byte_tell(unsigned i);
|
||||
/* Main-thread transport request; retains queued PCM and decoder state.
|
||||
* The mixer applies it at the next block boundary without waiting. */
|
||||
void audio_driver_mixer_stream_set_paused(unsigned i, bool paused);
|
||||
void audio_driver_mixer_stream_set_repeat(unsigned i, bool repeat);
|
||||
|
||||
|
||||
/* Raise a windowed stream's resident prefix as its feeder slides the
|
||||
|
||||
@@ -1055,6 +1055,9 @@ typedef struct settings
|
||||
|
||||
/* Multimedia */
|
||||
bool multimedia_builtin_mediaplayer_enable;
|
||||
bool media_player_continuous;
|
||||
bool media_player_crt_switch;
|
||||
bool media_player_refresh_rate;
|
||||
bool multimedia_builtin_imageviewer_enable;
|
||||
|
||||
/* Bundle */
|
||||
|
||||
@@ -191,6 +191,7 @@ static struct
|
||||
bool shared_dpb;
|
||||
bool packed_yuv;
|
||||
bool av1_only;
|
||||
bool h264_owned;
|
||||
} d3d11vd;
|
||||
|
||||
/* ---- teardown ---- */
|
||||
@@ -531,6 +532,7 @@ static void *d3d11vd_open(void *user, const rh264_hw_sps_t *sps,
|
||||
d->profile = d3d11vd_h264_vld_nofgt;
|
||||
d->format = DXGI_FORMAT_NV12;
|
||||
d->bit_depth = 8;
|
||||
d->packed_yuv = d3d11vd.packed_yuv;
|
||||
d->bitstream_raw = 2;
|
||||
d->slots = slots;
|
||||
d->coded_w = (unsigned)sps->width_in_mbs * 16;
|
||||
@@ -1284,6 +1286,7 @@ static bool d3d11vd_init_adapter(void *adapter, unsigned formats, bool av1_only)
|
||||
d3d11vd.ops.api = VIDEO_HW_API_D3D11;
|
||||
d3d11vd.up = true;
|
||||
d3d11vd.av1_only = av1_only;
|
||||
d3d11vd.h264_owned = !av1_only;
|
||||
if (!av1_only) rh264_set_hw_backend(&d3d11vd.ops);
|
||||
d3d11vd.shared_dpb = !!(formats & D3D11_VIDEO_DECODE_SHARED_DPB);
|
||||
d3d11vd.packed_yuv = !!(formats & D3D11_VIDEO_DECODE_PACKED_YUV);
|
||||
@@ -1300,6 +1303,7 @@ static bool d3d11vd_init_adapter(void *adapter, unsigned formats, bool av1_only)
|
||||
d3d11vd.vp9.packet_decoder = false;
|
||||
d3d11vd.vp9.open_color = NULL;
|
||||
d3d11vd.vp9.open_config = NULL;
|
||||
d3d11vd.vp9.restart = NULL;
|
||||
d3d11vd.vp9.drain = NULL;
|
||||
if (!av1_only) rvp9_set_hw_backend(&d3d11vd.vp9);
|
||||
#endif
|
||||
@@ -1320,14 +1324,27 @@ bool d3d11_video_decode_init_adapter_formats(void *adapter, unsigned formats)
|
||||
return d3d11vd_init_adapter(adapter, formats, false);
|
||||
}
|
||||
|
||||
#ifdef HAVE_RAV1
|
||||
bool d3d11_video_decode_init_av1_adapter(void *adapter, unsigned formats)
|
||||
bool d3d11_video_decode_init_packed_adapter(void *adapter, unsigned formats,
|
||||
bool h264)
|
||||
{
|
||||
unsigned native_formats = 0;
|
||||
if (formats & IMAGE_PIXFMT_YUV8_FP16) native_formats |= IMAGE_PIXFMT_NV12;
|
||||
if (formats & IMAGE_PIXFMT_YUV10_FP16) native_formats |= IMAGE_PIXFMT_P010;
|
||||
return native_formats && d3d11vd_init_adapter(adapter,
|
||||
native_formats | D3D11_VIDEO_DECODE_PACKED_YUV, true);
|
||||
if (!native_formats || !d3d11vd_init_adapter(adapter,
|
||||
native_formats | D3D11_VIDEO_DECODE_PACKED_YUV, true))
|
||||
return false;
|
||||
if (h264 && (native_formats & IMAGE_PIXFMT_NV12))
|
||||
{
|
||||
rh264_set_hw_backend(&d3d11vd.ops);
|
||||
d3d11vd.h264_owned = true;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
#ifdef HAVE_RAV1
|
||||
bool d3d11_video_decode_init_av1_adapter(void *adapter, unsigned formats)
|
||||
{
|
||||
return d3d11_video_decode_init_packed_adapter(adapter, formats, false);
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -1363,11 +1380,10 @@ void d3d11_video_decode_deinit(void)
|
||||
{
|
||||
if (!d3d11vd.up)
|
||||
return;
|
||||
if (!d3d11vd.av1_only)
|
||||
{
|
||||
if (d3d11vd.h264_owned)
|
||||
rh264_set_hw_backend(NULL);
|
||||
if (!d3d11vd.av1_only)
|
||||
rh265_unset_hw_backend(&d3d11vd.hevc);
|
||||
}
|
||||
#ifdef HAVE_RVP9
|
||||
if (!d3d11vd.av1_only) rvp9_unset_hw_backend(&d3d11vd.vp9);
|
||||
#endif
|
||||
|
||||
@@ -71,6 +71,10 @@ bool d3d11_video_decode_vp9_open(const rvp9_hdr *header,
|
||||
unsigned slots, rvp9_hw_ops_t *ops);
|
||||
void d3d11_video_decode_vp9_close(rvp9_hw_ops_t *ops);
|
||||
|
||||
/* Packed GPU exports, optionally replacing the H.264 backend. */
|
||||
bool d3d11_video_decode_init_packed_adapter(void *adapter, unsigned formats,
|
||||
bool h264);
|
||||
|
||||
#ifdef HAVE_RAV1
|
||||
/* Offers only AV1 with packed sample exports; other codec owners stay intact. */
|
||||
bool d3d11_video_decode_init_av1_adapter(void *adapter, unsigned formats);
|
||||
|
||||
@@ -0,0 +1,266 @@
|
||||
/* Copyright (C) 2026 The RetroArch team. SPDX-License-Identifier:
|
||||
* GPL-3.0-or-later */
|
||||
#include "mediacodec_av1_backend.h"
|
||||
#include "mediacodec_frame_export.h"
|
||||
#include <formats/image.h>
|
||||
#include <rthreads/retro_eventcount.h>
|
||||
#include <pthread.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
typedef struct mediacodec_av1_backend_context
|
||||
{
|
||||
mediacodec_av1_backend_t *backend;
|
||||
mediacodec_frame_exporter_t *exporter;
|
||||
mediacodec_av1_decoder_t *retired;
|
||||
retro_eventcount_t wake;
|
||||
retro_atomic_int_t state, refs;
|
||||
} mediacodec_av1_backend_context_t;
|
||||
|
||||
static void mediacodec_av1_backend_context_free(
|
||||
mediacodec_av1_backend_context_t *c)
|
||||
{
|
||||
mediacodec_av1_backend_t *backend = c->backend;
|
||||
if (c->exporter)
|
||||
mediacodec_frame_exporter_free(c->exporter);
|
||||
retro_eventcount_free(&c->wake);
|
||||
free(c);
|
||||
retro_atomic_fetch_sub_int(&backend->active, 1);
|
||||
}
|
||||
|
||||
static void mediacodec_av1_backend_context_unref(
|
||||
mediacodec_av1_backend_context_t *c)
|
||||
{
|
||||
if (retro_atomic_fetch_sub_int(&c->refs, 1) == 1)
|
||||
mediacodec_av1_backend_context_free(c);
|
||||
}
|
||||
|
||||
static void mediacodec_av1_backend_publish(
|
||||
mediacodec_av1_backend_context_t *c, int state)
|
||||
{
|
||||
retro_atomic_store_release_int(&c->state, state);
|
||||
retro_eventcount_notify(&c->wake);
|
||||
/* The publisher keeps its reference until notification has returned. */
|
||||
mediacodec_av1_backend_context_unref(c);
|
||||
}
|
||||
|
||||
static bool mediacodec_av1_backend_release(void *opaque)
|
||||
{
|
||||
mediacodec_av1_backend_context_t *c =
|
||||
(mediacodec_av1_backend_context_t *)opaque;
|
||||
if (c->exporter && !mediacodec_frame_exporter_free(c->exporter))
|
||||
return false;
|
||||
c->exporter = NULL;
|
||||
return true;
|
||||
}
|
||||
|
||||
static int mediacodec_av1_backend_export(void *opaque,
|
||||
const android_image_view_t *image,
|
||||
const mediacodec_output_format_t *format, const rav1_sequence_t *sequence,
|
||||
const video_color_metadata_t *color, video_hw_frame_t *frame)
|
||||
{
|
||||
mediacodec_av1_backend_context_t *c =
|
||||
(mediacodec_av1_backend_context_t *)opaque;
|
||||
return mediacodec_av1_frame_export(
|
||||
c->exporter, image, format, sequence, color, frame);
|
||||
}
|
||||
|
||||
static void mediacodec_av1_backend_enqueue(
|
||||
void *opaque, mediacodec_av1_decoder_t *decoder)
|
||||
{
|
||||
mediacodec_av1_backend_context_t *c =
|
||||
(mediacodec_av1_backend_context_t *)opaque;
|
||||
c->retired = decoder;
|
||||
mediacodec_av1_backend_publish(c, 1);
|
||||
}
|
||||
|
||||
static void *mediacodec_av1_backend_retire(void *opaque)
|
||||
{
|
||||
mediacodec_av1_backend_context_t *c =
|
||||
(mediacodec_av1_backend_context_t *)opaque;
|
||||
mediacodec_av1_decoder_t *decoder;
|
||||
int key;
|
||||
while (!retro_atomic_load_acquire_int(&c->state))
|
||||
{
|
||||
key = retro_eventcount_prepare_wait(&c->wake);
|
||||
if (retro_atomic_load_acquire_int(&c->state))
|
||||
{
|
||||
retro_eventcount_cancel_wait(&c->wake);
|
||||
break;
|
||||
}
|
||||
retro_eventcount_commit_wait(&c->wake, key);
|
||||
}
|
||||
decoder = c->retired;
|
||||
if (decoder)
|
||||
while (!mediacodec_av1_decoder_retire_poll(decoder))
|
||||
{
|
||||
key = retro_eventcount_prepare_wait(&c->wake);
|
||||
retro_eventcount_commit_wait_timeout(&c->wake, key, 10000);
|
||||
}
|
||||
mediacodec_av1_backend_context_unref(c);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Normalize the admitted sequence OBU into Android av1C during cold setup. */
|
||||
static uint8_t *mediacodec_av1_backend_config(
|
||||
const rav1_sequence_t *sequence, const rav1_obu_t *source, size_t *size)
|
||||
{
|
||||
rav1_sequence_t parsed;
|
||||
rav1_obu_t obu;
|
||||
uint8_t *config;
|
||||
size_t offset = 0, length, used = 5;
|
||||
*size = 0;
|
||||
if (source->size > 1024u * 1024u
|
||||
|| rav1_obu_next(&obu, source->data, source->size, &offset, true) != 1
|
||||
|| offset != source->size || obu.type != 1 || obu.temporal_id
|
||||
|| obu.spatial_id || obu.payload_size > 1024u * 1024u - 16
|
||||
|| !rav1_sequence_parse(&parsed, obu.payload, obu.payload_size)
|
||||
|| memcmp(&parsed, sequence, sizeof(parsed)))
|
||||
return NULL;
|
||||
config = (uint8_t *)malloc(obu.payload_size + 16);
|
||||
if (!config)
|
||||
return NULL;
|
||||
config[0] = 0x81;
|
||||
config[1] = (uint8_t)((sequence->profile << 5)
|
||||
| sequence->operating_points[0].level);
|
||||
config[2] = (uint8_t)((sequence->operating_points[0].tier << 7)
|
||||
| (sequence->bit_depth == 10 ? 0x40 : 0)
|
||||
| (sequence->subsampling_x << 3)
|
||||
| (sequence->subsampling_y << 2)
|
||||
| sequence->chroma_sample_position);
|
||||
/* Sample presentation delay cannot be inferred from frame display delay. */
|
||||
config[3] = 0;
|
||||
config[4] = 0x0a;
|
||||
length = obu.payload_size;
|
||||
do
|
||||
{
|
||||
config[used++] = (uint8_t)((length & 127) | (length > 127 ? 128 : 0));
|
||||
length >>= 7;
|
||||
} while (length);
|
||||
memcpy(config + used, obu.payload, obu.payload_size);
|
||||
*size = used + obu.payload_size;
|
||||
return config;
|
||||
}
|
||||
|
||||
static bool mediacodec_av1_backend_open(void *opaque,
|
||||
const rav1_sequence_t *sequence, const rav1_obu_t *sequence_obu,
|
||||
const video_color_metadata_t *color, unsigned slots, rav1_hw_ops_t *ops)
|
||||
{
|
||||
mediacodec_av1_backend_t *backend = (mediacodec_av1_backend_t *)opaque;
|
||||
mediacodec_av1_backend_context_t *c;
|
||||
mediacodec_av1_import_t importer;
|
||||
pthread_t thread;
|
||||
pthread_attr_t attr;
|
||||
JNIEnv *env;
|
||||
int active;
|
||||
uint8_t *config;
|
||||
size_t config_size;
|
||||
if (!ops)
|
||||
return false;
|
||||
memset(ops, 0, sizeof(*ops));
|
||||
if (!backend || !backend->environment || !sequence || !sequence_obu
|
||||
|| slots < 9 || slots > 32 || sequence->profile || sequence->monochrome
|
||||
|| sequence->subsampling_x != 1 || sequence->subsampling_y != 1
|
||||
|| (sequence->bit_depth != 8 && sequence->bit_depth != 10))
|
||||
return false;
|
||||
for (;;)
|
||||
{
|
||||
active = retro_atomic_load_acquire_int(&backend->active);
|
||||
if (active < 0 || (unsigned)active >= backend->max_sessions)
|
||||
return false;
|
||||
if (retro_atomic_cas_int(&backend->active, active, active + 1))
|
||||
break;
|
||||
}
|
||||
env = backend->environment(backend->user);
|
||||
if (!env)
|
||||
{
|
||||
retro_atomic_fetch_sub_int(&backend->active, 1);
|
||||
return false;
|
||||
}
|
||||
c = (mediacodec_av1_backend_context_t *)calloc(1, sizeof(*c));
|
||||
if (!c)
|
||||
{
|
||||
retro_atomic_fetch_sub_int(&backend->active, 1);
|
||||
return false;
|
||||
}
|
||||
c->backend = backend;
|
||||
if (!retro_eventcount_init(&c->wake))
|
||||
{
|
||||
free(c);
|
||||
retro_atomic_fetch_sub_int(&backend->active, 1);
|
||||
return false;
|
||||
}
|
||||
retro_atomic_int_init(&c->state, 0);
|
||||
retro_atomic_int_init(&c->refs, 2);
|
||||
c->exporter = mediacodec_frame_exporter_new(slots,
|
||||
sequence->bit_depth == 10 ? IMAGE_PIXFMT_P010 : IMAGE_PIXFMT_NV12);
|
||||
if (!c->exporter)
|
||||
{
|
||||
mediacodec_av1_backend_context_free(c);
|
||||
return false;
|
||||
}
|
||||
if (pthread_attr_init(&attr))
|
||||
{
|
||||
mediacodec_av1_backend_context_free(c);
|
||||
return false;
|
||||
}
|
||||
if (pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED))
|
||||
{
|
||||
pthread_attr_destroy(&attr);
|
||||
mediacodec_av1_backend_context_free(c);
|
||||
return false;
|
||||
}
|
||||
active = pthread_create(&thread, &attr, mediacodec_av1_backend_retire, c);
|
||||
pthread_attr_destroy(&attr);
|
||||
if (active)
|
||||
{
|
||||
mediacodec_av1_backend_context_free(c);
|
||||
return false;
|
||||
}
|
||||
memset(&importer, 0, sizeof(importer));
|
||||
importer.context = c;
|
||||
importer.export_image = mediacodec_av1_backend_export;
|
||||
importer.release = mediacodec_av1_backend_release;
|
||||
importer.enqueue = mediacodec_av1_backend_enqueue;
|
||||
config = mediacodec_av1_backend_config(sequence, sequence_obu, &config_size);
|
||||
if (!config
|
||||
|| !mediacodec_av1_decoder_open(env, config, config_size, color, slots,
|
||||
backend->max_input, &importer, ops))
|
||||
{
|
||||
free(config);
|
||||
mediacodec_av1_backend_publish(c, 2);
|
||||
return false;
|
||||
}
|
||||
free(config);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool mediacodec_av1_backend_init(mediacodec_av1_backend_t *backend,
|
||||
JNIEnv *(*environment)(void *), void *user, unsigned max_input,
|
||||
unsigned max_sessions)
|
||||
{
|
||||
#if !defined(HAVE_THREADS) || !defined(RETRO_ATOMIC_LOCK_FREE)
|
||||
return false;
|
||||
#endif
|
||||
if (!backend || !environment || !max_input || max_input > 64u * 1024u * 1024u
|
||||
|| !max_sessions || max_sessions > 8)
|
||||
return false;
|
||||
memset(backend, 0, sizeof(*backend));
|
||||
backend->user = user;
|
||||
backend->environment = environment;
|
||||
backend->max_input = max_input;
|
||||
backend->max_sessions = max_sessions;
|
||||
retro_atomic_int_init(&backend->active, 0);
|
||||
backend->offer.user = backend;
|
||||
backend->offer.open_config = mediacodec_av1_backend_open;
|
||||
backend->offer.drain = mediacodec_av1_decoder_finish;
|
||||
backend->offer.packet_decoder = true;
|
||||
backend->offer.close = mediacodec_av1_decoder_close;
|
||||
return true;
|
||||
}
|
||||
|
||||
unsigned mediacodec_av1_backend_active(mediacodec_av1_backend_t *backend)
|
||||
{
|
||||
return backend ? (unsigned)retro_atomic_load_acquire_int(&backend->active)
|
||||
: 0;
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
/* Copyright (C) 2026 The RetroArch team. SPDX-License-Identifier:
|
||||
* GPL-3.0-or-later */
|
||||
#ifndef MEDIACODEC_AV1_BACKEND_H
|
||||
#define MEDIACODEC_AV1_BACKEND_H
|
||||
#include "mediacodec_av1_decoder.h"
|
||||
#include <retro_atomic.h>
|
||||
RETRO_BEGIN_DECLS
|
||||
|
||||
typedef struct mediacodec_av1_backend
|
||||
{
|
||||
rav1_hw_backend_t offer;
|
||||
void *user;
|
||||
JNIEnv *(*environment)(void *user);
|
||||
unsigned max_input, max_sessions;
|
||||
retro_atomic_int_t active;
|
||||
} mediacodec_av1_backend_t;
|
||||
|
||||
/* Cold setup only; does not register or advertise hardware support. Caller
|
||||
* admits a validated renderer before publishing offer. environment returns the
|
||||
* calling decoder-worker's JNIEnv (e.g. rjni_env), never a saved foreign env.
|
||||
* Storage/user/callback outlive all admitted decoders and retired contexts.
|
||||
* max_input is the explicit packet staging bound, <=64 MiB. At most 1..8
|
||||
* sessions including retirement may coexist; seek storms refuse excess opens
|
||||
* without waiting. Each admitted session has one cold retirement worker,
|
||||
* created before native startup; decode/output/export add no thread, wait or
|
||||
* lock. Failed startup and close return without joining; active includes
|
||||
* deferred cleanup. */
|
||||
bool mediacodec_av1_backend_init(mediacodec_av1_backend_t *backend,
|
||||
JNIEnv *(*environment)(void *), void *user, unsigned max_input,
|
||||
unsigned max_sessions);
|
||||
unsigned mediacodec_av1_backend_active(mediacodec_av1_backend_t *backend);
|
||||
RETRO_END_DECLS
|
||||
#endif
|
||||
@@ -0,0 +1,343 @@
|
||||
/* Copyright (C) 2026 The RetroArch team.
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later */
|
||||
#include "mediacodec_av1_decoder.h"
|
||||
#include "android_image_ndk.h"
|
||||
#include "mediacodec_color.h"
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
struct mediacodec_av1_decoder
|
||||
{
|
||||
uint64_t serial, tag;
|
||||
mediacodec_session_t *session;
|
||||
android_image_queue_t *images;
|
||||
mediacodec_av1_import_t importer;
|
||||
android_image_view_t image;
|
||||
rav1_sequence_t sequence;
|
||||
rav1_grain_t grain;
|
||||
video_color_metadata_t color;
|
||||
mediacodec_output_format_t format, display_format;
|
||||
unsigned slots, capacity, output_slot, output_width, output_height;
|
||||
bool failed, closed, eos, output_eos, rendered;
|
||||
};
|
||||
|
||||
static bool mediacodec_av1_discard(android_image_view_t *image)
|
||||
{
|
||||
if (!image->ticket)
|
||||
return true;
|
||||
if (!android_image_queue_return(image->ticket, image->generation, -1))
|
||||
return false;
|
||||
memset(image, 0, sizeof(*image));
|
||||
return true;
|
||||
}
|
||||
|
||||
static int mediacodec_av1_service(mediacodec_av1_decoder_t *d)
|
||||
{
|
||||
android_image_view_t image;
|
||||
const mediacodec_output_format_t *format;
|
||||
int64_t pts;
|
||||
uint64_t tag;
|
||||
int status;
|
||||
if (d->failed)
|
||||
return -1;
|
||||
android_image_queue_poll(d->images);
|
||||
status = mediacodec_session_pump(d->session, &pts);
|
||||
if (status < 0)
|
||||
goto fail;
|
||||
if (status == 1)
|
||||
{
|
||||
if (pts <= 0 || (uint64_t)pts > d->serial)
|
||||
goto fail;
|
||||
if ((uint64_t)pts == d->tag)
|
||||
{
|
||||
d->rendered = true;
|
||||
if (!d->display_format.serial) d->display_format = d->format;
|
||||
}
|
||||
}
|
||||
if (status == 3)
|
||||
{
|
||||
if (!d->eos)
|
||||
goto fail;
|
||||
d->output_eos = true;
|
||||
}
|
||||
if (status == 2)
|
||||
{
|
||||
format = mediacodec_session_format(d->session);
|
||||
if (!format || !format->width || !format->height
|
||||
|| format->width > d->sequence.max_width
|
||||
|| format->height > d->sequence.max_height
|
||||
|| format->left >= format->right || format->top >= format->bottom
|
||||
|| format->right > format->width || format->bottom > format->height)
|
||||
goto fail;
|
||||
d->format = *format;
|
||||
}
|
||||
status = android_image_queue_acquire(d->images, &image);
|
||||
if (status < 0)
|
||||
goto fail;
|
||||
if (!status)
|
||||
return 0;
|
||||
tag = (uint64_t)image.timestamp_ns / 1000;
|
||||
if (image.timestamp_ns <= 0 || image.timestamp_ns % 1000 || tag > d->serial)
|
||||
{
|
||||
mediacodec_av1_discard(&image);
|
||||
goto fail;
|
||||
}
|
||||
if (tag != d->tag)
|
||||
{
|
||||
if (!mediacodec_av1_discard(&image))
|
||||
goto fail;
|
||||
return 0;
|
||||
}
|
||||
if (d->image.ticket || !d->format.serial)
|
||||
{
|
||||
mediacodec_av1_discard(&image);
|
||||
goto fail;
|
||||
}
|
||||
if (!d->display_format.serial) d->display_format = d->format;
|
||||
d->image = image;
|
||||
return 0;
|
||||
fail:
|
||||
d->failed = true;
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int mediacodec_av1_decode(void *opaque, const uint8_t *packet,
|
||||
size_t size, const rav1_sequence_t *sequence,
|
||||
const rav1_hw_picture_t *picture)
|
||||
{
|
||||
mediacodec_av1_decoder_t *d = (mediacodec_av1_decoder_t *)opaque;
|
||||
bool show;
|
||||
int status;
|
||||
if (!d || !packet || !size || !picture || d->closed || d->eos
|
||||
|| size > d->capacity || picture->slot < 0
|
||||
|| (unsigned)picture->slot >= d->slots
|
||||
|| d->serial >= ((~(uint64_t)0) >> 1) / 1000)
|
||||
return -1;
|
||||
if (!sequence || memcmp(sequence, &d->sequence, sizeof(*sequence))
|
||||
|| !picture->setup.size.upscaled_width || !picture->setup.size.height
|
||||
|| picture->setup.size.upscaled_width > sequence->max_width
|
||||
|| picture->setup.size.height > sequence->max_height)
|
||||
return -1;
|
||||
show = picture->header.show_frame || picture->header.show_existing;
|
||||
if (mediacodec_av1_service(d) < 0)
|
||||
return -1;
|
||||
status = mediacodec_session_submit(
|
||||
d->session, packet, size, (int64_t)(d->serial + 1), false);
|
||||
if (status < 0)
|
||||
{
|
||||
d->failed = true;
|
||||
return -1;
|
||||
}
|
||||
if (!status)
|
||||
return 1;
|
||||
if (show && !mediacodec_av1_discard(&d->image))
|
||||
{
|
||||
d->failed = true;
|
||||
return -1;
|
||||
}
|
||||
d->serial++;
|
||||
if (show)
|
||||
{
|
||||
d->tag = d->serial;
|
||||
d->output_slot = (unsigned)picture->slot;
|
||||
d->rendered = false;
|
||||
memset(&d->display_format, 0, sizeof(d->display_format));
|
||||
}
|
||||
if (show)
|
||||
{
|
||||
d->output_width = picture->setup.size.upscaled_width;
|
||||
d->output_height = picture->setup.size.height;
|
||||
d->grain = picture->grain.params;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int mediacodec_av1_output(void *opaque, unsigned slot,
|
||||
const rav1_grain_t *grain, video_hw_frame_t *out)
|
||||
{
|
||||
mediacodec_av1_decoder_t *d = (mediacodec_av1_decoder_t *)opaque;
|
||||
int status;
|
||||
if (!d || !out || !grain || d->closed || !d->tag || slot != d->output_slot
|
||||
|| memcmp(grain, &d->grain, sizeof(*grain)))
|
||||
return -1;
|
||||
if (mediacodec_av1_service(d) < 0)
|
||||
return -1;
|
||||
if (!d->image.ticket)
|
||||
{
|
||||
if (d->output_eos && !d->rendered)
|
||||
{
|
||||
d->failed = true;
|
||||
return -1;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
if (d->image.right - d->image.left != d->output_width
|
||||
|| d->image.bottom - d->image.top != d->output_height
|
||||
|| d->display_format.right - d->display_format.left != d->output_width
|
||||
|| d->display_format.bottom - d->display_format.top != d->output_height)
|
||||
{
|
||||
d->failed = true;
|
||||
return -1;
|
||||
}
|
||||
status = d->importer.export_image(d->importer.context, &d->image, &d->display_format,
|
||||
&d->sequence, &d->color, out);
|
||||
if (!status)
|
||||
{
|
||||
memset(&d->image, 0, sizeof(d->image));
|
||||
d->tag = 0;
|
||||
}
|
||||
if (status < 0)
|
||||
d->failed = true;
|
||||
return status;
|
||||
}
|
||||
|
||||
static int mediacodec_av1_decoder_drain(void *opaque)
|
||||
{
|
||||
mediacodec_av1_decoder_t *d = (mediacodec_av1_decoder_t *)opaque;
|
||||
int status;
|
||||
if (!d || d->closed || mediacodec_av1_service(d) < 0)
|
||||
return -1;
|
||||
if (d->eos)
|
||||
return 0;
|
||||
status = mediacodec_session_submit(d->session, NULL, 0, 0, true);
|
||||
if (status < 0)
|
||||
{
|
||||
d->failed = true;
|
||||
return -1;
|
||||
}
|
||||
if (!status)
|
||||
return 1;
|
||||
d->eos = true;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int mediacodec_av1_decoder_finish(void *opaque, bool discard)
|
||||
{
|
||||
mediacodec_av1_decoder_t *d = (mediacodec_av1_decoder_t *)opaque;
|
||||
int status;
|
||||
if (!d || d->closed)
|
||||
return -1;
|
||||
if (discard)
|
||||
{
|
||||
if (!mediacodec_av1_discard(&d->image))
|
||||
return -1;
|
||||
d->tag = 0;
|
||||
}
|
||||
status = mediacodec_av1_decoder_drain(d);
|
||||
if (status)
|
||||
return status;
|
||||
if (d->output_eos && d->tag && !d->rendered && !d->image.ticket)
|
||||
{
|
||||
d->failed = true;
|
||||
return -1;
|
||||
}
|
||||
return d->output_eos && !d->tag ? 0 : 1;
|
||||
}
|
||||
|
||||
bool mediacodec_av1_decoder_retire_poll(mediacodec_av1_decoder_t *d)
|
||||
{
|
||||
if (!d)
|
||||
return true;
|
||||
if (!d->closed || !mediacodec_av1_discard(&d->image))
|
||||
return false;
|
||||
if (d->images)
|
||||
{
|
||||
android_image_queue_poll(d->images);
|
||||
if (!android_image_queue_idle(d->images))
|
||||
return false;
|
||||
if (d->session)
|
||||
{
|
||||
mediacodec_session_free(d->session);
|
||||
d->session = NULL;
|
||||
}
|
||||
if (!android_image_queue_free(d->images))
|
||||
return false;
|
||||
d->images = NULL;
|
||||
}
|
||||
if (!d->importer.release(d->importer.context))
|
||||
return false;
|
||||
free(d);
|
||||
return true;
|
||||
}
|
||||
|
||||
void mediacodec_av1_decoder_close(rav1_hw_ops_t *ops)
|
||||
{
|
||||
mediacodec_av1_decoder_t *d;
|
||||
if (!ops || ops->decode != mediacodec_av1_decode || !ops->decoder)
|
||||
return;
|
||||
d = (mediacodec_av1_decoder_t *)ops->decoder;
|
||||
memset(ops, 0, sizeof(*ops));
|
||||
d->closed = true;
|
||||
android_image_queue_close(d->images);
|
||||
d->importer.enqueue(d->importer.context, d);
|
||||
}
|
||||
|
||||
bool mediacodec_av1_decoder_open(JNIEnv *env, const uint8_t *av1c,
|
||||
size_t av1c_size, const video_color_metadata_t *color, unsigned slots,
|
||||
unsigned max_input, const mediacodec_av1_import_t *importer,
|
||||
rav1_hw_ops_t *ops)
|
||||
{
|
||||
mediacodec_av1_decoder_t *d;
|
||||
mediacodec_config_t config;
|
||||
rav1_config_t record;
|
||||
void *window = NULL;
|
||||
if (!ops)
|
||||
return false;
|
||||
memset(ops, 0, sizeof(*ops));
|
||||
if (!env || !av1c || av1c_size > 1024u * 1024u || slots < 9 || slots > 32
|
||||
|| !max_input || max_input > 64u * 1024u * 1024u || !importer
|
||||
|| !importer->export_image || !importer->enqueue || !importer->release
|
||||
|| !rav1_config_parse(&record, av1c, av1c_size))
|
||||
return false;
|
||||
d = (mediacodec_av1_decoder_t *)calloc(1, sizeof(*d));
|
||||
if (!d)
|
||||
return false;
|
||||
if (!rav1_config_sequence(&d->sequence, &record) || d->sequence.profile
|
||||
|| d->sequence.monochrome || d->sequence.subsampling_x != 1
|
||||
|| d->sequence.subsampling_y != 1
|
||||
|| (d->sequence.bit_depth != 8 && d->sequence.bit_depth != 10)
|
||||
|| !d->sequence.max_width || d->sequence.max_width > 16384
|
||||
|| !d->sequence.max_height || d->sequence.max_height > 16384
|
||||
|| d->sequence.operating_point_count != 1
|
||||
|| d->sequence.operating_points[0].idc)
|
||||
goto fail;
|
||||
if (!mediacodec_av1_color(&d->sequence, color, &d->color))
|
||||
goto fail;
|
||||
d->slots = slots;
|
||||
d->capacity = max_input;
|
||||
memset(&config, 0, sizeof(config));
|
||||
if (!mediacodec_color_configure(&d->color, &config))
|
||||
goto fail;
|
||||
if ((d->color.transfer == 16 || d->color.transfer == 18)
|
||||
&& d->sequence.bit_depth != 10)
|
||||
goto fail;
|
||||
config.mime = "video/av01";
|
||||
config.width = d->sequence.max_width;
|
||||
config.height = d->sequence.max_height;
|
||||
config.max_input = max_input;
|
||||
config.profile = d->sequence.bit_depth == 10 ? 2 : 1;
|
||||
if (d->color.transfer == 16)
|
||||
config.profile = 4096;
|
||||
config.csd[0] = av1c;
|
||||
config.csd_size[0] = av1c_size;
|
||||
d->images =
|
||||
android_image_ndk_new(config.width, config.height, slots, &window);
|
||||
if (!d->images)
|
||||
goto fail;
|
||||
d->session = mediacodec_session_new(env, &config, window);
|
||||
if (!d->session)
|
||||
goto fail;
|
||||
d->importer = *importer;
|
||||
ops->decoder = d;
|
||||
ops->decode = mediacodec_av1_decode;
|
||||
ops->output = mediacodec_av1_output;
|
||||
return true;
|
||||
fail:
|
||||
if (d->images)
|
||||
{
|
||||
android_image_queue_close(d->images);
|
||||
android_image_queue_free(d->images);
|
||||
}
|
||||
free(d);
|
||||
return false;
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
/* Copyright (C) 2026 The RetroArch team.
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later */
|
||||
#ifndef MEDIACODEC_AV1_DECODER_H
|
||||
#define MEDIACODEC_AV1_DECODER_H
|
||||
#include <formats/rav1_hw.h>
|
||||
#include "mediacodec_session.h"
|
||||
#include "android_image_queue.h"
|
||||
RETRO_BEGIN_DECLS
|
||||
typedef struct mediacodec_av1_decoder mediacodec_av1_decoder_t;
|
||||
|
||||
typedef struct mediacodec_av1_import
|
||||
{
|
||||
void *context;
|
||||
/* Native output includes grain. Never apply it again during export.
|
||||
* 0 consumes image; BUSY/error leave its ticket untouched. */
|
||||
int (*export_image)(void *, const android_image_view_t *,
|
||||
const mediacodec_output_format_t *, const rav1_sequence_t *,
|
||||
const video_color_metadata_t *, video_hw_frame_t *);
|
||||
bool (*release)(void *);
|
||||
/* Guaranteed deferred execution; never retire inline on close. */
|
||||
void (*enqueue)(void *, mediacodec_av1_decoder_t *);
|
||||
} mediacodec_av1_import_t;
|
||||
|
||||
/* Cold factory; av1C includes a validated sequence OBU and is borrowed only
|
||||
* during open. Caller selects AV1 packet-decoder mode. Codec owns references.
|
||||
*/
|
||||
bool mediacodec_av1_decoder_open(JNIEnv *, const uint8_t *av1c, size_t,
|
||||
const video_color_metadata_t *, unsigned slots, unsigned max_input,
|
||||
const mediacodec_av1_import_t *, rav1_hw_ops_t *);
|
||||
int mediacodec_av1_decoder_finish(void *, bool discard);
|
||||
void mediacodec_av1_decoder_close(rav1_hw_ops_t *);
|
||||
bool mediacodec_av1_decoder_retire_poll(mediacodec_av1_decoder_t *);
|
||||
RETRO_END_DECLS
|
||||
#endif
|
||||
@@ -0,0 +1,105 @@
|
||||
/* Copyright (C) 2026 The RetroArch team. SPDX-License-Identifier:
|
||||
* GPL-3.0-or-later */
|
||||
#ifndef MEDIACODEC_AV1_REGISTRY_H
|
||||
#define MEDIACODEC_AV1_REGISTRY_H
|
||||
#include "mediacodec_av1_backend.h"
|
||||
#include <retro_inline.h>
|
||||
#include <limits.h>
|
||||
#include <string.h>
|
||||
#if defined(HAVE_THREADS) && defined(RETRO_ATOMIC_HAS_PTR) \
|
||||
&& defined(RETRO_ATOMIC_LOCK_FREE)
|
||||
#define MEDIACODEC_AV1_REGISTRY 1
|
||||
RETRO_BEGIN_DECLS
|
||||
|
||||
typedef struct mediacodec_av1_registry
|
||||
{
|
||||
mediacodec_av1_backend_t backend;
|
||||
rav1_hw_backend_t offer;
|
||||
void *owner;
|
||||
retro_atomic_int_t epoch;
|
||||
} mediacodec_av1_registry_t;
|
||||
|
||||
static INLINE bool mediacodec_av1_registry_open(void *opaque,
|
||||
const rav1_sequence_t *sequence, const rav1_obu_t *sequence_obu,
|
||||
const video_color_metadata_t *color, unsigned slots, rav1_hw_ops_t *ops)
|
||||
{
|
||||
mediacodec_av1_registry_t *r = (mediacodec_av1_registry_t *)opaque;
|
||||
int epoch;
|
||||
if (!ops)
|
||||
return false;
|
||||
memset(ops, 0, sizeof(*ops));
|
||||
epoch = retro_atomic_load_acquire_int(&r->epoch);
|
||||
if (epoch <= 0 || !(epoch & 1))
|
||||
return false;
|
||||
if (!r->backend.offer.open_config(
|
||||
r->backend.offer.user, sequence, sequence_obu, color, slots, ops))
|
||||
return false;
|
||||
if (retro_atomic_load_acquire_int(&r->epoch) != epoch)
|
||||
{
|
||||
r->backend.offer.close(ops);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/* Initialize once. Registry/configuration storage outlives all setup callers,
|
||||
* opened sessions and deferred retirement. No renderer object is captured. */
|
||||
static INLINE bool mediacodec_av1_registry_init(mediacodec_av1_registry_t *r,
|
||||
JNIEnv *(*environment)(void *), void *user, unsigned max_input,
|
||||
unsigned max_sessions)
|
||||
{
|
||||
if (!r)
|
||||
return false;
|
||||
memset(r, 0, sizeof(*r));
|
||||
if (!mediacodec_av1_backend_init(
|
||||
&r->backend, environment, user, max_input, max_sessions))
|
||||
return false;
|
||||
retro_atomic_int_init(&r->epoch, 0);
|
||||
r->offer.user = r;
|
||||
r->offer.open_config = mediacodec_av1_registry_open;
|
||||
r->offer.close = r->backend.offer.close;
|
||||
r->offer.drain = r->backend.offer.drain;
|
||||
r->offer.packet_decoder = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
/* Publish/withdraw share one driver lifecycle owner. Native renderer admission
|
||||
* must precede publication; an offer alone does not prove hardware support. */
|
||||
static INLINE bool mediacodec_av1_registry_publish(
|
||||
mediacodec_av1_registry_t *r, void *owner)
|
||||
{
|
||||
int epoch;
|
||||
if (!r || !r->offer.open_config || !owner)
|
||||
return false;
|
||||
epoch = retro_atomic_load_acquire_int(&r->epoch);
|
||||
if (epoch < 0)
|
||||
return false;
|
||||
if (epoch >= INT_MAX - 2)
|
||||
{
|
||||
retro_atomic_store_release_int(&r->epoch, -1);
|
||||
r->owner = NULL;
|
||||
rav1_unset_hw_backend(&r->offer);
|
||||
return false;
|
||||
}
|
||||
r->owner = owner;
|
||||
retro_atomic_store_release_int(&r->epoch, epoch + (epoch & 1 ? 2 : 1));
|
||||
rav1_set_hw_backend(&r->offer);
|
||||
return true;
|
||||
}
|
||||
|
||||
static INLINE bool mediacodec_av1_registry_withdraw(
|
||||
mediacodec_av1_registry_t *r, void *owner)
|
||||
{
|
||||
int epoch;
|
||||
if (!r || !r->offer.open_config || !owner || r->owner != owner)
|
||||
return false;
|
||||
epoch = retro_atomic_load_acquire_int(&r->epoch);
|
||||
retro_atomic_store_release_int(&r->epoch, epoch == INT_MAX ? -1 : epoch + 1);
|
||||
r->owner = NULL;
|
||||
rav1_unset_hw_backend(&r->offer);
|
||||
return true;
|
||||
}
|
||||
|
||||
RETRO_END_DECLS
|
||||
#endif
|
||||
#endif
|
||||
@@ -0,0 +1,134 @@
|
||||
/* Copyright (C) 2026 The RetroArch team. SPDX-License-Identifier: GPL-3.0-or-later */
|
||||
#include "mediacodec_hevc_backend.h"
|
||||
#include "mediacodec_frame_export.h"
|
||||
#include <formats/image.h>
|
||||
#include <rthreads/retro_eventcount.h>
|
||||
#include <pthread.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
typedef struct mediacodec_hevc_backend_context {
|
||||
mediacodec_hevc_backend_t *backend;
|
||||
mediacodec_frame_exporter_t *exporter;
|
||||
retro_eventcount_t wake;
|
||||
retro_atomic_int_t state,refs;
|
||||
mediacodec_hevc_decoder_t *retired;
|
||||
} mediacodec_hevc_backend_context_t;
|
||||
static void mediacodec_hevc_backend_context_free(mediacodec_hevc_backend_context_t *c)
|
||||
{
|
||||
mediacodec_hevc_backend_t *backend=c->backend;
|
||||
if(c->exporter)mediacodec_frame_exporter_free(c->exporter);
|
||||
retro_eventcount_free(&c->wake);
|
||||
free(c);
|
||||
retro_atomic_fetch_sub_int(&backend->active,1);
|
||||
}
|
||||
static void mediacodec_hevc_backend_context_unref(mediacodec_hevc_backend_context_t *c)
|
||||
{
|
||||
if(retro_atomic_fetch_sub_int(&c->refs,1)==1)mediacodec_hevc_backend_context_free(c);
|
||||
}
|
||||
static void mediacodec_hevc_backend_publish(mediacodec_hevc_backend_context_t *c,int state)
|
||||
{
|
||||
retro_atomic_store_release_int(&c->state,state);
|
||||
retro_eventcount_notify(&c->wake);
|
||||
/* The publisher keeps its reference until notification has returned. */
|
||||
mediacodec_hevc_backend_context_unref(c);
|
||||
}
|
||||
static bool mediacodec_hevc_backend_release(void *opaque)
|
||||
{
|
||||
mediacodec_hevc_backend_context_t *c=(mediacodec_hevc_backend_context_t*)opaque;
|
||||
if(c->exporter && !mediacodec_frame_exporter_free(c->exporter))return false;
|
||||
c->exporter=NULL;return true;
|
||||
}
|
||||
static int mediacodec_hevc_backend_export(void *opaque,const android_image_view_t *image,
|
||||
const mediacodec_output_format_t *format,const rh265_video_color_t *color,video_hw_frame_t *frame)
|
||||
{
|
||||
mediacodec_hevc_backend_context_t *c=(mediacodec_hevc_backend_context_t*)opaque;
|
||||
return mediacodec_hevc_frame_export(c->exporter,image,format,color,frame);
|
||||
}
|
||||
static void mediacodec_hevc_backend_enqueue(void *opaque,mediacodec_hevc_decoder_t *decoder)
|
||||
{
|
||||
mediacodec_hevc_backend_context_t *c=(mediacodec_hevc_backend_context_t*)opaque;
|
||||
c->retired=decoder;mediacodec_hevc_backend_publish(c,1);
|
||||
}
|
||||
static void *mediacodec_hevc_backend_retire(void *opaque)
|
||||
{
|
||||
mediacodec_hevc_backend_context_t *c=(mediacodec_hevc_backend_context_t*)opaque;
|
||||
mediacodec_hevc_decoder_t *decoder;
|
||||
int key;
|
||||
while(!retro_atomic_load_acquire_int(&c->state)) {
|
||||
key=retro_eventcount_prepare_wait(&c->wake);
|
||||
if(retro_atomic_load_acquire_int(&c->state)) {
|
||||
retro_eventcount_cancel_wait(&c->wake);break;
|
||||
}
|
||||
retro_eventcount_commit_wait(&c->wake,key);
|
||||
}
|
||||
decoder=c->retired;
|
||||
if(decoder)while(!mediacodec_hevc_decoder_retire_poll(decoder)) {
|
||||
key=retro_eventcount_prepare_wait(&c->wake);
|
||||
retro_eventcount_commit_wait_timeout(&c->wake,key,10000);
|
||||
}
|
||||
mediacodec_hevc_backend_context_unref(c);
|
||||
return NULL;
|
||||
}
|
||||
static bool mediacodec_hevc_backend_open(void *opaque,const rh265_video *source,
|
||||
const rh265_video_params_t *params,unsigned slots,rh265_hw_ops_t *ops)
|
||||
{
|
||||
mediacodec_hevc_backend_t *backend=(mediacodec_hevc_backend_t*)opaque;
|
||||
mediacodec_hevc_backend_context_t *c;
|
||||
mediacodec_hevc_import_t importer;
|
||||
pthread_t thread;
|
||||
pthread_attr_t attr;
|
||||
JNIEnv *env;
|
||||
int active;
|
||||
if(!ops)return false;
|
||||
memset(ops,0,sizeof(*ops));
|
||||
if(!backend || !backend->environment || !source || !params || !slots || slots>32
|
||||
|| params->chroma_format_idc!=1 || (params->bit_depth!=8 && params->bit_depth!=10))return false;
|
||||
for(;;) {
|
||||
active=retro_atomic_load_acquire_int(&backend->active);
|
||||
if(active<0 || (unsigned)active>=backend->max_sessions)return false;
|
||||
if(retro_atomic_cas_int(&backend->active,active,active+1))break;
|
||||
}
|
||||
env=backend->environment(backend->user);
|
||||
if(!env){retro_atomic_fetch_sub_int(&backend->active,1);return false;}
|
||||
c=(mediacodec_hevc_backend_context_t*)calloc(1,sizeof(*c));
|
||||
if(!c){retro_atomic_fetch_sub_int(&backend->active,1);return false;}
|
||||
c->backend=backend;
|
||||
if(!retro_eventcount_init(&c->wake)) {
|
||||
free(c);retro_atomic_fetch_sub_int(&backend->active,1);return false;
|
||||
}
|
||||
retro_atomic_int_init(&c->state,0);retro_atomic_int_init(&c->refs,2);
|
||||
c->exporter=mediacodec_frame_exporter_new(slots,params->bit_depth==10?IMAGE_PIXFMT_P010:IMAGE_PIXFMT_NV12);
|
||||
if(!c->exporter){mediacodec_hevc_backend_context_free(c);return false;}
|
||||
if(pthread_attr_init(&attr)){mediacodec_hevc_backend_context_free(c);return false;}
|
||||
if(pthread_attr_setdetachstate(&attr,PTHREAD_CREATE_DETACHED)) {
|
||||
pthread_attr_destroy(&attr);mediacodec_hevc_backend_context_free(c);return false;
|
||||
}
|
||||
active=pthread_create(&thread,&attr,mediacodec_hevc_backend_retire,c);
|
||||
pthread_attr_destroy(&attr);
|
||||
if(active){mediacodec_hevc_backend_context_free(c);return false;}
|
||||
memset(&importer,0,sizeof(importer));importer.context=c;
|
||||
importer.export_image=mediacodec_hevc_backend_export;importer.release=mediacodec_hevc_backend_release;
|
||||
importer.enqueue=mediacodec_hevc_backend_enqueue;
|
||||
if(!mediacodec_hevc_decoder_open(env,source,params,slots,backend->max_input,&importer,ops)) {
|
||||
mediacodec_hevc_backend_publish(c,2);return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
bool mediacodec_hevc_backend_init(mediacodec_hevc_backend_t *backend,
|
||||
JNIEnv *(*environment)(void*),void *user,unsigned max_input,unsigned max_sessions)
|
||||
{
|
||||
#if !defined(HAVE_THREADS) || !defined(RETRO_ATOMIC_LOCK_FREE)
|
||||
return false;
|
||||
#endif
|
||||
if(!backend || !environment || !max_input || max_input>64u*1024u*1024u
|
||||
|| !max_sessions || max_sessions>8)return false;
|
||||
memset(backend,0,sizeof(*backend));backend->user=user;backend->environment=environment;
|
||||
backend->max_input=max_input;backend->max_sessions=max_sessions;
|
||||
retro_atomic_int_init(&backend->active,0);
|
||||
backend->offer.user=backend;backend->offer.open=mediacodec_hevc_backend_open;
|
||||
backend->offer.close=mediacodec_hevc_decoder_close;return true;
|
||||
}
|
||||
unsigned mediacodec_hevc_backend_active(mediacodec_hevc_backend_t *backend)
|
||||
{
|
||||
return backend?(unsigned)retro_atomic_load_acquire_int(&backend->active):0;
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
/* Copyright (C) 2026 The RetroArch team. SPDX-License-Identifier: GPL-3.0-or-later */
|
||||
#ifndef MEDIACODEC_HEVC_BACKEND_H
|
||||
#define MEDIACODEC_HEVC_BACKEND_H
|
||||
#include "mediacodec_hevc_decoder.h"
|
||||
#include <retro_atomic.h>
|
||||
RETRO_BEGIN_DECLS
|
||||
typedef struct mediacodec_hevc_backend {
|
||||
rh265_hw_backend_t offer;
|
||||
void *user;
|
||||
JNIEnv *(*environment)(void *user);
|
||||
unsigned max_input,max_sessions;
|
||||
retro_atomic_int_t active;
|
||||
} mediacodec_hevc_backend_t;
|
||||
/* Cold setup only; does not register or advertise hardware support. Caller
|
||||
* admits a validated renderer before publishing offer. environment returns the
|
||||
* calling decoder-worker's JNIEnv (e.g. rjni_env), never a saved foreign env.
|
||||
* Storage/user/callback outlive all admitted decoders and retired contexts.
|
||||
* max_input is the explicit AU staging bound, <=64 MiB. At most 1..8 sessions
|
||||
* including retirement may coexist; seek storms refuse excess opens without
|
||||
* waiting. Each admitted session has one cold retirement worker, created before
|
||||
* native startup; decode/output/export add no thread, wait or lock. Failed
|
||||
* startup and close return without joining; active includes deferred cleanup. */
|
||||
bool mediacodec_hevc_backend_init(mediacodec_hevc_backend_t *backend,
|
||||
JNIEnv *(*environment)(void*),void *user,unsigned max_input,unsigned max_sessions);
|
||||
unsigned mediacodec_hevc_backend_active(mediacodec_hevc_backend_t *backend);
|
||||
RETRO_END_DECLS
|
||||
#endif
|
||||
@@ -0,0 +1,207 @@
|
||||
/* Copyright (C) 2026 The RetroArch team. SPDX-License-Identifier: GPL-3.0-or-later */
|
||||
#include "mediacodec_hevc_decoder.h"
|
||||
#include "mediacodec_hevc_packet.h"
|
||||
#include "android_image_ndk.h"
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
struct mediacodec_hevc_decoder {
|
||||
mediacodec_session_t *session;
|
||||
android_image_queue_t *images;
|
||||
mediacodec_hevc_import_t importer;
|
||||
rh265_video_params_t params;
|
||||
rh265_video_color_t color;
|
||||
mediacodec_output_format_t format;
|
||||
android_image_view_t image[32];
|
||||
uint64_t tag[32],last_tag;
|
||||
uint8_t *packet;
|
||||
unsigned slots,capacity;
|
||||
uint32_t rendered;
|
||||
int failed,eos,output_eos,closed;
|
||||
};
|
||||
static bool mediacodec_hevc_decoder_discard(android_image_view_t *v)
|
||||
{
|
||||
if(!v->ticket)return true;
|
||||
if(!android_image_queue_return(v->ticket,v->generation,-1))return false;
|
||||
memset(v,0,sizeof(*v));return true;
|
||||
}
|
||||
/* Exactly one codec output action and one reader acquire per invocation. */
|
||||
static int mediacodec_hevc_decoder_service(mediacodec_hevc_decoder_t *d)
|
||||
{
|
||||
android_image_view_t image;
|
||||
const mediacodec_output_format_t *format;
|
||||
int64_t pts;
|
||||
uint64_t tag;
|
||||
unsigned i;
|
||||
int status;
|
||||
if(d->failed)return -1;
|
||||
android_image_queue_poll(d->images);
|
||||
status=mediacodec_session_pump(d->session,&pts);
|
||||
if(status<0)goto fail;
|
||||
if(status==1) {
|
||||
if(pts<=0 || (uint64_t)pts>d->last_tag)goto fail;
|
||||
for(i=0;i<d->slots;i++)if(d->tag[i]==(uint64_t)pts) {
|
||||
d->rendered|=(uint32_t)1<<i;break;
|
||||
}
|
||||
}
|
||||
if(status==3) {
|
||||
if(!d->eos)goto fail;
|
||||
d->output_eos=1;
|
||||
}
|
||||
if(status==2) {
|
||||
format=mediacodec_session_format(d->session);
|
||||
if(!format || format->width!=(unsigned)d->params.width
|
||||
|| format->height!=(unsigned)d->params.height
|
||||
|| format->left!=(unsigned)d->params.crop_left
|
||||
|| format->top!=(unsigned)d->params.crop_top
|
||||
|| format->right!=(unsigned)(d->params.width-d->params.crop_right)
|
||||
|| format->bottom!=(unsigned)(d->params.height-d->params.crop_bottom))goto fail;
|
||||
d->format=*format;
|
||||
}
|
||||
status=android_image_queue_acquire(d->images,&image);
|
||||
if(status<0)goto fail;
|
||||
if(!status)return 0;
|
||||
tag=(uint64_t)image.timestamp_ns/1000;
|
||||
if(!tag || image.timestamp_ns%1000 || tag>d->last_tag) {
|
||||
mediacodec_hevc_decoder_discard(&image);goto fail;
|
||||
}
|
||||
for(i=0;i<d->slots;i++)if(d->tag[i]==tag)break;
|
||||
/* A discarded parser output may arrive after its reference slot was reused. */
|
||||
if(i==d->slots) {
|
||||
if(!mediacodec_hevc_decoder_discard(&image))goto fail;
|
||||
return 0;
|
||||
}
|
||||
if(d->image[i].ticket || !d->format.serial) {
|
||||
mediacodec_hevc_decoder_discard(&image);goto fail;
|
||||
}
|
||||
d->image[i]=image;return 0;
|
||||
fail:
|
||||
d->failed=1;return -1;
|
||||
}
|
||||
static int mediacodec_hevc_decoder_decode(void *opaque,const rh265_video_params_t *params,
|
||||
const rh265_access_unit_t *au,const rh265_nal_t *slices,
|
||||
const rh265_hw_picture_t *picture)
|
||||
{
|
||||
mediacodec_hevc_decoder_t *d=(mediacodec_hevc_decoder_t*)opaque;
|
||||
size_t bytes;
|
||||
unsigned i;
|
||||
int status;
|
||||
if(!d || !params || !au || !slices || !picture || d->closed || d->eos
|
||||
|| picture->slot>=d->slots || !picture->serial
|
||||
|| picture->serial>((~(uint64_t)0)>>1)/1000
|
||||
|| memcmp(params,&d->params,sizeof(*params)))return -1;
|
||||
if(mediacodec_hevc_decoder_service(d)<0)return -1;
|
||||
if(mediacodec_hevc_packet(au,slices,au->num_slices,d->packet,d->capacity,&bytes)!=1)
|
||||
return -1;
|
||||
status=mediacodec_session_submit(d->session,d->packet,bytes,(int64_t)picture->serial,false);
|
||||
if(status<0){d->failed=1;return -1;}
|
||||
if(!status)return 1;
|
||||
/* Codec-owned references do not need acquired Surface images. Drop prior
|
||||
* display outputs canceled by an IRAP/DPB transaction, including late arrivals. */
|
||||
for(i=0;i<d->slots;i++)if(!(picture->keep_outputs & ((uint32_t)1<<i))) {
|
||||
if(!mediacodec_hevc_decoder_discard(&d->image[i])){d->failed=1;return -1;}
|
||||
d->tag[i]=0;
|
||||
}
|
||||
if(!mediacodec_hevc_decoder_discard(&d->image[picture->slot])){d->failed=1;return -1;}
|
||||
d->rendered&=picture->keep_outputs & ~((uint32_t)1<<picture->slot);
|
||||
d->tag[picture->slot]=picture->output?picture->serial:0;
|
||||
d->last_tag=picture->serial;
|
||||
return 0;
|
||||
}
|
||||
static int mediacodec_hevc_decoder_output(void *opaque,unsigned slot,video_hw_frame_t *out)
|
||||
{
|
||||
mediacodec_hevc_decoder_t *d=(mediacodec_hevc_decoder_t*)opaque;
|
||||
int status;
|
||||
if(!d || !out || slot>=d->slots || d->closed)return -1;
|
||||
if(mediacodec_hevc_decoder_service(d)<0)return -1;
|
||||
if(!d->image[slot].ticket) {
|
||||
if(d->output_eos && d->tag[slot] && !(d->rendered & ((uint32_t)1<<slot))) {
|
||||
d->failed=1;return -1;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
status=d->importer.export_image(d->importer.context,&d->image[slot],&d->format,&d->color,out);
|
||||
if(!status) {
|
||||
memset(&d->image[slot],0,sizeof(d->image[slot]));d->tag[slot]=0;
|
||||
d->rendered&=~((uint32_t)1<<slot);
|
||||
}
|
||||
if(status<0)d->failed=1;
|
||||
return status;
|
||||
}
|
||||
static int mediacodec_hevc_decoder_drain(void *opaque)
|
||||
{
|
||||
mediacodec_hevc_decoder_t *d=(mediacodec_hevc_decoder_t*)opaque;
|
||||
int status;
|
||||
if(!d || d->closed || mediacodec_hevc_decoder_service(d)<0)return -1;
|
||||
if(d->eos)return 0;
|
||||
status=mediacodec_session_submit(d->session,NULL,0,0,true);
|
||||
if(status<0){d->failed=1;return -1;}
|
||||
if(!status)return 1;
|
||||
d->eos=1;return 0;
|
||||
}
|
||||
bool mediacodec_hevc_decoder_retire_poll(mediacodec_hevc_decoder_t *d)
|
||||
{
|
||||
unsigned i;
|
||||
if(!d)return true;
|
||||
if(!d->closed)return false;
|
||||
for(i=0;i<d->slots;i++)if(!mediacodec_hevc_decoder_discard(&d->image[i]))return false;
|
||||
if(d->images) {
|
||||
android_image_queue_poll(d->images);
|
||||
if(!android_image_queue_idle(d->images))return false;
|
||||
if(d->session){mediacodec_session_free(d->session);d->session=NULL;}
|
||||
if(!android_image_queue_free(d->images))return false;
|
||||
d->images=NULL;
|
||||
}
|
||||
/* Ticket return is published before the renderer finishes clearing its
|
||||
* frame-pool slot. Image idleness alone does not prove context retirement. */
|
||||
if(!d->importer.release(d->importer.context))return false;
|
||||
free(d->packet);free(d);return true;
|
||||
}
|
||||
void mediacodec_hevc_decoder_close(rh265_hw_ops_t *ops)
|
||||
{
|
||||
mediacodec_hevc_decoder_t *d;
|
||||
if(!ops || !(d=(mediacodec_hevc_decoder_t*)ops->decoder))return;
|
||||
memset(ops,0,sizeof(*ops));d->closed=1;
|
||||
android_image_queue_close(d->images);
|
||||
d->importer.enqueue(d->importer.context,d);
|
||||
}
|
||||
bool mediacodec_hevc_decoder_open(JNIEnv *env,const rh265_video *source,
|
||||
const rh265_video_params_t *params,unsigned slots,unsigned max_input,
|
||||
const mediacodec_hevc_import_t *importer,rh265_hw_ops_t *ops)
|
||||
{
|
||||
mediacodec_hevc_decoder_t *d;
|
||||
mediacodec_config_t config;
|
||||
rh265_nal_t sets[3];
|
||||
uint8_t *csd;
|
||||
size_t bytes;
|
||||
void *window=NULL;
|
||||
if(!ops)return false;
|
||||
memset(ops,0,sizeof(*ops));
|
||||
if(!env || !source || !params || !slots || slots>32 || !max_input
|
||||
|| max_input>64u*1024u*1024u || params->width<=0 || params->height<=0
|
||||
|| params->chroma_format_idc!=1 || (params->bit_depth!=8 && params->bit_depth!=10)
|
||||
|| params->pps_id<0 || !importer || !importer->export_image
|
||||
|| !importer->enqueue || !importer->release)return false;
|
||||
if(rh265_video_get_parameter_sets(source,(unsigned)params->pps_id,sets)
|
||||
|| mediacodec_hevc_csd(sets,NULL,0,&bytes)!=0 || !bytes)return false;
|
||||
csd=(uint8_t*)malloc(bytes);if(!csd)return false;
|
||||
if(mediacodec_hevc_csd(sets,csd,bytes,&bytes)!=1){free(csd);return false;}
|
||||
d=(mediacodec_hevc_decoder_t*)calloc(1,sizeof(*d));
|
||||
if(!d){free(csd);return false;}
|
||||
d->params=*params;d->slots=slots;d->capacity=max_input;
|
||||
d->packet=(uint8_t*)malloc(max_input);
|
||||
if(!d->packet || rh265_video_get_color(source,(unsigned)params->pps_id,&d->color))goto fail;
|
||||
d->images=android_image_ndk_new((unsigned)params->width,(unsigned)params->height,slots,&window);
|
||||
if(!d->images)goto fail;
|
||||
memset(&config,0,sizeof(config));config.mime="video/hevc";
|
||||
config.width=(unsigned)params->width;config.height=(unsigned)params->height;
|
||||
config.max_input=max_input;config.profile=params->bit_depth==10?2:1;
|
||||
config.csd[0]=csd;config.csd_size[0]=bytes;
|
||||
d->session=mediacodec_session_new(env,&config,window);
|
||||
if(!d->session)goto fail;
|
||||
d->importer=*importer;free(csd);
|
||||
ops->decoder=d;ops->decode=mediacodec_hevc_decoder_decode;ops->output=mediacodec_hevc_decoder_output;ops->drain=mediacodec_hevc_decoder_drain;
|
||||
return true;
|
||||
fail:
|
||||
if(d->images){android_image_queue_close(d->images);android_image_queue_free(d->images);}
|
||||
free(d->packet);free(d);free(csd);return false;
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
/* Copyright (C) 2026 The RetroArch team. SPDX-License-Identifier: GPL-3.0-or-later */
|
||||
#ifndef MEDIACODEC_HEVC_DECODER_H
|
||||
#define MEDIACODEC_HEVC_DECODER_H
|
||||
#include <formats/rh265_hw.h>
|
||||
#include "mediacodec_session.h"
|
||||
#include "android_image_queue.h"
|
||||
#include <retro_common_api.h>
|
||||
RETRO_BEGIN_DECLS
|
||||
typedef struct mediacodec_hevc_decoder mediacodec_hevc_decoder_t;
|
||||
typedef struct mediacodec_hevc_import {
|
||||
void *context;
|
||||
/* Worker callback: wrap an image lease without using a graphics context.
|
||||
* 0 consumes the ticket and produces a frame; 1 BUSY/-1 failure take nothing.
|
||||
* The frame and its abandonment callback must retain their own context. */
|
||||
int (*export_image)(void *context,const android_image_view_t *image,
|
||||
const mediacodec_output_format_t *format,const rh265_video_color_t *color,
|
||||
video_hw_frame_t *frame);
|
||||
/* Cold nonblocking retirement: false keeps context for a later poll. */
|
||||
bool (*release)(void *context);
|
||||
/* Guaranteed non-inline enqueue of a cold retirement job. The executor calls
|
||||
* retire_poll later until true, without spinning or waiting on a frame path. */
|
||||
void (*enqueue)(void *context,mediacodec_hevc_decoder_t *decoder);
|
||||
} mediacodec_hevc_import_t;
|
||||
/* Cold decoder-worker factory. Takes import context only on success. No
|
||||
* renderer/backend registration occurs here. max_input provisions fixed staging. */
|
||||
bool mediacodec_hevc_decoder_open(JNIEnv *env,const rh265_video *source,
|
||||
const rh265_video_params_t *params,unsigned slots,unsigned max_input,
|
||||
const mediacodec_hevc_import_t *importer,rh265_hw_ops_t *ops);
|
||||
void mediacodec_hevc_decoder_close(rh265_hw_ops_t *ops);
|
||||
/* Cold retirement executor only. True consumes decoder. */
|
||||
bool mediacodec_hevc_decoder_retire_poll(mediacodec_hevc_decoder_t *decoder);
|
||||
RETRO_END_DECLS
|
||||
#endif
|
||||
@@ -0,0 +1,52 @@
|
||||
/* Copyright (C) 2026 The RetroArch team. SPDX-License-Identifier: GPL-3.0-or-later */
|
||||
#include "mediacodec_hevc_packet.h"
|
||||
#include <string.h>
|
||||
#define MEDIACODEC_PACKET_MAX (64u*1024u*1024u)
|
||||
static int mediacodec_hevc_packet_overlap(const void *a,size_t na,const void *b,size_t nb)
|
||||
{
|
||||
uintptr_t x=(uintptr_t)a,y=(uintptr_t)b;
|
||||
if(na>(uintptr_t)-1-x || nb>(uintptr_t)-1-y)return 1;
|
||||
return x<y+nb && y<x+na;
|
||||
}
|
||||
static int mediacodec_hevc_packet_pack(const rh265_nal_t *nals,unsigned count,int parameter_sets,
|
||||
int type,int temporal,size_t coded,uint8_t *out,size_t capacity,size_t *needed)
|
||||
{
|
||||
size_t total=0,bytes=0,offset=0;unsigned i;
|
||||
if(!needed)return -1;
|
||||
*needed=0;
|
||||
if(!nals || !count || (!out && capacity))return -1;
|
||||
for(i=0;i<count;i++) {
|
||||
const rh265_nal_t *nal=&nals[i];unsigned nal_type;
|
||||
if(!nal->data || nal->size<3 || nal->size>MEDIACODEC_PACKET_MAX-4
|
||||
|| (parameter_sets && nal->size>65536))return -1;
|
||||
nal_type=(nal->data[0]>>1)&63;
|
||||
if((nal->data[0]&0x81) || (nal->data[1]>>3) || !(nal->data[1]&7))return -1;
|
||||
if(parameter_sets) {
|
||||
if(nal_type!=32+i || (nal->data[1]&7)!=1)return -1;
|
||||
} else if(!(nal_type<=9 || (nal_type>=16 && nal_type<=21))
|
||||
|| (int)nal_type!=type || (int)(nal->data[1]&7)-1!=temporal)return -1;
|
||||
if(total>MEDIACODEC_PACKET_MAX-4-nal->size)return -1;
|
||||
total+=4+nal->size;bytes+=nal->size;
|
||||
}
|
||||
if(!parameter_sets && bytes!=coded)return -1;
|
||||
if(!out || capacity<total){*needed=total;return 0;}
|
||||
/* Each accepted NAL contributes at least seven bytes to the 64 MiB
|
||||
* bound, so descriptor byte multiplication also fits 32-bit size_t. */
|
||||
if(mediacodec_hevc_packet_overlap(out,total,nals,(size_t)count*sizeof(*nals)))return -1;
|
||||
for(i=0;i<count;i++)if(mediacodec_hevc_packet_overlap(out,total,nals[i].data,nals[i].size))return -1;
|
||||
for(i=0;i<count;i++) {
|
||||
out[offset++]=0;out[offset++]=0;out[offset++]=0;out[offset++]=1;
|
||||
memcpy(out+offset,nals[i].data,nals[i].size);offset+=nals[i].size;
|
||||
}
|
||||
*needed=total;return 1;
|
||||
}
|
||||
int mediacodec_hevc_csd(const rh265_nal_t sets[3],uint8_t *output,size_t capacity,size_t *needed)
|
||||
{return mediacodec_hevc_packet_pack(sets,3,1,0,0,0,output,capacity,needed);}
|
||||
int mediacodec_hevc_packet(const rh265_access_unit_t *au,const rh265_nal_t *slices,
|
||||
unsigned count,uint8_t *output,size_t capacity,size_t *needed)
|
||||
{
|
||||
if(needed)*needed=0;
|
||||
if(!au || !au->num_slices || au->num_slices>count)return -1;
|
||||
return mediacodec_hevc_packet_pack(slices,au->num_slices,0,au->first.nal_type,au->first.temporal_id,
|
||||
au->coded_bytes,output,capacity,needed);
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
/* Copyright (C) 2026 The RetroArch team. SPDX-License-Identifier: GPL-3.0-or-later */
|
||||
#ifndef MEDIACODEC_HEVC_PACKET_H
|
||||
#define MEDIACODEC_HEVC_PACKET_H
|
||||
#include <formats/rh265.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <retro_common_api.h>
|
||||
RETRO_BEGIN_DECLS
|
||||
/* Android HEVC csd-0 is VPS/SPS/PPS with Annex-B start codes. These functions
|
||||
* consume validated, escaped NALs borrowed from the existing rh265 parser;
|
||||
* they do not replace syntax validation or establish hardware eligibility.
|
||||
* 1 copied; 0 valid but storage needed; -1 invalid/overlapping input.
|
||||
* needed receives exact bytes for 0/1, zero for -1. NULL/0 measures only.
|
||||
* Payload output stays untouched on failure/BUSY. needed is independent
|
||||
* caller storage, never aliased with input metadata or payload output.
|
||||
* No allocation; a caller may provision/reuse bounded staging off frame paths. */
|
||||
int mediacodec_hevc_csd(const rh265_nal_t sets[3],uint8_t *output,size_t capacity,size_t *needed);
|
||||
int mediacodec_hevc_packet(const rh265_access_unit_t *au,const rh265_nal_t *slices,
|
||||
unsigned count,uint8_t *output,size_t capacity,size_t *needed);
|
||||
RETRO_END_DECLS
|
||||
#endif
|
||||
@@ -0,0 +1,70 @@
|
||||
/* Copyright (C) 2026 The RetroArch team. SPDX-License-Identifier: GPL-3.0-or-later */
|
||||
#ifndef MEDIACODEC_HEVC_REGISTRY_H
|
||||
#define MEDIACODEC_HEVC_REGISTRY_H
|
||||
#include "mediacodec_hevc_backend.h"
|
||||
#include <retro_inline.h>
|
||||
#include <limits.h>
|
||||
#include <string.h>
|
||||
#if defined(HAVE_THREADS) && defined(RETRO_ATOMIC_HAS_PTR) && defined(RETRO_ATOMIC_LOCK_FREE)
|
||||
#define MEDIACODEC_HEVC_REGISTRY 1
|
||||
RETRO_BEGIN_DECLS
|
||||
typedef struct mediacodec_hevc_registry {
|
||||
mediacodec_hevc_backend_t backend;
|
||||
rh265_hw_backend_t offer;
|
||||
void *owner;
|
||||
retro_atomic_int_t epoch;
|
||||
} mediacodec_hevc_registry_t;
|
||||
static INLINE bool mediacodec_hevc_registry_open(void *opaque,const rh265_video *source,
|
||||
const rh265_video_params_t *params,unsigned slots,rh265_hw_ops_t *ops)
|
||||
{
|
||||
mediacodec_hevc_registry_t *r=(mediacodec_hevc_registry_t*)opaque;
|
||||
int epoch;
|
||||
if(!ops)return false;
|
||||
memset(ops,0,sizeof(*ops));
|
||||
epoch=retro_atomic_load_acquire_int(&r->epoch);
|
||||
if(epoch<=0 || !(epoch&1))return false;
|
||||
if(!r->backend.offer.open(r->backend.offer.user,source,params,slots,ops))return false;
|
||||
if(retro_atomic_load_acquire_int(&r->epoch)!=epoch) {
|
||||
r->backend.offer.close(ops);return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
/* Initialize once. Registry/configuration storage outlives all setup callers,
|
||||
* opened sessions and deferred retirement. No renderer object is captured. */
|
||||
static INLINE bool mediacodec_hevc_registry_init(mediacodec_hevc_registry_t *r,
|
||||
JNIEnv *(*environment)(void*),void *user,unsigned max_input,unsigned max_sessions)
|
||||
{
|
||||
if(!r)return false;
|
||||
memset(r,0,sizeof(*r));
|
||||
if(!mediacodec_hevc_backend_init(&r->backend,environment,user,max_input,max_sessions))return false;
|
||||
retro_atomic_int_init(&r->epoch,0);
|
||||
r->offer.user=r;r->offer.open=mediacodec_hevc_registry_open;
|
||||
r->offer.close=r->backend.offer.close;return true;
|
||||
}
|
||||
/* Publish/withdraw share one driver lifecycle owner. Native renderer admission
|
||||
* must precede publication; an offer alone does not prove hardware support. */
|
||||
static INLINE bool mediacodec_hevc_registry_publish(mediacodec_hevc_registry_t *r,void *owner)
|
||||
{
|
||||
int epoch;
|
||||
if(!r || !r->offer.open || !owner)return false;
|
||||
epoch=retro_atomic_load_acquire_int(&r->epoch);
|
||||
if(epoch<0)return false;
|
||||
if(epoch>=INT_MAX-2) {
|
||||
retro_atomic_store_release_int(&r->epoch,-1);r->owner=NULL;
|
||||
rh265_unset_hw_backend(&r->offer);return false;
|
||||
}
|
||||
r->owner=owner;
|
||||
retro_atomic_store_release_int(&r->epoch,epoch+(epoch&1?2:1));
|
||||
rh265_set_hw_backend(&r->offer);return true;
|
||||
}
|
||||
static INLINE bool mediacodec_hevc_registry_withdraw(mediacodec_hevc_registry_t *r,void *owner)
|
||||
{
|
||||
int epoch;
|
||||
if(!r || !r->offer.open || !owner || r->owner!=owner)return false;
|
||||
epoch=retro_atomic_load_acquire_int(&r->epoch);
|
||||
retro_atomic_store_release_int(&r->epoch,epoch==INT_MAX?-1:epoch+1);
|
||||
r->owner=NULL;rh265_unset_hw_backend(&r->offer);return true;
|
||||
}
|
||||
RETRO_END_DECLS
|
||||
#endif
|
||||
#endif
|
||||
@@ -0,0 +1,218 @@
|
||||
/* Copyright (C) 2026 The RetroArch team. SPDX-License-Identifier:
|
||||
* GPL-3.0-or-later */
|
||||
#include "mediacodec_vp9_backend.h"
|
||||
#include "mediacodec_frame_export.h"
|
||||
#include <formats/image.h>
|
||||
#include <rthreads/retro_eventcount.h>
|
||||
#include <pthread.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
typedef struct mediacodec_vp9_backend_context
|
||||
{
|
||||
mediacodec_vp9_backend_t *backend;
|
||||
mediacodec_frame_exporter_t *exporter;
|
||||
mediacodec_vp9_decoder_t *retired;
|
||||
retro_eventcount_t wake;
|
||||
retro_atomic_int_t state, refs;
|
||||
} mediacodec_vp9_backend_context_t;
|
||||
|
||||
static void mediacodec_vp9_backend_context_free(
|
||||
mediacodec_vp9_backend_context_t *c)
|
||||
{
|
||||
mediacodec_vp9_backend_t *backend = c->backend;
|
||||
if (c->exporter)
|
||||
mediacodec_frame_exporter_free(c->exporter);
|
||||
retro_eventcount_free(&c->wake);
|
||||
free(c);
|
||||
retro_atomic_fetch_sub_int(&backend->active, 1);
|
||||
}
|
||||
|
||||
static void mediacodec_vp9_backend_context_unref(
|
||||
mediacodec_vp9_backend_context_t *c)
|
||||
{
|
||||
if (retro_atomic_fetch_sub_int(&c->refs, 1) == 1)
|
||||
mediacodec_vp9_backend_context_free(c);
|
||||
}
|
||||
|
||||
static void mediacodec_vp9_backend_publish(
|
||||
mediacodec_vp9_backend_context_t *c, int state)
|
||||
{
|
||||
retro_atomic_store_release_int(&c->state, state);
|
||||
retro_eventcount_notify(&c->wake);
|
||||
/* The publisher keeps its reference until notification has returned. */
|
||||
mediacodec_vp9_backend_context_unref(c);
|
||||
}
|
||||
|
||||
static bool mediacodec_vp9_backend_release(void *opaque)
|
||||
{
|
||||
mediacodec_vp9_backend_context_t *c =
|
||||
(mediacodec_vp9_backend_context_t *)opaque;
|
||||
if (c->exporter && !mediacodec_frame_exporter_free(c->exporter))
|
||||
return false;
|
||||
c->exporter = NULL;
|
||||
return true;
|
||||
}
|
||||
|
||||
static int mediacodec_vp9_backend_export(void *opaque,
|
||||
const android_image_view_t *image,
|
||||
const mediacodec_output_format_t *format, const rvp9_hdr *header,
|
||||
const video_color_metadata_t *color, video_hw_frame_t *frame)
|
||||
{
|
||||
mediacodec_vp9_backend_context_t *c =
|
||||
(mediacodec_vp9_backend_context_t *)opaque;
|
||||
return mediacodec_vp9_frame_export(
|
||||
c->exporter, image, format, header, color, frame);
|
||||
}
|
||||
|
||||
static void mediacodec_vp9_backend_enqueue(
|
||||
void *opaque, mediacodec_vp9_decoder_t *decoder)
|
||||
{
|
||||
mediacodec_vp9_backend_context_t *c =
|
||||
(mediacodec_vp9_backend_context_t *)opaque;
|
||||
c->retired = decoder;
|
||||
mediacodec_vp9_backend_publish(c, 1);
|
||||
}
|
||||
|
||||
static void *mediacodec_vp9_backend_retire(void *opaque)
|
||||
{
|
||||
mediacodec_vp9_backend_context_t *c =
|
||||
(mediacodec_vp9_backend_context_t *)opaque;
|
||||
mediacodec_vp9_decoder_t *decoder;
|
||||
int key;
|
||||
while (!retro_atomic_load_acquire_int(&c->state))
|
||||
{
|
||||
key = retro_eventcount_prepare_wait(&c->wake);
|
||||
if (retro_atomic_load_acquire_int(&c->state))
|
||||
{
|
||||
retro_eventcount_cancel_wait(&c->wake);
|
||||
break;
|
||||
}
|
||||
retro_eventcount_commit_wait(&c->wake, key);
|
||||
}
|
||||
decoder = c->retired;
|
||||
if (decoder)
|
||||
while (!mediacodec_vp9_decoder_retire_poll(decoder))
|
||||
{
|
||||
key = retro_eventcount_prepare_wait(&c->wake);
|
||||
retro_eventcount_commit_wait_timeout(&c->wake, key, 10000);
|
||||
}
|
||||
mediacodec_vp9_backend_context_unref(c);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static bool mediacodec_vp9_backend_open(void *opaque, const rvp9_hdr *header,
|
||||
const video_color_metadata_t *color, unsigned slots, rvp9_hw_ops_t *ops)
|
||||
{
|
||||
mediacodec_vp9_backend_t *backend = (mediacodec_vp9_backend_t *)opaque;
|
||||
mediacodec_vp9_backend_context_t *c;
|
||||
mediacodec_vp9_import_t importer;
|
||||
pthread_t thread;
|
||||
pthread_attr_t attr;
|
||||
JNIEnv *env;
|
||||
int active;
|
||||
if (!ops)
|
||||
return false;
|
||||
memset(ops, 0, sizeof(*ops));
|
||||
if (!backend || !backend->environment || !header || slots < 10 || slots > 32
|
||||
|| header->subsampling_x != 1 || header->subsampling_y != 1
|
||||
|| (header->profile != 0 && header->profile != 2)
|
||||
|| header->bit_depth != (header->profile == 2 ? 10 : 8))
|
||||
return false;
|
||||
for (;;)
|
||||
{
|
||||
active = retro_atomic_load_acquire_int(&backend->active);
|
||||
if (active < 0 || (unsigned)active >= backend->max_sessions)
|
||||
return false;
|
||||
if (retro_atomic_cas_int(&backend->active, active, active + 1))
|
||||
break;
|
||||
}
|
||||
env = backend->environment(backend->user);
|
||||
if (!env)
|
||||
{
|
||||
retro_atomic_fetch_sub_int(&backend->active, 1);
|
||||
return false;
|
||||
}
|
||||
c = (mediacodec_vp9_backend_context_t *)calloc(1, sizeof(*c));
|
||||
if (!c)
|
||||
{
|
||||
retro_atomic_fetch_sub_int(&backend->active, 1);
|
||||
return false;
|
||||
}
|
||||
c->backend = backend;
|
||||
if (!retro_eventcount_init(&c->wake))
|
||||
{
|
||||
free(c);
|
||||
retro_atomic_fetch_sub_int(&backend->active, 1);
|
||||
return false;
|
||||
}
|
||||
retro_atomic_int_init(&c->state, 0);
|
||||
retro_atomic_int_init(&c->refs, 2);
|
||||
c->exporter = mediacodec_frame_exporter_new(slots,
|
||||
header->bit_depth == 10 ? IMAGE_PIXFMT_P010 : IMAGE_PIXFMT_NV12);
|
||||
if (!c->exporter)
|
||||
{
|
||||
mediacodec_vp9_backend_context_free(c);
|
||||
return false;
|
||||
}
|
||||
if (pthread_attr_init(&attr))
|
||||
{
|
||||
mediacodec_vp9_backend_context_free(c);
|
||||
return false;
|
||||
}
|
||||
if (pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED))
|
||||
{
|
||||
pthread_attr_destroy(&attr);
|
||||
mediacodec_vp9_backend_context_free(c);
|
||||
return false;
|
||||
}
|
||||
active = pthread_create(&thread, &attr, mediacodec_vp9_backend_retire, c);
|
||||
pthread_attr_destroy(&attr);
|
||||
if (active)
|
||||
{
|
||||
mediacodec_vp9_backend_context_free(c);
|
||||
return false;
|
||||
}
|
||||
memset(&importer, 0, sizeof(importer));
|
||||
importer.context = c;
|
||||
importer.export_image = mediacodec_vp9_backend_export;
|
||||
importer.release = mediacodec_vp9_backend_release;
|
||||
importer.enqueue = mediacodec_vp9_backend_enqueue;
|
||||
if (!mediacodec_vp9_decoder_open_color(
|
||||
env, header, color, slots, backend->max_input, &importer, ops))
|
||||
{
|
||||
mediacodec_vp9_backend_publish(c, 2);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool mediacodec_vp9_backend_init(mediacodec_vp9_backend_t *backend,
|
||||
JNIEnv *(*environment)(void *), void *user, unsigned max_input,
|
||||
unsigned max_sessions)
|
||||
{
|
||||
#if !defined(HAVE_THREADS) || !defined(RETRO_ATOMIC_LOCK_FREE)
|
||||
return false;
|
||||
#endif
|
||||
if (!backend || !environment || !max_input || max_input > 64u * 1024u * 1024u
|
||||
|| !max_sessions || max_sessions > 8)
|
||||
return false;
|
||||
memset(backend, 0, sizeof(*backend));
|
||||
backend->user = user;
|
||||
backend->environment = environment;
|
||||
backend->max_input = max_input;
|
||||
backend->max_sessions = max_sessions;
|
||||
retro_atomic_int_init(&backend->active, 0);
|
||||
backend->offer.user = backend;
|
||||
backend->offer.open_color = mediacodec_vp9_backend_open;
|
||||
backend->offer.drain = mediacodec_vp9_decoder_finish;
|
||||
backend->offer.packet_decoder = true;
|
||||
backend->offer.close = mediacodec_vp9_decoder_close;
|
||||
return true;
|
||||
}
|
||||
|
||||
unsigned mediacodec_vp9_backend_active(mediacodec_vp9_backend_t *backend)
|
||||
{
|
||||
return backend ? (unsigned)retro_atomic_load_acquire_int(&backend->active)
|
||||
: 0;
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
/* Copyright (C) 2026 The RetroArch team. SPDX-License-Identifier:
|
||||
* GPL-3.0-or-later */
|
||||
#ifndef MEDIACODEC_VP9_BACKEND_H
|
||||
#define MEDIACODEC_VP9_BACKEND_H
|
||||
#include "mediacodec_vp9_decoder.h"
|
||||
#include <retro_atomic.h>
|
||||
RETRO_BEGIN_DECLS
|
||||
|
||||
typedef struct mediacodec_vp9_backend
|
||||
{
|
||||
rvp9_hw_backend_t offer;
|
||||
void *user;
|
||||
JNIEnv *(*environment)(void *user);
|
||||
unsigned max_input, max_sessions;
|
||||
retro_atomic_int_t active;
|
||||
} mediacodec_vp9_backend_t;
|
||||
|
||||
/* Cold setup only; does not register or advertise hardware support. Caller
|
||||
* admits a validated renderer before publishing offer. environment returns the
|
||||
* calling decoder-worker's JNIEnv (e.g. rjni_env), never a saved foreign env.
|
||||
* Storage/user/callback outlive all admitted decoders and retired contexts.
|
||||
* max_input is the explicit packet staging bound, <=64 MiB. At most 1..8
|
||||
* sessions including retirement may coexist; seek storms refuse excess opens
|
||||
* without waiting. Each admitted session has one cold retirement worker,
|
||||
* created before native startup; decode/output/export add no thread, wait or
|
||||
* lock. Failed startup and close return without joining; active includes
|
||||
* deferred cleanup. */
|
||||
bool mediacodec_vp9_backend_init(mediacodec_vp9_backend_t *backend,
|
||||
JNIEnv *(*environment)(void *), void *user, unsigned max_input,
|
||||
unsigned max_sessions);
|
||||
unsigned mediacodec_vp9_backend_active(mediacodec_vp9_backend_t *backend);
|
||||
RETRO_END_DECLS
|
||||
#endif
|
||||
@@ -0,0 +1,322 @@
|
||||
/* Copyright (C) 2026 The RetroArch team.
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later */
|
||||
#include "mediacodec_vp9_decoder.h"
|
||||
#include "android_image_ndk.h"
|
||||
#include "mediacodec_color.h"
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
struct mediacodec_vp9_decoder
|
||||
{
|
||||
uint64_t serial, tag;
|
||||
mediacodec_session_t *session;
|
||||
android_image_queue_t *images;
|
||||
mediacodec_vp9_import_t importer;
|
||||
android_image_view_t image;
|
||||
rvp9_hdr header;
|
||||
video_color_metadata_t color;
|
||||
mediacodec_output_format_t format;
|
||||
unsigned slots, capacity, output_slot;
|
||||
bool failed, closed, eos, output_eos, rendered;
|
||||
};
|
||||
|
||||
static bool mediacodec_vp9_discard(android_image_view_t *image)
|
||||
{
|
||||
if (!image->ticket)
|
||||
return true;
|
||||
if (!android_image_queue_return(image->ticket, image->generation, -1))
|
||||
return false;
|
||||
memset(image, 0, sizeof(*image));
|
||||
return true;
|
||||
}
|
||||
|
||||
static int mediacodec_vp9_service(mediacodec_vp9_decoder_t *d)
|
||||
{
|
||||
android_image_view_t image;
|
||||
const mediacodec_output_format_t *format;
|
||||
int64_t pts;
|
||||
uint64_t tag;
|
||||
int status;
|
||||
if (d->failed)
|
||||
return -1;
|
||||
android_image_queue_poll(d->images);
|
||||
status = mediacodec_session_pump(d->session, &pts);
|
||||
if (status < 0)
|
||||
goto fail;
|
||||
if (status == 1)
|
||||
{
|
||||
if (pts <= 0 || (uint64_t)pts > d->serial)
|
||||
goto fail;
|
||||
if ((uint64_t)pts == d->tag)
|
||||
d->rendered = true;
|
||||
}
|
||||
if (status == 3)
|
||||
{
|
||||
if (!d->eos)
|
||||
goto fail;
|
||||
d->output_eos = true;
|
||||
}
|
||||
if (status == 2)
|
||||
{
|
||||
format = mediacodec_session_format(d->session);
|
||||
if (!format || format->width != (unsigned)d->header.w
|
||||
|| format->height != (unsigned)d->header.h || format->left
|
||||
|| format->top || format->right != (unsigned)d->header.w
|
||||
|| format->bottom != (unsigned)d->header.h)
|
||||
goto fail;
|
||||
d->format = *format;
|
||||
}
|
||||
status = android_image_queue_acquire(d->images, &image);
|
||||
if (status < 0)
|
||||
goto fail;
|
||||
if (!status)
|
||||
return 0;
|
||||
tag = (uint64_t)image.timestamp_ns / 1000;
|
||||
if (image.timestamp_ns <= 0 || image.timestamp_ns % 1000 || tag > d->serial)
|
||||
{
|
||||
mediacodec_vp9_discard(&image);
|
||||
goto fail;
|
||||
}
|
||||
if (tag != d->tag)
|
||||
{
|
||||
if (!mediacodec_vp9_discard(&image))
|
||||
goto fail;
|
||||
return 0;
|
||||
}
|
||||
if (d->image.ticket || !d->format.serial)
|
||||
{
|
||||
mediacodec_vp9_discard(&image);
|
||||
goto fail;
|
||||
}
|
||||
d->image = image;
|
||||
return 0;
|
||||
fail:
|
||||
d->failed = true;
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int mediacodec_vp9_decode(void *opaque, const uint8_t *packet,
|
||||
size_t size, const rvp9_hw_picture_t *picture)
|
||||
{
|
||||
mediacodec_vp9_decoder_t *d = (mediacodec_vp9_decoder_t *)opaque;
|
||||
const rvp9_hdr *h;
|
||||
int status;
|
||||
if (!d || !packet || !size || !picture || d->closed || d->eos
|
||||
|| size > d->capacity || picture->slot < 0
|
||||
|| (unsigned)picture->slot >= d->slots
|
||||
|| d->serial >= ((~(uint64_t)0) >> 1) / 1000)
|
||||
return -1;
|
||||
h = &picture->header;
|
||||
if (h->w != d->header.w || h->h != d->header.h
|
||||
|| h->profile != d->header.profile
|
||||
|| h->color_space != d->header.color_space
|
||||
|| h->color_range != d->header.color_range
|
||||
|| h->bit_depth != d->header.bit_depth || h->subsampling_x != 1
|
||||
|| h->subsampling_y != 1)
|
||||
return -1;
|
||||
if (mediacodec_vp9_service(d) < 0)
|
||||
return -1;
|
||||
status = mediacodec_session_submit(
|
||||
d->session, packet, size, (int64_t)(d->serial + 1), false);
|
||||
if (status < 0)
|
||||
{
|
||||
d->failed = true;
|
||||
return -1;
|
||||
}
|
||||
if (!status)
|
||||
return 1;
|
||||
if (h->show_frame && !mediacodec_vp9_discard(&d->image))
|
||||
{
|
||||
d->failed = true;
|
||||
return -1;
|
||||
}
|
||||
d->serial++;
|
||||
if (h->show_frame)
|
||||
{
|
||||
d->tag = d->serial;
|
||||
d->output_slot = (unsigned)picture->slot;
|
||||
d->rendered = false;
|
||||
}
|
||||
d->header = *h;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int mediacodec_vp9_output(
|
||||
void *opaque, unsigned slot, video_hw_frame_t *out)
|
||||
{
|
||||
mediacodec_vp9_decoder_t *d = (mediacodec_vp9_decoder_t *)opaque;
|
||||
int status;
|
||||
if (!d || !out || d->closed || !d->tag || slot != d->output_slot)
|
||||
return -1;
|
||||
if (mediacodec_vp9_service(d) < 0)
|
||||
return -1;
|
||||
if (!d->image.ticket)
|
||||
{
|
||||
if (d->output_eos && !d->rendered)
|
||||
{
|
||||
d->failed = true;
|
||||
return -1;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
status = d->importer.export_image(d->importer.context, &d->image, &d->format,
|
||||
&d->header, &d->color, out);
|
||||
if (!status)
|
||||
{
|
||||
memset(&d->image, 0, sizeof(d->image));
|
||||
d->tag = 0;
|
||||
}
|
||||
if (status < 0)
|
||||
d->failed = true;
|
||||
return status;
|
||||
}
|
||||
|
||||
int mediacodec_vp9_decoder_drain(void *opaque)
|
||||
{
|
||||
mediacodec_vp9_decoder_t *d = (mediacodec_vp9_decoder_t *)opaque;
|
||||
int status;
|
||||
if (!d || d->closed || mediacodec_vp9_service(d) < 0)
|
||||
return -1;
|
||||
if (d->eos)
|
||||
return 0;
|
||||
status = mediacodec_session_submit(d->session, NULL, 0, 0, true);
|
||||
if (status < 0)
|
||||
{
|
||||
d->failed = true;
|
||||
return -1;
|
||||
}
|
||||
if (!status)
|
||||
return 1;
|
||||
d->eos = true;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int mediacodec_vp9_decoder_finish(void *opaque, bool discard)
|
||||
{
|
||||
mediacodec_vp9_decoder_t *d = (mediacodec_vp9_decoder_t *)opaque;
|
||||
int status;
|
||||
if (!d || d->closed)
|
||||
return -1;
|
||||
if (discard)
|
||||
{
|
||||
if (!mediacodec_vp9_discard(&d->image))
|
||||
return -1;
|
||||
d->tag = 0;
|
||||
}
|
||||
status = mediacodec_vp9_decoder_drain(d);
|
||||
if (status)
|
||||
return status;
|
||||
if (d->output_eos && d->tag && !d->rendered && !d->image.ticket)
|
||||
{
|
||||
d->failed = true;
|
||||
return -1;
|
||||
}
|
||||
return d->output_eos && !d->tag ? 0 : 1;
|
||||
}
|
||||
|
||||
bool mediacodec_vp9_decoder_retire_poll(mediacodec_vp9_decoder_t *d)
|
||||
{
|
||||
if (!d)
|
||||
return true;
|
||||
if (!d->closed || !mediacodec_vp9_discard(&d->image))
|
||||
return false;
|
||||
if (d->images)
|
||||
{
|
||||
android_image_queue_poll(d->images);
|
||||
if (!android_image_queue_idle(d->images))
|
||||
return false;
|
||||
if (d->session)
|
||||
{
|
||||
mediacodec_session_free(d->session);
|
||||
d->session = NULL;
|
||||
}
|
||||
if (!android_image_queue_free(d->images))
|
||||
return false;
|
||||
d->images = NULL;
|
||||
}
|
||||
if (!d->importer.release(d->importer.context))
|
||||
return false;
|
||||
free(d);
|
||||
return true;
|
||||
}
|
||||
|
||||
void mediacodec_vp9_decoder_close(rvp9_hw_ops_t *ops)
|
||||
{
|
||||
mediacodec_vp9_decoder_t *d;
|
||||
if (!ops || ops->decode != mediacodec_vp9_decode || !ops->decoder)
|
||||
return;
|
||||
d = (mediacodec_vp9_decoder_t *)ops->decoder;
|
||||
memset(ops, 0, sizeof(*ops));
|
||||
d->closed = true;
|
||||
android_image_queue_close(d->images);
|
||||
d->importer.enqueue(d->importer.context, d);
|
||||
}
|
||||
|
||||
bool mediacodec_vp9_decoder_open_color(JNIEnv *env, const rvp9_hdr *header,
|
||||
const video_color_metadata_t *color, unsigned slots, unsigned max_input,
|
||||
const mediacodec_vp9_import_t *importer, rvp9_hw_ops_t *ops)
|
||||
{
|
||||
mediacodec_vp9_decoder_t *d;
|
||||
mediacodec_config_t config;
|
||||
void *window = NULL;
|
||||
if (!ops)
|
||||
return false;
|
||||
memset(ops, 0, sizeof(*ops));
|
||||
if (!env || !header || header->w <= 0 || header->h <= 0 || header->frame_type
|
||||
|| header->show_existing_frame || header->subsampling_x != 1
|
||||
|| header->subsampling_y != 1
|
||||
|| (header->profile != 0 && header->profile != 2)
|
||||
|| header->bit_depth != (header->profile == 2 ? 10 : 8) || slots < 10
|
||||
|| slots > 32 || !max_input || max_input > 64u * 1024u * 1024u
|
||||
|| !importer || !importer->export_image || !importer->enqueue
|
||||
|| !importer->release)
|
||||
return false;
|
||||
d = (mediacodec_vp9_decoder_t *)calloc(1, sizeof(*d));
|
||||
if (!d)
|
||||
return false;
|
||||
d->header = *header;
|
||||
d->slots = slots;
|
||||
d->capacity = max_input;
|
||||
memset(&config, 0, sizeof(config));
|
||||
if (!mediacodec_vp9_color(header, color, &d->color)
|
||||
|| !mediacodec_color_configure(&d->color, &config))
|
||||
goto fail;
|
||||
if ((d->color.transfer == 16 || d->color.transfer == 18)
|
||||
&& header->bit_depth != 10)
|
||||
goto fail;
|
||||
config.mime = "video/x-vnd.on2.vp9";
|
||||
config.width = (unsigned)header->w;
|
||||
config.height = (unsigned)header->h;
|
||||
config.max_input = max_input;
|
||||
config.profile = header->profile == 2 ? 4 : 1;
|
||||
if (d->color.transfer == 16 || d->color.transfer == 18)
|
||||
config.profile = 4096;
|
||||
d->images = android_image_ndk_new(
|
||||
(unsigned)header->w, (unsigned)header->h, slots, &window);
|
||||
if (!d->images)
|
||||
goto fail;
|
||||
d->session = mediacodec_session_new(env, &config, window);
|
||||
if (!d->session)
|
||||
goto fail;
|
||||
d->importer = *importer;
|
||||
ops->decoder = d;
|
||||
ops->decode = mediacodec_vp9_decode;
|
||||
ops->output = mediacodec_vp9_output;
|
||||
return true;
|
||||
fail:
|
||||
if (d->images)
|
||||
{
|
||||
android_image_queue_close(d->images);
|
||||
android_image_queue_free(d->images);
|
||||
}
|
||||
free(d);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool mediacodec_vp9_decoder_open(JNIEnv *env, const rvp9_hdr *header,
|
||||
unsigned slots, unsigned max_input,
|
||||
const mediacodec_vp9_import_t *importer, rvp9_hw_ops_t *ops)
|
||||
{
|
||||
return mediacodec_vp9_decoder_open_color(
|
||||
env, header, NULL, slots, max_input, importer, ops);
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
/* Copyright (C) 2026 The RetroArch team.
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later */
|
||||
#ifndef MEDIACODEC_VP9_DECODER_H
|
||||
#define MEDIACODEC_VP9_DECODER_H
|
||||
#include <formats/rvp9_hw.h>
|
||||
#include "mediacodec_session.h"
|
||||
#include "android_image_queue.h"
|
||||
RETRO_BEGIN_DECLS
|
||||
typedef struct mediacodec_vp9_decoder mediacodec_vp9_decoder_t;
|
||||
|
||||
typedef struct mediacodec_vp9_import
|
||||
{
|
||||
void *context;
|
||||
/* 0 consumes image; BUSY/error leave its ticket untouched. Color retains
|
||||
* coded/container metadata; the exporter must reconcile native format. */
|
||||
int (*export_image)(void *, const android_image_view_t *,
|
||||
const mediacodec_output_format_t *, const rvp9_hdr *,
|
||||
const video_color_metadata_t *, video_hw_frame_t *);
|
||||
bool (*release)(void *);
|
||||
/* Guaranteed deferred execution; never retire inline on close. */
|
||||
void (*enqueue)(void *, mediacodec_vp9_decoder_t *);
|
||||
} mediacodec_vp9_import_t;
|
||||
|
||||
/* Cold worker factory. Caller must select VP9 packet-decoder mode. The codec
|
||||
* owns references; input is copied by session_submit before acceptance. */
|
||||
bool mediacodec_vp9_decoder_open(JNIEnv *, const rvp9_hdr *, unsigned slots,
|
||||
unsigned max_input, const mediacodec_vp9_import_t *, rvp9_hw_ops_t *);
|
||||
/* Copies container CICP/mastering/light metadata before native startup. */
|
||||
bool mediacodec_vp9_decoder_open_color(JNIEnv *, const rvp9_hdr *,
|
||||
const video_color_metadata_t *, unsigned slots, unsigned max_input,
|
||||
const mediacodec_vp9_import_t *, rvp9_hw_ops_t *);
|
||||
int mediacodec_vp9_decoder_drain(void *);
|
||||
/* Nonblocking terminal service; 0 complete, 1 pending, negative failure. */
|
||||
int mediacodec_vp9_decoder_finish(void *, bool discard);
|
||||
void mediacodec_vp9_decoder_close(rvp9_hw_ops_t *);
|
||||
bool mediacodec_vp9_decoder_retire_poll(mediacodec_vp9_decoder_t *);
|
||||
RETRO_END_DECLS
|
||||
#endif
|
||||
@@ -0,0 +1,105 @@
|
||||
/* Copyright (C) 2026 The RetroArch team. SPDX-License-Identifier:
|
||||
* GPL-3.0-or-later */
|
||||
#ifndef MEDIACODEC_VP9_REGISTRY_H
|
||||
#define MEDIACODEC_VP9_REGISTRY_H
|
||||
#include "mediacodec_vp9_backend.h"
|
||||
#include <retro_inline.h>
|
||||
#include <limits.h>
|
||||
#include <string.h>
|
||||
#if defined(HAVE_THREADS) && defined(RETRO_ATOMIC_HAS_PTR) \
|
||||
&& defined(RETRO_ATOMIC_LOCK_FREE)
|
||||
#define MEDIACODEC_VP9_REGISTRY 1
|
||||
RETRO_BEGIN_DECLS
|
||||
|
||||
typedef struct mediacodec_vp9_registry
|
||||
{
|
||||
mediacodec_vp9_backend_t backend;
|
||||
rvp9_hw_backend_t offer;
|
||||
void *owner;
|
||||
retro_atomic_int_t epoch;
|
||||
} mediacodec_vp9_registry_t;
|
||||
|
||||
static INLINE bool mediacodec_vp9_registry_open(void *opaque,
|
||||
const rvp9_hdr *header, const video_color_metadata_t *color,
|
||||
unsigned slots, rvp9_hw_ops_t *ops)
|
||||
{
|
||||
mediacodec_vp9_registry_t *r = (mediacodec_vp9_registry_t *)opaque;
|
||||
int epoch;
|
||||
if (!ops)
|
||||
return false;
|
||||
memset(ops, 0, sizeof(*ops));
|
||||
epoch = retro_atomic_load_acquire_int(&r->epoch);
|
||||
if (epoch <= 0 || !(epoch & 1))
|
||||
return false;
|
||||
if (!r->backend.offer.open_color(
|
||||
r->backend.offer.user, header, color, slots, ops))
|
||||
return false;
|
||||
if (retro_atomic_load_acquire_int(&r->epoch) != epoch)
|
||||
{
|
||||
r->backend.offer.close(ops);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/* Initialize once. Registry/configuration storage outlives all setup callers,
|
||||
* opened sessions and deferred retirement. No renderer object is captured. */
|
||||
static INLINE bool mediacodec_vp9_registry_init(mediacodec_vp9_registry_t *r,
|
||||
JNIEnv *(*environment)(void *), void *user, unsigned max_input,
|
||||
unsigned max_sessions)
|
||||
{
|
||||
if (!r)
|
||||
return false;
|
||||
memset(r, 0, sizeof(*r));
|
||||
if (!mediacodec_vp9_backend_init(
|
||||
&r->backend, environment, user, max_input, max_sessions))
|
||||
return false;
|
||||
retro_atomic_int_init(&r->epoch, 0);
|
||||
r->offer.user = r;
|
||||
r->offer.open_color = mediacodec_vp9_registry_open;
|
||||
r->offer.close = r->backend.offer.close;
|
||||
r->offer.drain = r->backend.offer.drain;
|
||||
r->offer.packet_decoder = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
/* Publish/withdraw share one driver lifecycle owner. Native renderer admission
|
||||
* must precede publication; an offer alone does not prove hardware support. */
|
||||
static INLINE bool mediacodec_vp9_registry_publish(
|
||||
mediacodec_vp9_registry_t *r, void *owner)
|
||||
{
|
||||
int epoch;
|
||||
if (!r || !r->offer.open_color || !owner)
|
||||
return false;
|
||||
epoch = retro_atomic_load_acquire_int(&r->epoch);
|
||||
if (epoch < 0)
|
||||
return false;
|
||||
if (epoch >= INT_MAX - 2)
|
||||
{
|
||||
retro_atomic_store_release_int(&r->epoch, -1);
|
||||
r->owner = NULL;
|
||||
rvp9_unset_hw_backend(&r->offer);
|
||||
return false;
|
||||
}
|
||||
r->owner = owner;
|
||||
retro_atomic_store_release_int(&r->epoch, epoch + (epoch & 1 ? 2 : 1));
|
||||
rvp9_set_hw_backend(&r->offer);
|
||||
return true;
|
||||
}
|
||||
|
||||
static INLINE bool mediacodec_vp9_registry_withdraw(
|
||||
mediacodec_vp9_registry_t *r, void *owner)
|
||||
{
|
||||
int epoch;
|
||||
if (!r || !r->offer.open_color || !owner || r->owner != owner)
|
||||
return false;
|
||||
epoch = retro_atomic_load_acquire_int(&r->epoch);
|
||||
retro_atomic_store_release_int(&r->epoch, epoch == INT_MAX ? -1 : epoch + 1);
|
||||
r->owner = NULL;
|
||||
rvp9_unset_hw_backend(&r->offer);
|
||||
return true;
|
||||
}
|
||||
|
||||
RETRO_END_DECLS
|
||||
#endif
|
||||
#endif
|
||||
@@ -0,0 +1,148 @@
|
||||
/* Copyright (C) 2026 The RetroArch team.
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later */
|
||||
#ifndef RETROARCH_METAL_DECODER_SNAPSHOT_H
|
||||
#define RETROARCH_METAL_DECODER_SNAPSHOT_H
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
#include <retro_atomic.h>
|
||||
#include <rthreads/rthreads.h>
|
||||
|
||||
typedef struct metal_decoder_snapshot_view
|
||||
{
|
||||
void *device;
|
||||
uintptr_t generation;
|
||||
bool hdr;
|
||||
} metal_decoder_snapshot_view_t;
|
||||
|
||||
#if defined(HAVE_THREADS) && defined(RETRO_ATOMIC_HAS_PTR) \
|
||||
&& defined(RETRO_ATOMIC_LOCK_FREE)
|
||||
#define METAL_DECODER_SNAPSHOT 1
|
||||
|
||||
typedef struct metal_decoder_snapshot
|
||||
{
|
||||
slock_t *lock;
|
||||
void *device;
|
||||
void *(*retain)(void *);
|
||||
void (*release)(void *);
|
||||
retro_atomic_ptr_t owner;
|
||||
retro_atomic_size_t generation;
|
||||
retro_atomic_int_t hdr;
|
||||
} metal_decoder_snapshot_t;
|
||||
|
||||
static INLINE bool metal_decoder_snapshot_init(metal_decoder_snapshot_t *s,
|
||||
slock_t *lock, void *(*retain)(void *), void (*release)(void *))
|
||||
{
|
||||
if (!s)
|
||||
return false;
|
||||
memset(s, 0, sizeof(*s));
|
||||
if (!lock || !retain || !release)
|
||||
return false;
|
||||
s->lock = lock;
|
||||
s->retain = retain;
|
||||
s->release = release;
|
||||
retro_atomic_ptr_init(&s->owner, NULL);
|
||||
retro_atomic_size_init(&s->generation, 0);
|
||||
retro_atomic_int_init(&s->hdr, 0);
|
||||
return true;
|
||||
}
|
||||
|
||||
/* Cold setup only. Reuse the renderer's existing snapshot lock. */
|
||||
static INLINE bool metal_decoder_snapshot_acquire(
|
||||
void *opaque, metal_decoder_snapshot_view_t *out)
|
||||
{
|
||||
metal_decoder_snapshot_t *s = (metal_decoder_snapshot_t *)opaque;
|
||||
if (!out)
|
||||
return false;
|
||||
memset(out, 0, sizeof(*out));
|
||||
if (!s || !s->lock)
|
||||
return false;
|
||||
slock_lock(s->lock);
|
||||
out->device = s->device ? s->retain(s->device) : NULL;
|
||||
if (out->device)
|
||||
{
|
||||
out->generation =
|
||||
(uintptr_t)retro_atomic_load_acquire_size(&s->generation);
|
||||
out->hdr = retro_atomic_load_acquire_int(&s->hdr) != 0;
|
||||
}
|
||||
slock_unlock(s->lock);
|
||||
return out->device != NULL;
|
||||
}
|
||||
|
||||
static INLINE bool metal_decoder_snapshot_current(
|
||||
void *opaque, uintptr_t generation)
|
||||
{
|
||||
const metal_decoder_snapshot_t *s = (const metal_decoder_snapshot_t *)opaque;
|
||||
return s && s->lock && (generation & 1)
|
||||
&& retro_atomic_load_acquire_size(&s->generation)
|
||||
== (size_t)generation;
|
||||
}
|
||||
|
||||
/* Lifecycle only. Codec offer hooks run under the same existing setup lock. */
|
||||
static INLINE bool metal_decoder_snapshot_publish(metal_decoder_snapshot_t *s,
|
||||
void *owner, void *device, bool hdr, void (*publish)(void *), void *user)
|
||||
{
|
||||
void *old, *owned;
|
||||
size_t generation;
|
||||
if (!s || !s->lock || !owner || !device)
|
||||
return false;
|
||||
owned = s->retain(device);
|
||||
if (!owned)
|
||||
return false;
|
||||
slock_lock(s->lock);
|
||||
generation = retro_atomic_load_acquire_size(&s->generation);
|
||||
if (generation >= (size_t)-1 - 2)
|
||||
{
|
||||
slock_unlock(s->lock);
|
||||
s->release(owned);
|
||||
return false;
|
||||
}
|
||||
old = s->device;
|
||||
s->device = owned;
|
||||
retro_atomic_store_release_int(&s->hdr, hdr ? 1 : 0);
|
||||
retro_atomic_store_release_size(
|
||||
&s->generation, generation + ((generation & 1) ? 2 : 1));
|
||||
retro_atomic_store_release_ptr(&s->owner, owner);
|
||||
if (publish)
|
||||
publish(user);
|
||||
slock_unlock(s->lock);
|
||||
if (old)
|
||||
s->release(old);
|
||||
return true;
|
||||
}
|
||||
|
||||
static INLINE bool metal_decoder_snapshot_withdraw(metal_decoder_snapshot_t *s,
|
||||
void *owner, void (*withdraw)(void *), void *user)
|
||||
{
|
||||
void *old;
|
||||
size_t generation;
|
||||
if (!s || !s->lock || !owner)
|
||||
return false;
|
||||
slock_lock(s->lock);
|
||||
if (retro_atomic_load_acquire_ptr(&s->owner) != owner)
|
||||
{
|
||||
slock_unlock(s->lock);
|
||||
return false;
|
||||
}
|
||||
if (withdraw)
|
||||
withdraw(user);
|
||||
old = s->device;
|
||||
s->device = NULL;
|
||||
generation = retro_atomic_load_acquire_size(&s->generation);
|
||||
retro_atomic_store_release_size(&s->generation, generation + 1);
|
||||
retro_atomic_store_release_int(&s->hdr, 0);
|
||||
retro_atomic_store_release_ptr(&s->owner, NULL);
|
||||
slock_unlock(s->lock);
|
||||
if (old)
|
||||
s->release(old);
|
||||
return true;
|
||||
}
|
||||
|
||||
/* Frame-path capability query: no lock, retain, allocation or wait. */
|
||||
static INLINE bool metal_decoder_snapshot_is_owner(
|
||||
const metal_decoder_snapshot_t *s, const void *owner)
|
||||
{
|
||||
return s && s->lock && owner
|
||||
&& retro_atomic_load_acquire_ptr(&s->owner) == owner;
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
@@ -1,68 +0,0 @@
|
||||
/* Copyright (C) 2026 The RetroArch team. SPDX-License-Identifier: GPL-3.0-or-later */
|
||||
#ifndef RETROARCH_METAL_HEVC_DRIVER_H
|
||||
#define RETROARCH_METAL_HEVC_DRIVER_H
|
||||
#include "videotoolbox_hevc_registry.h"
|
||||
#include <dispatch/dispatch.h>
|
||||
|
||||
#ifdef VIDEOTOOLBOX_HEVC_REGISTRY
|
||||
#define METAL_HEVC_DRIVER 1
|
||||
/* The including platform supplies its setup-time decode-setting policy. */
|
||||
static bool metal_hevc_decode_enabled(void *user);
|
||||
static videotoolbox_hevc_registry_t metal_hevc_registry;
|
||||
static retro_atomic_ptr_t metal_hevc_active_registry;
|
||||
static dispatch_once_t metal_hevc_once;
|
||||
static dispatch_queue_t metal_hevc_retirement_queue;
|
||||
|
||||
static void *metal_hevc_retain(void *device) { return (void*)CFRetain(device); }
|
||||
static void metal_hevc_release(void *device) { CFRelease(device); }
|
||||
static void metal_hevc_enqueue(void *user, void (*run)(void*), void *work)
|
||||
{
|
||||
(void)user;
|
||||
dispatch_async_f(metal_hevc_retirement_queue, work, run);
|
||||
}
|
||||
static VIDEOTOOLBOX_HEVC_AVAILABLE void metal_hevc_initialize(void *unused)
|
||||
{
|
||||
slock_t *lock = slock_new();
|
||||
(void)unused;
|
||||
if (!lock) return;
|
||||
metal_hevc_retirement_queue = dispatch_queue_create(
|
||||
"org.libretro.metal.hevc.retirement", DISPATCH_QUEUE_SERIAL);
|
||||
if (!metal_hevc_retirement_queue)
|
||||
{ slock_free(lock); return; }
|
||||
if (!videotoolbox_hevc_registry_init(&metal_hevc_registry, lock,
|
||||
metal_hevc_retain, metal_hevc_release, metal_hevc_decode_enabled,
|
||||
NULL, metal_hevc_enqueue, NULL, true))
|
||||
{ slock_free(lock); return; }
|
||||
/* Process-lifetime storage/queue survive every driver and queued owner. */
|
||||
retro_atomic_store_release_ptr(&metal_hevc_active_registry, &metal_hevc_registry);
|
||||
}
|
||||
static bool metal_hevc_start_supported(void *owner, void *device, void *sdr_pipeline, bool hdr_enabled)
|
||||
{
|
||||
videotoolbox_hevc_registry_t *registry;
|
||||
if (!owner || !device || !sdr_pipeline) return false;
|
||||
if (@available(macOS 10.14, iOS 12.0, tvOS 12.0, *))
|
||||
{
|
||||
dispatch_once_f(&metal_hevc_once, NULL, metal_hevc_initialize);
|
||||
registry = (videotoolbox_hevc_registry_t*)retro_atomic_load_acquire_ptr(&metal_hevc_active_registry);
|
||||
return registry && videotoolbox_hevc_registry_publish_support(registry, owner, device, hdr_enabled);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
static bool metal_hevc_start(void *owner, void *device, void *sdr_pipeline)
|
||||
{
|
||||
return metal_hevc_start_supported(owner, device, sdr_pipeline, false);
|
||||
}
|
||||
static void metal_hevc_stop(void *owner)
|
||||
{
|
||||
videotoolbox_hevc_registry_t *registry = (videotoolbox_hevc_registry_t*)
|
||||
retro_atomic_load_acquire_ptr(&metal_hevc_active_registry);
|
||||
if (registry) videotoolbox_hevc_registry_withdraw(registry, owner);
|
||||
}
|
||||
static bool metal_hevc_owns(void *owner)
|
||||
{
|
||||
const videotoolbox_hevc_registry_t *registry = (const videotoolbox_hevc_registry_t*)
|
||||
retro_atomic_load_acquire_ptr(&metal_hevc_active_registry);
|
||||
return registry && videotoolbox_hevc_registry_is_owner(registry, owner);
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
@@ -0,0 +1,195 @@
|
||||
/* Copyright (C) 2026 The RetroArch team.
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later */
|
||||
#ifndef RETROARCH_METAL_VIDEO_DECODER_DRIVER_H
|
||||
#define RETROARCH_METAL_VIDEO_DECODER_DRIVER_H
|
||||
#include "metal_decoder_snapshot.h"
|
||||
#ifdef HAVE_RMP4
|
||||
#include "videotoolbox_hevc_registry.h"
|
||||
#endif
|
||||
#ifdef HAVE_RVP9
|
||||
#include "videotoolbox_vp9_backend.h"
|
||||
#endif
|
||||
#ifdef HAVE_RAV1
|
||||
#include "videotoolbox_av1_backend.h"
|
||||
#endif
|
||||
#if defined(HAVE_RVP9) || defined(HAVE_RAV1)
|
||||
#include <TargetConditionals.h>
|
||||
#include <dlfcn.h>
|
||||
#endif
|
||||
#include <dispatch/dispatch.h>
|
||||
|
||||
#if defined(METAL_DECODER_SNAPSHOT) && (defined(VIDEOTOOLBOX_HEVC_REGISTRY) \
|
||||
|| defined(VIDEOTOOLBOX_VP9_OWNER) || defined(VIDEOTOOLBOX_AV1_OWNER))
|
||||
#define METAL_VIDEO_DECODER_DRIVER 1
|
||||
static bool metal_video_decoder_enabled(void *user);
|
||||
static dispatch_once_t metal_video_decoder_once;
|
||||
static retro_atomic_ptr_t metal_video_decoder_active;
|
||||
#ifdef VIDEOTOOLBOX_HEVC_REGISTRY
|
||||
static videotoolbox_hevc_registry_t metal_video_decoder_hevc;
|
||||
static dispatch_queue_t metal_video_decoder_retirement_queue;
|
||||
#else
|
||||
static metal_decoder_snapshot_t metal_video_decoder_snapshot;
|
||||
#endif
|
||||
#ifdef VIDEOTOOLBOX_VP9_OWNER
|
||||
static videotoolbox_vp9_backend_t metal_video_decoder_vp9;
|
||||
#endif
|
||||
#ifdef VIDEOTOOLBOX_AV1_OWNER
|
||||
static videotoolbox_av1_backend_t metal_video_decoder_av1;
|
||||
#endif
|
||||
#if defined(VIDEOTOOLBOX_VP9_OWNER) || defined(VIDEOTOOLBOX_AV1_OWNER)
|
||||
typedef void (*metal_video_decoder_register_t)(CMVideoCodecType);
|
||||
|
||||
static metal_video_decoder_register_t metal_video_decoder_registration(void)
|
||||
{
|
||||
metal_video_decoder_register_t registration = NULL;
|
||||
#if TARGET_OS_OSX && !TARGET_OS_MACCATALYST
|
||||
if (@available(macOS 11.0, *))
|
||||
{
|
||||
void *symbol = dlsym(RTLD_DEFAULT,
|
||||
"VTRegisterSupplementalVideoDecoderIfAvailable");
|
||||
if (sizeof(registration) == sizeof(symbol))
|
||||
memcpy(®istration, &symbol, sizeof(registration));
|
||||
}
|
||||
#endif
|
||||
return registration;
|
||||
}
|
||||
#endif
|
||||
|
||||
static void *metal_video_decoder_retain(void *device)
|
||||
{
|
||||
return (void *)CFRetain(device);
|
||||
}
|
||||
|
||||
static void metal_video_decoder_release(void *device)
|
||||
{
|
||||
CFRelease(device);
|
||||
}
|
||||
|
||||
#ifdef VIDEOTOOLBOX_HEVC_REGISTRY
|
||||
static void metal_video_decoder_enqueue(void *user, void (*run)(void *),
|
||||
void *work)
|
||||
{
|
||||
(void)user;
|
||||
dispatch_async_f(metal_video_decoder_retirement_queue, work, run);
|
||||
}
|
||||
#endif
|
||||
|
||||
static void metal_video_decoder_publish(void *unused)
|
||||
{
|
||||
(void)unused;
|
||||
#ifdef VIDEOTOOLBOX_HEVC_REGISTRY
|
||||
videotoolbox_hevc_registry_publish_offer(&metal_video_decoder_hevc);
|
||||
#endif
|
||||
#ifdef VIDEOTOOLBOX_VP9_OWNER
|
||||
rvp9_set_hw_backend(&metal_video_decoder_vp9.offer);
|
||||
#endif
|
||||
#ifdef VIDEOTOOLBOX_AV1_OWNER
|
||||
rav1_set_hw_backend(&metal_video_decoder_av1.offer);
|
||||
#endif
|
||||
}
|
||||
|
||||
static void metal_video_decoder_withdraw(void *unused)
|
||||
{
|
||||
(void)unused;
|
||||
#ifdef VIDEOTOOLBOX_HEVC_REGISTRY
|
||||
videotoolbox_hevc_registry_withdraw_offer(&metal_video_decoder_hevc);
|
||||
#endif
|
||||
#ifdef VIDEOTOOLBOX_VP9_OWNER
|
||||
rvp9_unset_hw_backend(&metal_video_decoder_vp9.offer);
|
||||
#endif
|
||||
#ifdef VIDEOTOOLBOX_AV1_OWNER
|
||||
rav1_unset_hw_backend(&metal_video_decoder_av1.offer);
|
||||
#endif
|
||||
}
|
||||
|
||||
#ifdef VIDEOTOOLBOX_HEVC_REGISTRY
|
||||
static VIDEOTOOLBOX_HEVC_AVAILABLE void
|
||||
#else
|
||||
static void
|
||||
#endif
|
||||
metal_video_decoder_initialize(void *unused)
|
||||
{
|
||||
metal_decoder_snapshot_t *snapshot;
|
||||
slock_t *lock = slock_new();
|
||||
(void)unused;
|
||||
if (!lock)
|
||||
return;
|
||||
#ifdef VIDEOTOOLBOX_HEVC_REGISTRY
|
||||
metal_video_decoder_retirement_queue = dispatch_queue_create(
|
||||
"org.libretro.metal.hevc.retirement", DISPATCH_QUEUE_SERIAL);
|
||||
if (!metal_video_decoder_retirement_queue)
|
||||
{
|
||||
slock_free(lock);
|
||||
return;
|
||||
}
|
||||
if (!videotoolbox_hevc_registry_init(&metal_video_decoder_hevc, lock,
|
||||
metal_video_decoder_retain, metal_video_decoder_release,
|
||||
metal_video_decoder_enabled, NULL, metal_video_decoder_enqueue,
|
||||
NULL, true))
|
||||
{
|
||||
slock_free(lock);
|
||||
return;
|
||||
}
|
||||
snapshot = &metal_video_decoder_hevc.snapshot;
|
||||
#else
|
||||
snapshot = &metal_video_decoder_snapshot;
|
||||
if (!metal_decoder_snapshot_init(snapshot, lock,
|
||||
metal_video_decoder_retain, metal_video_decoder_release))
|
||||
{
|
||||
slock_free(lock);
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
#ifdef VIDEOTOOLBOX_VP9_OWNER
|
||||
if (!videotoolbox_vp9_backend_init(&metal_video_decoder_vp9, snapshot,
|
||||
metal_decoder_snapshot_acquire, metal_video_decoder_release,
|
||||
metal_decoder_snapshot_current, metal_video_decoder_enabled,
|
||||
metal_video_decoder_registration(), 8u * 1024 * 1024, 1))
|
||||
return;
|
||||
#endif
|
||||
#ifdef VIDEOTOOLBOX_AV1_OWNER
|
||||
if (!videotoolbox_av1_backend_init(&metal_video_decoder_av1, snapshot,
|
||||
metal_decoder_snapshot_acquire, metal_video_decoder_release,
|
||||
metal_decoder_snapshot_current, metal_video_decoder_enabled,
|
||||
metal_video_decoder_registration(), 8u * 1024 * 1024))
|
||||
return;
|
||||
#endif
|
||||
retro_atomic_store_release_ptr(&metal_video_decoder_active, snapshot);
|
||||
}
|
||||
|
||||
static bool metal_video_decoder_start_supported(void *owner, void *device,
|
||||
void *sdr_pipeline, bool hdr_enabled)
|
||||
{
|
||||
metal_decoder_snapshot_t *snapshot;
|
||||
if (!owner || !device || !sdr_pipeline)
|
||||
return false;
|
||||
if (@available(macOS 10.14, iOS 12.0, tvOS 12.0, *))
|
||||
{
|
||||
dispatch_once_f(&metal_video_decoder_once, NULL,
|
||||
metal_video_decoder_initialize);
|
||||
snapshot = (metal_decoder_snapshot_t *)retro_atomic_load_acquire_ptr(
|
||||
&metal_video_decoder_active);
|
||||
return snapshot && metal_decoder_snapshot_publish(snapshot, owner,
|
||||
device, hdr_enabled, metal_video_decoder_publish, NULL);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static void metal_video_decoder_stop(void *owner)
|
||||
{
|
||||
metal_decoder_snapshot_t *snapshot = (metal_decoder_snapshot_t *)
|
||||
retro_atomic_load_acquire_ptr(&metal_video_decoder_active);
|
||||
if (snapshot)
|
||||
metal_decoder_snapshot_withdraw(snapshot, owner,
|
||||
metal_video_decoder_withdraw, NULL);
|
||||
}
|
||||
|
||||
static bool metal_video_decoder_owns(void *owner)
|
||||
{
|
||||
const metal_decoder_snapshot_t *snapshot =
|
||||
(const metal_decoder_snapshot_t *)retro_atomic_load_acquire_ptr(
|
||||
&metal_video_decoder_active);
|
||||
return snapshot && metal_decoder_snapshot_is_owner(snapshot, owner);
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
@@ -0,0 +1,319 @@
|
||||
/* Copyright (C) 2026 The RetroArch team.
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later */
|
||||
#include "vaapi_av1_backend.h"
|
||||
#include <limits.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#if defined(HAVE_THREADS) && defined(RETRO_ATOMIC_LOCK_FREE) \
|
||||
&& defined(RETRO_ATOMIC_HAS_PTR)
|
||||
#include <pthread.h>
|
||||
#include <rthreads/retro_eventcount.h>
|
||||
|
||||
#ifndef VAAPI_AV1_NODE_PATH
|
||||
#define VAAPI_AV1_NODE_PATH vaapi_av1_backend_node_path
|
||||
#if defined(__linux__) && !defined(__ANDROID__) && defined(NEED_DYNAMIC)
|
||||
#include <sys/sysmacros.h>
|
||||
#include <xf86drm.h>
|
||||
|
||||
static bool vaapi_av1_backend_node_path(
|
||||
unsigned major_id, unsigned minor_id, char *path, size_t capacity)
|
||||
{
|
||||
dylib_t library = dylib_load("libdrm.so.2");
|
||||
int (*get_device)(dev_t, uint32_t, drmDevicePtr *);
|
||||
void (*free_device)(drmDevicePtr *);
|
||||
drmDevicePtr device = NULL;
|
||||
function_t symbol;
|
||||
size_t length;
|
||||
bool result = false;
|
||||
if (!library)
|
||||
return false;
|
||||
symbol = dylib_proc(library, "drmGetDeviceFromDevId");
|
||||
if (!symbol || sizeof(symbol) != sizeof(get_device))
|
||||
goto finish;
|
||||
memcpy(&get_device, &symbol, sizeof(get_device));
|
||||
symbol = dylib_proc(library, "drmFreeDevice");
|
||||
if (!symbol || sizeof(symbol) != sizeof(free_device))
|
||||
goto finish;
|
||||
memcpy(&free_device, &symbol, sizeof(free_device));
|
||||
if (!get_device(makedev(major_id, minor_id), 0, &device) && device)
|
||||
{
|
||||
if ((device->available_nodes & (1 << DRM_NODE_RENDER)) && device->nodes
|
||||
&& device->nodes[DRM_NODE_RENDER])
|
||||
{
|
||||
length = strlen(device->nodes[DRM_NODE_RENDER]);
|
||||
if (length && length < capacity)
|
||||
{
|
||||
memcpy(path, device->nodes[DRM_NODE_RENDER], length + 1);
|
||||
result = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (device)
|
||||
free_device(&device);
|
||||
finish:
|
||||
dylib_close(library);
|
||||
return result;
|
||||
}
|
||||
#else
|
||||
static bool vaapi_av1_backend_node_path(
|
||||
unsigned major_id, unsigned minor_id, char *path, size_t capacity)
|
||||
{
|
||||
(void)major_id;
|
||||
(void)minor_id;
|
||||
(void)path;
|
||||
(void)capacity;
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
|
||||
typedef struct vaapi_av1_backend_session
|
||||
{
|
||||
rav1_hw_ops_t native;
|
||||
vaapi_av1_owner_t *retired;
|
||||
vaapi_av1_backend_t *backend;
|
||||
retro_eventcount_t wake;
|
||||
retro_atomic_int_t state, refs;
|
||||
} vaapi_av1_backend_session_t;
|
||||
|
||||
static void vaapi_av1_backend_unref(vaapi_av1_backend_session_t *session)
|
||||
{
|
||||
vaapi_av1_backend_t *backend;
|
||||
if (retro_atomic_fetch_sub_int(&session->refs, 1) != 1)
|
||||
return;
|
||||
backend = session->backend;
|
||||
retro_eventcount_free(&session->wake);
|
||||
free(session);
|
||||
retro_atomic_fetch_sub_int(&backend->active, 1);
|
||||
}
|
||||
|
||||
static void vaapi_av1_backend_release(vaapi_av1_backend_session_t *session)
|
||||
{
|
||||
retro_atomic_store_release_int(&session->state, 1);
|
||||
retro_eventcount_notify(&session->wake);
|
||||
vaapi_av1_backend_unref(session);
|
||||
}
|
||||
|
||||
static void *vaapi_av1_backend_retire(void *opaque)
|
||||
{
|
||||
vaapi_av1_backend_session_t *session = (vaapi_av1_backend_session_t *)opaque;
|
||||
int key;
|
||||
for (;;)
|
||||
{
|
||||
key = retro_eventcount_prepare_wait(&session->wake);
|
||||
if (retro_atomic_load_acquire_int(&session->state))
|
||||
{
|
||||
retro_eventcount_cancel_wait(&session->wake);
|
||||
break;
|
||||
}
|
||||
retro_eventcount_commit_wait(&session->wake, key);
|
||||
}
|
||||
/* This is the cold retirement executor, never a frame/UI caller. */
|
||||
while (!vaapi_av1_owner_retire(session->retired))
|
||||
{
|
||||
key = retro_eventcount_prepare_wait(&session->wake);
|
||||
retro_eventcount_commit_wait_timeout(&session->wake, key, 10000);
|
||||
}
|
||||
vaapi_av1_backend_unref(session);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static int vaapi_av1_backend_decode(void *opaque, const uint8_t *packet,
|
||||
size_t size, const rav1_sequence_t *sequence,
|
||||
const rav1_hw_picture_t *picture)
|
||||
{
|
||||
vaapi_av1_backend_session_t *session = (vaapi_av1_backend_session_t *)opaque;
|
||||
return session->native.decode(
|
||||
session->native.decoder, packet, size, sequence, picture);
|
||||
}
|
||||
|
||||
static int vaapi_av1_backend_output(void *opaque, unsigned slot,
|
||||
const rav1_grain_t *grain, video_hw_frame_t *frame)
|
||||
{
|
||||
vaapi_av1_backend_session_t *session = (vaapi_av1_backend_session_t *)opaque;
|
||||
return session->native.output(session->native.decoder, slot, grain, frame);
|
||||
}
|
||||
|
||||
static void vaapi_av1_backend_close(rav1_hw_ops_t *ops)
|
||||
{
|
||||
vaapi_av1_backend_session_t *session;
|
||||
if (!ops || ops->decode != vaapi_av1_backend_decode || !ops->decoder)
|
||||
return;
|
||||
session = (vaapi_av1_backend_session_t *)ops->decoder;
|
||||
session->retired = vaapi_av1_owner_detach(&session->native);
|
||||
memset(ops, 0, sizeof(*ops));
|
||||
vaapi_av1_backend_release(session);
|
||||
}
|
||||
|
||||
static bool vaapi_av1_backend_open_config(void *opaque,
|
||||
const rav1_sequence_t *header, const rav1_obu_t *sequence_obu,
|
||||
const video_color_metadata_t *container, unsigned slots,
|
||||
rav1_hw_ops_t *ops)
|
||||
{
|
||||
vaapi_av1_backend_t *backend = (vaapi_av1_backend_t *)opaque;
|
||||
vaapi_av1_backend_session_t *session;
|
||||
video_color_metadata_t color;
|
||||
pthread_attr_t attr;
|
||||
pthread_t thread;
|
||||
char path[1024];
|
||||
unsigned major_id, minor_id;
|
||||
int epoch, active, result;
|
||||
(void)sequence_obu;
|
||||
if (!ops || ops->decoder || ops->decode || ops->output || !backend || !header
|
||||
|| slots < 9 || slots > 32 || header->profile || header->monochrome
|
||||
|| header->subsampling_x != 1 || header->subsampling_y != 1
|
||||
|| header->chroma_sample_position > 2
|
||||
|| (header->bit_depth != 8 && header->bit_depth != 10)
|
||||
|| !header->max_width || !header->max_height
|
||||
|| header->max_width > 65536 || header->max_height > 65536
|
||||
|| header->operating_point_count != 1
|
||||
|| header->operating_points[0].idc
|
||||
|| !vaapi_av1_color_resolve(header, container, &color))
|
||||
return false;
|
||||
epoch = retro_atomic_load_acquire_int(&backend->epoch);
|
||||
if (epoch <= 0 || !(epoch & 1))
|
||||
return false;
|
||||
major_id = (unsigned)retro_atomic_load_acquire_int(&backend->major_id);
|
||||
minor_id = (unsigned)retro_atomic_load_acquire_int(&backend->minor_id);
|
||||
if (retro_atomic_load_acquire_int(&backend->epoch) != epoch)
|
||||
return false;
|
||||
for (;;)
|
||||
{
|
||||
active = retro_atomic_load_acquire_int(&backend->active);
|
||||
if (active < 0 || (unsigned)active >= backend->max_sessions)
|
||||
return false;
|
||||
if (retro_atomic_cas_int(&backend->active, active, active + 1))
|
||||
break;
|
||||
}
|
||||
if (!VAAPI_AV1_NODE_PATH(major_id, minor_id, path, sizeof(path))
|
||||
|| retro_atomic_load_acquire_int(&backend->epoch) != epoch)
|
||||
{
|
||||
retro_atomic_fetch_sub_int(&backend->active, 1);
|
||||
return false;
|
||||
}
|
||||
session = (vaapi_av1_backend_session_t *)calloc(1, sizeof(*session));
|
||||
if (!session)
|
||||
{
|
||||
retro_atomic_fetch_sub_int(&backend->active, 1);
|
||||
return false;
|
||||
}
|
||||
session->backend = backend;
|
||||
if (!retro_eventcount_init(&session->wake))
|
||||
{
|
||||
free(session);
|
||||
retro_atomic_fetch_sub_int(&backend->active, 1);
|
||||
return false;
|
||||
}
|
||||
retro_atomic_int_init(&session->state, 0);
|
||||
retro_atomic_int_init(&session->refs, 1);
|
||||
if (pthread_attr_init(&attr))
|
||||
{
|
||||
vaapi_av1_backend_unref(session);
|
||||
return false;
|
||||
}
|
||||
if (pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED))
|
||||
{
|
||||
pthread_attr_destroy(&attr);
|
||||
vaapi_av1_backend_unref(session);
|
||||
return false;
|
||||
}
|
||||
retro_atomic_store_release_int(&session->refs, 2);
|
||||
result = pthread_create(&thread, &attr, vaapi_av1_backend_retire, session);
|
||||
pthread_attr_destroy(&attr);
|
||||
if (result)
|
||||
{
|
||||
retro_atomic_store_release_int(&session->refs, 1);
|
||||
vaapi_av1_backend_unref(session);
|
||||
return false;
|
||||
}
|
||||
if (!vaapi_av1_owner_open(&session->retired, path, major_id, minor_id,
|
||||
header, &color, 32, &session->native))
|
||||
{
|
||||
vaapi_av1_backend_release(session);
|
||||
return false;
|
||||
}
|
||||
if (retro_atomic_load_acquire_int(&backend->epoch) != epoch)
|
||||
{
|
||||
session->retired = vaapi_av1_owner_detach(&session->native);
|
||||
vaapi_av1_backend_release(session);
|
||||
return false;
|
||||
}
|
||||
memset(ops, 0, sizeof(*ops));
|
||||
ops->decoder = session;
|
||||
ops->decode = vaapi_av1_backend_decode;
|
||||
ops->output = vaapi_av1_backend_output;
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool vaapi_av1_backend_open(void *opaque, const rav1_sequence_t *header,
|
||||
unsigned slots, rav1_hw_ops_t *ops)
|
||||
{
|
||||
return vaapi_av1_backend_open_config(opaque, header, NULL, NULL, slots, ops);
|
||||
}
|
||||
#endif
|
||||
|
||||
bool vaapi_av1_backend_init(vaapi_av1_backend_t *backend, unsigned max_sessions)
|
||||
{
|
||||
#if defined(HAVE_THREADS) && defined(RETRO_ATOMIC_LOCK_FREE) \
|
||||
&& defined(RETRO_ATOMIC_HAS_PTR)
|
||||
if (!backend || !max_sessions || max_sessions > 8)
|
||||
return false;
|
||||
memset(backend, 0, sizeof(*backend));
|
||||
backend->max_sessions = max_sessions;
|
||||
retro_atomic_int_init(&backend->epoch, 0);
|
||||
retro_atomic_int_init(&backend->major_id, 0);
|
||||
retro_atomic_int_init(&backend->minor_id, 0);
|
||||
retro_atomic_int_init(&backend->active, 0);
|
||||
backend->offer.user = backend;
|
||||
backend->offer.open = vaapi_av1_backend_open;
|
||||
backend->offer.close = vaapi_av1_backend_close;
|
||||
backend->offer.open_config = vaapi_av1_backend_open_config;
|
||||
return true;
|
||||
#else
|
||||
(void)backend;
|
||||
(void)max_sessions;
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
bool vaapi_av1_backend_publish(vaapi_av1_backend_t *backend, void *owner,
|
||||
unsigned major_id, unsigned minor_id)
|
||||
{
|
||||
int epoch;
|
||||
if (!backend || !backend->offer.open || !owner || major_id > INT_MAX
|
||||
|| minor_id > INT_MAX)
|
||||
return false;
|
||||
epoch = retro_atomic_load_acquire_int(&backend->epoch);
|
||||
if (epoch < 0)
|
||||
return false;
|
||||
if (epoch >= INT_MAX - 2)
|
||||
{
|
||||
retro_atomic_store_release_int(&backend->epoch, -1);
|
||||
backend->owner = NULL;
|
||||
rav1_unset_hw_backend(&backend->offer);
|
||||
return false;
|
||||
}
|
||||
if (epoch & 1)
|
||||
epoch++;
|
||||
retro_atomic_store_release_int(&backend->epoch, epoch);
|
||||
retro_atomic_store_release_int(&backend->major_id, (int)major_id);
|
||||
retro_atomic_store_release_int(&backend->minor_id, (int)minor_id);
|
||||
backend->owner = owner;
|
||||
retro_atomic_store_release_int(&backend->epoch, epoch + 1);
|
||||
rav1_set_hw_backend(&backend->offer);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool vaapi_av1_backend_withdraw(vaapi_av1_backend_t *backend, void *owner)
|
||||
{
|
||||
int epoch;
|
||||
if (!backend || !backend->offer.open || !owner || backend->owner != owner)
|
||||
return false;
|
||||
epoch = retro_atomic_load_acquire_int(&backend->epoch);
|
||||
retro_atomic_store_release_int(
|
||||
&backend->epoch, epoch == INT_MAX ? -1 : epoch + 1);
|
||||
backend->owner = NULL;
|
||||
rav1_unset_hw_backend(&backend->offer);
|
||||
return true;
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
/* Copyright (C) 2026 The RetroArch team.
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later */
|
||||
#ifndef RARCH_VAAPI_AV1_BACKEND_H
|
||||
#define RARCH_VAAPI_AV1_BACKEND_H
|
||||
#include "vaapi_av1_owner.h"
|
||||
#include <retro_atomic.h>
|
||||
RETRO_BEGIN_DECLS
|
||||
|
||||
typedef struct vaapi_av1_backend
|
||||
{
|
||||
rav1_hw_backend_t offer;
|
||||
void *owner;
|
||||
unsigned max_sessions;
|
||||
retro_atomic_int_t epoch, major_id, minor_id, active;
|
||||
} vaapi_av1_backend_t;
|
||||
|
||||
/* Initialize once. Storage and immutable configuration outlive setup readers,
|
||||
* sessions and deferred retirement. Requires threads and lock-free atomics. */
|
||||
bool vaapi_av1_backend_init(
|
||||
vaapi_av1_backend_t *backend, unsigned max_sessions);
|
||||
/* One serialized renderer lifecycle owner. Publish only after native PRIME
|
||||
* import prerequisites and the actual render-device identity are verified. */
|
||||
bool vaapi_av1_backend_publish(vaapi_av1_backend_t *backend, void *owner,
|
||||
unsigned major_id, unsigned minor_id);
|
||||
bool vaapi_av1_backend_withdraw(vaapi_av1_backend_t *backend, void *owner);
|
||||
|
||||
RETRO_END_DECLS
|
||||
#endif
|
||||
@@ -0,0 +1,261 @@
|
||||
/* Copyright (C) 2026 The RetroArch team.
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later */
|
||||
#include "vaapi_av1_decoder.h"
|
||||
#include <formats/image_yuv_blit.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
struct vaapi_av1_decoder
|
||||
{
|
||||
vaapi_av1_export_t pending;
|
||||
vaapi_av1_export_ops_t exports;
|
||||
vaapi_av1_pool_t *pool;
|
||||
vaapi_frame_pool_t *frames;
|
||||
rav1_hw_picture_t picture;
|
||||
video_color_metadata_t container, colors[32];
|
||||
VAFilmGrainStructAV1 grains[32];
|
||||
int references[32], displays[32];
|
||||
unsigned widths[32], heights[32], yuv[32], chroma_position[32];
|
||||
bool closed, failed;
|
||||
};
|
||||
bool vaapi_av1_color_resolve(const rav1_sequence_t *s,
|
||||
const video_color_metadata_t *container, video_color_metadata_t *out)
|
||||
{
|
||||
video_color_metadata_t color;
|
||||
unsigned coded[3], *fields[3], i, range;
|
||||
if (!s || !out || s->color_range > 1 || !s->color_primaries
|
||||
|| !s->transfer_characteristics || !s->matrix_coefficients
|
||||
|| s->color_primaries > 255 || s->transfer_characteristics > 255
|
||||
|| s->matrix_coefficients > 255)
|
||||
return false;
|
||||
memset(&color, 0, sizeof(color));
|
||||
if (container)
|
||||
color = *container;
|
||||
range = s->color_range ? 2 : 1;
|
||||
if (color.range > 2 || (color.range && color.range != range))
|
||||
return false;
|
||||
coded[0] = s->color_primaries;
|
||||
coded[1] = s->transfer_characteristics;
|
||||
coded[2] = s->matrix_coefficients;
|
||||
fields[0] = &color.primaries;
|
||||
fields[1] = &color.transfer;
|
||||
fields[2] = &color.matrix;
|
||||
for (i = 0; i < 3; i++)
|
||||
if (coded[i] != 2)
|
||||
{
|
||||
if (*fields[i] && *fields[i] != 2 && *fields[i] != coded[i])
|
||||
return false;
|
||||
*fields[i] = coded[i];
|
||||
}
|
||||
color.range = range;
|
||||
*out = color;
|
||||
return true;
|
||||
}
|
||||
int vaapi_av1_decoder_poll(vaapi_av1_decoder_t *d)
|
||||
{
|
||||
unsigned i;
|
||||
int result = 1;
|
||||
if (!d)
|
||||
return -1;
|
||||
vaapi_frame_collect(d->frames);
|
||||
if (d->pending.active && !d->pending.ready
|
||||
&& !vaapi_av1_export_close(&d->pending))
|
||||
result = -1;
|
||||
if (d->pool->active)
|
||||
for (i = 0; i < d->pool->session->slots; i++)
|
||||
if (d->pool->transactions[i].native.active)
|
||||
{
|
||||
int status = vaapi_av1_pool_poll(d->pool, i);
|
||||
if (status < 0)
|
||||
{
|
||||
d->failed = true;
|
||||
result = -1;
|
||||
}
|
||||
else if (status > 0 && result > 0)
|
||||
result = 0;
|
||||
}
|
||||
return d->failed ? -1 : result;
|
||||
}
|
||||
static int vaapi_av1_free_surface(
|
||||
uint32_t occupied, unsigned slots, int preferred)
|
||||
{
|
||||
unsigned i;
|
||||
if (preferred >= 0 && (unsigned)preferred < slots
|
||||
&& !(occupied & (UINT32_C(1) << preferred)))
|
||||
return preferred;
|
||||
for (i = 0; i < slots; i++)
|
||||
if (!(occupied & (UINT32_C(1) << i)))
|
||||
return (int)i;
|
||||
return -1;
|
||||
}
|
||||
static int vaapi_av1_decoder_decode(void *opaque, const uint8_t *packet,
|
||||
size_t size, const rav1_sequence_t *sequence, const rav1_hw_picture_t *p)
|
||||
{
|
||||
vaapi_av1_decoder_t *d = (vaapi_av1_decoder_t *)opaque;
|
||||
video_color_metadata_t color;
|
||||
uint32_t occupied, writes;
|
||||
unsigned i, slots, logical;
|
||||
int reference, display, result;
|
||||
if (!d || d->closed || !sequence || !p || p->header.show_existing
|
||||
|| vaapi_av1_decoder_poll(d) < 0 || !d->pool->active
|
||||
|| !vaapi_av1_color_resolve(sequence, &d->container, &color))
|
||||
return -1;
|
||||
slots = d->pool->session->slots;
|
||||
if (p->slot < 0 || (unsigned)p->slot >= slots
|
||||
|| sequence->chroma_sample_position > 2
|
||||
|| sequence->bit_depth != d->pool->session->depth
|
||||
|| sequence->max_width > d->pool->session->width
|
||||
|| sequence->max_height > d->pool->session->height
|
||||
|| (sequence->film_grain_params_present && slots < 18))
|
||||
return -1;
|
||||
logical = (unsigned)p->slot;
|
||||
d->picture = *p;
|
||||
occupied = vaapi_av1_pool_held(d->pool);
|
||||
for (i = 0; i < 8; i++)
|
||||
{
|
||||
int slot = p->reference_slots[i], physical, grained;
|
||||
if (slot == -1)
|
||||
continue;
|
||||
if (slot < 0 || (unsigned)slot >= slots || slot == p->slot)
|
||||
return -1;
|
||||
physical = d->references[slot];
|
||||
grained = d->displays[slot];
|
||||
if (physical < 0 || grained < 0)
|
||||
return -1;
|
||||
d->picture.reference_slots[i] = physical;
|
||||
occupied |= (UINT32_C(1) << physical) | (UINT32_C(1) << grained);
|
||||
}
|
||||
reference = vaapi_av1_free_surface(occupied, slots, d->references[logical]);
|
||||
if (reference < 0)
|
||||
return 1;
|
||||
occupied |= UINT32_C(1) << reference;
|
||||
display = p->grain.params.apply
|
||||
? vaapi_av1_free_surface(occupied, slots, d->displays[logical])
|
||||
: reference;
|
||||
if (display < 0)
|
||||
return 1;
|
||||
d->picture.slot = reference;
|
||||
result = vaapi_av1_pool_decode(
|
||||
d->pool, packet, size, sequence, &d->picture, (unsigned)display);
|
||||
if (!result)
|
||||
{
|
||||
writes = (UINT32_C(1) << reference) | (UINT32_C(1) << display);
|
||||
for (i = 0; i < slots; i++)
|
||||
if ((d->references[i] >= 0
|
||||
&& (writes & (UINT32_C(1) << d->references[i])))
|
||||
|| (d->displays[i] >= 0
|
||||
&& (writes & (UINT32_C(1) << d->displays[i]))))
|
||||
d->references[i] = d->displays[i] = -1;
|
||||
d->references[logical] = reference;
|
||||
d->displays[logical] = display;
|
||||
d->widths[logical] = p->setup.size.upscaled_width;
|
||||
d->heights[logical] = p->setup.size.height;
|
||||
d->colors[logical] = color;
|
||||
d->chroma_position[logical] = sequence->chroma_sample_position;
|
||||
d->grains[logical] = d->pool->work.film_grain_info;
|
||||
d->yuv[logical] = (color.range == 2 ? IMAGE_YUV_FLAG_FULL_RANGE : 0)
|
||||
| (color.matrix == 1 ? IMAGE_YUV_FLAG_BT709 : 0);
|
||||
}
|
||||
else if (result < 0)
|
||||
d->failed = true;
|
||||
return result;
|
||||
}
|
||||
static int vaapi_av1_decoder_output(void *opaque, unsigned logical,
|
||||
const rav1_grain_t *grain, video_hw_frame_t *out)
|
||||
{
|
||||
vaapi_av1_decoder_t *d = (vaapi_av1_decoder_t *)opaque;
|
||||
VAFilmGrainStructAV1 native;
|
||||
unsigned reference, display;
|
||||
if (!d || d->closed || !out || !grain || !d->pool->active
|
||||
|| logical >= d->pool->session->slots || vaapi_av1_decoder_poll(d) < 0
|
||||
|| d->references[logical] < 0 || d->displays[logical] < 0
|
||||
|| !vaapi_av1_grain(&native, grain)
|
||||
|| memcmp(&native, &d->grains[logical], sizeof(native)))
|
||||
return -1;
|
||||
reference = (unsigned)d->references[logical];
|
||||
display = (unsigned)d->displays[logical];
|
||||
if (d->pending.active && d->pending.slot != display)
|
||||
return 1;
|
||||
if (!d->pending.active)
|
||||
{
|
||||
if (!(d->pool->ready & (UINT32_C(1) << display)))
|
||||
return d->pool->transactions[reference].native.active ? 1 : -1;
|
||||
if (d->pool->exports & (UINT32_C(1) << display))
|
||||
return 1;
|
||||
if (!vaapi_av1_export_open(&d->pending, d->pool, display, &d->exports))
|
||||
{
|
||||
d->failed = true;
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
return vaapi_frame_export_position(d->frames, &d->pending, 0, 0,
|
||||
d->widths[logical], d->heights[logical], d->yuv[logical],
|
||||
d->chroma_position[logical], &d->colors[logical], out);
|
||||
}
|
||||
bool vaapi_av1_decoder_open(vaapi_av1_pool_t *pool,
|
||||
const rav1_sequence_t *params, const video_color_metadata_t *color,
|
||||
const vaapi_av1_export_ops_t *exports, rav1_hw_ops_t *ops)
|
||||
{
|
||||
vaapi_av1_decoder_t *d;
|
||||
video_color_metadata_t resolved;
|
||||
unsigned i;
|
||||
if (!pool || !pool->active || pool->broken || !params || !ops || ops->decoder
|
||||
|| ops->decode || ops->output || !exports || !exports->surface
|
||||
|| !exports->cloexec || !exports->close || !exports->identity
|
||||
|| !pool->session->ready || pool->session->closing || params->profile
|
||||
|| params->monochrome || params->chroma_sample_position > 2
|
||||
|| params->subsampling_x != 1 || params->subsampling_y != 1
|
||||
|| params->bit_depth != pool->session->depth || !params->max_width
|
||||
|| !params->max_height || params->max_width > pool->session->width
|
||||
|| params->max_height > pool->session->height
|
||||
|| params->operating_point_count != 1
|
||||
|| params->operating_points[0].idc
|
||||
|| (params->film_grain_params_present && pool->session->slots < 18)
|
||||
|| !vaapi_av1_color_resolve(params, color, &resolved))
|
||||
return false;
|
||||
d = (vaapi_av1_decoder_t *)calloc(1, sizeof(*d));
|
||||
if (!d)
|
||||
return false;
|
||||
d->frames = vaapi_frame_pool_new(pool->session->slots);
|
||||
if (!d->frames)
|
||||
{
|
||||
free(d);
|
||||
return false;
|
||||
}
|
||||
d->pool = pool;
|
||||
d->exports = *exports;
|
||||
if (color)
|
||||
d->container = *color;
|
||||
for (i = 0; i < 32; i++)
|
||||
d->references[i] = d->displays[i] = -1;
|
||||
memset(ops, 0, sizeof(*ops));
|
||||
ops->decoder = d;
|
||||
ops->decode = vaapi_av1_decoder_decode;
|
||||
ops->output = vaapi_av1_decoder_output;
|
||||
return true;
|
||||
}
|
||||
vaapi_av1_decoder_t *vaapi_av1_decoder_detach(rav1_hw_ops_t *ops)
|
||||
{
|
||||
vaapi_av1_decoder_t *d;
|
||||
if (!ops || !ops->decoder || ops->decode != vaapi_av1_decoder_decode
|
||||
|| ops->output != vaapi_av1_decoder_output)
|
||||
return NULL;
|
||||
d = (vaapi_av1_decoder_t *)ops->decoder;
|
||||
d->closed = true;
|
||||
vaapi_frame_pool_close(d->frames);
|
||||
memset(ops, 0, sizeof(*ops));
|
||||
return d;
|
||||
}
|
||||
bool vaapi_av1_decoder_retire(vaapi_av1_decoder_t *d)
|
||||
{
|
||||
if (!d)
|
||||
return true;
|
||||
if (!d->closed)
|
||||
return false;
|
||||
vaapi_av1_decoder_poll(d);
|
||||
if (d->pending.active && !vaapi_av1_export_close(&d->pending))
|
||||
return false;
|
||||
if (!vaapi_frame_pool_free(d->frames))
|
||||
return false;
|
||||
free(d);
|
||||
return true;
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
/* Copyright (C) 2026 The RetroArch team.
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later */
|
||||
#ifndef RARCH_VAAPI_AV1_DECODER_H
|
||||
#define RARCH_VAAPI_AV1_DECODER_H
|
||||
#include "vaapi_frame.h"
|
||||
#include "vaapi_av1_export.h"
|
||||
RETRO_BEGIN_DECLS
|
||||
|
||||
typedef struct vaapi_av1_decoder vaapi_av1_decoder_t;
|
||||
/* Source CICP intersection; mastering/light data stays intact. */
|
||||
bool vaapi_av1_color_resolve(const rav1_sequence_t *sequence,
|
||||
const video_color_metadata_t *container, video_color_metadata_t *out);
|
||||
/* Cold decoder owner; borrowed pool/session/runtime outlive detach/retirement.
|
||||
* Initially empty ops. Copies metadata and creates its opaque carriers.
|
||||
* Failure leaves ops unchanged; no native context ownership is transferred. */
|
||||
bool vaapi_av1_decoder_open(vaapi_av1_pool_t *pool,
|
||||
const rav1_sequence_t *params, const video_color_metadata_t *color,
|
||||
const vaapi_av1_export_ops_t *exports, rav1_hw_ops_t *ops);
|
||||
/* Decoder owner, including paused/closing ticks. One bounded pass over native
|
||||
* completions and renderer returns. 1 decode transactions idle, 0 pending, -1
|
||||
* error. A failed transaction does not prevent collection of other returned
|
||||
* leases. */
|
||||
int vaapi_av1_decoder_poll(vaapi_av1_decoder_t *decoder);
|
||||
/* Decoder owner: stop admission, clear only this adapter's ops, transfer the
|
||||
* adapter to the owner's existing deferred-retirement executor. No wait. */
|
||||
vaapi_av1_decoder_t *vaapi_av1_decoder_detach(rav1_hw_ops_t *ops);
|
||||
/* Detached decoder owner. True consumes adapter after all frame leases return;
|
||||
* false retains it for retry. Pool/native transactions remain caller-owned. */
|
||||
bool vaapi_av1_decoder_retire(vaapi_av1_decoder_t *decoder);
|
||||
RETRO_END_DECLS
|
||||
#endif
|
||||
@@ -0,0 +1,30 @@
|
||||
/* Copyright (C) 2026 The RetroArch team. SPDX-License-Identifier:
|
||||
* GPL-3.0-or-later */
|
||||
#include "vaapi_av1_export.h"
|
||||
static bool vaapi_av1_export_acquire(
|
||||
void *owner, unsigned slot, uint64_t *ticket)
|
||||
{
|
||||
return vaapi_av1_pool_acquire((vaapi_av1_pool_t *)owner, slot, ticket);
|
||||
}
|
||||
static bool vaapi_av1_export_current(
|
||||
void *owner, unsigned slot, uint64_t ticket)
|
||||
{
|
||||
const vaapi_av1_pool_t *pool = (const vaapi_av1_pool_t *)owner;
|
||||
return pool && pool->active && !pool->broken && slot < 32 && ticket
|
||||
&& (pool->exports & (UINT32_C(1) << slot))
|
||||
&& pool->generation[slot] == ticket;
|
||||
}
|
||||
static bool vaapi_av1_export_release(
|
||||
void *owner, unsigned slot, uint64_t ticket)
|
||||
{
|
||||
return vaapi_av1_pool_release((vaapi_av1_pool_t *)owner, slot, ticket);
|
||||
}
|
||||
bool vaapi_av1_export_open(vaapi_av1_export_t *source, vaapi_av1_pool_t *pool,
|
||||
unsigned slot, const vaapi_av1_export_ops_t *ops)
|
||||
{
|
||||
static const vaapi_export_owner_ops_t ownership = {vaapi_av1_export_acquire,
|
||||
vaapi_av1_export_current, vaapi_av1_export_release};
|
||||
if (!pool)
|
||||
return false;
|
||||
return vaapi_export_open(source, pool, pool->session, &ownership, slot, ops);
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
/* Copyright (C) 2026 The RetroArch team. SPDX-License-Identifier:
|
||||
* GPL-3.0-or-later */
|
||||
#ifndef RARCH_VAAPI_AV1_EXPORT_H
|
||||
#define RARCH_VAAPI_AV1_EXPORT_H
|
||||
#include "vaapi_av1_pool.h"
|
||||
#include "vaapi_hevc_export.h"
|
||||
typedef vaapi_hevc_export_t vaapi_av1_export_t;
|
||||
typedef vaapi_hevc_export_ops_t vaapi_av1_export_ops_t;
|
||||
RETRO_BEGIN_DECLS
|
||||
/* Owner only. Completed picture, generation checked; source survives close.
|
||||
* Descriptor validation is not renderer import admission. False needs close. */
|
||||
bool vaapi_av1_export_open(vaapi_av1_export_t *source, vaapi_av1_pool_t *pool,
|
||||
unsigned slot, const vaapi_av1_export_ops_t *ops);
|
||||
static INLINE int vaapi_av1_export_take(
|
||||
vaapi_av1_export_t *source, unsigned object)
|
||||
{
|
||||
return vaapi_hevc_export_take(source, object);
|
||||
}
|
||||
static INLINE bool vaapi_av1_export_close(vaapi_av1_export_t *source)
|
||||
{
|
||||
return vaapi_hevc_export_close(source);
|
||||
}
|
||||
RETRO_END_DECLS
|
||||
#endif
|
||||
@@ -0,0 +1,160 @@
|
||||
/* Copyright (C) 2026 The RetroArch team.
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later */
|
||||
#include "vaapi_av1_owner.h"
|
||||
#include "vaapi_av1_runtime.h"
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
struct vaapi_av1_owner
|
||||
{
|
||||
vaapi_av1_runtime_t runtime;
|
||||
vaapi_av1_pool_t pool;
|
||||
rav1_hw_ops_t decoder;
|
||||
vaapi_av1_decoder_t *retired;
|
||||
VADisplay terminated_display;
|
||||
bool published, closing, terminating;
|
||||
};
|
||||
|
||||
static int vaapi_av1_owner_decode(void *opaque, const uint8_t *packet,
|
||||
size_t size, const rav1_sequence_t *sequence,
|
||||
const rav1_hw_picture_t *picture)
|
||||
{
|
||||
vaapi_av1_owner_t *owner = (vaapi_av1_owner_t *)opaque;
|
||||
if (!owner || owner->closing || !owner->decoder.decode)
|
||||
return -1;
|
||||
return owner->decoder.decode(
|
||||
owner->decoder.decoder, packet, size, sequence, picture);
|
||||
}
|
||||
|
||||
static int vaapi_av1_owner_output(void *opaque, unsigned slot,
|
||||
const rav1_grain_t *grain, video_hw_frame_t *frame)
|
||||
{
|
||||
vaapi_av1_owner_t *owner = (vaapi_av1_owner_t *)opaque;
|
||||
if (!owner || owner->closing || !owner->decoder.output)
|
||||
return -1;
|
||||
return owner->decoder.output(owner->decoder.decoder, slot, grain, frame);
|
||||
}
|
||||
|
||||
bool vaapi_av1_owner_open_with_io(vaapi_av1_owner_t **out, const char *path,
|
||||
unsigned major_id, unsigned minor_id, const rav1_sequence_t *header,
|
||||
const video_color_metadata_t *color, unsigned slots,
|
||||
const vaapi_av1_runtime_io_t *io, const vaapi_av1_export_ops_t *exports,
|
||||
rav1_hw_ops_t *ops)
|
||||
{
|
||||
vaapi_av1_owner_t *owner;
|
||||
vaapi_av1_export_ops_t native_exports;
|
||||
video_color_metadata_t resolved;
|
||||
bool opened;
|
||||
if (!out || *out || !ops || ops->decoder || ops->decode || ops->output
|
||||
|| !path || !*path || !header || header->profile || header->monochrome
|
||||
|| header->subsampling_x != 1 || header->subsampling_y != 1
|
||||
|| (header->bit_depth != 8 && header->bit_depth != 10)
|
||||
|| !header->max_width || !header->max_height
|
||||
|| header->max_width > 65536 || header->max_height > 65536
|
||||
|| header->operating_point_count != 1
|
||||
|| header->operating_points[0].idc
|
||||
|| header->chroma_sample_position > 2
|
||||
|| header->film_grain_params_present > 1
|
||||
|| slots < (header->film_grain_params_present ? 18u : 9u) || slots > 32
|
||||
|| !vaapi_av1_color_resolve(header, color, &resolved))
|
||||
return false;
|
||||
owner = (vaapi_av1_owner_t *)calloc(1, sizeof(*owner));
|
||||
if (!owner)
|
||||
return false;
|
||||
*out = owner;
|
||||
if (io)
|
||||
opened = vaapi_av1_runtime_open_with_io(&owner->runtime, path, major_id,
|
||||
minor_id, header->max_width, header->max_height, header->bit_depth,
|
||||
slots, io);
|
||||
else
|
||||
opened = vaapi_av1_runtime_open(&owner->runtime, path, major_id, minor_id,
|
||||
header->max_width, header->max_height, header->bit_depth, slots);
|
||||
if (!opened
|
||||
|| !vaapi_av1_pool_init(
|
||||
&owner->pool, &owner->runtime.session, &owner->runtime.submit))
|
||||
return false;
|
||||
if (exports)
|
||||
native_exports = *exports;
|
||||
else if (!vaapi_hevc_export_bind(&native_exports, &owner->runtime))
|
||||
return false;
|
||||
if (!vaapi_av1_decoder_open(
|
||||
&owner->pool, header, &resolved, &native_exports, &owner->decoder))
|
||||
return false;
|
||||
memset(ops, 0, sizeof(*ops));
|
||||
ops->decoder = owner;
|
||||
ops->decode = vaapi_av1_owner_decode;
|
||||
ops->output = vaapi_av1_owner_output;
|
||||
owner->published = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool vaapi_av1_owner_open(vaapi_av1_owner_t **out, const char *path,
|
||||
unsigned major_id, unsigned minor_id, const rav1_sequence_t *header,
|
||||
const video_color_metadata_t *color, unsigned slots, rav1_hw_ops_t *ops)
|
||||
{
|
||||
return vaapi_av1_owner_open_with_io(
|
||||
out, path, major_id, minor_id, header, color, slots, NULL, NULL, ops);
|
||||
}
|
||||
|
||||
vaapi_av1_owner_t *vaapi_av1_owner_detach(rav1_hw_ops_t *ops)
|
||||
{
|
||||
vaapi_av1_owner_t *owner;
|
||||
if (!ops || ops->decode != vaapi_av1_owner_decode
|
||||
|| ops->output != vaapi_av1_owner_output || !ops->decoder)
|
||||
return NULL;
|
||||
owner = (vaapi_av1_owner_t *)ops->decoder;
|
||||
owner->closing = true;
|
||||
owner->retired = vaapi_av1_decoder_detach(&owner->decoder);
|
||||
memset(ops, 0, sizeof(*ops));
|
||||
return owner;
|
||||
}
|
||||
|
||||
bool vaapi_av1_owner_retire(vaapi_av1_owner_t *owner)
|
||||
{
|
||||
if (!owner)
|
||||
return true;
|
||||
if (owner->published && !owner->closing)
|
||||
return false;
|
||||
owner->closing = true;
|
||||
if (!owner->terminating)
|
||||
{
|
||||
unsigned i;
|
||||
bool polled = false;
|
||||
if (owner->decoder.decoder)
|
||||
owner->retired = vaapi_av1_decoder_detach(&owner->decoder);
|
||||
if (owner->retired)
|
||||
{
|
||||
if (!vaapi_av1_decoder_retire(owner->retired))
|
||||
return false;
|
||||
owner->retired = NULL;
|
||||
polled = true;
|
||||
}
|
||||
if (owner->pool.exports)
|
||||
return false;
|
||||
if (owner->pool.active && !owner->pool.broken)
|
||||
{
|
||||
bool pending = false;
|
||||
for (i = 0; i < owner->pool.session->slots; i++)
|
||||
if (owner->pool.transactions[i].native.active)
|
||||
{
|
||||
int status = polled ? 1 : vaapi_av1_pool_poll(&owner->pool, i);
|
||||
if (status > 0)
|
||||
pending = true;
|
||||
else if (status < 0)
|
||||
owner->pool.broken = true;
|
||||
}
|
||||
if (!owner->pool.broken
|
||||
&& (pending || !vaapi_av1_pool_close(&owner->pool)))
|
||||
return false;
|
||||
}
|
||||
owner->terminated_display = owner->runtime.display;
|
||||
owner->terminating = true;
|
||||
}
|
||||
if (!vaapi_av1_runtime_close(&owner->runtime))
|
||||
return false;
|
||||
if (owner->pool.active
|
||||
&& !vaapi_av1_pool_terminated(&owner->pool, owner->terminated_display))
|
||||
return false;
|
||||
free(owner);
|
||||
return true;
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
/* Copyright (C) 2026 The RetroArch team.
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later */
|
||||
#ifndef RARCH_VAAPI_AV1_OWNER_H
|
||||
#define RARCH_VAAPI_AV1_OWNER_H
|
||||
#include "vaapi_av1_decoder.h"
|
||||
#include "vaapi_av1_runtime.h"
|
||||
RETRO_BEGIN_DECLS
|
||||
typedef struct vaapi_av1_owner vaapi_av1_owner_t;
|
||||
/* Cold owner; empty out/ops. False can publish a partial owned context.
|
||||
* Transfer that context to retirement; borrowed metadata expires on return. */
|
||||
bool vaapi_av1_owner_open(vaapi_av1_owner_t **out, const char *path,
|
||||
unsigned major_id, unsigned minor_id, const rav1_sequence_t *header,
|
||||
const video_color_metadata_t *color, unsigned slots, rav1_hw_ops_t *ops);
|
||||
bool vaapi_av1_owner_open_with_io(vaapi_av1_owner_t **out, const char *path,
|
||||
unsigned major_id, unsigned minor_id, const rav1_sequence_t *header,
|
||||
const video_color_metadata_t *color, unsigned slots,
|
||||
const vaapi_av1_runtime_io_t *io, const vaapi_av1_export_ops_t *exports,
|
||||
rav1_hw_ops_t *ops);
|
||||
/* Decoder owner: stop admission and clear only this owner's callbacks. */
|
||||
vaapi_av1_owner_t *vaapi_av1_owner_detach(rav1_hw_ops_t *ops);
|
||||
/* Cold executor only. Bounded completion/return pass; never wait for GPU.
|
||||
* False retains all resources. No termination before exports return. */
|
||||
bool vaapi_av1_owner_retire(vaapi_av1_owner_t *owner);
|
||||
RETRO_END_DECLS
|
||||
#endif
|
||||
@@ -0,0 +1,467 @@
|
||||
/* Copyright (C) 2026 The RetroArch team.
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later */
|
||||
#ifndef RARCH_VAAPI_AV1_PICTURE_H
|
||||
#define RARCH_VAAPI_AV1_PICTURE_H
|
||||
#include "vaapi_av1_tiles.h"
|
||||
|
||||
static INLINE int vaapi_av1_clip16(int64_t value)
|
||||
{
|
||||
return value < -32768 ? -32768 : value > 32767 ? 32767 : (int)value;
|
||||
}
|
||||
|
||||
static INLINE int64_t vaapi_av1_round(int64_t value, unsigned shift)
|
||||
{
|
||||
int64_t half = (int64_t)1 << (shift - 1);
|
||||
return value < 0 ? -((-value + half) >> shift) : (value + half) >> shift;
|
||||
}
|
||||
|
||||
/* AV1 7.11.3.6/7: the specified reciprocal table is nearest-integer division.
|
||||
*/
|
||||
static INLINE unsigned vaapi_av1_warp_invalid(const int32_t matrix[6])
|
||||
{
|
||||
unsigned exponent = 0, fraction, divisor, shift, i;
|
||||
int alpha, beta, gamma, delta;
|
||||
int64_t reciprocal, v, w;
|
||||
if (!matrix)
|
||||
return 1;
|
||||
for (i = 2; i < 6; i++)
|
||||
{
|
||||
int center = i == 2 || i == 5 ? 65536 : 0;
|
||||
if (matrix[i] < center - 8192 || matrix[i] > center + 8192)
|
||||
return 1;
|
||||
}
|
||||
if (matrix[2] <= 0)
|
||||
return 1;
|
||||
divisor = (unsigned)matrix[2];
|
||||
while (divisor >> (exponent + 1))
|
||||
exponent++;
|
||||
fraction = divisor - (1u << exponent);
|
||||
if (exponent > 8)
|
||||
fraction = (fraction + (1u << (exponent - 9))) >> (exponent - 8);
|
||||
else
|
||||
fraction <<= 8 - exponent;
|
||||
reciprocal = (4194304u + (256 + fraction) / 2) / (256 + fraction);
|
||||
shift = exponent + 14;
|
||||
v = (int64_t)matrix[4] * 65536;
|
||||
w = (int64_t)matrix[3] * matrix[4];
|
||||
alpha = vaapi_av1_clip16((int64_t)matrix[2] - 65536);
|
||||
beta = vaapi_av1_clip16(matrix[3]);
|
||||
gamma = vaapi_av1_clip16(vaapi_av1_round(v * reciprocal, shift));
|
||||
delta = vaapi_av1_clip16(
|
||||
(int64_t)matrix[5] - vaapi_av1_round(w * reciprocal, shift) - 65536);
|
||||
alpha = (int)vaapi_av1_round(alpha, 6) * 64;
|
||||
beta = (int)vaapi_av1_round(beta, 6) * 64;
|
||||
gamma = (int)vaapi_av1_round(gamma, 6) * 64;
|
||||
delta = (int)vaapi_av1_round(delta, 6) * 64;
|
||||
if (alpha < 0)
|
||||
alpha = -alpha;
|
||||
if (beta < 0)
|
||||
beta = -beta;
|
||||
if (gamma < 0)
|
||||
gamma = -gamma;
|
||||
if (delta < 0)
|
||||
delta = -delta;
|
||||
return 4 * alpha + 7 * beta >= 65536 || 4 * gamma + 4 * delta >= 65536;
|
||||
}
|
||||
|
||||
static INLINE bool vaapi_av1_grain(
|
||||
VAFilmGrainStructAV1 *out, const rav1_grain_t *g)
|
||||
{
|
||||
VAFilmGrainStructAV1 value;
|
||||
unsigned i, plane, positions;
|
||||
if (!out || !g || g->apply > 1
|
||||
|| vaapi_av1_tiles_overlap(out, sizeof(*out), g, sizeof(*g)))
|
||||
return false;
|
||||
memset(&value, 0, sizeof(value));
|
||||
if (!g->apply)
|
||||
{
|
||||
*out = value;
|
||||
return true;
|
||||
}
|
||||
if (g->seed > 65535 || g->scaling_shift < 8 || g->scaling_shift > 11
|
||||
|| g->ar_coeff_lag > 3 || g->ar_coeff_shift < 6
|
||||
|| g->ar_coeff_shift > 9 || g->scale_shift > 3
|
||||
|| g->chroma_scaling_from_luma > 1 || g->overlap > 1
|
||||
|| g->clip_to_restricted_range > 1 || g->num_points[0] > 14
|
||||
|| g->num_points[1] > 10 || g->num_points[2] > 10)
|
||||
return false;
|
||||
value.film_grain_info_fields.bits.apply_grain = 1;
|
||||
value.film_grain_info_fields.bits.chroma_scaling_from_luma
|
||||
= g->chroma_scaling_from_luma;
|
||||
value.film_grain_info_fields.bits.grain_scaling_minus_8
|
||||
= g->scaling_shift - 8;
|
||||
value.film_grain_info_fields.bits.ar_coeff_lag = g->ar_coeff_lag;
|
||||
value.film_grain_info_fields.bits.ar_coeff_shift_minus_6
|
||||
= g->ar_coeff_shift - 6;
|
||||
value.film_grain_info_fields.bits.grain_scale_shift = g->scale_shift;
|
||||
value.film_grain_info_fields.bits.overlap_flag = g->overlap;
|
||||
value.film_grain_info_fields.bits.clip_to_restricted_range
|
||||
= g->clip_to_restricted_range;
|
||||
value.grain_seed = (uint16_t)g->seed;
|
||||
for (plane = 0; plane < 3; plane++)
|
||||
for (i = 1; i < g->num_points[plane]; i++)
|
||||
if (g->point_value[plane][i] <= g->point_value[plane][i - 1])
|
||||
return false;
|
||||
for (i = 0; i < 2; i++)
|
||||
if (g->chroma_mult[i] > 255 || g->chroma_luma_mult[i] > 255
|
||||
|| g->chroma_offset[i] > 511)
|
||||
return false;
|
||||
value.num_y_points = g->num_points[0];
|
||||
value.num_cb_points = g->num_points[1];
|
||||
value.num_cr_points = g->num_points[2];
|
||||
memcpy(value.point_y_value, g->point_value[0], g->num_points[0]);
|
||||
memcpy(value.point_y_scaling, g->point_scaling[0], g->num_points[0]);
|
||||
memcpy(value.point_cb_value, g->point_value[1], g->num_points[1]);
|
||||
memcpy(value.point_cb_scaling, g->point_scaling[1], g->num_points[1]);
|
||||
memcpy(value.point_cr_value, g->point_value[2], g->num_points[2]);
|
||||
memcpy(value.point_cr_scaling, g->point_scaling[2], g->num_points[2]);
|
||||
positions = 2 * g->ar_coeff_lag * (g->ar_coeff_lag + 1);
|
||||
if (g->num_points[0])
|
||||
memcpy(value.ar_coeffs_y, g->ar_coeffs[0], positions);
|
||||
positions += !!g->num_points[0];
|
||||
if (g->chroma_scaling_from_luma || g->num_points[1])
|
||||
memcpy(value.ar_coeffs_cb, g->ar_coeffs[1], positions);
|
||||
if (g->chroma_scaling_from_luma || g->num_points[2])
|
||||
memcpy(value.ar_coeffs_cr, g->ar_coeffs[2], positions);
|
||||
value.cb_mult = (uint8_t)g->chroma_mult[0];
|
||||
value.cb_luma_mult = (uint8_t)g->chroma_luma_mult[0];
|
||||
value.cb_offset = (uint16_t)g->chroma_offset[0];
|
||||
value.cr_mult = (uint8_t)g->chroma_mult[1];
|
||||
value.cr_luma_mult = (uint8_t)g->chroma_luma_mult[1];
|
||||
value.cr_offset = (uint16_t)g->chroma_offset[1];
|
||||
*out = value;
|
||||
return true;
|
||||
}
|
||||
|
||||
/* Effective parsed state, native physical slots, separate grain destination.
|
||||
* Normal tiles only. Failure preserves out; no VA call or allocation. */
|
||||
static INLINE bool vaapi_av1_picture(VADecPictureParameterBufferAV1 *out,
|
||||
const rav1_sequence_t *s, const rav1_hw_picture_t *p,
|
||||
const VASurfaceID *surfaces, unsigned slots, VASurfaceID display)
|
||||
{
|
||||
static const int feature_max[8] = {255, 63, 63, 63, 63, 7, 0, 0};
|
||||
VADecPictureParameterBufferAV1 value;
|
||||
const rav1_picture_t *q;
|
||||
unsigned i, j, cols, rows, sb, a, b, shift, uv_shift = 0;
|
||||
bool restoration = false, chroma_restoration = false;
|
||||
if (!out || !s || !p || !surfaces || slots < 9 || slots > 32 || s->profile
|
||||
|| (s->bit_depth != 8 && s->bit_depth != 10) || s->monochrome
|
||||
|| s->subsampling_x != 1 || s->subsampling_y != 1
|
||||
|| s->operating_point_count != 1 || s->operating_points[0].idc
|
||||
|| !s->max_width || s->max_width > 65536 || !s->max_height
|
||||
|| s->max_height > 65536 || s->matrix_coefficients > 255
|
||||
|| p->header.show_existing || p->header.frame_type > RAV1_FRAME_SWITCH
|
||||
|| p->header.primary_ref_frame > 7
|
||||
|| p->header.frame_is_intra
|
||||
!= (p->header.frame_type == RAV1_FRAME_KEY
|
||||
|| p->header.frame_type == RAV1_FRAME_INTRA_ONLY)
|
||||
|| p->slot < 0 || (unsigned)p->slot >= slots
|
||||
|| surfaces[p->slot] == VA_INVALID_SURFACE || !p->setup.size.width
|
||||
|| !p->setup.size.height
|
||||
|| p->setup.size.width > p->setup.size.upscaled_width
|
||||
|| p->setup.size.upscaled_width > s->max_width
|
||||
|| p->setup.size.height > s->max_height
|
||||
|| p->setup.size.superres_denom < 8
|
||||
|| p->setup.size.superres_denom > 16
|
||||
|| p->setup.interpolation_filter > 4
|
||||
|| vaapi_av1_tiles_overlap(out, sizeof(*out), p, sizeof(*p))
|
||||
|| vaapi_av1_tiles_overlap(out, sizeof(*out), s, sizeof(*s))
|
||||
|| vaapi_av1_tiles_overlap(
|
||||
out, sizeof(*out), surfaces, slots * sizeof(*surfaces)))
|
||||
return false;
|
||||
if (s->enable_order_hint
|
||||
? (!s->order_hint_bits || s->order_hint_bits > 8
|
||||
|| p->header.order_hint >= (1u << s->order_hint_bits))
|
||||
: (s->order_hint_bits || p->header.order_hint))
|
||||
return false;
|
||||
cols = p->tiles.layout.columns;
|
||||
rows = p->tiles.layout.rows;
|
||||
if (!cols || cols > 64 || !rows || rows > 64
|
||||
|| p->tiles.context_update_tile_id >= cols * rows)
|
||||
return false;
|
||||
q = &p->picture;
|
||||
if (q->base_q_idx > 255 || q->tx_mode > 2 || q->delta_q_res > 3
|
||||
|| q->delta_lf_res > 3 || q->qm_y > 15 || q->qm_u > 15 || q->qm_v > 15
|
||||
|| q->loop_filter_sharpness > 7 || q->cdef_bits > 3
|
||||
|| q->cdef_damping > 6 || (q->cdef_damping && q->cdef_damping < 3)
|
||||
|| q->delta_q_y_dc < -64 || q->delta_q_y_dc > 63
|
||||
|| q->delta_q_u_dc < -64 || q->delta_q_u_dc > 63
|
||||
|| q->delta_q_u_ac < -64 || q->delta_q_u_ac > 63
|
||||
|| q->delta_q_v_dc < -64 || q->delta_q_v_dc > 63
|
||||
|| q->delta_q_v_ac < -64 || q->delta_q_v_ac > 63)
|
||||
return false;
|
||||
memset(&value, 0, sizeof(value));
|
||||
value.profile = 0;
|
||||
value.bit_depth_idx = s->bit_depth == 10;
|
||||
value.matrix_coefficients = (uint8_t)s->matrix_coefficients;
|
||||
value.order_hint_bits_minus_1
|
||||
= s->enable_order_hint ? (uint8_t)(s->order_hint_bits - 1) : 0;
|
||||
value.current_frame = surfaces[p->slot];
|
||||
value.current_display_picture = value.current_frame;
|
||||
value.frame_width_minus1 = (uint16_t)(p->setup.size.upscaled_width - 1);
|
||||
value.frame_height_minus1 = (uint16_t)(p->setup.size.height - 1);
|
||||
value.primary_ref_frame = (uint8_t)p->header.primary_ref_frame;
|
||||
value.order_hint = (uint8_t)p->header.order_hint;
|
||||
for (i = 0; i < 8; i++)
|
||||
{
|
||||
int slot = p->reference_slots[i];
|
||||
if (slot < -1 || slot == p->slot
|
||||
|| (slot >= 0
|
||||
&& ((unsigned)slot >= slots
|
||||
|| surfaces[slot] == VA_INVALID_SURFACE
|
||||
|| surfaces[slot] == value.current_frame)))
|
||||
return false;
|
||||
value.ref_frame_map[i] = slot < 0 ? VA_INVALID_SURFACE : surfaces[slot];
|
||||
}
|
||||
for (i = 0; i < 7; i++)
|
||||
if (!p->header.frame_is_intra)
|
||||
{
|
||||
j = p->setup.ref_frame_idx[i];
|
||||
if (j >= 8 || p->reference_slots[j] < 0 || !p->reference_info[j].valid)
|
||||
return false;
|
||||
value.ref_frame_idx[i] = (uint8_t)j;
|
||||
}
|
||||
if (p->grain.params.apply)
|
||||
{
|
||||
if (!s->film_grain_params_present || display == VA_INVALID_SURFACE
|
||||
|| display == value.current_frame)
|
||||
return false;
|
||||
for (i = 0; i < 8; i++)
|
||||
if (display == value.ref_frame_map[i])
|
||||
return false;
|
||||
value.current_display_picture = display;
|
||||
}
|
||||
if (!vaapi_av1_grain(&value.film_grain_info, &p->grain.params))
|
||||
return false;
|
||||
#define VAAPI_AV1_FLAG(field, source) \
|
||||
do \
|
||||
{ \
|
||||
if ((source) > 1) \
|
||||
return false; \
|
||||
value.field = (source); \
|
||||
} while (0)
|
||||
VAAPI_AV1_FLAG(seq_info_fields.fields.still_picture, s->still_picture);
|
||||
VAAPI_AV1_FLAG(seq_info_fields.fields.use_128x128_superblock,
|
||||
s->use_128x128_superblock);
|
||||
VAAPI_AV1_FLAG(
|
||||
seq_info_fields.fields.enable_filter_intra, s->enable_filter_intra);
|
||||
VAAPI_AV1_FLAG(seq_info_fields.fields.enable_intra_edge_filter,
|
||||
s->enable_intra_edge_filter);
|
||||
VAAPI_AV1_FLAG(seq_info_fields.fields.enable_interintra_compound,
|
||||
s->enable_interintra_compound);
|
||||
VAAPI_AV1_FLAG(seq_info_fields.fields.enable_masked_compound,
|
||||
s->enable_masked_compound);
|
||||
VAAPI_AV1_FLAG(
|
||||
seq_info_fields.fields.enable_dual_filter, s->enable_dual_filter);
|
||||
VAAPI_AV1_FLAG(
|
||||
seq_info_fields.fields.enable_order_hint, s->enable_order_hint);
|
||||
VAAPI_AV1_FLAG(seq_info_fields.fields.enable_jnt_comp, s->enable_jnt_comp);
|
||||
VAAPI_AV1_FLAG(seq_info_fields.fields.enable_cdef, s->enable_cdef);
|
||||
VAAPI_AV1_FLAG(seq_info_fields.fields.color_range, s->color_range);
|
||||
VAAPI_AV1_FLAG(seq_info_fields.fields.subsampling_x, s->subsampling_x);
|
||||
VAAPI_AV1_FLAG(seq_info_fields.fields.subsampling_y, s->subsampling_y);
|
||||
VAAPI_AV1_FLAG(seq_info_fields.fields.film_grain_params_present,
|
||||
s->film_grain_params_present);
|
||||
VAAPI_AV1_FLAG(pic_info_fields.bits.show_frame, p->header.show_frame);
|
||||
VAAPI_AV1_FLAG(
|
||||
pic_info_fields.bits.showable_frame, p->header.showable_frame);
|
||||
VAAPI_AV1_FLAG(
|
||||
pic_info_fields.bits.error_resilient_mode, p->header.error_resilient);
|
||||
VAAPI_AV1_FLAG(
|
||||
pic_info_fields.bits.disable_cdf_update, p->header.disable_cdf_update);
|
||||
VAAPI_AV1_FLAG(pic_info_fields.bits.allow_screen_content_tools,
|
||||
p->header.allow_screen_content_tools);
|
||||
VAAPI_AV1_FLAG(
|
||||
pic_info_fields.bits.force_integer_mv, p->header.force_integer_mv);
|
||||
VAAPI_AV1_FLAG(pic_info_fields.bits.allow_intrabc, p->setup.allow_intrabc);
|
||||
VAAPI_AV1_FLAG(pic_info_fields.bits.allow_high_precision_mv,
|
||||
p->setup.allow_high_precision_mv);
|
||||
VAAPI_AV1_FLAG(pic_info_fields.bits.is_motion_mode_switchable,
|
||||
p->setup.motion_mode_switchable);
|
||||
VAAPI_AV1_FLAG(
|
||||
pic_info_fields.bits.use_ref_frame_mvs, p->setup.use_ref_frame_mvs);
|
||||
VAAPI_AV1_FLAG(pic_info_fields.bits.disable_frame_end_update_cdf,
|
||||
p->setup.disable_frame_end_update_cdf);
|
||||
VAAPI_AV1_FLAG(pic_info_fields.bits.uniform_tile_spacing_flag,
|
||||
p->tiles.uniform_spacing);
|
||||
VAAPI_AV1_FLAG(pic_info_fields.bits.allow_warped_motion,
|
||||
p->motion.allow_warped_motion);
|
||||
VAAPI_AV1_FLAG(
|
||||
seg_info.segment_info_fields.bits.enabled, q->segmentation_enabled);
|
||||
VAAPI_AV1_FLAG(seg_info.segment_info_fields.bits.update_map,
|
||||
q->segmentation_update_map);
|
||||
VAAPI_AV1_FLAG(seg_info.segment_info_fields.bits.temporal_update,
|
||||
q->segmentation_temporal_update);
|
||||
VAAPI_AV1_FLAG(seg_info.segment_info_fields.bits.update_data,
|
||||
q->segmentation_update_data);
|
||||
VAAPI_AV1_FLAG(loop_filter_info_fields.bits.mode_ref_delta_enabled,
|
||||
q->loop_filter_delta_enabled);
|
||||
VAAPI_AV1_FLAG(loop_filter_info_fields.bits.mode_ref_delta_update,
|
||||
q->loop_filter_delta_update);
|
||||
VAAPI_AV1_FLAG(qmatrix_fields.bits.using_qmatrix, q->using_qmatrix);
|
||||
VAAPI_AV1_FLAG(
|
||||
mode_control_fields.bits.delta_q_present_flag, q->delta_q_present);
|
||||
VAAPI_AV1_FLAG(
|
||||
mode_control_fields.bits.delta_lf_present_flag, q->delta_lf_present);
|
||||
VAAPI_AV1_FLAG(mode_control_fields.bits.delta_lf_multi, q->delta_lf_multi);
|
||||
VAAPI_AV1_FLAG(
|
||||
mode_control_fields.bits.reference_select, p->motion.reference_select);
|
||||
VAAPI_AV1_FLAG(mode_control_fields.bits.reduced_tx_set_used,
|
||||
p->motion.reduced_tx_set);
|
||||
VAAPI_AV1_FLAG(mode_control_fields.bits.skip_mode_present,
|
||||
p->motion.skip_mode_present);
|
||||
#undef VAAPI_AV1_FLAG
|
||||
value.pic_info_fields.bits.frame_type = p->header.frame_type;
|
||||
value.pic_info_fields.bits.use_superres = p->setup.size.superres_denom != 8;
|
||||
value.superres_scale_denominator = (uint8_t)p->setup.size.superres_denom;
|
||||
value.interp_filter = (uint8_t)p->setup.interpolation_filter;
|
||||
value.tile_cols = (uint8_t)cols;
|
||||
value.tile_rows = (uint8_t)rows;
|
||||
value.context_update_tile_id = (uint16_t)p->tiles.context_update_tile_id;
|
||||
sb = s->use_128x128_superblock ? 32 : 16;
|
||||
if (p->tiles.mi_col_starts[0] || p->tiles.mi_row_starts[0]
|
||||
|| p->tiles.mi_col_starts[cols] != (p->setup.size.width + 7) / 8 * 2
|
||||
|| p->tiles.mi_row_starts[rows] != (p->setup.size.height + 7) / 8 * 2)
|
||||
return false;
|
||||
for (i = 0; i < cols + rows; i++)
|
||||
{
|
||||
j = i < cols ? i : i - cols;
|
||||
a = i < cols ? p->tiles.mi_col_starts[j] : p->tiles.mi_row_starts[j];
|
||||
b = i < cols ? p->tiles.mi_col_starts[j + 1]
|
||||
: p->tiles.mi_row_starts[j + 1];
|
||||
if (b <= a || a % sb || b > 16384)
|
||||
return false;
|
||||
if (i < cols && j + 1 < cols)
|
||||
value.width_in_sbs_minus_1[j]
|
||||
= (uint16_t)((b + sb - 1) / sb - a / sb - 1);
|
||||
else if (i >= cols && j + 1 < rows)
|
||||
value.height_in_sbs_minus_1[j]
|
||||
= (uint16_t)((b + sb - 1) / sb - a / sb - 1);
|
||||
}
|
||||
value.base_qindex = (uint8_t)q->base_q_idx;
|
||||
value.y_dc_delta_q = (int8_t)q->delta_q_y_dc;
|
||||
value.u_dc_delta_q = (int8_t)q->delta_q_u_dc;
|
||||
value.u_ac_delta_q = (int8_t)q->delta_q_u_ac;
|
||||
value.v_dc_delta_q = (int8_t)q->delta_q_v_dc;
|
||||
value.v_ac_delta_q = (int8_t)q->delta_q_v_ac;
|
||||
if (q->using_qmatrix)
|
||||
{
|
||||
value.qmatrix_fields.bits.qm_y = q->qm_y;
|
||||
value.qmatrix_fields.bits.qm_u = q->qm_u;
|
||||
value.qmatrix_fields.bits.qm_v = q->qm_v;
|
||||
}
|
||||
value.mode_control_fields.bits.log2_delta_q_res = q->delta_q_res;
|
||||
value.mode_control_fields.bits.log2_delta_lf_res = q->delta_lf_res;
|
||||
value.mode_control_fields.bits.tx_mode = q->tx_mode;
|
||||
value.loop_filter_info_fields.bits.sharpness_level
|
||||
= q->loop_filter_sharpness;
|
||||
for (i = 0; i < 4; i++)
|
||||
if (q->loop_filter_level[i] > 63)
|
||||
return false;
|
||||
value.filter_level[0] = (uint8_t)q->loop_filter_level[0];
|
||||
value.filter_level[1] = (uint8_t)q->loop_filter_level[1];
|
||||
value.filter_level_u = (uint8_t)q->loop_filter_level[2];
|
||||
value.filter_level_v = (uint8_t)q->loop_filter_level[3];
|
||||
value.cdef_damping_minus_3
|
||||
= q->cdef_damping ? (uint8_t)(q->cdef_damping - 3) : 0;
|
||||
value.cdef_bits = (uint8_t)q->cdef_bits;
|
||||
for (i = 0; i < 8; i++)
|
||||
{
|
||||
if (q->reference.loop_filter_ref_deltas[i] < -64
|
||||
|| q->reference.loop_filter_ref_deltas[i] > 63
|
||||
|| q->reference.segment_enabled[i] > 255 || q->cdef_y_pri[i] > 15
|
||||
|| q->cdef_uv_pri[i] > 15
|
||||
|| (q->cdef_y_sec[i] > 2 && q->cdef_y_sec[i] != 4)
|
||||
|| (q->cdef_uv_sec[i] > 2 && q->cdef_uv_sec[i] != 4))
|
||||
return false;
|
||||
value.ref_deltas[i] = (int8_t)q->reference.loop_filter_ref_deltas[i];
|
||||
value.seg_info.feature_mask[i] = (uint8_t)q->reference.segment_enabled[i];
|
||||
for (j = 0; j < 8; j++)
|
||||
if (q->reference.segment_enabled[i] & (1u << j))
|
||||
{
|
||||
int v = q->reference.segment_data[i][j];
|
||||
if (v > feature_max[j] || v < (j < 5 ? -feature_max[j] : 0))
|
||||
return false;
|
||||
value.seg_info.feature_data[i][j] = (int16_t)v;
|
||||
}
|
||||
value.cdef_y_strengths[i] = (uint8_t)((q->cdef_y_pri[i] << 2)
|
||||
| (q->cdef_y_sec[i] == 4 ? 3 : q->cdef_y_sec[i]));
|
||||
value.cdef_uv_strengths[i] = (uint8_t)((q->cdef_uv_pri[i] << 2)
|
||||
| (q->cdef_uv_sec[i] == 4 ? 3 : q->cdef_uv_sec[i]));
|
||||
}
|
||||
for (i = 0; i < 2; i++)
|
||||
{
|
||||
if (q->reference.loop_filter_mode_deltas[i] < -64
|
||||
|| q->reference.loop_filter_mode_deltas[i] > 63)
|
||||
return false;
|
||||
value.mode_deltas[i] = (int8_t)q->reference.loop_filter_mode_deltas[i];
|
||||
}
|
||||
for (i = 0; i < 3; i++)
|
||||
{
|
||||
if (q->restoration_type[i] > RAV1_RESTORE_SWITCHABLE)
|
||||
return false;
|
||||
restoration |= q->restoration_type[i] != RAV1_RESTORE_NONE;
|
||||
if (i)
|
||||
chroma_restoration |= q->restoration_type[i] != RAV1_RESTORE_NONE;
|
||||
}
|
||||
value.loop_restoration_fields.bits.yframe_restoration_type
|
||||
= q->restoration_type[0];
|
||||
value.loop_restoration_fields.bits.cbframe_restoration_type
|
||||
= q->restoration_type[1];
|
||||
value.loop_restoration_fields.bits.crframe_restoration_type
|
||||
= q->restoration_type[2];
|
||||
if (restoration)
|
||||
{
|
||||
a = q->restoration_size[0];
|
||||
if (a < (s->use_128x128_superblock ? 128u : 64u) || a > 256
|
||||
|| (a & (a - 1)))
|
||||
return false;
|
||||
for (shift = 0; a > 64; a >>= 1)
|
||||
shift++;
|
||||
if (chroma_restoration)
|
||||
{
|
||||
b = q->restoration_size[1];
|
||||
if (b < 32 || b > 256 || b != q->restoration_size[2]
|
||||
|| (b != q->restoration_size[0]
|
||||
&& b * 2 != q->restoration_size[0]))
|
||||
return false;
|
||||
uv_shift = b != q->restoration_size[0];
|
||||
}
|
||||
value.loop_restoration_fields.bits.lr_unit_shift = shift;
|
||||
value.loop_restoration_fields.bits.lr_uv_shift = uv_shift;
|
||||
}
|
||||
for (i = 0; i < 7; i++)
|
||||
{
|
||||
unsigned type = p->motion.global.type[i];
|
||||
if (type > RAV1_GLOBAL_AFFINE)
|
||||
return false;
|
||||
value.wm[i].wmtype = (VAAV1TransformationType)type;
|
||||
if (type == RAV1_GLOBAL_IDENTITY)
|
||||
{
|
||||
value.wm[i].wmmat[2] = value.wm[i].wmmat[5] = 65536;
|
||||
continue;
|
||||
}
|
||||
for (j = 0; j < 6; j++)
|
||||
{
|
||||
int32_t v = p->motion.global.params[i][j];
|
||||
int center = j == 2 || j == 5 ? 65536 : 0;
|
||||
int limit = j < 2 ? 4194304 : 8192;
|
||||
if (v < center - limit || v > center + limit)
|
||||
return false;
|
||||
value.wm[i].wmmat[j] = v;
|
||||
}
|
||||
if ((type == RAV1_GLOBAL_TRANSLATION
|
||||
&& (value.wm[i].wmmat[2] != 65536
|
||||
|| value.wm[i].wmmat[5] != 65536 || value.wm[i].wmmat[3]
|
||||
|| value.wm[i].wmmat[4]))
|
||||
|| (type == RAV1_GLOBAL_ROTZOOM
|
||||
&& (value.wm[i].wmmat[4] != -value.wm[i].wmmat[3]
|
||||
|| value.wm[i].wmmat[5] != value.wm[i].wmmat[2])))
|
||||
return false;
|
||||
value.wm[i].invalid
|
||||
= (uint8_t)vaapi_av1_warp_invalid(p->motion.global.params[i]);
|
||||
}
|
||||
*out = value;
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,187 @@
|
||||
/* Copyright (C) 2026 The RetroArch team.
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later */
|
||||
#include "vaapi_av1_pool.h"
|
||||
#include <limits.h>
|
||||
#include <string.h>
|
||||
bool vaapi_av1_pool_init(vaapi_av1_pool_t *p,
|
||||
const vaapi_av1_session_t *session, const vaapi_av1_submit_ops_t *ops)
|
||||
{
|
||||
vaapi_av1_submit_ops_t native;
|
||||
uint64_t generation;
|
||||
if (!p || p->active || !session || !session->ready || session->closing
|
||||
|| !session->display || session->context == VA_INVALID_ID
|
||||
|| session->slots < 9 || session->slots > 32
|
||||
|| (session->depth != 8 && session->depth != 10) || !ops
|
||||
|| !ops->create || !ops->destroy || !ops->begin || !ops->render
|
||||
|| !ops->end || !ops->sync || p->next_generation == (uint64_t)-1)
|
||||
return false;
|
||||
#if VA_CHECK_VERSION(1, 8, 0)
|
||||
if (session->profile != VAProfileAV1Profile0)
|
||||
return false;
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
native = *ops;
|
||||
generation = p->next_generation;
|
||||
memset(p, 0, sizeof(*p));
|
||||
p->next_generation = generation;
|
||||
p->session = session;
|
||||
p->display = session->display;
|
||||
p->ops = native;
|
||||
p->active = true;
|
||||
return true;
|
||||
}
|
||||
uint32_t vaapi_av1_pool_held(const vaapi_av1_pool_t *p)
|
||||
{
|
||||
uint32_t held;
|
||||
unsigned i;
|
||||
if (!p || !p->active)
|
||||
return 0;
|
||||
held = p->exports;
|
||||
for (i = 0; i < p->session->slots; i++)
|
||||
if (p->transactions[i].native.active)
|
||||
held |= p->reads[i] | p->writes[i];
|
||||
return held;
|
||||
}
|
||||
int vaapi_av1_pool_decode(vaapi_av1_pool_t *p, const uint8_t *packet,
|
||||
size_t size, const rav1_sequence_t *sequence,
|
||||
const rav1_hw_picture_t *picture, unsigned display_slot)
|
||||
{
|
||||
uint32_t reads = 0, writes, reference_bit;
|
||||
unsigned i, slot, count = 0;
|
||||
vaapi_av1_submit_t *transaction;
|
||||
bool accepted;
|
||||
if (!p || !p->active || p->broken || !sequence || !picture || !packet
|
||||
|| !size || size > UINT_MAX || picture->slot < 0
|
||||
|| (unsigned)picture->slot >= p->session->slots
|
||||
|| display_slot >= p->session->slots
|
||||
|| p->next_generation >= (uint64_t)-2)
|
||||
return -1;
|
||||
slot = (unsigned)picture->slot;
|
||||
if (!!picture->grain.params.apply != (slot != display_slot))
|
||||
return -1;
|
||||
reference_bit = UINT32_C(1) << slot;
|
||||
writes = reference_bit | (UINT32_C(1) << display_slot);
|
||||
if (vaapi_av1_pool_held(p) & writes)
|
||||
return 1;
|
||||
for (i = 0; i < 8; i++)
|
||||
{
|
||||
int reference = picture->reference_slots[i];
|
||||
uint32_t bit;
|
||||
if (reference == -1)
|
||||
continue;
|
||||
if (reference < 0 || (unsigned)reference >= p->session->slots)
|
||||
return -1;
|
||||
bit = UINT32_C(1) << reference;
|
||||
if ((writes & bit) || !(p->valid & p->references & bit))
|
||||
return -1;
|
||||
reads |= bit;
|
||||
}
|
||||
if (!vaapi_av1_picture(&p->work, sequence, picture, p->session->surfaces,
|
||||
p->session->slots, p->session->surfaces[display_slot])
|
||||
|| !vaapi_av1_tiles(p->tiles, 4096, &count, packet, size, picture))
|
||||
return -1;
|
||||
transaction = &p->transactions[slot];
|
||||
accepted = vaapi_av1_submit_start(transaction, p->session, &p->ops, &p->work,
|
||||
p->tiles, count, packet, (unsigned)size);
|
||||
if (transaction->native.active)
|
||||
{
|
||||
p->reads[slot] = reads;
|
||||
p->writes[slot] = writes;
|
||||
p->valid &= ~writes;
|
||||
p->ready &= ~writes;
|
||||
p->references &= ~writes;
|
||||
}
|
||||
if (!accepted)
|
||||
{
|
||||
if (transaction->native.active)
|
||||
p->broken = true;
|
||||
return -1;
|
||||
}
|
||||
p->valid |= writes;
|
||||
p->references |= reference_bit;
|
||||
p->generation[slot] = ++p->next_generation;
|
||||
if (display_slot != slot)
|
||||
p->generation[display_slot] = ++p->next_generation;
|
||||
return 0;
|
||||
}
|
||||
int vaapi_av1_pool_poll(vaapi_av1_pool_t *p, unsigned slot)
|
||||
{
|
||||
vaapi_av1_submit_t *transaction;
|
||||
uint32_t writes;
|
||||
int status;
|
||||
if (!p || !p->active || slot >= p->session->slots)
|
||||
return -1;
|
||||
transaction = &p->transactions[slot];
|
||||
if (!transaction->native.active)
|
||||
return (p->ready & (UINT32_C(1) << slot)) ? 0 : -1;
|
||||
writes = p->writes[slot];
|
||||
if (!transaction->native.submitted)
|
||||
{
|
||||
if (vaapi_av1_submit_retire(transaction))
|
||||
p->reads[slot] = p->writes[slot] = 0;
|
||||
return -1;
|
||||
}
|
||||
status = vaapi_av1_submit_poll(transaction);
|
||||
if (status == 1)
|
||||
return 1;
|
||||
if (status < 0)
|
||||
{
|
||||
p->broken = true;
|
||||
p->valid &= ~writes;
|
||||
p->references &= ~writes;
|
||||
p->ready &= ~writes;
|
||||
}
|
||||
else
|
||||
p->ready |= writes;
|
||||
if (vaapi_av1_submit_retire(transaction))
|
||||
p->reads[slot] = p->writes[slot] = 0;
|
||||
return status;
|
||||
}
|
||||
bool vaapi_av1_pool_acquire(
|
||||
vaapi_av1_pool_t *p, unsigned slot, uint64_t *ticket)
|
||||
{
|
||||
uint32_t bit;
|
||||
if (!p || !p->active || p->broken || !ticket || slot >= p->session->slots)
|
||||
return false;
|
||||
bit = UINT32_C(1) << slot;
|
||||
if (!(p->ready & p->valid & bit) || (p->exports & bit)
|
||||
|| !p->generation[slot])
|
||||
return false;
|
||||
p->exports |= bit;
|
||||
*ticket = p->generation[slot];
|
||||
return true;
|
||||
}
|
||||
bool vaapi_av1_pool_release(vaapi_av1_pool_t *p, unsigned slot, uint64_t ticket)
|
||||
{
|
||||
uint32_t bit;
|
||||
if (!p || !p->active || !ticket || slot >= p->session->slots)
|
||||
return false;
|
||||
bit = UINT32_C(1) << slot;
|
||||
if (!(p->exports & bit) || p->generation[slot] != ticket)
|
||||
return false;
|
||||
p->exports &= ~bit;
|
||||
return true;
|
||||
}
|
||||
bool vaapi_av1_pool_close(vaapi_av1_pool_t *p)
|
||||
{
|
||||
uint64_t generation;
|
||||
if (!p || !p->active)
|
||||
return true;
|
||||
if (p->broken || vaapi_av1_pool_held(p))
|
||||
return false;
|
||||
generation = p->next_generation;
|
||||
memset(p, 0, sizeof(*p));
|
||||
p->next_generation = generation;
|
||||
return true;
|
||||
}
|
||||
bool vaapi_av1_pool_terminated(vaapi_av1_pool_t *p, VADisplay display)
|
||||
{
|
||||
uint64_t generation;
|
||||
if (!p || !p->active || !display || p->display != display || p->exports)
|
||||
return false;
|
||||
generation = p->next_generation;
|
||||
memset(p, 0, sizeof(*p));
|
||||
p->next_generation = generation;
|
||||
return true;
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
/* Copyright (C) 2026 The RetroArch team.
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later */
|
||||
#ifndef RARCH_VAAPI_AV1_POOL_H
|
||||
#define RARCH_VAAPI_AV1_POOL_H
|
||||
#include "vaapi_av1_submit.h"
|
||||
RETRO_BEGIN_DECLS
|
||||
typedef struct vaapi_av1_pool
|
||||
{
|
||||
const vaapi_av1_session_t *session;
|
||||
VADisplay display;
|
||||
vaapi_av1_submit_ops_t ops;
|
||||
vaapi_av1_submit_t transactions[32];
|
||||
VADecPictureParameterBufferAV1 work;
|
||||
VASliceParameterBufferAV1 tiles[4096];
|
||||
uint64_t generation[32], next_generation;
|
||||
uint32_t reads[32], writes[32], valid, references, ready, exports;
|
||||
bool active, broken;
|
||||
} vaapi_av1_pool_t;
|
||||
/* Cold owner storage. All slots supplied to decode are physical surfaces. */
|
||||
bool vaapi_av1_pool_init(vaapi_av1_pool_t *pool,
|
||||
const vaapi_av1_session_t *session, const vaapi_av1_submit_ops_t *ops);
|
||||
uint32_t vaapi_av1_pool_held(const vaapi_av1_pool_t *pool);
|
||||
/* 0 accepted, 1 busy without submission, -1 failure. Display is a separate
|
||||
* physical destination for grain, otherwise the picture destination. */
|
||||
int vaapi_av1_pool_decode(vaapi_av1_pool_t *pool, const uint8_t *packet,
|
||||
size_t size, const rav1_sequence_t *sequence,
|
||||
const rav1_hw_picture_t *picture, unsigned display_slot);
|
||||
/* At most two zero-timeout native queries; pending work retains both writes. */
|
||||
int vaapi_av1_pool_poll(vaapi_av1_pool_t *pool, unsigned reference_slot);
|
||||
bool vaapi_av1_pool_acquire(
|
||||
vaapi_av1_pool_t *pool, unsigned slot, uint64_t *ticket);
|
||||
bool vaapi_av1_pool_release(
|
||||
vaapi_av1_pool_t *pool, unsigned slot, uint64_t ticket);
|
||||
bool vaapi_av1_pool_close(vaapi_av1_pool_t *pool);
|
||||
/* Only after matching successful vaTerminate and all exports have returned. */
|
||||
bool vaapi_av1_pool_terminated(vaapi_av1_pool_t *pool, VADisplay display);
|
||||
RETRO_END_DECLS
|
||||
#endif
|
||||
@@ -0,0 +1,24 @@
|
||||
/* Copyright (C) 2026 The RetroArch team.
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later */
|
||||
#ifndef RARCH_VAAPI_AV1_RUNTIME_H
|
||||
#define RARCH_VAAPI_AV1_RUNTIME_H
|
||||
#include "vaapi_hevc_runtime.h"
|
||||
#include "vaapi_av1_session.h"
|
||||
typedef vaapi_hevc_runtime_t vaapi_av1_runtime_t;
|
||||
typedef vaapi_hevc_runtime_io_t vaapi_av1_runtime_io_t;
|
||||
RETRO_BEGIN_DECLS
|
||||
/* Cold owner; renderer DRM identity must match the opened render node.
|
||||
* Close even after failed startup, retaining storage when close refuses. */
|
||||
bool vaapi_av1_runtime_open(vaapi_av1_runtime_t *runtime, const char *path,
|
||||
unsigned major_id, unsigned minor_id, unsigned width, unsigned height,
|
||||
unsigned depth, unsigned slots);
|
||||
bool vaapi_av1_runtime_open_with_io(vaapi_av1_runtime_t *runtime,
|
||||
const char *path, unsigned major_id, unsigned minor_id, unsigned width,
|
||||
unsigned height, unsigned depth, unsigned slots,
|
||||
const vaapi_av1_runtime_io_t *io);
|
||||
static INLINE bool vaapi_av1_runtime_close(vaapi_av1_runtime_t *runtime)
|
||||
{
|
||||
return vaapi_hevc_runtime_close(runtime);
|
||||
}
|
||||
RETRO_END_DECLS
|
||||
#endif
|
||||
@@ -0,0 +1,27 @@
|
||||
/* Copyright (C) 2026 The RetroArch team. SPDX-License-Identifier:
|
||||
* GPL-3.0-or-later */
|
||||
#ifndef RARCH_VAAPI_AV1_SESSION_H
|
||||
#define RARCH_VAAPI_AV1_SESSION_H
|
||||
#include "vaapi_hevc_session.h"
|
||||
#include <retro_inline.h>
|
||||
/* Share native VA operations and surface retirement, not codec parameters. */
|
||||
typedef vaapi_hevc_session_t vaapi_av1_session_t;
|
||||
typedef vaapi_hevc_session_ops_t vaapi_av1_session_ops_t;
|
||||
RETRO_BEGIN_DECLS
|
||||
/* Cold decoder owner, initialized borrowed display. Profile 0, 8/10-bit 420.
|
||||
* Nine to thirty-two surfaces. Older VA SDKs refuse without native calls.
|
||||
* Failed startup can retain native handles; close even after false. */
|
||||
bool vaapi_av1_session_open(vaapi_av1_session_t *session, VADisplay display,
|
||||
const vaapi_av1_session_ops_t *ops, unsigned width, unsigned height,
|
||||
unsigned depth, unsigned slots);
|
||||
static INLINE bool vaapi_av1_session_close(vaapi_av1_session_t *session)
|
||||
{
|
||||
return vaapi_hevc_session_close(session);
|
||||
}
|
||||
static INLINE bool vaapi_av1_session_terminated(
|
||||
vaapi_av1_session_t *session, VADisplay display)
|
||||
{
|
||||
return vaapi_hevc_session_terminated(session, display);
|
||||
}
|
||||
RETRO_END_DECLS
|
||||
#endif
|
||||
@@ -0,0 +1,184 @@
|
||||
/* Copyright (C) 2026 The RetroArch team.
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later */
|
||||
#include "vaapi_av1_submit.h"
|
||||
#include <string.h>
|
||||
|
||||
bool vaapi_av1_submit_start(vaapi_av1_submit_t *t, const vaapi_av1_session_t *s,
|
||||
const vaapi_av1_submit_ops_t *ops,
|
||||
const VADecPictureParameterBufferAV1 *p,
|
||||
const VASliceParameterBufferAV1 *tiles, unsigned count,
|
||||
const uint8_t *packet, unsigned size)
|
||||
{
|
||||
vaapi_av1_submit_ops_t native;
|
||||
vaapi_hevc_submit_t *n;
|
||||
const void *data[3];
|
||||
unsigned bytes[3], elements[3], i, j, end = 0;
|
||||
bool reference_found = false, display_found = false;
|
||||
static const VABufferType types[3] = {VAPictureParameterBufferType,
|
||||
VASliceParameterBufferType, VASliceDataBufferType};
|
||||
if (!t || t->native.active || !s || !s->ready || s->closing || !s->display
|
||||
|| s->context == VA_INVALID_ID || s->slots < 9 || s->slots > 32 || !ops
|
||||
|| !ops->create || !ops->destroy || !ops->begin || !ops->render
|
||||
|| !ops->end || !ops->sync || !p || !tiles || !packet || !size
|
||||
|| !count || count > 4096 || p->profile
|
||||
|| (s->depth != 8 && s->depth != 10)
|
||||
|| p->bit_depth_idx != (s->depth == 10)
|
||||
|| p->frame_width_minus1 + 1u > s->width
|
||||
|| p->frame_height_minus1 + 1u > s->height || !p->tile_cols
|
||||
|| p->tile_cols > 64 || !p->tile_rows || p->tile_rows > 64
|
||||
|| p->context_update_tile_id >= count
|
||||
|| count != (unsigned)p->tile_cols * p->tile_rows
|
||||
|| p->pic_info_fields.bits.large_scale_tile
|
||||
|| p->current_frame == VA_INVALID_SURFACE
|
||||
|| p->current_display_picture == VA_INVALID_SURFACE)
|
||||
return false;
|
||||
#if VA_CHECK_VERSION(1, 8, 0)
|
||||
if (s->profile != VAProfileAV1Profile0)
|
||||
return false;
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
if (vaapi_av1_tiles_overlap(t, sizeof(*t), s, sizeof(*s))
|
||||
|| vaapi_av1_tiles_overlap(t, sizeof(*t), ops, sizeof(*ops))
|
||||
|| vaapi_av1_tiles_overlap(t, sizeof(*t), p, sizeof(*p))
|
||||
|| vaapi_av1_tiles_overlap(
|
||||
t, sizeof(*t), tiles, count * sizeof(*tiles))
|
||||
|| vaapi_av1_tiles_overlap(t, sizeof(*t), packet, size))
|
||||
return false;
|
||||
if (!!p->film_grain_info.film_grain_info_fields.bits.apply_grain
|
||||
!= (p->current_display_picture != p->current_frame))
|
||||
return false;
|
||||
for (i = 0; i < s->slots; i++)
|
||||
{
|
||||
reference_found |= s->surfaces[i] == p->current_frame;
|
||||
display_found |= s->surfaces[i] == p->current_display_picture;
|
||||
}
|
||||
if (!reference_found || !display_found)
|
||||
return false;
|
||||
for (i = 0; i < 8; i++)
|
||||
{
|
||||
VASurfaceID id = p->ref_frame_map[i];
|
||||
bool found = false;
|
||||
if (id == VA_INVALID_SURFACE)
|
||||
continue;
|
||||
if (id == p->current_frame || id == p->current_display_picture)
|
||||
return false;
|
||||
for (j = 0; j < s->slots; j++)
|
||||
found |= id == s->surfaces[j];
|
||||
if (!found)
|
||||
return false;
|
||||
}
|
||||
if (p->pic_info_fields.bits.frame_type == RAV1_FRAME_INTER
|
||||
|| p->pic_info_fields.bits.frame_type == RAV1_FRAME_SWITCH)
|
||||
for (i = 0; i < 7; i++)
|
||||
if (p->ref_frame_idx[i] >= 8
|
||||
|| p->ref_frame_map[p->ref_frame_idx[i]] == VA_INVALID_SURFACE)
|
||||
return false;
|
||||
for (i = 0; i < count; i++)
|
||||
{
|
||||
unsigned at = tiles[i].slice_data_offset;
|
||||
unsigned length = tiles[i].slice_data_size;
|
||||
if (!length || at < end || at > size || length > size - at
|
||||
|| tiles[i].slice_data_flag != VA_SLICE_DATA_FLAG_ALL
|
||||
|| tiles[i].tile_row != i / p->tile_cols
|
||||
|| tiles[i].tile_column != i % p->tile_cols)
|
||||
return false;
|
||||
end = at + length;
|
||||
}
|
||||
data[0] = p;
|
||||
data[1] = tiles;
|
||||
data[2] = packet;
|
||||
bytes[0] = sizeof(*p);
|
||||
bytes[1] = sizeof(*tiles);
|
||||
bytes[2] = size;
|
||||
elements[0] = elements[2] = 1;
|
||||
elements[1] = count;
|
||||
native = *ops;
|
||||
memset(t, 0, sizeof(*t));
|
||||
n = &t->native;
|
||||
n->ops = native;
|
||||
n->display = s->display;
|
||||
n->surface = p->current_frame;
|
||||
n->active = true;
|
||||
t->display_surface = p->current_display_picture;
|
||||
t->display_complete = t->display_surface == n->surface;
|
||||
for (i = 0; i < 5; i++)
|
||||
n->buffers[i] = VA_INVALID_ID;
|
||||
for (i = 0; i < 3; i++)
|
||||
{
|
||||
n->error = n->ops.create(n->display, s->context, types[i], bytes[i],
|
||||
elements[i], (void *)data[i], &n->buffers[n->count]);
|
||||
n->count++;
|
||||
for (j = 0; j + 1 < n->count; j++)
|
||||
if (n->buffers[j] == n->buffers[n->count - 1])
|
||||
{
|
||||
n->buffers[n->count - 1] = VA_INVALID_ID;
|
||||
n->error = VA_STATUS_ERROR_OPERATION_FAILED;
|
||||
return false;
|
||||
}
|
||||
if (n->error != VA_STATUS_SUCCESS)
|
||||
return false;
|
||||
if (n->buffers[n->count - 1] == VA_INVALID_ID)
|
||||
{
|
||||
n->error = VA_STATUS_ERROR_OPERATION_FAILED;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
n->poisoned = true;
|
||||
n->error = n->ops.begin(n->display, s->context, n->surface);
|
||||
if (n->error != VA_STATUS_SUCCESS)
|
||||
return false;
|
||||
n->error = n->ops.render(n->display, s->context, n->buffers, (int)n->count);
|
||||
if (n->error != VA_STATUS_SUCCESS)
|
||||
return false;
|
||||
n->error = n->ops.end(n->display, s->context);
|
||||
if (n->error != VA_STATUS_SUCCESS)
|
||||
return false;
|
||||
n->submitted = true;
|
||||
n->poisoned = false;
|
||||
return true;
|
||||
}
|
||||
int vaapi_av1_submit_poll(vaapi_av1_submit_t *t)
|
||||
{
|
||||
int result;
|
||||
vaapi_hevc_submit_t *n;
|
||||
if (!t)
|
||||
return -1;
|
||||
n = &t->native;
|
||||
result = vaapi_hevc_submit_poll(n);
|
||||
if (result == 1 || !n->complete || n->poisoned)
|
||||
return result;
|
||||
if (!t->display_complete)
|
||||
{
|
||||
n->error = n->ops.sync(n->display, t->display_surface, 0);
|
||||
if (n->error == VA_STATUS_ERROR_TIMEDOUT)
|
||||
return 1;
|
||||
if (n->error != VA_STATUS_SUCCESS
|
||||
&& n->error != VA_STATUS_ERROR_DECODING_ERROR)
|
||||
{
|
||||
n->poisoned = true;
|
||||
return -1;
|
||||
}
|
||||
t->display_completion = n->error;
|
||||
t->display_complete = true;
|
||||
}
|
||||
return result || t->display_completion != VA_STATUS_SUCCESS ? -1 : 0;
|
||||
}
|
||||
bool vaapi_av1_submit_retire(vaapi_av1_submit_t *t)
|
||||
{
|
||||
if (!t)
|
||||
return true;
|
||||
if (t->native.submitted && !t->display_complete)
|
||||
return false;
|
||||
if (!vaapi_hevc_submit_retire(&t->native))
|
||||
return false;
|
||||
memset(t, 0, sizeof(*t));
|
||||
return true;
|
||||
}
|
||||
bool vaapi_av1_submit_terminated(vaapi_av1_submit_t *t, VADisplay display)
|
||||
{
|
||||
if (!t || !vaapi_hevc_submit_terminated(&t->native, display))
|
||||
return false;
|
||||
memset(t, 0, sizeof(*t));
|
||||
return true;
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
/* Copyright (C) 2026 The RetroArch team.
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later */
|
||||
#ifndef RARCH_VAAPI_AV1_SUBMIT_H
|
||||
#define RARCH_VAAPI_AV1_SUBMIT_H
|
||||
#include "vaapi_hevc_submit.h"
|
||||
#include "vaapi_av1_session.h"
|
||||
#include "vaapi_av1_picture.h"
|
||||
typedef vaapi_hevc_submit_ops_t vaapi_av1_submit_ops_t;
|
||||
typedef struct vaapi_av1_submit
|
||||
{
|
||||
vaapi_hevc_submit_t native;
|
||||
VASurfaceID display_surface;
|
||||
VAStatus display_completion;
|
||||
bool display_complete;
|
||||
} vaapi_av1_submit_t;
|
||||
RETRO_BEGIN_DECLS
|
||||
/* Decoder owner; false may retain native ownership. Both grain destinations
|
||||
* must complete before retirement. No completion check waits. */
|
||||
bool vaapi_av1_submit_start(vaapi_av1_submit_t *transaction,
|
||||
const vaapi_av1_session_t *session, const vaapi_av1_submit_ops_t *ops,
|
||||
const VADecPictureParameterBufferAV1 *picture,
|
||||
const VASliceParameterBufferAV1 *tiles, unsigned count,
|
||||
const uint8_t *packet, unsigned size);
|
||||
int vaapi_av1_submit_poll(vaapi_av1_submit_t *transaction);
|
||||
bool vaapi_av1_submit_retire(vaapi_av1_submit_t *transaction);
|
||||
bool vaapi_av1_submit_terminated(
|
||||
vaapi_av1_submit_t *transaction, VADisplay display);
|
||||
RETRO_END_DECLS
|
||||
#endif
|
||||
@@ -0,0 +1,81 @@
|
||||
/* Copyright (C) 2026 The RetroArch team.
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later */
|
||||
#ifndef RARCH_VAAPI_AV1_TILES_H
|
||||
#define RARCH_VAAPI_AV1_TILES_H
|
||||
#include <va/va.h>
|
||||
#include <va/va_dec_av1.h>
|
||||
#include <formats/rav1_hw.h>
|
||||
|
||||
static INLINE bool vaapi_av1_tiles_overlap(
|
||||
const void *a, size_t an, const void *b, size_t bn)
|
||||
{
|
||||
uintptr_t aa = (uintptr_t)a, bb = (uintptr_t)b;
|
||||
return aa <= bb ? bb - aa < an : aa - bb < bn;
|
||||
}
|
||||
|
||||
/* Whole FRAME OBU, normal tiles only. Caller storage is independent of input.
|
||||
* Refusal preserves output/count. Success writes only count native records.
|
||||
* No packet copy, native call, allocation or borrowed pointer in the records.
|
||||
*/
|
||||
static INLINE bool vaapi_av1_tiles(VASliceParameterBufferAV1 *out,
|
||||
unsigned capacity, unsigned *count, const uint8_t *packet, size_t size,
|
||||
const rav1_hw_picture_t *picture)
|
||||
{
|
||||
rav1_obu_t obu;
|
||||
rav1_tile_group_t group;
|
||||
rav1_tile_t tile;
|
||||
const rav1_tile_layout_t *layout;
|
||||
size_t offset = 0, base, bytes;
|
||||
uintptr_t address;
|
||||
unsigned total, i;
|
||||
if (!out || !count || !packet || !size || size > UINT32_MAX || !picture
|
||||
|| !capacity || capacity > 4096 || picture->header.show_existing)
|
||||
return false;
|
||||
layout = &picture->tiles.layout;
|
||||
if (!layout->columns || layout->columns > 64 || !layout->rows
|
||||
|| layout->rows > 64 || layout->size_bytes > 4)
|
||||
return false;
|
||||
total = layout->columns * layout->rows;
|
||||
bytes = (size_t)capacity * sizeof(*out);
|
||||
if (total > capacity || picture->tiles.context_update_tile_id >= total
|
||||
|| vaapi_av1_tiles_overlap(out, bytes, packet, size)
|
||||
|| vaapi_av1_tiles_overlap(out, bytes, picture, sizeof(*picture))
|
||||
|| vaapi_av1_tiles_overlap(out, bytes, count, sizeof(*count))
|
||||
|| vaapi_av1_tiles_overlap(count, sizeof(*count), packet, size)
|
||||
|| vaapi_av1_tiles_overlap(
|
||||
count, sizeof(*count), picture, sizeof(*picture))
|
||||
|| rav1_obu_next(&obu, packet, size, &offset, true) != 1
|
||||
|| offset != size || obu.type != 6 || obu.temporal_id
|
||||
|| obu.spatial_id)
|
||||
return false;
|
||||
address = (uintptr_t)picture->tile_group.data;
|
||||
if (address < (uintptr_t)obu.payload || address - (uintptr_t)packet > size)
|
||||
return false;
|
||||
base = (size_t)(address - (uintptr_t)packet);
|
||||
if (picture->tile_group.size != size - base || picture->tile_group.first
|
||||
|| picture->tile_group.next || picture->tile_group.last != total - 1
|
||||
|| picture->tile_group.columns != layout->columns
|
||||
|| picture->tile_group.size_bytes != layout->size_bytes)
|
||||
return false;
|
||||
/* Reparse all spans before publishing any native record. */
|
||||
if (!rav1_tile_group_init(&group, picture->tile_group.data,
|
||||
picture->tile_group.size, layout, true)
|
||||
|| group.offset != picture->tile_group.offset)
|
||||
return false;
|
||||
for (i = 0; i < total; i++)
|
||||
{
|
||||
size_t at;
|
||||
if (rav1_tile_group_next(&tile, &group) != 1)
|
||||
return false;
|
||||
at = base + (size_t)(tile.data - group.data);
|
||||
memset(&out[i], 0, sizeof(out[i]));
|
||||
out[i].slice_data_size = (uint32_t)tile.size;
|
||||
out[i].slice_data_offset = (uint32_t)at;
|
||||
out[i].slice_data_flag = VA_SLICE_DATA_FLAG_ALL;
|
||||
out[i].tile_row = (uint16_t)tile.row;
|
||||
out[i].tile_column = (uint16_t)tile.column;
|
||||
}
|
||||
*count = total;
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,269 @@
|
||||
/* Copyright (C) 2026 The RetroArch team.
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later */
|
||||
#include "vaapi_frame.h"
|
||||
#include "vaapi_hevc_export.h"
|
||||
#include <retro_atomic.h>
|
||||
#include <formats/image.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
typedef struct vaapi_frame_slot
|
||||
{
|
||||
retro_atomic_int_t state; /* free, pending, claimed, mutation, returned */
|
||||
vaapi_hevc_export_t source;
|
||||
vaapi_frame_view_t view;
|
||||
void *pool;
|
||||
uint64_t generation, ticket;
|
||||
unsigned slot;
|
||||
} vaapi_frame_slot_t;
|
||||
|
||||
struct vaapi_frame_pool
|
||||
{
|
||||
vaapi_frame_slot_t slots[32];
|
||||
unsigned count;
|
||||
bool closed;
|
||||
};
|
||||
|
||||
vaapi_frame_pool_t *vaapi_frame_pool_new(unsigned count)
|
||||
{
|
||||
vaapi_frame_pool_t *pool;
|
||||
unsigned i;
|
||||
if (!count || count > 32)
|
||||
return NULL;
|
||||
#ifndef RETRO_ATOMIC_LOCK_FREE
|
||||
return NULL;
|
||||
#endif
|
||||
pool = (vaapi_frame_pool_t *)calloc(1, sizeof(*pool));
|
||||
if (!pool)
|
||||
return NULL;
|
||||
pool->count = count;
|
||||
for (i = 0; i < count; i++)
|
||||
retro_atomic_int_init(&pool->slots[i].state, 0);
|
||||
return pool;
|
||||
}
|
||||
|
||||
void vaapi_frame_pool_close(vaapi_frame_pool_t *pool)
|
||||
{
|
||||
if (pool)
|
||||
pool->closed = true;
|
||||
}
|
||||
|
||||
bool vaapi_frame_pool_free(vaapi_frame_pool_t *pool)
|
||||
{
|
||||
unsigned i;
|
||||
if (!pool)
|
||||
return true;
|
||||
if (!pool->closed)
|
||||
return false;
|
||||
for (i = 0; i < pool->count; i++)
|
||||
if (retro_atomic_load_acquire_int(&pool->slots[i].state))
|
||||
return false;
|
||||
free(pool);
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool vaapi_frame_put(void *owner, uint64_t index, uint64_t generation,
|
||||
vaapi_hevc_export_t *source, int next)
|
||||
{
|
||||
vaapi_frame_pool_t *pool = (vaapi_frame_pool_t *)owner;
|
||||
vaapi_frame_slot_t *slot;
|
||||
if (!pool || index >= pool->count)
|
||||
return false;
|
||||
slot = &pool->slots[(unsigned)index];
|
||||
if (!retro_atomic_cas_int(&slot->state, 2, 3))
|
||||
return false;
|
||||
if (slot->generation != generation
|
||||
|| (source
|
||||
&& (slot->source.active || source == &slot->source
|
||||
|| !source->active || !source->ready
|
||||
|| vaapi_export_owner(source) != slot->pool || source->slot != slot->slot
|
||||
|| source->ticket != slot->ticket))
|
||||
|| (!source && !slot->source.active))
|
||||
{
|
||||
retro_atomic_store_release_int(&slot->state, 2);
|
||||
return false;
|
||||
}
|
||||
if (source)
|
||||
{
|
||||
slot->source = *source;
|
||||
memset(source, 0, sizeof(*source));
|
||||
}
|
||||
retro_atomic_store_release_int(&slot->state, next);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool vaapi_frame_unclaim(void *owner, uint64_t index, uint64_t generation,
|
||||
vaapi_hevc_export_t *source)
|
||||
{
|
||||
return vaapi_frame_put(owner, index, generation, source, 1);
|
||||
}
|
||||
|
||||
bool vaapi_frame_finish(void *owner, uint64_t index, uint64_t generation,
|
||||
vaapi_hevc_export_t *source)
|
||||
{
|
||||
return vaapi_frame_put(owner, index, generation, source, 4);
|
||||
}
|
||||
|
||||
bool vaapi_frame_take(void *owner, uint64_t index, uint64_t generation,
|
||||
vaapi_hevc_export_t *out)
|
||||
{
|
||||
vaapi_frame_pool_t *pool = (vaapi_frame_pool_t *)owner;
|
||||
vaapi_frame_slot_t *slot;
|
||||
if (!pool || index >= pool->count || !out || out->active)
|
||||
return false;
|
||||
slot = &pool->slots[(unsigned)index];
|
||||
if (!retro_atomic_cas_int(&slot->state, 2, 3))
|
||||
return false;
|
||||
if (slot->generation != generation || !slot->source.active
|
||||
|| out == &slot->source)
|
||||
{
|
||||
retro_atomic_store_release_int(&slot->state, 2);
|
||||
return false;
|
||||
}
|
||||
*out = slot->source;
|
||||
memset(&slot->source, 0, sizeof(slot->source));
|
||||
retro_atomic_store_release_int(&slot->state, 2);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool vaapi_frame_claim(void *owner, uint64_t index, uint64_t generation,
|
||||
uint64_t image, vaapi_frame_view_t *view)
|
||||
{
|
||||
vaapi_frame_pool_t *pool = (vaapi_frame_pool_t *)owner;
|
||||
vaapi_frame_slot_t *slot;
|
||||
if (!pool || index >= pool->count || !view)
|
||||
return false;
|
||||
slot = &pool->slots[(unsigned)index];
|
||||
if (image != (uint64_t)(uintptr_t)slot
|
||||
|| !retro_atomic_cas_int(&slot->state, 1, 2))
|
||||
return false;
|
||||
if (slot->generation != generation)
|
||||
{
|
||||
retro_atomic_store_release_int(&slot->state, 1);
|
||||
return false;
|
||||
}
|
||||
*view = slot->view;
|
||||
return true;
|
||||
}
|
||||
|
||||
static void vaapi_frame_abandon(
|
||||
void *owner, uint64_t index, uint64_t generation)
|
||||
{
|
||||
vaapi_frame_pool_t *pool = (vaapi_frame_pool_t *)owner;
|
||||
vaapi_frame_slot_t *slot;
|
||||
if (!pool || index >= pool->count)
|
||||
return;
|
||||
slot = &pool->slots[(unsigned)index];
|
||||
if (!retro_atomic_cas_int(&slot->state, 1, 3))
|
||||
return;
|
||||
if (slot->generation != generation)
|
||||
{
|
||||
retro_atomic_store_release_int(&slot->state, 1);
|
||||
return;
|
||||
}
|
||||
retro_atomic_store_release_int(&slot->state, 4);
|
||||
}
|
||||
|
||||
bool vaapi_frame_callback(void (*abandon)(void *, uint64_t, uint64_t))
|
||||
{
|
||||
return abandon == vaapi_frame_abandon;
|
||||
}
|
||||
|
||||
int vaapi_frame_export(vaapi_frame_pool_t *pool, vaapi_hevc_export_t *source,
|
||||
unsigned x, unsigned y, unsigned width, unsigned height, unsigned yuv,
|
||||
const video_color_metadata_t *color, video_hw_frame_t *out)
|
||||
{
|
||||
return vaapi_frame_export_position(
|
||||
pool, source, x, y, width, height, yuv, 0, color, out);
|
||||
}
|
||||
|
||||
int vaapi_frame_export_position(vaapi_frame_pool_t *pool,
|
||||
vaapi_hevc_export_t *source, unsigned x, unsigned y, unsigned width,
|
||||
unsigned height, unsigned yuv, unsigned chroma_position,
|
||||
const video_color_metadata_t *color, video_hw_frame_t *out)
|
||||
{
|
||||
vaapi_frame_slot_t *slot;
|
||||
video_hw_frame_t frame;
|
||||
unsigned i;
|
||||
if (chroma_position > 2 || !pool || pool->closed || !source
|
||||
|| !source->active || !source->ready || !vaapi_export_owner(source)
|
||||
|| !source->ticket || !out || !width || !height || ((x | y) & 1)
|
||||
|| x >= source->descriptor.width || y >= source->descriptor.height
|
||||
|| width > source->descriptor.width - x
|
||||
|| height > source->descriptor.height - y
|
||||
|| (source->descriptor.fourcc != VA_FOURCC_NV12
|
||||
&& source->descriptor.fourcc != VA_FOURCC_P010))
|
||||
return -1;
|
||||
if (!vaapi_export_current(source)
|
||||
|| !source->descriptor.num_objects
|
||||
|| source->descriptor.num_objects > 4)
|
||||
return -1;
|
||||
for (i = 0; i < source->descriptor.num_objects; i++)
|
||||
if (source->descriptor.objects[i].fd < 0)
|
||||
return -1;
|
||||
for (i = 0; i < pool->count; i++)
|
||||
if (!retro_atomic_load_acquire_int(&pool->slots[i].state))
|
||||
break;
|
||||
if (i == pool->count)
|
||||
return 1;
|
||||
slot = &pool->slots[i];
|
||||
if (slot->generation == ~(uint64_t)0 || source == &slot->source)
|
||||
return -1;
|
||||
slot->generation++;
|
||||
slot->pool = vaapi_export_owner(source);
|
||||
slot->slot = source->slot;
|
||||
slot->ticket = source->ticket;
|
||||
slot->source = *source;
|
||||
memset(&slot->view, 0, sizeof(slot->view));
|
||||
slot->view.source = &slot->source;
|
||||
slot->view.x = x;
|
||||
slot->view.y = y;
|
||||
slot->view.width = width;
|
||||
slot->view.height = height;
|
||||
slot->view.yuv = yuv;
|
||||
slot->view.chroma_position = chroma_position;
|
||||
slot->view.pixfmt = source->descriptor.fourcc == VA_FOURCC_P010
|
||||
? IMAGE_PIXFMT_P010
|
||||
: IMAGE_PIXFMT_NV12;
|
||||
if (color)
|
||||
slot->view.color = *color;
|
||||
memset(&frame, 0, sizeof(frame));
|
||||
frame.image = (uint64_t)(uintptr_t)slot;
|
||||
frame.api = VIDEO_HW_API_VULKAN;
|
||||
frame.owner = pool;
|
||||
frame.done = i;
|
||||
frame.done_value = slot->generation;
|
||||
frame.abandon = vaapi_frame_abandon;
|
||||
frame.width = width;
|
||||
frame.height = height;
|
||||
frame.crop_x = x;
|
||||
frame.crop_y = y;
|
||||
frame.pixfmt = slot->view.pixfmt;
|
||||
frame.color = slot->view.color;
|
||||
frame.yuv = yuv;
|
||||
memset(source, 0, sizeof(*source));
|
||||
retro_atomic_store_release_int(&slot->state, 1);
|
||||
*out = frame;
|
||||
return 0;
|
||||
}
|
||||
|
||||
unsigned vaapi_frame_collect(vaapi_frame_pool_t *pool)
|
||||
{
|
||||
unsigned i, collected = 0;
|
||||
if (!pool)
|
||||
return 0;
|
||||
for (i = 0; i < pool->count; i++)
|
||||
{
|
||||
vaapi_frame_slot_t *slot = &pool->slots[i];
|
||||
if (retro_atomic_load_acquire_int(&slot->state) != 4)
|
||||
continue;
|
||||
if (!vaapi_hevc_export_close(&slot->source))
|
||||
continue;
|
||||
memset(&slot->view, 0, sizeof(slot->view));
|
||||
slot->pool = NULL;
|
||||
slot->ticket = 0;
|
||||
retro_atomic_store_release_int(&slot->state, 0);
|
||||
collected++;
|
||||
}
|
||||
return collected;
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
/* Copyright (C) 2026 The RetroArch team.
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later */
|
||||
#ifndef RARCH_VAAPI_FRAME_H
|
||||
#define RARCH_VAAPI_FRAME_H
|
||||
#include <boolean.h>
|
||||
#include <formats/video_hw_frame.h>
|
||||
RETRO_BEGIN_DECLS
|
||||
struct vaapi_hevc_export;
|
||||
typedef struct vaapi_frame_pool vaapi_frame_pool_t;
|
||||
|
||||
typedef struct vaapi_frame_view
|
||||
{
|
||||
const struct vaapi_hevc_export *source;
|
||||
video_color_metadata_t color;
|
||||
unsigned x, y, width, height, yuv, pixfmt, chroma_position;
|
||||
} vaapi_frame_view_t;
|
||||
|
||||
/* Cold decoder-worker setup. One producer, one renderer owner per lease.
|
||||
* Refuses non-lock-free atomics. Close/free/collect are producer-only. */
|
||||
vaapi_frame_pool_t *vaapi_frame_pool_new(unsigned count);
|
||||
void vaapi_frame_pool_close(vaapi_frame_pool_t *pool);
|
||||
bool vaapi_frame_pool_free(vaapi_frame_pool_t *pool);
|
||||
/* Producer moves a completed export into an opaque internal Vulkan frame.
|
||||
* 0 consumes source, 1 BUSY/-1 refusal consume nothing; out unchanged on error.
|
||||
* Session/runtime/pool backing source must outlive the returned lease. */
|
||||
int vaapi_frame_export(vaapi_frame_pool_t *pool,
|
||||
struct vaapi_hevc_export *source, unsigned x, unsigned y, unsigned width,
|
||||
unsigned height, unsigned yuv, const video_color_metadata_t *color,
|
||||
video_hw_frame_t *out);
|
||||
/* AV1 positions: 0 unspecified, 1 left/vertical-center, 2 top-left. */
|
||||
int vaapi_frame_export_position(vaapi_frame_pool_t *pool,
|
||||
struct vaapi_hevc_export *source, unsigned x, unsigned y, unsigned width,
|
||||
unsigned height, unsigned yuv, unsigned chroma_position,
|
||||
const video_color_metadata_t *color, video_hw_frame_t *out);
|
||||
bool vaapi_frame_callback(void (*abandon)(void *, uint64_t, uint64_t));
|
||||
/* Claim publishes a read-only source pointer valid ONLY while claimed and
|
||||
* until take. Cold imports may borrow FDs without consuming this export. */
|
||||
bool vaapi_frame_claim(void *owner, uint64_t index, uint64_t generation,
|
||||
uint64_t image, vaapi_frame_view_t *view);
|
||||
/* Moves the claimed export into empty source for the imported frame owner. */
|
||||
bool vaapi_frame_take(void *owner, uint64_t index, uint64_t generation,
|
||||
struct vaapi_hevc_export *source);
|
||||
/* Before GPU use: restore pending. Pass the taken export back, or NULL if
|
||||
* source was only borrowed. False consumes nothing. */
|
||||
bool vaapi_frame_unclaim(void *owner, uint64_t index, uint64_t generation,
|
||||
struct vaapi_hevc_export *source);
|
||||
/* After GPU/resource retirement: publish return. Same ownership rule as
|
||||
* unclaim; never closes FDs or calls the decoder pool on the renderer. */
|
||||
bool vaapi_frame_finish(void *owner, uint64_t index, uint64_t generation,
|
||||
struct vaapi_hevc_export *source);
|
||||
/* One bounded producer pass. Close returned exports and release VA tickets on
|
||||
* the correct owner. Refused native ticket returns remain held for retry. */
|
||||
unsigned vaapi_frame_collect(vaapi_frame_pool_t *pool);
|
||||
RETRO_END_DECLS
|
||||
#endif
|
||||
@@ -0,0 +1,198 @@
|
||||
/* Copyright (C) 2026 The RetroArch team. SPDX-License-Identifier: GPL-3.0-or-later */
|
||||
#ifndef RARCH_VAAPI_HEVC_H
|
||||
#define RARCH_VAAPI_HEVC_H
|
||||
#include <va/va.h>
|
||||
#include <formats/rh265_hw.h>
|
||||
|
||||
/* Worker-owned prepared picture and live surface IDs. No VA calls or leases.
|
||||
* The initial adapter excludes PCM, tiles and dependent slice segments.
|
||||
* False leaves the caller's submission buffer unchanged. */
|
||||
static INLINE bool vaapi_hevc_picture(VAPictureParameterBufferHEVC *out,
|
||||
const rh265_video_params_t *s,const rh265_access_unit_t *au,
|
||||
const rh265_hw_picture_t *p,const VASurfaceID *surfaces,unsigned slots)
|
||||
{
|
||||
VAPictureParameterBufferHEVC v;
|
||||
const rh265_slice_info_t *h;
|
||||
unsigned i,j,list,used=0;
|
||||
uint32_t flags[15];
|
||||
if (!out || !s || !au || !p || !surfaces || !slots || slots>32
|
||||
|| p->slot>=slots || p->num_refs>15 || !au->num_slices
|
||||
|| s->chroma_format_idc!=1 || (s->bit_depth!=8 && s->bit_depth!=10)
|
||||
|| s->pcm_enabled || s->tiles_enabled || s->dependent_slice_segments_enabled
|
||||
|| s->width<8 || s->width>65535 || s->height<8 || s->height>65535
|
||||
|| p->width!=(unsigned)s->width || p->height!=(unsigned)s->height
|
||||
|| p->bit_depth!=(unsigned)s->bit_depth
|
||||
|| s->log2_min_cb<3 || s->log2_min_cb>6
|
||||
|| s->log2_ctb<s->log2_min_cb || s->log2_ctb>6
|
||||
|| s->log2_min_tb<2 || s->log2_min_tb>5
|
||||
|| s->log2_max_tb<s->log2_min_tb || s->log2_max_tb>5
|
||||
|| s->log2_max_tb>s->log2_ctb
|
||||
|| (s->width&((1<<s->log2_min_cb)-1))
|
||||
|| (s->height&((1<<s->log2_min_cb)-1))
|
||||
|| s->max_dec_pic_buffering<1 || s->max_dec_pic_buffering>16
|
||||
|| p->num_refs>=(unsigned)s->max_dec_pic_buffering
|
||||
|| s->max_num_reorder_pics<0 || s->max_num_reorder_pics>=s->max_dec_pic_buffering
|
||||
|| s->log2_max_poc_lsb<4 || s->log2_max_poc_lsb>16
|
||||
|| p->poc_log2!=(unsigned)s->log2_max_poc_lsb
|
||||
|| s->num_st_rps<0 || s->num_st_rps>64
|
||||
|| s->num_long_term_ref_pics<0 || s->num_long_term_ref_pics>32
|
||||
|| s->num_ref_idx_l0_default<1 || s->num_ref_idx_l0_default>15
|
||||
|| s->num_ref_idx_l1_default<1 || s->num_ref_idx_l1_default>15
|
||||
|| s->init_qp<-(s->bit_depth-8)*6 || s->init_qp>51
|
||||
|| s->cb_qp_offset< -12 || s->cb_qp_offset>12
|
||||
|| s->cr_qp_offset< -12 || s->cr_qp_offset>12
|
||||
|| s->beta_offset_div2< -6 || s->beta_offset_div2>6
|
||||
|| s->tc_offset_div2< -6 || s->tc_offset_div2>6
|
||||
|| s->log2_parallel_merge_level<2 || s->log2_parallel_merge_level>s->log2_ctb
|
||||
|| s->diff_cu_qp_delta_depth<0 || s->diff_cu_qp_delta_depth>s->log2_ctb-s->log2_min_cb
|
||||
|| s->max_transform_hierarchy_depth_inter<0
|
||||
|| s->max_transform_hierarchy_depth_inter>s->log2_ctb-s->log2_min_tb
|
||||
|| s->max_transform_hierarchy_depth_intra<0
|
||||
|| s->max_transform_hierarchy_depth_intra>s->log2_ctb-s->log2_min_tb
|
||||
|| s->num_extra_slice_header_bits<0 || s->num_extra_slice_header_bits>7
|
||||
|| surfaces[p->slot]==VA_INVALID_SURFACE) return false;
|
||||
h=&au->first;
|
||||
if (h->num_negative<0 || h->num_negative>16 || h->num_positive<0
|
||||
|| h->num_positive>16 || h->long_term.count>16
|
||||
|| h->nal_type<0 || h->nal_type>21 || (h->nal_type>9 && h->nal_type<16)
|
||||
|| h->slice_type<0 || h->slice_type>2 || !au->slice_types
|
||||
|| (au->slice_types&~7u) || !(au->slice_types&(1u<<h->slice_type))
|
||||
|| !!au->intra!=(au->slice_types==4)) return false;
|
||||
for (i=0;i<p->num_refs;i++)
|
||||
{
|
||||
if (p->refs[i].slot>=slots || p->refs[i].slot==p->slot
|
||||
|| p->refs[i].long_term>1 || surfaces[p->refs[i].slot]==VA_INVALID_SURFACE)
|
||||
return false;
|
||||
for (j=0;j<i;j++)
|
||||
if (p->refs[i].slot==p->refs[j].slot || p->refs[i].poc==p->refs[j].poc
|
||||
|| surfaces[p->refs[i].slot]==surfaces[p->refs[j].slot]) return false;
|
||||
if (surfaces[p->refs[i].slot]==surfaces[p->slot]) return false;
|
||||
flags[i]=p->refs[i].long_term?VA_PICTURE_HEVC_LONG_TERM_REFERENCE:0;
|
||||
}
|
||||
for (list=0;list<3;list++)
|
||||
{
|
||||
unsigned count=list==2?h->long_term.count:(unsigned)(list?h->num_positive:h->num_negative);
|
||||
for (i=0;i<count;i++)
|
||||
{
|
||||
unsigned found=p->num_refs;
|
||||
uint32_t flag=list==2?VA_PICTURE_HEVC_RPS_LT_CURR:
|
||||
list?VA_PICTURE_HEVC_RPS_ST_CURR_AFTER:VA_PICTURE_HEVC_RPS_ST_CURR_BEFORE;
|
||||
int64_t poc;
|
||||
unsigned active;
|
||||
if (list==2)
|
||||
{
|
||||
uint32_t mask=(1u<<s->log2_max_poc_lsb)-1;
|
||||
if (!s->long_term_ref_pics_present || h->long_term.used[i]>1
|
||||
|| h->long_term.msb_present[i]>1 || h->long_term.poc_lsb[i]>mask)
|
||||
return false;
|
||||
active=h->long_term.used[i];
|
||||
poc=(int64_t)p->poc-h->poc_lsb+h->long_term.poc_lsb[i]
|
||||
-(int64_t)h->long_term.msb_cycle[i]*(mask+1);
|
||||
for (j=0;j<p->num_refs;j++)
|
||||
if (p->refs[j].long_term && (h->long_term.msb_present[i]
|
||||
? p->refs[j].poc==poc
|
||||
: ((uint32_t)p->refs[j].poc&mask)==h->long_term.poc_lsb[i]))
|
||||
{ if (found!=p->num_refs) return false; found=j; }
|
||||
}
|
||||
else
|
||||
{
|
||||
const int32_t *delta=list?h->delta_poc_after:h->delta_poc_before;
|
||||
const uint8_t *use=list?h->used_after:h->used_before;
|
||||
if (use[i]>1 || (list?delta[i]<=0:delta[i]>=0)
|
||||
|| (i && (list?delta[i]<=delta[i-1]:delta[i]>=delta[i-1]))) return false;
|
||||
active=use[i]; poc=(int64_t)p->poc+delta[i];
|
||||
if (poc<INT32_MIN || poc>INT32_MAX) return false;
|
||||
for (j=0;j<p->num_refs;j++)
|
||||
if (!p->refs[j].long_term && p->refs[j].poc==poc) found=j;
|
||||
}
|
||||
if (!active) continue;
|
||||
if (found==p->num_refs || used++>=8
|
||||
|| (flags[found]&(VA_PICTURE_HEVC_RPS_ST_CURR_BEFORE
|
||||
|VA_PICTURE_HEVC_RPS_ST_CURR_AFTER|VA_PICTURE_HEVC_RPS_LT_CURR))) return false;
|
||||
flags[found]|=flag;
|
||||
}
|
||||
}
|
||||
memset(&v,0,sizeof(v));
|
||||
v.CurrPic.picture_id=surfaces[p->slot]; v.CurrPic.pic_order_cnt=p->poc;
|
||||
for (i=0;i<15;i++)
|
||||
{
|
||||
v.ReferenceFrames[i].picture_id=VA_INVALID_SURFACE;
|
||||
v.ReferenceFrames[i].flags=VA_PICTURE_HEVC_INVALID;
|
||||
if (i<p->num_refs)
|
||||
{
|
||||
v.ReferenceFrames[i].picture_id=surfaces[p->refs[i].slot];
|
||||
v.ReferenceFrames[i].pic_order_cnt=p->refs[i].poc;
|
||||
v.ReferenceFrames[i].flags=flags[i];
|
||||
}
|
||||
}
|
||||
v.pic_width_in_luma_samples=(uint16_t)s->width;
|
||||
v.pic_height_in_luma_samples=(uint16_t)s->height;
|
||||
#define VAAPI_HEVC_PIC(dst,src) v.pic_fields.bits.dst=!!s->src
|
||||
v.pic_fields.bits.chroma_format_idc=1;
|
||||
VAAPI_HEVC_PIC(scaling_list_enabled_flag,scaling_list_enabled);
|
||||
VAAPI_HEVC_PIC(transform_skip_enabled_flag,transform_skip_enabled);
|
||||
VAAPI_HEVC_PIC(amp_enabled_flag,amp_enabled);
|
||||
VAAPI_HEVC_PIC(strong_intra_smoothing_enabled_flag,strong_intra_smoothing);
|
||||
VAAPI_HEVC_PIC(sign_data_hiding_enabled_flag,sign_data_hiding);
|
||||
VAAPI_HEVC_PIC(constrained_intra_pred_flag,constrained_intra_pred);
|
||||
VAAPI_HEVC_PIC(cu_qp_delta_enabled_flag,cu_qp_delta_enabled);
|
||||
VAAPI_HEVC_PIC(weighted_pred_flag,weighted_pred);
|
||||
VAAPI_HEVC_PIC(weighted_bipred_flag,weighted_bipred);
|
||||
VAAPI_HEVC_PIC(transquant_bypass_enabled_flag,transquant_bypass_enabled);
|
||||
VAAPI_HEVC_PIC(entropy_coding_sync_enabled_flag,entropy_coding_sync_enabled);
|
||||
VAAPI_HEVC_PIC(pps_loop_filter_across_slices_enabled_flag,loop_filter_across_slices);
|
||||
#undef VAAPI_HEVC_PIC
|
||||
v.pic_fields.bits.NoPicReorderingFlag=!s->max_num_reorder_pics;
|
||||
v.pic_fields.bits.NoBiPredFlag=!(au->slice_types&1u);
|
||||
v.sps_max_dec_pic_buffering_minus1=(uint8_t)(s->max_dec_pic_buffering-1);
|
||||
v.bit_depth_luma_minus8=v.bit_depth_chroma_minus8=(uint8_t)(s->bit_depth-8);
|
||||
v.log2_min_luma_coding_block_size_minus3=(uint8_t)(s->log2_min_cb-3);
|
||||
v.log2_diff_max_min_luma_coding_block_size=(uint8_t)(s->log2_ctb-s->log2_min_cb);
|
||||
v.log2_min_transform_block_size_minus2=(uint8_t)(s->log2_min_tb-2);
|
||||
v.log2_diff_max_min_transform_block_size=(uint8_t)(s->log2_max_tb-s->log2_min_tb);
|
||||
v.max_transform_hierarchy_depth_intra=(uint8_t)s->max_transform_hierarchy_depth_intra;
|
||||
v.max_transform_hierarchy_depth_inter=(uint8_t)s->max_transform_hierarchy_depth_inter;
|
||||
v.init_qp_minus26=(int8_t)(s->init_qp-26);
|
||||
v.diff_cu_qp_delta_depth=(uint8_t)s->diff_cu_qp_delta_depth;
|
||||
v.pps_cb_qp_offset=(int8_t)s->cb_qp_offset; v.pps_cr_qp_offset=(int8_t)s->cr_qp_offset;
|
||||
v.log2_parallel_merge_level_minus2=(uint8_t)(s->log2_parallel_merge_level-2);
|
||||
#define VAAPI_HEVC_SLICE(dst,src) v.slice_parsing_fields.bits.dst=!!s->src
|
||||
VAAPI_HEVC_SLICE(lists_modification_present_flag,lists_modification_present);
|
||||
VAAPI_HEVC_SLICE(long_term_ref_pics_present_flag,long_term_ref_pics_present);
|
||||
VAAPI_HEVC_SLICE(sps_temporal_mvp_enabled_flag,temporal_mvp_enabled);
|
||||
VAAPI_HEVC_SLICE(cabac_init_present_flag,cabac_init_present);
|
||||
VAAPI_HEVC_SLICE(output_flag_present_flag,output_flag_present);
|
||||
VAAPI_HEVC_SLICE(pps_slice_chroma_qp_offsets_present_flag,slice_chroma_qp_offsets_present);
|
||||
VAAPI_HEVC_SLICE(sample_adaptive_offset_enabled_flag,sao_enabled);
|
||||
VAAPI_HEVC_SLICE(deblocking_filter_override_enabled_flag,deblocking_filter_override_enabled);
|
||||
VAAPI_HEVC_SLICE(pps_disable_deblocking_filter_flag,deblocking_filter_disabled);
|
||||
VAAPI_HEVC_SLICE(slice_segment_header_extension_present_flag,slice_segment_header_extension_present);
|
||||
#undef VAAPI_HEVC_SLICE
|
||||
v.slice_parsing_fields.bits.RapPicFlag=h->nal_type>=16;
|
||||
v.slice_parsing_fields.bits.IdrPicFlag=h->nal_type==19 || h->nal_type==20;
|
||||
v.slice_parsing_fields.bits.IntraPicFlag=!!au->intra;
|
||||
v.log2_max_pic_order_cnt_lsb_minus4=(uint8_t)(s->log2_max_poc_lsb-4);
|
||||
v.num_short_term_ref_pic_sets=(uint8_t)s->num_st_rps;
|
||||
v.num_long_term_ref_pic_sps=(uint8_t)s->num_long_term_ref_pics;
|
||||
v.num_ref_idx_l0_default_active_minus1=(uint8_t)(s->num_ref_idx_l0_default-1);
|
||||
v.num_ref_idx_l1_default_active_minus1=(uint8_t)(s->num_ref_idx_l1_default-1);
|
||||
v.pps_beta_offset_div2=(int8_t)s->beta_offset_div2;
|
||||
v.pps_tc_offset_div2=(int8_t)s->tc_offset_div2;
|
||||
v.num_extra_slice_header_bits=(uint8_t)s->num_extra_slice_header_bits;
|
||||
v.st_rps_bits=h->short_term_rps_bits;
|
||||
*out=v; return true;
|
||||
}
|
||||
static INLINE bool vaapi_hevc_matrix(VAIQMatrixBufferHEVC *out,
|
||||
const rh265_video_params_t *s)
|
||||
{
|
||||
if (!out || !s || !s->scaling_list_enabled) return false;
|
||||
memset(out,0,sizeof(*out));
|
||||
memcpy(out->ScalingList4x4,s->list4,sizeof(s->list4));
|
||||
memcpy(out->ScalingList8x8,s->list8,sizeof(s->list8));
|
||||
memcpy(out->ScalingList16x16,s->list16,sizeof(s->list16));
|
||||
memcpy(out->ScalingList32x32,s->list32,sizeof(s->list32));
|
||||
memcpy(out->ScalingListDC16x16,s->dc16,sizeof(s->dc16));
|
||||
memcpy(out->ScalingListDC32x32,s->dc32,sizeof(s->dc32));
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,330 @@
|
||||
/* Copyright (C) 2026 The RetroArch team.
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later */
|
||||
#include "vaapi_hevc_backend.h"
|
||||
#include <formats/image_yuv_blit.h>
|
||||
#include <limits.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#if defined(HAVE_THREADS) && defined(RETRO_ATOMIC_LOCK_FREE) \
|
||||
&& defined(RETRO_ATOMIC_HAS_PTR)
|
||||
#include <pthread.h>
|
||||
#include <rthreads/retro_eventcount.h>
|
||||
|
||||
#ifndef VAAPI_HEVC_NODE_PATH
|
||||
#define VAAPI_HEVC_NODE_PATH vaapi_hevc_backend_node_path
|
||||
#if defined(__linux__) && !defined(__ANDROID__) && defined(NEED_DYNAMIC)
|
||||
#include <sys/sysmacros.h>
|
||||
#include <xf86drm.h>
|
||||
|
||||
static bool vaapi_hevc_backend_node_path(
|
||||
unsigned major_id, unsigned minor_id, char *path, size_t capacity)
|
||||
{
|
||||
dylib_t library = dylib_load("libdrm.so.2");
|
||||
int (*get_device)(dev_t, uint32_t, drmDevicePtr *);
|
||||
void (*free_device)(drmDevicePtr *);
|
||||
drmDevicePtr device = NULL;
|
||||
function_t symbol;
|
||||
size_t length;
|
||||
bool result = false;
|
||||
if (!library)
|
||||
return false;
|
||||
symbol = dylib_proc(library, "drmGetDeviceFromDevId");
|
||||
if (!symbol || sizeof(symbol) != sizeof(get_device))
|
||||
goto finish;
|
||||
memcpy(&get_device, &symbol, sizeof(get_device));
|
||||
symbol = dylib_proc(library, "drmFreeDevice");
|
||||
if (!symbol || sizeof(symbol) != sizeof(free_device))
|
||||
goto finish;
|
||||
memcpy(&free_device, &symbol, sizeof(free_device));
|
||||
if (!get_device(makedev(major_id, minor_id), 0, &device) && device)
|
||||
{
|
||||
if ((device->available_nodes & (1 << DRM_NODE_RENDER)) && device->nodes
|
||||
&& device->nodes[DRM_NODE_RENDER])
|
||||
{
|
||||
length = strlen(device->nodes[DRM_NODE_RENDER]);
|
||||
if (length && length < capacity)
|
||||
{
|
||||
memcpy(path, device->nodes[DRM_NODE_RENDER], length + 1);
|
||||
result = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (device)
|
||||
free_device(&device);
|
||||
finish:
|
||||
dylib_close(library);
|
||||
return result;
|
||||
}
|
||||
#else
|
||||
static bool vaapi_hevc_backend_node_path(
|
||||
unsigned major_id, unsigned minor_id, char *path, size_t capacity)
|
||||
{
|
||||
(void)major_id;
|
||||
(void)minor_id;
|
||||
(void)path;
|
||||
(void)capacity;
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
|
||||
typedef struct vaapi_hevc_backend_session
|
||||
{
|
||||
rh265_hw_ops_t native;
|
||||
vaapi_hevc_owner_t *retired;
|
||||
vaapi_hevc_backend_t *backend;
|
||||
retro_eventcount_t wake;
|
||||
retro_atomic_int_t state, refs;
|
||||
} vaapi_hevc_backend_session_t;
|
||||
|
||||
static void vaapi_hevc_backend_unref(vaapi_hevc_backend_session_t *session)
|
||||
{
|
||||
vaapi_hevc_backend_t *backend;
|
||||
if (retro_atomic_fetch_sub_int(&session->refs, 1) != 1)
|
||||
return;
|
||||
backend = session->backend;
|
||||
retro_eventcount_free(&session->wake);
|
||||
free(session);
|
||||
retro_atomic_fetch_sub_int(&backend->active, 1);
|
||||
}
|
||||
|
||||
static void vaapi_hevc_backend_release(vaapi_hevc_backend_session_t *session)
|
||||
{
|
||||
retro_atomic_store_release_int(&session->state, 1);
|
||||
retro_eventcount_notify(&session->wake);
|
||||
vaapi_hevc_backend_unref(session);
|
||||
}
|
||||
|
||||
static void *vaapi_hevc_backend_retire(void *opaque)
|
||||
{
|
||||
vaapi_hevc_backend_session_t *session =
|
||||
(vaapi_hevc_backend_session_t *)opaque;
|
||||
int key;
|
||||
for (;;)
|
||||
{
|
||||
key = retro_eventcount_prepare_wait(&session->wake);
|
||||
if (retro_atomic_load_acquire_int(&session->state))
|
||||
{
|
||||
retro_eventcount_cancel_wait(&session->wake);
|
||||
break;
|
||||
}
|
||||
retro_eventcount_commit_wait(&session->wake, key);
|
||||
}
|
||||
/* This is the cold retirement executor, never a frame/UI caller. */
|
||||
while (!vaapi_hevc_owner_retire(session->retired))
|
||||
{
|
||||
key = retro_eventcount_prepare_wait(&session->wake);
|
||||
retro_eventcount_commit_wait_timeout(&session->wake, key, 10000);
|
||||
}
|
||||
vaapi_hevc_backend_unref(session);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static int vaapi_hevc_backend_decode(void *opaque,
|
||||
const rh265_video_params_t *params, const rh265_access_unit_t *au,
|
||||
const rh265_nal_t *slices, const rh265_hw_picture_t *picture)
|
||||
{
|
||||
vaapi_hevc_backend_session_t *session =
|
||||
(vaapi_hevc_backend_session_t *)opaque;
|
||||
return session->native.decode(
|
||||
session->native.decoder, params, au, slices, picture);
|
||||
}
|
||||
|
||||
static int vaapi_hevc_backend_output(
|
||||
void *opaque, unsigned slot, video_hw_frame_t *frame)
|
||||
{
|
||||
vaapi_hevc_backend_session_t *session =
|
||||
(vaapi_hevc_backend_session_t *)opaque;
|
||||
return session->native.output(session->native.decoder, slot, frame);
|
||||
}
|
||||
|
||||
static int vaapi_hevc_backend_drain(void *opaque)
|
||||
{
|
||||
vaapi_hevc_backend_session_t *session =
|
||||
(vaapi_hevc_backend_session_t *)opaque;
|
||||
return session->native.drain(session->native.decoder);
|
||||
}
|
||||
|
||||
static void vaapi_hevc_backend_close(rh265_hw_ops_t *ops)
|
||||
{
|
||||
vaapi_hevc_backend_session_t *session;
|
||||
if (!ops || ops->decode != vaapi_hevc_backend_decode || !ops->decoder)
|
||||
return;
|
||||
session = (vaapi_hevc_backend_session_t *)ops->decoder;
|
||||
session->retired = vaapi_hevc_owner_detach(&session->native);
|
||||
memset(ops, 0, sizeof(*ops));
|
||||
vaapi_hevc_backend_release(session);
|
||||
}
|
||||
|
||||
static bool vaapi_hevc_backend_open(void *opaque, const rh265_video *source,
|
||||
const rh265_video_params_t *params, unsigned slots, rh265_hw_ops_t *ops)
|
||||
{
|
||||
vaapi_hevc_backend_t *backend = (vaapi_hevc_backend_t *)opaque;
|
||||
vaapi_hevc_backend_session_t *session;
|
||||
rh265_video_color_t coded;
|
||||
video_color_metadata_t color;
|
||||
pthread_attr_t attr;
|
||||
pthread_t thread;
|
||||
char path[1024];
|
||||
unsigned major_id, minor_id, yuv;
|
||||
int epoch, active, result;
|
||||
if (!ops || ops->decoder || !backend || !source || !params || slots < 2
|
||||
|| slots > 32 || params->pps_id < 0
|
||||
|| (params->bit_depth != 8 && params->bit_depth != 10)
|
||||
|| params->chroma_format_idc != 1)
|
||||
return false;
|
||||
epoch = retro_atomic_load_acquire_int(&backend->epoch);
|
||||
if (epoch <= 0 || !(epoch & 1))
|
||||
return false;
|
||||
major_id = (unsigned)retro_atomic_load_acquire_int(&backend->major_id);
|
||||
minor_id = (unsigned)retro_atomic_load_acquire_int(&backend->minor_id);
|
||||
if (retro_atomic_load_acquire_int(&backend->epoch) != epoch
|
||||
|| rh265_video_get_color(source, (unsigned)params->pps_id, &coded))
|
||||
return false;
|
||||
memset(&color, 0, sizeof(color));
|
||||
color.primaries = coded.primaries;
|
||||
color.transfer = coded.transfer;
|
||||
color.matrix = coded.matrix;
|
||||
color.range = coded.full_range ? 2 : 1;
|
||||
yuv = coded.full_range ? IMAGE_YUV_FLAG_FULL_RANGE : 0;
|
||||
if (coded.matrix == 1)
|
||||
yuv |= IMAGE_YUV_FLAG_BT709;
|
||||
for (;;)
|
||||
{
|
||||
active = retro_atomic_load_acquire_int(&backend->active);
|
||||
if (active < 0 || (unsigned)active >= backend->max_sessions)
|
||||
return false;
|
||||
if (retro_atomic_cas_int(&backend->active, active, active + 1))
|
||||
break;
|
||||
}
|
||||
if (!VAAPI_HEVC_NODE_PATH(major_id, minor_id, path, sizeof(path))
|
||||
|| retro_atomic_load_acquire_int(&backend->epoch) != epoch)
|
||||
{
|
||||
retro_atomic_fetch_sub_int(&backend->active, 1);
|
||||
return false;
|
||||
}
|
||||
session = (vaapi_hevc_backend_session_t *)calloc(1, sizeof(*session));
|
||||
if (!session)
|
||||
{
|
||||
retro_atomic_fetch_sub_int(&backend->active, 1);
|
||||
return false;
|
||||
}
|
||||
session->backend = backend;
|
||||
if (!retro_eventcount_init(&session->wake))
|
||||
{
|
||||
free(session);
|
||||
retro_atomic_fetch_sub_int(&backend->active, 1);
|
||||
return false;
|
||||
}
|
||||
retro_atomic_int_init(&session->state, 0);
|
||||
retro_atomic_int_init(&session->refs, 1);
|
||||
if (pthread_attr_init(&attr))
|
||||
{
|
||||
vaapi_hevc_backend_unref(session);
|
||||
return false;
|
||||
}
|
||||
if (pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED))
|
||||
{
|
||||
pthread_attr_destroy(&attr);
|
||||
vaapi_hevc_backend_unref(session);
|
||||
return false;
|
||||
}
|
||||
retro_atomic_store_release_int(&session->refs, 2);
|
||||
result = pthread_create(&thread, &attr, vaapi_hevc_backend_retire, session);
|
||||
pthread_attr_destroy(&attr);
|
||||
if (result)
|
||||
{
|
||||
retro_atomic_store_release_int(&session->refs, 1);
|
||||
vaapi_hevc_backend_unref(session);
|
||||
return false;
|
||||
}
|
||||
if (!vaapi_hevc_owner_open_source(&session->retired, source, path, major_id,
|
||||
minor_id, params, &color, yuv, slots, backend->max_input,
|
||||
&session->native))
|
||||
{
|
||||
vaapi_hevc_backend_release(session);
|
||||
return false;
|
||||
}
|
||||
if (retro_atomic_load_acquire_int(&backend->epoch) != epoch)
|
||||
{
|
||||
session->retired = vaapi_hevc_owner_detach(&session->native);
|
||||
vaapi_hevc_backend_release(session);
|
||||
return false;
|
||||
}
|
||||
memset(ops, 0, sizeof(*ops));
|
||||
ops->decoder = session;
|
||||
ops->decode = vaapi_hevc_backend_decode;
|
||||
ops->output = vaapi_hevc_backend_output;
|
||||
ops->drain = vaapi_hevc_backend_drain;
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
|
||||
bool vaapi_hevc_backend_init(
|
||||
vaapi_hevc_backend_t *backend, size_t max_input, unsigned max_sessions)
|
||||
{
|
||||
#if defined(HAVE_THREADS) && defined(RETRO_ATOMIC_LOCK_FREE) \
|
||||
&& defined(RETRO_ATOMIC_HAS_PTR)
|
||||
if (!backend || max_input < 128 || max_input > 64u * 1024u * 1024u
|
||||
|| !max_sessions || max_sessions > 8)
|
||||
return false;
|
||||
memset(backend, 0, sizeof(*backend));
|
||||
backend->max_input = max_input;
|
||||
backend->max_sessions = max_sessions;
|
||||
retro_atomic_int_init(&backend->epoch, 0);
|
||||
retro_atomic_int_init(&backend->major_id, 0);
|
||||
retro_atomic_int_init(&backend->minor_id, 0);
|
||||
retro_atomic_int_init(&backend->active, 0);
|
||||
backend->offer.user = backend;
|
||||
backend->offer.open = vaapi_hevc_backend_open;
|
||||
backend->offer.close = vaapi_hevc_backend_close;
|
||||
return true;
|
||||
#else
|
||||
(void)backend;
|
||||
(void)max_input;
|
||||
(void)max_sessions;
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
bool vaapi_hevc_backend_publish(vaapi_hevc_backend_t *backend, void *owner,
|
||||
unsigned major_id, unsigned minor_id)
|
||||
{
|
||||
int epoch;
|
||||
if (!backend || !backend->offer.open || !owner || major_id > INT_MAX
|
||||
|| minor_id > INT_MAX)
|
||||
return false;
|
||||
epoch = retro_atomic_load_acquire_int(&backend->epoch);
|
||||
if (epoch < 0)
|
||||
return false;
|
||||
if (epoch >= INT_MAX - 2)
|
||||
{
|
||||
retro_atomic_store_release_int(&backend->epoch, -1);
|
||||
backend->owner = NULL;
|
||||
rh265_unset_hw_backend(&backend->offer);
|
||||
return false;
|
||||
}
|
||||
if (epoch & 1)
|
||||
epoch++;
|
||||
retro_atomic_store_release_int(&backend->epoch, epoch);
|
||||
retro_atomic_store_release_int(&backend->major_id, (int)major_id);
|
||||
retro_atomic_store_release_int(&backend->minor_id, (int)minor_id);
|
||||
backend->owner = owner;
|
||||
retro_atomic_store_release_int(&backend->epoch, epoch + 1);
|
||||
rh265_set_hw_backend(&backend->offer);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool vaapi_hevc_backend_withdraw(vaapi_hevc_backend_t *backend, void *owner)
|
||||
{
|
||||
int epoch;
|
||||
if (!backend || !backend->offer.open || !owner || backend->owner != owner)
|
||||
return false;
|
||||
epoch = retro_atomic_load_acquire_int(&backend->epoch);
|
||||
retro_atomic_store_release_int(
|
||||
&backend->epoch, epoch == INT_MAX ? -1 : epoch + 1);
|
||||
backend->owner = NULL;
|
||||
rh265_unset_hw_backend(&backend->offer);
|
||||
return true;
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
/* Copyright (C) 2026 The RetroArch team.
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later */
|
||||
#ifndef RARCH_VAAPI_HEVC_BACKEND_H
|
||||
#define RARCH_VAAPI_HEVC_BACKEND_H
|
||||
#include "vaapi_hevc_owner.h"
|
||||
#include <retro_atomic.h>
|
||||
RETRO_BEGIN_DECLS
|
||||
|
||||
typedef struct vaapi_hevc_backend
|
||||
{
|
||||
rh265_hw_backend_t offer;
|
||||
void *owner;
|
||||
size_t max_input;
|
||||
unsigned max_sessions;
|
||||
retro_atomic_int_t epoch, major_id, minor_id, active;
|
||||
} vaapi_hevc_backend_t;
|
||||
|
||||
/* Initialize once. Storage and immutable configuration outlive setup readers,
|
||||
* sessions and deferred retirement. Requires threads and lock-free atomics. */
|
||||
bool vaapi_hevc_backend_init(
|
||||
vaapi_hevc_backend_t *backend, size_t max_input, unsigned max_sessions);
|
||||
/* One serialized renderer lifecycle owner. Publish only after native PRIME
|
||||
* import prerequisites and the actual render-device identity are verified. */
|
||||
bool vaapi_hevc_backend_publish(vaapi_hevc_backend_t *backend, void *owner,
|
||||
unsigned major_id, unsigned minor_id);
|
||||
bool vaapi_hevc_backend_withdraw(vaapi_hevc_backend_t *backend, void *owner);
|
||||
|
||||
RETRO_END_DECLS
|
||||
#endif
|
||||
@@ -0,0 +1,185 @@
|
||||
/* Copyright (C) 2026 The RetroArch team.
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later */
|
||||
#include "vaapi_hevc_decoder.h"
|
||||
#include <formats/image_yuv_blit.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
struct vaapi_hevc_decoder
|
||||
{
|
||||
vaapi_hevc_export_t pending;
|
||||
vaapi_hevc_export_ops_t exports;
|
||||
vaapi_hevc_pool_t *pool;
|
||||
vaapi_frame_pool_t *frames;
|
||||
rh265_video_params_t params;
|
||||
video_color_metadata_t color;
|
||||
unsigned width, height, yuv;
|
||||
bool closed, failed;
|
||||
};
|
||||
|
||||
int vaapi_hevc_decoder_poll(vaapi_hevc_decoder_t *decoder)
|
||||
{
|
||||
unsigned i;
|
||||
int result = 1;
|
||||
if (!decoder)
|
||||
return -1;
|
||||
vaapi_frame_collect(decoder->frames);
|
||||
if (decoder->pending.active && !decoder->pending.ready
|
||||
&& !vaapi_hevc_export_close(&decoder->pending))
|
||||
result = -1;
|
||||
if (decoder->pool->active)
|
||||
for (i = 0; i < decoder->pool->session->slots; i++)
|
||||
if (decoder->pool->transactions[i].active)
|
||||
{
|
||||
int status = vaapi_hevc_pool_poll(decoder->pool, i);
|
||||
if (status < 0)
|
||||
{
|
||||
decoder->failed = true;
|
||||
result = -1;
|
||||
}
|
||||
else if (status > 0 && result > 0)
|
||||
result = 0;
|
||||
}
|
||||
if (decoder->failed)
|
||||
return -1;
|
||||
return result;
|
||||
}
|
||||
|
||||
static int vaapi_hevc_decoder_decode(void *opaque,
|
||||
const rh265_video_params_t *params, const rh265_access_unit_t *au,
|
||||
const rh265_nal_t *slices, const rh265_hw_picture_t *picture)
|
||||
{
|
||||
vaapi_hevc_decoder_t *decoder = (vaapi_hevc_decoder_t *)opaque;
|
||||
int result;
|
||||
if (!decoder || decoder->closed || !params
|
||||
|| memcmp(params, &decoder->params, sizeof(*params))
|
||||
|| vaapi_hevc_decoder_poll(decoder) < 0)
|
||||
return -1;
|
||||
result = vaapi_hevc_pool_decode(decoder->pool, params, au, picture, slices);
|
||||
if (result < 0)
|
||||
decoder->failed = true;
|
||||
return result;
|
||||
}
|
||||
|
||||
static int vaapi_hevc_decoder_output(
|
||||
void *opaque, unsigned slot, video_hw_frame_t *out)
|
||||
{
|
||||
vaapi_hevc_decoder_t *decoder = (vaapi_hevc_decoder_t *)opaque;
|
||||
if (!decoder || decoder->closed || !out || !decoder->pool->active
|
||||
|| slot >= decoder->pool->session->slots
|
||||
|| vaapi_hevc_decoder_poll(decoder) < 0)
|
||||
return -1;
|
||||
if (decoder->pending.active && decoder->pending.slot != slot)
|
||||
return 1;
|
||||
if (!decoder->pending.active)
|
||||
{
|
||||
if (!(decoder->pool->ready & (UINT32_C(1) << slot)))
|
||||
return decoder->pool->transactions[slot].active ? 1 : -1;
|
||||
if (decoder->pool->exports & (UINT32_C(1) << slot))
|
||||
return 1;
|
||||
if (!vaapi_hevc_export_open(
|
||||
&decoder->pending, decoder->pool, slot, &decoder->exports))
|
||||
{
|
||||
decoder->failed = true;
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
return vaapi_frame_export(decoder->frames, &decoder->pending,
|
||||
(unsigned)decoder->params.crop_left,
|
||||
(unsigned)decoder->params.crop_top, decoder->width, decoder->height,
|
||||
decoder->yuv, &decoder->color, out);
|
||||
}
|
||||
|
||||
static int vaapi_hevc_decoder_drain(void *opaque)
|
||||
{
|
||||
vaapi_hevc_decoder_t *decoder = (vaapi_hevc_decoder_t *)opaque;
|
||||
if (!decoder || decoder->closed)
|
||||
return -1;
|
||||
return vaapi_hevc_decoder_poll(decoder) < 0 ? -1 : 0;
|
||||
}
|
||||
|
||||
bool vaapi_hevc_decoder_open(vaapi_hevc_pool_t *pool,
|
||||
const rh265_video_params_t *params, const video_color_metadata_t *color,
|
||||
unsigned yuv, const vaapi_hevc_export_ops_t *exports, rh265_hw_ops_t *ops)
|
||||
{
|
||||
vaapi_hevc_decoder_t *decoder;
|
||||
const vaapi_hevc_session_t *session;
|
||||
if (!pool || !pool->active || pool->broken || !params || !color || !ops
|
||||
|| ops->decoder || !exports || !exports->surface || !exports->cloexec
|
||||
|| !exports->close || !exports->identity
|
||||
|| (yuv & ~(IMAGE_YUV_FLAG_FULL_RANGE | IMAGE_YUV_FLAG_BT709)))
|
||||
return false;
|
||||
session = pool->session;
|
||||
if (!session || !session->ready || session->closing
|
||||
|| (session->slice_mode != VA_DEC_SLICE_MODE_BASE
|
||||
&& (session->slice_mode != VA_DEC_SLICE_MODE_NORMAL
|
||||
|| !pool->source || !pool->long_work))
|
||||
|| !session->slots || session->slots > 32 || params->width <= 0
|
||||
|| params->height <= 0 || (unsigned)params->width != session->width
|
||||
|| (unsigned)params->height != session->height
|
||||
|| (unsigned)params->bit_depth != session->depth
|
||||
|| (params->bit_depth != 8 && params->bit_depth != 10)
|
||||
|| params->chroma_format_idc != 1 || params->pcm_enabled
|
||||
|| params->tiles_enabled || params->dependent_slice_segments_enabled
|
||||
|| params->crop_left < 0 || params->crop_right < 0
|
||||
|| params->crop_top < 0 || params->crop_bottom < 0
|
||||
|| ((params->crop_left | params->crop_top) & 1)
|
||||
|| params->crop_left >= params->width
|
||||
|| params->crop_right >= params->width - params->crop_left
|
||||
|| params->crop_top >= params->height
|
||||
|| params->crop_bottom >= params->height - params->crop_top)
|
||||
return false;
|
||||
decoder = (vaapi_hevc_decoder_t *)calloc(1, sizeof(*decoder));
|
||||
if (!decoder)
|
||||
return false;
|
||||
decoder->frames = vaapi_frame_pool_new(session->slots);
|
||||
if (!decoder->frames)
|
||||
{
|
||||
free(decoder);
|
||||
return false;
|
||||
}
|
||||
decoder->pool = pool;
|
||||
decoder->params = *params;
|
||||
decoder->color = *color;
|
||||
decoder->yuv = yuv;
|
||||
decoder->exports = *exports;
|
||||
decoder->width =
|
||||
(unsigned)(params->width - params->crop_left - params->crop_right);
|
||||
decoder->height =
|
||||
(unsigned)(params->height - params->crop_top - params->crop_bottom);
|
||||
memset(ops, 0, sizeof(*ops));
|
||||
ops->decoder = decoder;
|
||||
ops->decode = vaapi_hevc_decoder_decode;
|
||||
ops->output = vaapi_hevc_decoder_output;
|
||||
ops->drain = vaapi_hevc_decoder_drain;
|
||||
return true;
|
||||
}
|
||||
|
||||
vaapi_hevc_decoder_t *vaapi_hevc_decoder_detach(rh265_hw_ops_t *ops)
|
||||
{
|
||||
vaapi_hevc_decoder_t *decoder;
|
||||
if (!ops || ops->decode != vaapi_hevc_decoder_decode
|
||||
|| ops->output != vaapi_hevc_decoder_output || !ops->decoder)
|
||||
return NULL;
|
||||
decoder = (vaapi_hevc_decoder_t *)ops->decoder;
|
||||
decoder->closed = true;
|
||||
decoder->pool->source = NULL;
|
||||
vaapi_frame_pool_close(decoder->frames);
|
||||
memset(ops, 0, sizeof(*ops));
|
||||
return decoder;
|
||||
}
|
||||
|
||||
bool vaapi_hevc_decoder_retire(vaapi_hevc_decoder_t *decoder)
|
||||
{
|
||||
if (!decoder)
|
||||
return true;
|
||||
if (!decoder->closed)
|
||||
return false;
|
||||
vaapi_hevc_decoder_poll(decoder);
|
||||
if (decoder->pending.active && !vaapi_hevc_export_close(&decoder->pending))
|
||||
return false;
|
||||
if (!vaapi_frame_pool_free(decoder->frames))
|
||||
return false;
|
||||
free(decoder);
|
||||
return true;
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
/* Copyright (C) 2026 The RetroArch team.
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later */
|
||||
#ifndef RARCH_VAAPI_HEVC_DECODER_H
|
||||
#define RARCH_VAAPI_HEVC_DECODER_H
|
||||
#include "vaapi_frame.h"
|
||||
#include "vaapi_hevc_export.h"
|
||||
RETRO_BEGIN_DECLS
|
||||
|
||||
typedef struct vaapi_hevc_decoder vaapi_hevc_decoder_t;
|
||||
/* Cold decoder owner; borrowed pool/session/runtime outlive detach/retirement.
|
||||
* Initially empty ops; BASE-mode adapter. Copies metadata and creates its opaque carriers.
|
||||
* Failure leaves ops unchanged; no native context ownership is transferred. */
|
||||
bool vaapi_hevc_decoder_open(vaapi_hevc_pool_t *pool,
|
||||
const rh265_video_params_t *params, const video_color_metadata_t *color,
|
||||
unsigned yuv, const vaapi_hevc_export_ops_t *exports,
|
||||
rh265_hw_ops_t *ops);
|
||||
/* Decoder owner, including paused/closing ticks. One bounded pass over native
|
||||
* completions and renderer returns. 1 decode transactions idle, 0 pending, -1
|
||||
* error. A failed transaction does not prevent collection of other returned
|
||||
* leases. */
|
||||
int vaapi_hevc_decoder_poll(vaapi_hevc_decoder_t *decoder);
|
||||
/* Decoder owner: stop admission, clear only this adapter's ops, transfer the
|
||||
* adapter to the owner's existing deferred-retirement executor. No wait. */
|
||||
vaapi_hevc_decoder_t *vaapi_hevc_decoder_detach(rh265_hw_ops_t *ops);
|
||||
/* Detached decoder owner. True consumes adapter after all frame leases return;
|
||||
* false retains it for retry. Pool/native transactions remain caller-owned. */
|
||||
bool vaapi_hevc_decoder_retire(vaapi_hevc_decoder_t *decoder);
|
||||
RETRO_END_DECLS
|
||||
#endif
|
||||
@@ -0,0 +1,240 @@
|
||||
/* Copyright (C) 2026 The RetroArch team.
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later */
|
||||
#include "vaapi_hevc_export.h"
|
||||
#if defined(__linux__) && !defined(__ANDROID__)
|
||||
#include <fcntl.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/vfs.h>
|
||||
|
||||
static bool vaapi_hevc_fd_identity(int fd, uint64_t *device, uint64_t *inode)
|
||||
{
|
||||
struct stat st;
|
||||
struct statfs fs;
|
||||
/* Older anonymous-inode DMA-BUF exports have no unique buffer identity. */
|
||||
if (fstatfs(fd, &fs) != 0 || fs.f_type != 0x444d4142 || fstat(fd, &st) != 0
|
||||
|| !st.st_ino)
|
||||
return false;
|
||||
*device = (uint64_t)st.st_dev;
|
||||
*inode = (uint64_t)st.st_ino;
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool vaapi_hevc_fd_cloexec(int fd)
|
||||
{
|
||||
int flags = fcntl(fd, F_GETFD);
|
||||
return flags >= 0 && fcntl(fd, F_SETFD, flags | FD_CLOEXEC) == 0;
|
||||
}
|
||||
|
||||
static void vaapi_hevc_fd_close(int fd)
|
||||
{
|
||||
close(fd);
|
||||
}
|
||||
#endif
|
||||
bool vaapi_hevc_export_bind(
|
||||
vaapi_hevc_export_ops_t *ops, const vaapi_hevc_runtime_t *runtime)
|
||||
{
|
||||
#if defined(__linux__) && !defined(__ANDROID__)
|
||||
vaapi_hevc_export_ops_t native;
|
||||
function_t symbol;
|
||||
if (!ops || !runtime || !runtime->ready || runtime->closing || !runtime->va
|
||||
|| !runtime->io.symbol)
|
||||
return false;
|
||||
symbol = runtime->io.symbol(runtime->va, "vaExportSurfaceHandle");
|
||||
if (!symbol || sizeof(symbol) != sizeof(native.surface))
|
||||
return false;
|
||||
memcpy(&native.surface, &symbol, sizeof(symbol));
|
||||
native.cloexec = vaapi_hevc_fd_cloexec;
|
||||
native.close = vaapi_hevc_fd_close;
|
||||
native.identity = vaapi_hevc_fd_identity;
|
||||
*ops = native;
|
||||
return true;
|
||||
#else
|
||||
(void)ops;
|
||||
(void)runtime;
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
static bool vaapi_hevc_export_valid(const vaapi_hevc_export_t *e)
|
||||
{
|
||||
const VADRMPRIMESurfaceDescriptor *d = &e->descriptor;
|
||||
const vaapi_hevc_session_t *s = e->native_session
|
||||
? e->native_session : e->pool->session;
|
||||
unsigned i, j, k, used = 0;
|
||||
if ((s->depth != 8 && s->depth != 10) || !d->width || !d->height
|
||||
|| (d->width & 1) || (d->height & 1)
|
||||
|| d->fourcc != (s->depth == 10 ? VA_FOURCC_P010 : VA_FOURCC_NV12)
|
||||
|| d->width != s->width || d->height != s->height || !d->num_objects
|
||||
|| d->num_objects > 4 || d->num_layers != 2)
|
||||
return false;
|
||||
for (i = 0; i < 4; i++)
|
||||
{
|
||||
if (i >= d->num_objects)
|
||||
{
|
||||
if (d->objects[i].fd >= 0)
|
||||
return false;
|
||||
continue;
|
||||
}
|
||||
if (d->objects[i].fd < 0 || !d->objects[i].size
|
||||
|| d->objects[i].drm_format_modifier == (uint64_t)-1
|
||||
|| d->objects[i].drm_format_modifier
|
||||
== (((uint64_t)0x00ffffffu << 32) | 0xffffffffu))
|
||||
return false;
|
||||
for (j = 0; j < i; j++)
|
||||
if (d->objects[i].fd == d->objects[j].fd)
|
||||
return false;
|
||||
}
|
||||
for (i = 0; i < 2; i++)
|
||||
{
|
||||
uint32_t format = s->depth == 10 ? (i ? VA_FOURCC('G', 'R', '3', '2')
|
||||
: VA_FOURCC('R', '1', '6', ' '))
|
||||
: (i ? VA_FOURCC('G', 'R', '8', '8')
|
||||
: VA_FOURCC('R', '8', ' ', ' '));
|
||||
if (d->layers[i].drm_format != format || !d->layers[i].num_planes
|
||||
|| d->layers[i].num_planes > 4)
|
||||
return false;
|
||||
for (j = 0; j < d->layers[i].num_planes; j++)
|
||||
{
|
||||
uint32_t index = d->layers[i].object_index[j],
|
||||
offset = d->layers[i].offset[j],
|
||||
pitch = d->layers[i].pitch[j];
|
||||
if (index >= d->num_objects || !pitch
|
||||
|| offset >= d->objects[index].size)
|
||||
return false;
|
||||
used |= 1u << index;
|
||||
for (k = 0; k < j; k++)
|
||||
if (index == d->layers[i].object_index[k]
|
||||
&& offset == d->layers[i].offset[k])
|
||||
return false;
|
||||
if (!d->objects[index].drm_format_modifier)
|
||||
{
|
||||
uint64_t rows = i ? ((uint64_t)d->height + 1) / 2 : d->height;
|
||||
uint64_t row = (uint64_t)d->width * (s->depth == 10 ? 2 : 1);
|
||||
if (d->layers[i].num_planes != 1 || pitch < row
|
||||
|| (rows - 1) * pitch + row > d->objects[index].size - offset)
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return used == ((1u << d->num_objects) - 1);
|
||||
}
|
||||
|
||||
bool vaapi_export_open(vaapi_hevc_export_t *e, void *owner,
|
||||
const vaapi_hevc_session_t *session,
|
||||
const vaapi_export_owner_ops_t *ownership, unsigned slot,
|
||||
const vaapi_hevc_export_ops_t *ops)
|
||||
{
|
||||
vaapi_hevc_export_ops_t native;
|
||||
vaapi_export_owner_ops_t owner_ops;
|
||||
uint64_t ticket;
|
||||
unsigned i;
|
||||
if (!e || e->active || !owner || !session || !session->display
|
||||
|| slot >= session->slots || !ownership || !ownership->acquire
|
||||
|| !ownership->current || !ownership->release
|
||||
|| !ops || !ops->surface || !ops->cloexec || !ops->close
|
||||
|| !ops->identity)
|
||||
return false;
|
||||
native = *ops;
|
||||
owner_ops = *ownership;
|
||||
if (!owner_ops.acquire(owner, slot, &ticket))
|
||||
return false;
|
||||
memset(e, 0, sizeof(*e));
|
||||
e->native_owner = owner;
|
||||
e->native_session = session;
|
||||
e->ownership = owner_ops;
|
||||
e->slot = slot;
|
||||
e->ticket = ticket;
|
||||
e->ops = native;
|
||||
e->active = true;
|
||||
for (i = 0; i < 4; i++)
|
||||
e->descriptor.objects[i].fd = -1;
|
||||
if (e->ops.surface(session->display, session->surfaces[slot],
|
||||
VA_SURFACE_ATTRIB_MEM_TYPE_DRM_PRIME_2,
|
||||
VA_EXPORT_SURFACE_READ_ONLY | VA_EXPORT_SURFACE_SEPARATE_LAYERS,
|
||||
&e->descriptor)
|
||||
!= VA_STATUS_SUCCESS
|
||||
|| !vaapi_hevc_export_valid(e))
|
||||
goto failed;
|
||||
for (i = 0; i < e->descriptor.num_objects; i++)
|
||||
if (!e->ops.cloexec(e->descriptor.objects[i].fd)
|
||||
|| !e->ops.identity(e->descriptor.objects[i].fd,
|
||||
&e->object_device[i], &e->object_inode[i])
|
||||
|| !e->object_inode[i])
|
||||
goto failed;
|
||||
e->ready = true;
|
||||
return true;
|
||||
failed:
|
||||
vaapi_hevc_export_close(e);
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool vaapi_export_hevc_acquire(void *owner, unsigned slot,
|
||||
uint64_t *ticket)
|
||||
{
|
||||
return vaapi_hevc_pool_acquire((vaapi_hevc_pool_t *)owner, slot, ticket);
|
||||
}
|
||||
static bool vaapi_export_hevc_current(void *owner, unsigned slot,
|
||||
uint64_t ticket)
|
||||
{
|
||||
const vaapi_hevc_pool_t *pool = (const vaapi_hevc_pool_t *)owner;
|
||||
return pool && pool->active && !pool->broken && slot < 32 && ticket
|
||||
&& (pool->exports & (UINT32_C(1) << slot))
|
||||
&& pool->generation[slot] == ticket;
|
||||
}
|
||||
static bool vaapi_export_hevc_release(void *owner, unsigned slot,
|
||||
uint64_t ticket)
|
||||
{
|
||||
return vaapi_hevc_pool_release((vaapi_hevc_pool_t *)owner, slot, ticket);
|
||||
}
|
||||
bool vaapi_hevc_export_open(vaapi_hevc_export_t *e, vaapi_hevc_pool_t *pool,
|
||||
unsigned slot, const vaapi_hevc_export_ops_t *ops)
|
||||
{
|
||||
static const vaapi_export_owner_ops_t ownership = {
|
||||
vaapi_export_hevc_acquire, vaapi_export_hevc_current,
|
||||
vaapi_export_hevc_release};
|
||||
bool result;
|
||||
if (!pool)
|
||||
return false;
|
||||
result = vaapi_export_open(e, pool, pool->session, &ownership, slot, ops);
|
||||
if (e && e->active && e->native_owner == pool)
|
||||
e->pool = pool;
|
||||
return result;
|
||||
}
|
||||
|
||||
int vaapi_hevc_export_take(vaapi_hevc_export_t *e, unsigned object)
|
||||
{
|
||||
int fd;
|
||||
if (!e || !e->ready || object >= e->descriptor.num_objects)
|
||||
return -1;
|
||||
fd = e->descriptor.objects[object].fd;
|
||||
e->descriptor.objects[object].fd = -1;
|
||||
return fd;
|
||||
}
|
||||
|
||||
bool vaapi_hevc_export_close(vaapi_hevc_export_t *e)
|
||||
{
|
||||
unsigned i, j;
|
||||
if (!e || !e->active)
|
||||
return true;
|
||||
e->ready = false;
|
||||
for (i = 0; i < 4; i++)
|
||||
if (e->descriptor.objects[i].fd >= 0)
|
||||
{
|
||||
int fd = e->descriptor.objects[i].fd;
|
||||
for (j = i; j < 4; j++)
|
||||
if (e->descriptor.objects[j].fd == fd)
|
||||
e->descriptor.objects[j].fd = -1;
|
||||
e->ops.close(fd);
|
||||
}
|
||||
if (e->native_owner)
|
||||
{
|
||||
if (!e->ownership.release || !e->ownership.release(
|
||||
e->native_owner, e->slot, e->ticket))
|
||||
return false;
|
||||
}
|
||||
else if (!vaapi_hevc_pool_release(e->pool, e->slot, e->ticket))
|
||||
return false;
|
||||
memset(e, 0, sizeof(*e));
|
||||
return true;
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
/* Copyright (C) 2026 The RetroArch team.
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later */
|
||||
#ifndef RARCH_VAAPI_HEVC_EXPORT_H
|
||||
#define RARCH_VAAPI_HEVC_EXPORT_H
|
||||
#include "vaapi_hevc_pool.h"
|
||||
#include "vaapi_hevc_runtime.h"
|
||||
#include <va/va_drmcommon.h>
|
||||
RETRO_BEGIN_DECLS
|
||||
|
||||
typedef struct vaapi_hevc_export_ops
|
||||
{
|
||||
VAStatus (*surface)(VADisplay, VASurfaceID, uint32_t, uint32_t, void *);
|
||||
bool (*cloexec)(int);
|
||||
void (*close)(int);
|
||||
bool (*identity)(int, uint64_t *, uint64_t *);
|
||||
} vaapi_hevc_export_ops_t;
|
||||
|
||||
typedef struct vaapi_export_owner_ops
|
||||
{
|
||||
bool (*acquire)(void *, unsigned, uint64_t *);
|
||||
bool (*current)(void *, unsigned, uint64_t);
|
||||
bool (*release)(void *, unsigned, uint64_t);
|
||||
} vaapi_export_owner_ops_t;
|
||||
|
||||
typedef struct vaapi_hevc_export
|
||||
{
|
||||
vaapi_hevc_pool_t *pool;
|
||||
void *native_owner;
|
||||
const vaapi_hevc_session_t *native_session;
|
||||
vaapi_export_owner_ops_t ownership;
|
||||
vaapi_hevc_export_ops_t ops;
|
||||
VADRMPRIMESurfaceDescriptor descriptor;
|
||||
uint64_t object_device[4], object_inode[4];
|
||||
uint64_t ticket;
|
||||
unsigned slot;
|
||||
bool active, ready;
|
||||
} vaapi_hevc_export_t;
|
||||
|
||||
/* Identity only; safe on the renderer while its export is claimed. */
|
||||
static INLINE void *vaapi_export_owner(const vaapi_hevc_export_t *source)
|
||||
{
|
||||
return source ? (source->native_owner ? source->native_owner : source->pool)
|
||||
: NULL;
|
||||
}
|
||||
/* Producer owner only; never query a decoder pool from the renderer. */
|
||||
static INLINE bool vaapi_export_current(const vaapi_hevc_export_t *source)
|
||||
{
|
||||
const vaapi_hevc_pool_t *pool;
|
||||
if (!source || !source->active || !source->ticket)
|
||||
return false;
|
||||
if (source->native_owner)
|
||||
return source->ownership.current
|
||||
&& source->ownership.current(source->native_owner,
|
||||
source->slot, source->ticket);
|
||||
pool = source->pool;
|
||||
return pool && pool->active && !pool->broken && source->slot < 32
|
||||
&& (pool->exports & (UINT32_C(1) << source->slot))
|
||||
&& pool->generation[source->slot] == source->ticket;
|
||||
}
|
||||
/* Native codec ownership and session stay borrowed through close. */
|
||||
bool vaapi_export_open(vaapi_hevc_export_t *source, void *owner,
|
||||
const vaapi_hevc_session_t *session,
|
||||
const vaapi_export_owner_ops_t *ownership, unsigned slot,
|
||||
const vaapi_hevc_export_ops_t *ops);
|
||||
|
||||
/* Cold binding, Linux only; output unchanged on refusal. Runtime libraries
|
||||
* and the pool must outlive every export. */
|
||||
bool vaapi_hevc_export_bind(
|
||||
vaapi_hevc_export_ops_t *ops, const vaapi_hevc_runtime_t *runtime);
|
||||
/* Owner thread, initially zeroed export storage. Only completed pool pictures.
|
||||
* Capture unique DMA-BUF object identities here, never on the renderer.
|
||||
* Separate Y/UV layers retain all modifiers and auxiliary planes. Descriptor
|
||||
* validation is not renderer import admission. False still requires close. */
|
||||
bool vaapi_hevc_export_open(vaapi_hevc_export_t *e, vaapi_hevc_pool_t *pool,
|
||||
unsigned slot, const vaapi_hevc_export_ops_t *ops);
|
||||
/* Transfer one object FD to the importer. It owns the FD even if import fails.
|
||||
* Other descriptor metadata remains valid. No duplicate transfer. */
|
||||
int vaapi_hevc_export_take(vaapi_hevc_export_t *e, unsigned object);
|
||||
/* After all imported GPU resources/use retire, or before any import begins.
|
||||
* Closes remaining FDs and returns the pool ticket exactly once. */
|
||||
bool vaapi_hevc_export_close(vaapi_hevc_export_t *e);
|
||||
RETRO_END_DECLS
|
||||
#endif
|
||||
@@ -0,0 +1,146 @@
|
||||
/* Copyright (C) 2026 The RetroArch team.
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later */
|
||||
#ifndef RARCH_VAAPI_HEVC_LONG_PACKET_H
|
||||
#define RARCH_VAAPI_HEVC_LONG_PACKET_H
|
||||
|
||||
#include "vaapi_hevc_packet.h"
|
||||
#include "vaapi_hevc_slice.h"
|
||||
|
||||
#define VAAPI_HEVC_SUBSET_MAX 65535
|
||||
|
||||
/* Allocate workspace and data on the cold owner, never on a decoder stack.
|
||||
* The entry array stores coded offsets, not cumulative byte positions. */
|
||||
typedef struct vaapi_hevc_long_work
|
||||
{
|
||||
uint8_t *data;
|
||||
size_t capacity;
|
||||
rh265_slice_decode_t parsed;
|
||||
VAPictureParameterBufferHEVC picture;
|
||||
VAIQMatrixBufferHEVC matrix;
|
||||
VASliceParameterBufferHEVC slices[256];
|
||||
uint32_t lengths[RH265_SLICE_ENTRY_POINTS_MAX];
|
||||
uint32_t entries[VAAPI_HEVC_SUBSET_MAX];
|
||||
unsigned used, num_entries;
|
||||
} vaapi_hevc_long_work_t;
|
||||
|
||||
/* Borrowed validated AU/NALs and source belong to this decoder owner thread.
|
||||
* All input/work/data storage must be disjoint. Refusal resets used/count;
|
||||
* scratch fields may change, but no payload is copied until all headers pass.
|
||||
* No allocation, VA call or shared DPB advancement. */
|
||||
static INLINE bool vaapi_hevc_long_packet(rh265_video *source,
|
||||
const rh265_video_params_t *params, const rh265_access_unit_t *au,
|
||||
const rh265_hw_picture_t *picture, const VASurfaceID *surfaces,
|
||||
unsigned slots, const rh265_nal_t *slices, vaapi_hevc_long_work_t *work)
|
||||
{
|
||||
unsigned i, j, entries = 0;
|
||||
size_t total = 0, padded, offset = 0;
|
||||
if (!work)
|
||||
return false;
|
||||
work->used = work->num_entries = 0;
|
||||
if (!source || !params || !au || !picture || !surfaces || !slices
|
||||
|| !work->data || !slots || slots > 32 || !au->num_slices
|
||||
|| au->num_slices > 256)
|
||||
return false;
|
||||
for (i = 0; i < au->num_slices; i++)
|
||||
{
|
||||
const rh265_nal_t *nal = &slices[i];
|
||||
if (!nal->data || nal->size < 3 || nal->size > UINT_MAX
|
||||
|| total > UINT_MAX - nal->size)
|
||||
return false;
|
||||
total += nal->size;
|
||||
}
|
||||
if (total != au->coded_bytes || total > UINT_MAX - 127u)
|
||||
return false;
|
||||
padded = (total + 127u) & ~(size_t)127u;
|
||||
if (padded > work->capacity
|
||||
|| vaapi_hevc_packet_overlap(work, sizeof(*work), work->data, padded)
|
||||
|| vaapi_hevc_packet_overlap(
|
||||
work, sizeof(*work), params, sizeof(*params))
|
||||
|| vaapi_hevc_packet_overlap(work, sizeof(*work), au, sizeof(*au))
|
||||
|| vaapi_hevc_packet_overlap(
|
||||
work, sizeof(*work), picture, sizeof(*picture))
|
||||
|| vaapi_hevc_packet_overlap(
|
||||
work, sizeof(*work), slices, au->num_slices * sizeof(*slices))
|
||||
|| vaapi_hevc_packet_overlap(
|
||||
work, sizeof(*work), surfaces, slots * sizeof(*surfaces))
|
||||
|| vaapi_hevc_packet_overlap(
|
||||
work->data, padded, params, sizeof(*params))
|
||||
|| vaapi_hevc_packet_overlap(work->data, padded, au, sizeof(*au))
|
||||
|| vaapi_hevc_packet_overlap(
|
||||
work->data, padded, picture, sizeof(*picture))
|
||||
|| vaapi_hevc_packet_overlap(
|
||||
work->data, padded, surfaces, slots * sizeof(*surfaces))
|
||||
|| vaapi_hevc_packet_overlap(
|
||||
work->data, padded, slices, au->num_slices * sizeof(*slices)))
|
||||
return false;
|
||||
for (i = 0; i < au->num_slices; i++)
|
||||
if (vaapi_hevc_packet_overlap(
|
||||
work, sizeof(*work), slices[i].data, slices[i].size)
|
||||
|| vaapi_hevc_packet_overlap(
|
||||
work->data, padded, slices[i].data, slices[i].size))
|
||||
return false;
|
||||
if (!vaapi_hevc_picture(&work->picture, params, au, picture, surfaces, slots)
|
||||
|| (params->scaling_list_enabled
|
||||
&& !vaapi_hevc_matrix(&work->matrix, params)))
|
||||
return false;
|
||||
for (i = 0; i < au->num_slices; i++)
|
||||
{
|
||||
const rh265_nal_t *nal = &slices[i];
|
||||
const rh265_slice_info_t *h;
|
||||
size_t bytes;
|
||||
if (rh265_video_get_slice_decode(source, nal->data, nal->size,
|
||||
&work->parsed, work->lengths, RH265_SLICE_ENTRY_POINTS_MAX))
|
||||
return false;
|
||||
h = &work->parsed.picture;
|
||||
if (h->nal_type != au->first.nal_type
|
||||
|| h->temporal_id != au->first.temporal_id
|
||||
|| !!h->first_slice_in_pic != (i == 0)
|
||||
|| h->poc_lsb != au->first.poc_lsb
|
||||
|| !(au->slice_types & (1u << h->slice_type))
|
||||
|| work->parsed.num_entry_points > VAAPI_HEVC_SUBSET_MAX - entries
|
||||
|| !vaapi_hevc_long_slice(&work->slices[i], params, &work->parsed,
|
||||
&work->picture, nal->size, (unsigned)offset,
|
||||
i + 1 == au->num_slices))
|
||||
return false;
|
||||
bytes = h->header_bytes;
|
||||
for (j = 0; j < work->parsed.num_entry_points; j++)
|
||||
{
|
||||
uint32_t length = work->lengths[j];
|
||||
if (!length || length >= nal->size - bytes)
|
||||
return false;
|
||||
bytes += length;
|
||||
work->entries[entries + j] = length - 1;
|
||||
}
|
||||
work->slices[i].entry_offset_to_subset_array = (uint16_t)entries;
|
||||
entries += work->parsed.num_entry_points;
|
||||
offset += nal->size;
|
||||
}
|
||||
offset = 0;
|
||||
for (i = 0; i < au->num_slices; i++)
|
||||
{
|
||||
memcpy(work->data + offset, slices[i].data, slices[i].size);
|
||||
offset += slices[i].size;
|
||||
}
|
||||
memset(work->data + offset, 0, padded - offset);
|
||||
work->used = (unsigned)padded;
|
||||
work->num_entries = entries;
|
||||
return true;
|
||||
}
|
||||
|
||||
static INLINE bool vaapi_hevc_long_submit(vaapi_hevc_submit_t *t,
|
||||
const vaapi_hevc_session_t *session, const vaapi_hevc_submit_ops_t *ops,
|
||||
rh265_video *source, const rh265_video_params_t *params,
|
||||
const rh265_access_unit_t *au, const rh265_hw_picture_t *picture,
|
||||
const rh265_nal_t *slices, vaapi_hevc_long_work_t *work)
|
||||
{
|
||||
if (!t || t->active || !session || !session->ready || session->closing
|
||||
|| session->slice_mode != VA_DEC_SLICE_MODE_NORMAL
|
||||
|| !vaapi_hevc_long_packet(source, params, au, picture,
|
||||
session->surfaces, session->slots, slices, work))
|
||||
return false;
|
||||
return vaapi_hevc_submit_start_entries(t, session, ops, &work->picture,
|
||||
params->scaling_list_enabled ? &work->matrix : NULL, work->slices,
|
||||
au->num_slices, work->data, work->used, work->entries,
|
||||
work->num_entries);
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,238 @@
|
||||
/* Copyright (C) 2026 The RetroArch team.
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later */
|
||||
#include "vaapi_hevc_owner.h"
|
||||
#include <formats/image_yuv_blit.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
struct vaapi_hevc_owner
|
||||
{
|
||||
vaapi_hevc_runtime_t runtime;
|
||||
vaapi_hevc_pool_t pool;
|
||||
rh265_hw_ops_t decoder;
|
||||
vaapi_hevc_decoder_t *retired;
|
||||
vaapi_hevc_short_work_t *work;
|
||||
vaapi_hevc_long_work_t *long_work;
|
||||
VADisplay terminated_display;
|
||||
bool published, closing, terminating;
|
||||
};
|
||||
|
||||
static int vaapi_hevc_owner_decode(void *opaque,
|
||||
const rh265_video_params_t *params, const rh265_access_unit_t *au,
|
||||
const rh265_nal_t *slices, const rh265_hw_picture_t *picture)
|
||||
{
|
||||
vaapi_hevc_owner_t *owner = (vaapi_hevc_owner_t *)opaque;
|
||||
if (!owner || owner->closing || !owner->decoder.decode)
|
||||
return -1;
|
||||
return owner->decoder.decode(
|
||||
owner->decoder.decoder, params, au, slices, picture);
|
||||
}
|
||||
|
||||
static int vaapi_hevc_owner_output(
|
||||
void *opaque, unsigned slot, video_hw_frame_t *frame)
|
||||
{
|
||||
vaapi_hevc_owner_t *owner = (vaapi_hevc_owner_t *)opaque;
|
||||
if (!owner || owner->closing || !owner->decoder.output)
|
||||
return -1;
|
||||
return owner->decoder.output(owner->decoder.decoder, slot, frame);
|
||||
}
|
||||
|
||||
static int vaapi_hevc_owner_drain(void *opaque)
|
||||
{
|
||||
vaapi_hevc_owner_t *owner = (vaapi_hevc_owner_t *)opaque;
|
||||
if (!owner || owner->closing || !owner->decoder.drain)
|
||||
return -1;
|
||||
return owner->decoder.drain(owner->decoder.decoder);
|
||||
}
|
||||
|
||||
static bool vaapi_hevc_owner_open_common(vaapi_hevc_owner_t **out,
|
||||
const char *path, unsigned major_id, unsigned minor_id,
|
||||
const rh265_video_params_t *params, const video_color_metadata_t *color,
|
||||
unsigned yuv, unsigned slots, size_t max_input,
|
||||
const vaapi_hevc_runtime_io_t *io, const vaapi_hevc_export_ops_t *exports,
|
||||
rh265_hw_ops_t *ops, const rh265_video *source)
|
||||
{
|
||||
vaapi_hevc_owner_t *owner;
|
||||
vaapi_hevc_export_ops_t native_exports;
|
||||
unsigned slice_mode =
|
||||
source ? VAAPI_HEVC_SLICE_AUTO : VA_DEC_SLICE_MODE_BASE;
|
||||
bool opened;
|
||||
if (!out || *out || !ops || ops->decoder || !path || !*path || !params
|
||||
|| !color
|
||||
|| (yuv & ~(IMAGE_YUV_FLAG_FULL_RANGE | IMAGE_YUV_FLAG_BT709))
|
||||
|| slots < 2 || slots > 32 || max_input < 128
|
||||
|| max_input > 64u * 1024u * 1024u || params->width < 8
|
||||
|| params->height < 8 || params->width > 65535
|
||||
|| params->height > 65535 || ((params->width | params->height) & 1)
|
||||
|| (params->bit_depth != 8 && params->bit_depth != 10)
|
||||
|| params->chroma_format_idc != 1 || params->pcm_enabled
|
||||
|| params->tiles_enabled || params->dependent_slice_segments_enabled)
|
||||
return false;
|
||||
owner = (vaapi_hevc_owner_t *)calloc(1, sizeof(*owner));
|
||||
if (!owner)
|
||||
return false;
|
||||
*out = owner;
|
||||
owner->work = (vaapi_hevc_short_work_t *)calloc(1, sizeof(*owner->work));
|
||||
if (!owner->work)
|
||||
return false;
|
||||
owner->work->data = (uint8_t *)malloc(max_input);
|
||||
if (!owner->work->data)
|
||||
return false;
|
||||
owner->work->capacity = max_input;
|
||||
if (source)
|
||||
{
|
||||
owner->long_work =
|
||||
(vaapi_hevc_long_work_t *)calloc(1, sizeof(*owner->long_work));
|
||||
if (!owner->long_work)
|
||||
return false;
|
||||
owner->long_work->data = owner->work->data;
|
||||
owner->long_work->capacity = max_input;
|
||||
}
|
||||
if (io)
|
||||
opened = vaapi_hevc_runtime_open_with_io(&owner->runtime, path, major_id,
|
||||
minor_id, (unsigned)params->width, (unsigned)params->height,
|
||||
(unsigned)params->bit_depth, slots, slice_mode, io);
|
||||
else
|
||||
opened = vaapi_hevc_runtime_open(&owner->runtime, path, major_id,
|
||||
minor_id, (unsigned)params->width, (unsigned)params->height,
|
||||
(unsigned)params->bit_depth, slots, slice_mode);
|
||||
if (!opened)
|
||||
return false;
|
||||
if (owner->runtime.session.slice_mode == VA_DEC_SLICE_MODE_NORMAL)
|
||||
/* The factory borrows a mutable decoder through a const view. Queries
|
||||
* change only its parser scratch on the same decoder owner thread. */
|
||||
opened = vaapi_hevc_pool_init_long(&owner->pool, &owner->runtime.session,
|
||||
&owner->runtime.submit, (rh265_video *)source, owner->long_work);
|
||||
else
|
||||
opened = vaapi_hevc_pool_init(&owner->pool, &owner->runtime.session,
|
||||
&owner->runtime.submit, owner->work);
|
||||
if (!opened)
|
||||
return false;
|
||||
if (exports)
|
||||
native_exports = *exports;
|
||||
else if (!vaapi_hevc_export_bind(&native_exports, &owner->runtime))
|
||||
return false;
|
||||
if (!vaapi_hevc_decoder_open(&owner->pool, params, color, yuv,
|
||||
&native_exports, &owner->decoder))
|
||||
return false;
|
||||
memset(ops, 0, sizeof(*ops));
|
||||
ops->decoder = owner;
|
||||
ops->decode = vaapi_hevc_owner_decode;
|
||||
ops->output = vaapi_hevc_owner_output;
|
||||
ops->drain = vaapi_hevc_owner_drain;
|
||||
owner->published = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool vaapi_hevc_owner_open_with_io(vaapi_hevc_owner_t **out, const char *path,
|
||||
unsigned major_id, unsigned minor_id, const rh265_video_params_t *params,
|
||||
const video_color_metadata_t *color, unsigned yuv, unsigned slots,
|
||||
size_t max_input, const vaapi_hevc_runtime_io_t *io,
|
||||
const vaapi_hevc_export_ops_t *exports, rh265_hw_ops_t *ops)
|
||||
{
|
||||
return vaapi_hevc_owner_open_common(out, path, major_id, minor_id, params,
|
||||
color, yuv, slots, max_input, io, exports, ops, NULL);
|
||||
}
|
||||
|
||||
bool vaapi_hevc_owner_open_source_with_io(vaapi_hevc_owner_t **out,
|
||||
const rh265_video *source, const char *path, unsigned major_id,
|
||||
unsigned minor_id, const rh265_video_params_t *params,
|
||||
const video_color_metadata_t *color, unsigned yuv, unsigned slots,
|
||||
size_t max_input, const vaapi_hevc_runtime_io_t *io,
|
||||
const vaapi_hevc_export_ops_t *exports, rh265_hw_ops_t *ops)
|
||||
{
|
||||
if (!source)
|
||||
return false;
|
||||
return vaapi_hevc_owner_open_common(out, path, major_id, minor_id, params,
|
||||
color, yuv, slots, max_input, io, exports, ops, source);
|
||||
}
|
||||
|
||||
bool vaapi_hevc_owner_open_source(vaapi_hevc_owner_t **out,
|
||||
const rh265_video *source, const char *path, unsigned major_id,
|
||||
unsigned minor_id, const rh265_video_params_t *params,
|
||||
const video_color_metadata_t *color, unsigned yuv, unsigned slots,
|
||||
size_t max_input, rh265_hw_ops_t *ops)
|
||||
{
|
||||
return vaapi_hevc_owner_open_source_with_io(out, source, path, major_id,
|
||||
minor_id, params, color, yuv, slots, max_input, NULL, NULL, ops);
|
||||
}
|
||||
|
||||
bool vaapi_hevc_owner_open(vaapi_hevc_owner_t **out, const char *path,
|
||||
unsigned major_id, unsigned minor_id, const rh265_video_params_t *params,
|
||||
const video_color_metadata_t *color, unsigned yuv, unsigned slots,
|
||||
size_t max_input, rh265_hw_ops_t *ops)
|
||||
{
|
||||
return vaapi_hevc_owner_open_with_io(out, path, major_id, minor_id, params,
|
||||
color, yuv, slots, max_input, NULL, NULL, ops);
|
||||
}
|
||||
|
||||
vaapi_hevc_owner_t *vaapi_hevc_owner_detach(rh265_hw_ops_t *ops)
|
||||
{
|
||||
vaapi_hevc_owner_t *owner;
|
||||
if (!ops || ops->decode != vaapi_hevc_owner_decode
|
||||
|| ops->output != vaapi_hevc_owner_output || !ops->decoder)
|
||||
return NULL;
|
||||
owner = (vaapi_hevc_owner_t *)ops->decoder;
|
||||
owner->closing = true;
|
||||
owner->retired = vaapi_hevc_decoder_detach(&owner->decoder);
|
||||
memset(ops, 0, sizeof(*ops));
|
||||
return owner;
|
||||
}
|
||||
|
||||
bool vaapi_hevc_owner_retire(vaapi_hevc_owner_t *owner)
|
||||
{
|
||||
if (!owner)
|
||||
return true;
|
||||
if (owner->published && !owner->closing)
|
||||
return false;
|
||||
owner->closing = true;
|
||||
owner->pool.source = NULL;
|
||||
if (!owner->terminating)
|
||||
{
|
||||
unsigned i;
|
||||
bool polled = false;
|
||||
if (owner->decoder.decoder)
|
||||
owner->retired = vaapi_hevc_decoder_detach(&owner->decoder);
|
||||
if (owner->retired)
|
||||
{
|
||||
if (!vaapi_hevc_decoder_retire(owner->retired))
|
||||
return false;
|
||||
owner->retired = NULL;
|
||||
polled = true;
|
||||
}
|
||||
if (owner->pool.exports)
|
||||
return false;
|
||||
if (owner->pool.active && !owner->pool.broken)
|
||||
{
|
||||
bool pending = false;
|
||||
for (i = 0; i < owner->pool.session->slots; i++)
|
||||
if (owner->pool.transactions[i].active)
|
||||
{
|
||||
int status = polled ? 1 : vaapi_hevc_pool_poll(&owner->pool, i);
|
||||
if (status > 0)
|
||||
pending = true;
|
||||
else if (status < 0)
|
||||
owner->pool.broken = true;
|
||||
}
|
||||
if (!owner->pool.broken
|
||||
&& (pending || !vaapi_hevc_pool_close(&owner->pool)))
|
||||
return false;
|
||||
}
|
||||
owner->terminated_display = owner->runtime.display;
|
||||
owner->terminating = true;
|
||||
}
|
||||
if (!vaapi_hevc_runtime_close(&owner->runtime))
|
||||
return false;
|
||||
if (owner->pool.active
|
||||
&& !vaapi_hevc_pool_terminated(
|
||||
&owner->pool, owner->terminated_display))
|
||||
return false;
|
||||
if (owner->work)
|
||||
{
|
||||
free(owner->work->data);
|
||||
free(owner->work);
|
||||
}
|
||||
free(owner->long_work);
|
||||
free(owner);
|
||||
return true;
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
/* Copyright (C) 2026 The RetroArch team.
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later */
|
||||
#ifndef RARCH_VAAPI_HEVC_OWNER_H
|
||||
#define RARCH_VAAPI_HEVC_OWNER_H
|
||||
#include "vaapi_hevc_decoder.h"
|
||||
RETRO_BEGIN_DECLS
|
||||
|
||||
typedef struct vaapi_hevc_owner vaapi_hevc_owner_t;
|
||||
/* Cold decoder owner, empty out/ops. Path and metadata are borrowed only during
|
||||
* open. A false result can still publish an owned partial context in out:
|
||||
* transfer it to the existing retirement executor, never discard it. */
|
||||
bool vaapi_hevc_owner_open(vaapi_hevc_owner_t **out, const char *path,
|
||||
unsigned major_id, unsigned minor_id, const rh265_video_params_t *params,
|
||||
const video_color_metadata_t *color, unsigned yuv, unsigned slots,
|
||||
size_t max_input, rh265_hw_ops_t *ops);
|
||||
/* Same lifetime, explicit typed platform operations for capability/ABI tests.
|
||||
* NULL io uses native bindings; NULL exports resolves the native exporter. */
|
||||
bool vaapi_hevc_owner_open_with_io(vaapi_hevc_owner_t **out, const char *path,
|
||||
unsigned major_id, unsigned minor_id, const rh265_video_params_t *params,
|
||||
const video_color_metadata_t *color, unsigned yuv, unsigned slots,
|
||||
size_t max_input, const vaapi_hevc_runtime_io_t *io,
|
||||
const vaapi_hevc_export_ops_t *exports, rh265_hw_ops_t *ops);
|
||||
/* Live decoder owner-thread borrow, released at detach or partial retirement.
|
||||
* AUTO implements NORMAL and BASE. Source and params describe the same PPS;
|
||||
* only parser query scratch changes during decode, never shared DPB state. */
|
||||
bool vaapi_hevc_owner_open_source(vaapi_hevc_owner_t **out,
|
||||
const rh265_video *source, const char *path, unsigned major_id,
|
||||
unsigned minor_id, const rh265_video_params_t *params,
|
||||
const video_color_metadata_t *color, unsigned yuv, unsigned slots,
|
||||
size_t max_input, rh265_hw_ops_t *ops);
|
||||
bool vaapi_hevc_owner_open_source_with_io(vaapi_hevc_owner_t **out,
|
||||
const rh265_video *source, const char *path, unsigned major_id,
|
||||
unsigned minor_id, const rh265_video_params_t *params,
|
||||
const video_color_metadata_t *color, unsigned yuv, unsigned slots,
|
||||
size_t max_input, const vaapi_hevc_runtime_io_t *io,
|
||||
const vaapi_hevc_export_ops_t *exports, rh265_hw_ops_t *ops);
|
||||
/* Decoder owner: clear only this owner's offer, stop admission and transfer
|
||||
* the returned context to the existing cold retirement executor. */
|
||||
vaapi_hevc_owner_t *vaapi_hevc_owner_detach(rh265_hw_ops_t *ops);
|
||||
/* Cold retirement executor only. One completion/return pass, never a GPU wait.
|
||||
* VA destruction/termination can call the native driver. True consumes owner;
|
||||
* false retains all still-owned resources. No termination before exports
|
||||
* retire. A poisoned pool recovers only through successful display termination.
|
||||
*/
|
||||
bool vaapi_hevc_owner_retire(vaapi_hevc_owner_t *owner);
|
||||
RETRO_END_DECLS
|
||||
#endif
|
||||
@@ -0,0 +1,88 @@
|
||||
/* Copyright (C) 2026 The RetroArch team. SPDX-License-Identifier: GPL-3.0-or-later */
|
||||
#ifndef RARCH_VAAPI_HEVC_PACKET_H
|
||||
#define RARCH_VAAPI_HEVC_PACKET_H
|
||||
#include "vaapi_hevc.h"
|
||||
#include "vaapi_hevc_submit.h"
|
||||
#include <limits.h>
|
||||
static INLINE bool vaapi_hevc_packet_overlap(const void *a,size_t an,const void *b,size_t bn)
|
||||
{
|
||||
uintptr_t x=(uintptr_t)a,y=(uintptr_t)b;
|
||||
return x<=y?y-x<an:x-y<bn;
|
||||
}
|
||||
/* BASE-mode raw slice stream: start-code offsets, escaped NALs, 128-byte tail
|
||||
* padding. Not the NORMAL-mode long-slice layout. The AU and NALs must have
|
||||
* passed rh265_video_get_access_unit. Separate caller-owned output buffers;
|
||||
* used must be separate from all input/output storage. Failure leaves the
|
||||
* output buffers unchanged and sets used to zero. No allocation/VA calls. */
|
||||
static INLINE bool vaapi_hevc_short_packet(uint8_t *data,size_t capacity,
|
||||
VASliceParameterBufferBase *out,unsigned out_count,const rh265_nal_t *slices,
|
||||
const rh265_access_unit_t *au,unsigned *used)
|
||||
{
|
||||
size_t total=0,padded,offset=0,descriptor_bytes;
|
||||
unsigned i,type,temporal;
|
||||
if(!used)return false;
|
||||
*used=0;
|
||||
if(!data || !out || !slices || !au || !au->num_slices || au->num_slices>256
|
||||
|| au->num_slices>out_count || au->first.nal_type<0
|
||||
|| au->first.temporal_id<0 || au->first.temporal_id>6)return false;
|
||||
descriptor_bytes=au->num_slices*sizeof(*out);
|
||||
type=(unsigned)au->first.nal_type;temporal=(unsigned)au->first.temporal_id+1;
|
||||
if(!(type<=9 || (type>=16 && type<=21)))return false;
|
||||
for(i=0;i<au->num_slices;i++)
|
||||
{
|
||||
const rh265_nal_t *n=&slices[i];
|
||||
if(!n->data || n->size<3 || n->size>UINT_MAX-4u
|
||||
|| total>UINT_MAX-4u-n->size)return false;
|
||||
if((n->data[0]&0x81) || (n->data[0]>>1)!=type
|
||||
|| n->data[1]!=temporal || !!(n->data[2]&0x80)!=(i==0))return false;
|
||||
total+=n->size+4;
|
||||
}
|
||||
if(total-4u*au->num_slices!=au->coded_bytes || total>UINT_MAX-127u)return false;
|
||||
padded=(total+127u)&~(size_t)127u;
|
||||
if(padded>capacity
|
||||
|| vaapi_hevc_packet_overlap(data,padded,out,descriptor_bytes)
|
||||
|| vaapi_hevc_packet_overlap(data,padded,slices,au->num_slices*sizeof(*slices))
|
||||
|| vaapi_hevc_packet_overlap(out,descriptor_bytes,slices,au->num_slices*sizeof(*slices))
|
||||
|| vaapi_hevc_packet_overlap(data,padded,au,sizeof(*au))
|
||||
|| vaapi_hevc_packet_overlap(out,descriptor_bytes,au,sizeof(*au))
|
||||
|| vaapi_hevc_packet_overlap(data,padded,used,sizeof(*used))
|
||||
|| vaapi_hevc_packet_overlap(out,descriptor_bytes,used,sizeof(*used)))return false;
|
||||
for(i=0;i<au->num_slices;i++)
|
||||
if(vaapi_hevc_packet_overlap(data,padded,slices[i].data,slices[i].size)
|
||||
|| vaapi_hevc_packet_overlap(out,descriptor_bytes,slices[i].data,slices[i].size))return false;
|
||||
for(i=0;i<au->num_slices;i++)
|
||||
{
|
||||
size_t size=slices[i].size;
|
||||
out[i].slice_data_offset=(uint32_t)offset;
|
||||
out[i].slice_data_size=(uint32_t)(size+4);
|
||||
out[i].slice_data_flag=VA_SLICE_DATA_FLAG_ALL;
|
||||
data[offset]=data[offset+1]=data[offset+2]=0;data[offset+3]=1;
|
||||
memcpy(data+offset+4,slices[i].data,size);offset+=size+4;
|
||||
}
|
||||
memset(data+offset,0,padded-offset);*used=(unsigned)padded;return true;
|
||||
}
|
||||
typedef struct vaapi_hevc_short_work {
|
||||
uint8_t *data;
|
||||
size_t capacity;
|
||||
VAPictureParameterBufferHEVC picture;
|
||||
VAIQMatrixBufferHEVC matrix;
|
||||
VASliceParameterBufferBase slices[256];
|
||||
unsigned used;
|
||||
} vaapi_hevc_short_work_t;
|
||||
/* Preallocate work/data on the cold owner, never on the stack. Does not commit
|
||||
* the shared DPB plan: commit only after true. False can retain native buffers
|
||||
* in t; retire them before retry. Caller retains surface/reference leases. */
|
||||
static INLINE bool vaapi_hevc_short_submit(vaapi_hevc_submit_t *t,
|
||||
const vaapi_hevc_session_t *session,const vaapi_hevc_submit_ops_t *ops,
|
||||
const rh265_video_params_t *params,const rh265_access_unit_t *au,
|
||||
const rh265_hw_picture_t *picture,const rh265_nal_t *slices,vaapi_hevc_short_work_t *work)
|
||||
{
|
||||
if(!t || t->active || !session || !session->ready || session->closing
|
||||
|| session->slice_mode!=VA_DEC_SLICE_MODE_BASE || !work || !params)return false;
|
||||
if(!vaapi_hevc_picture(&work->picture,params,au,picture,session->surfaces,session->slots))return false;
|
||||
if(params->scaling_list_enabled && !vaapi_hevc_matrix(&work->matrix,params))return false;
|
||||
if(!vaapi_hevc_short_packet(work->data,work->capacity,work->slices,256,slices,au,&work->used))return false;
|
||||
return vaapi_hevc_submit_start(t,session,ops,&work->picture,
|
||||
params->scaling_list_enabled?&work->matrix:NULL,work->slices,au->num_slices,work->data,work->used);
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,173 @@
|
||||
/* Copyright (C) 2026 The RetroArch team. SPDX-License-Identifier: GPL-3.0-or-later */
|
||||
#include "vaapi_hevc_pool.h"
|
||||
|
||||
static bool vaapi_hevc_pool_init_work(vaapi_hevc_pool_t *p,
|
||||
const vaapi_hevc_session_t *session, const vaapi_hevc_submit_ops_t *ops,
|
||||
vaapi_hevc_short_work_t *work, rh265_video *source,
|
||||
vaapi_hevc_long_work_t *long_work)
|
||||
{
|
||||
vaapi_hevc_submit_ops_t native;
|
||||
uint64_t generation;
|
||||
if (!p || p->active || !session || !session->ready || session->closing
|
||||
|| !session->display || session->context == VA_INVALID_ID
|
||||
|| !session->slots || session->slots > 32 || !ops || !ops->create
|
||||
|| !ops->destroy || !ops->begin || !ops->render || !ops->end
|
||||
|| !ops->sync)
|
||||
return false;
|
||||
if (session->slice_mode == VA_DEC_SLICE_MODE_BASE)
|
||||
{
|
||||
if (!work || !work->data || !work->capacity || long_work || source)
|
||||
return false;
|
||||
}
|
||||
else if (session->slice_mode == VA_DEC_SLICE_MODE_NORMAL)
|
||||
{
|
||||
if (work || !source || !long_work || !long_work->data
|
||||
|| !long_work->capacity)
|
||||
return false;
|
||||
}
|
||||
else
|
||||
return false;
|
||||
native = *ops;
|
||||
generation = p->next_generation;
|
||||
memset(p, 0, sizeof(*p));
|
||||
p->next_generation = generation;
|
||||
p->ops = native;
|
||||
p->session = session;
|
||||
p->display = session->display;
|
||||
p->work = work;
|
||||
p->long_work = long_work;
|
||||
p->source = source;
|
||||
p->active = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool vaapi_hevc_pool_init(vaapi_hevc_pool_t *p,
|
||||
const vaapi_hevc_session_t *session, const vaapi_hevc_submit_ops_t *ops,
|
||||
vaapi_hevc_short_work_t *work)
|
||||
{
|
||||
return vaapi_hevc_pool_init_work(p, session, ops, work, NULL, NULL);
|
||||
}
|
||||
|
||||
bool vaapi_hevc_pool_init_long(vaapi_hevc_pool_t *p,
|
||||
const vaapi_hevc_session_t *session, const vaapi_hevc_submit_ops_t *ops,
|
||||
rh265_video *source, vaapi_hevc_long_work_t *work)
|
||||
{
|
||||
return vaapi_hevc_pool_init_work(p, session, ops, NULL, source, work);
|
||||
}
|
||||
|
||||
uint32_t vaapi_hevc_pool_held(const vaapi_hevc_pool_t *p)
|
||||
{
|
||||
unsigned i;
|
||||
uint32_t held;
|
||||
if (!p || !p->active)
|
||||
return 0;
|
||||
held = p->exports;
|
||||
for (i = 0; i < p->session->slots; i++)
|
||||
{
|
||||
if (p->transactions[i].active)
|
||||
held |= UINT32_C(1) << i;
|
||||
held |= p->reads[i];
|
||||
}
|
||||
return held;
|
||||
}
|
||||
int vaapi_hevc_pool_decode(vaapi_hevc_pool_t *p,const rh265_video_params_t *params,
|
||||
const rh265_access_unit_t *au,const rh265_hw_picture_t *picture,const rh265_nal_t *slices)
|
||||
{
|
||||
unsigned i;
|
||||
uint32_t bit,reads=0;
|
||||
vaapi_hevc_submit_t *t;
|
||||
bool accepted;
|
||||
if (!p || !p->active || p->broken || !picture
|
||||
|| picture->slot >= p->session->slots || picture->num_refs > 15
|
||||
|| !picture->serial || p->last_serial == (uint64_t)-1
|
||||
|| picture->serial != p->last_serial + 1
|
||||
|| p->next_generation == (uint64_t)-1)
|
||||
return -1;
|
||||
bit = UINT32_C(1) << picture->slot;
|
||||
if (vaapi_hevc_pool_held(p) & bit)
|
||||
return 1;
|
||||
for (i = 0; i < picture->num_refs; i++)
|
||||
{
|
||||
unsigned slot = picture->refs[i].slot;
|
||||
uint32_t ref;
|
||||
if (slot >= p->session->slots || slot == picture->slot)
|
||||
return -1;
|
||||
ref = UINT32_C(1) << slot;
|
||||
if (!(p->valid & ref) || (reads & ref)
|
||||
|| p->poc[slot] != picture->refs[i].poc)
|
||||
return -1;
|
||||
reads |= ref;
|
||||
}
|
||||
t = &p->transactions[picture->slot];
|
||||
if (p->session->slice_mode == VA_DEC_SLICE_MODE_NORMAL)
|
||||
accepted = vaapi_hevc_long_submit(t, p->session, &p->ops, p->source, params,
|
||||
au, picture, slices, p->long_work);
|
||||
else
|
||||
accepted = vaapi_hevc_short_submit(
|
||||
t, p->session, &p->ops, params, au, picture, slices, p->work);
|
||||
if (t->active)
|
||||
{
|
||||
p->reads[picture->slot] = reads;
|
||||
p->valid &= ~bit;
|
||||
p->ready &= ~bit;
|
||||
}
|
||||
if (!accepted)
|
||||
{
|
||||
if (t->active)
|
||||
p->broken = true;
|
||||
return -1;
|
||||
}
|
||||
p->valid |= bit;
|
||||
p->generation[picture->slot] = ++p->next_generation;
|
||||
p->poc[picture->slot] = picture->poc;
|
||||
p->last_serial = picture->serial;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int vaapi_hevc_pool_poll(vaapi_hevc_pool_t *p, unsigned slot)
|
||||
{
|
||||
vaapi_hevc_submit_t *t;
|
||||
uint32_t bit;
|
||||
int status;
|
||||
if (!p || !p->active || slot >= p->session->slots)
|
||||
return -1;
|
||||
bit = UINT32_C(1) << slot;
|
||||
t = &p->transactions[slot];
|
||||
if (!t->active)
|
||||
return (p->ready & bit) ? 0 : -1;
|
||||
if (!t->submitted)
|
||||
{
|
||||
if (vaapi_hevc_submit_retire(t))
|
||||
p->reads[slot] = 0;
|
||||
return -1;
|
||||
}
|
||||
status = vaapi_hevc_submit_poll(t);
|
||||
if (status == 1)
|
||||
return 1;
|
||||
if (status < 0)
|
||||
{
|
||||
p->broken = true;
|
||||
p->valid &= ~bit;
|
||||
p->ready &= ~bit;
|
||||
}
|
||||
else p->ready|=bit;
|
||||
if(t->complete)
|
||||
{
|
||||
p->reads[slot]=0;
|
||||
vaapi_hevc_submit_retire(t);
|
||||
}
|
||||
return status;
|
||||
}
|
||||
bool vaapi_hevc_pool_close(vaapi_hevc_pool_t *p)
|
||||
{
|
||||
uint64_t generation;
|
||||
if(!p || !p->active)return true;
|
||||
if(vaapi_hevc_pool_held(p) || p->broken)return false;
|
||||
generation=p->next_generation;memset(p,0,sizeof(*p));p->next_generation=generation;return true;
|
||||
}
|
||||
bool vaapi_hevc_pool_terminated(vaapi_hevc_pool_t *p,VADisplay display)
|
||||
{
|
||||
uint64_t generation;
|
||||
if(!p || !p->active || !display || p->display!=display || p->exports)return false;
|
||||
generation=p->next_generation;memset(p,0,sizeof(*p));p->next_generation=generation;return true;
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
/* Copyright (C) 2026 The RetroArch team. SPDX-License-Identifier:
|
||||
* GPL-3.0-or-later */
|
||||
#ifndef RARCH_VAAPI_HEVC_POOL_H
|
||||
#define RARCH_VAAPI_HEVC_POOL_H
|
||||
#include "vaapi_hevc_packet.h"
|
||||
#include "vaapi_hevc_long_packet.h"
|
||||
RETRO_BEGIN_DECLS
|
||||
|
||||
typedef struct vaapi_hevc_pool
|
||||
{
|
||||
const vaapi_hevc_session_t *session;
|
||||
VADisplay display;
|
||||
vaapi_hevc_short_work_t *work;
|
||||
vaapi_hevc_long_work_t *long_work;
|
||||
rh265_video *source;
|
||||
vaapi_hevc_submit_ops_t ops;
|
||||
vaapi_hevc_submit_t transactions[32];
|
||||
uint64_t generation[32], next_generation, last_serial;
|
||||
int32_t poc[32];
|
||||
uint32_t reads[32], valid, ready, exports;
|
||||
bool active, broken;
|
||||
} vaapi_hevc_pool_t;
|
||||
|
||||
/* Cold-allocated pool/work/data, initially zeroed. Session and native bindings
|
||||
* outlive close. Owner-thread only, including lease returns delivered by the
|
||||
* renderer's existing retirement mechanism. Shared rh265 state owns DPB/output
|
||||
* selection; this pool additionally protects native writes, reads and exports.
|
||||
*/
|
||||
bool vaapi_hevc_pool_init(vaapi_hevc_pool_t *p,
|
||||
const vaapi_hevc_session_t *session, const vaapi_hevc_submit_ops_t *ops,
|
||||
vaapi_hevc_short_work_t *work);
|
||||
/* NORMAL owner-thread parser borrow ends at detach. Retirement never queries
|
||||
* source. Workspace/data and session outlive native transaction retirement. */
|
||||
bool vaapi_hevc_pool_init_long(vaapi_hevc_pool_t *p,
|
||||
const vaapi_hevc_session_t *session, const vaapi_hevc_submit_ops_t *ops,
|
||||
rh265_video *source, vaapi_hevc_long_work_t *work);
|
||||
uint32_t vaapi_hevc_pool_held(const vaapi_hevc_pool_t *p);
|
||||
/* 0 accepted (caller commits prepared shared DPB), 1 physical backpressure,
|
||||
* -1 refused/broken. A failed native command requires termination recovery. */
|
||||
int vaapi_hevc_pool_decode(vaapi_hevc_pool_t *p,
|
||||
const rh265_video_params_t *params, const rh265_access_unit_t *au,
|
||||
const rh265_hw_picture_t *picture, const rh265_nal_t *slices);
|
||||
/* One slot, at most one zero-timeout check. 0 decoded, 1 pending, -1 error.
|
||||
* Cleanup failures remain held and are retried without another native check. */
|
||||
int vaapi_hevc_pool_poll(vaapi_hevc_pool_t *p, unsigned slot);
|
||||
/* One export ticket per surface incarnation. The renderer's frame object may
|
||||
* have multiple references; return the ticket once, after its last GPU use. */
|
||||
static INLINE bool vaapi_hevc_pool_acquire(vaapi_hevc_pool_t *p,unsigned slot,uint64_t *ticket)
|
||||
{
|
||||
uint32_t bit;
|
||||
if(!p || !p->active || p->broken || !ticket || slot>=p->session->slots)return false;
|
||||
bit=UINT32_C(1)<<slot;
|
||||
if(!(p->ready&bit) || (p->exports&bit) || !p->generation[slot])return false;
|
||||
p->exports|=bit;*ticket=p->generation[slot];return true;
|
||||
}
|
||||
static INLINE bool vaapi_hevc_pool_release(vaapi_hevc_pool_t *p,unsigned slot,uint64_t ticket)
|
||||
{
|
||||
uint32_t bit;
|
||||
if(!p || !p->active || slot>=p->session->slots || !ticket)return false;
|
||||
bit=UINT32_C(1)<<slot;
|
||||
if(!(p->exports&bit) || p->generation[slot]!=ticket)return false;
|
||||
p->exports&=~bit;return true;
|
||||
}
|
||||
bool vaapi_hevc_pool_close(vaapi_hevc_pool_t *p);
|
||||
/* Only after actual successful termination of this display, with no exports. */
|
||||
bool vaapi_hevc_pool_terminated(vaapi_hevc_pool_t *p, VADisplay display);
|
||||
RETRO_END_DECLS
|
||||
#endif
|
||||
@@ -0,0 +1,246 @@
|
||||
/* Copyright (C) 2026 The RetroArch team. SPDX-License-Identifier: GPL-3.0-or-later */
|
||||
#if defined(__linux__) && !defined(_GNU_SOURCE)
|
||||
#define _GNU_SOURCE
|
||||
#endif
|
||||
#include "vaapi_hevc_runtime.h"
|
||||
#include "vaapi_vp9_runtime.h"
|
||||
#include "vaapi_av1_runtime.h"
|
||||
#include <string.h>
|
||||
#if defined(__linux__) && !defined(__ANDROID__) && defined(NEED_DYNAMIC) && !defined(VAAPI_HEVC_RUNTIME_TEST)
|
||||
#include <fcntl.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/sysmacros.h>
|
||||
#define VAAPI_HEVC_NATIVE_RUNTIME 1
|
||||
static int vaapi_hevc_open_node(const char *path,unsigned major_id,unsigned minor_id)
|
||||
{
|
||||
struct stat status;
|
||||
int fd=open(path,O_RDWR|O_CLOEXEC);
|
||||
if(fd<0)return -1;
|
||||
if(fstat(fd,&status)<0 || !S_ISCHR(status.st_mode)
|
||||
|| major(status.st_rdev)!=major_id || minor(status.st_rdev)!=minor_id)
|
||||
{close(fd);return -1;}
|
||||
return fd;
|
||||
}
|
||||
static void vaapi_hevc_close_node(int fd){close(fd);}
|
||||
#endif
|
||||
static bool vaapi_runtime_open_with_io(vaapi_hevc_runtime_t *r,
|
||||
const char *path, unsigned major_id, unsigned minor_id, unsigned width,
|
||||
unsigned height, unsigned depth, unsigned slots, unsigned slice_mode,
|
||||
const vaapi_hevc_runtime_io_t *io, bool vp9, bool av1);
|
||||
bool vaapi_hevc_runtime_open(vaapi_hevc_runtime_t *r,const char *path,
|
||||
unsigned major_id,unsigned minor_id,unsigned width,unsigned height,
|
||||
unsigned depth,unsigned slots,unsigned slice_mode)
|
||||
{
|
||||
#ifdef VAAPI_HEVC_NATIVE_RUNTIME
|
||||
static const vaapi_hevc_runtime_io_t io={vaapi_hevc_open_node,vaapi_hevc_close_node,
|
||||
dylib_load,dylib_proc,dylib_close};
|
||||
return vaapi_hevc_runtime_open_with_io(r,path,major_id,minor_id,width,height,depth,slots,slice_mode,&io);
|
||||
#else
|
||||
(void)r;(void)path;(void)major_id;(void)minor_id;(void)width;(void)height;
|
||||
(void)depth;(void)slots;(void)slice_mode;return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
static bool vaapi_runtime_open_with_io(vaapi_hevc_runtime_t *r, const char *path,
|
||||
unsigned major_id, unsigned minor_id, unsigned width, unsigned height,
|
||||
unsigned depth, unsigned slots, unsigned slice_mode,
|
||||
const vaapi_hevc_runtime_io_t *io, bool vp9, bool av1)
|
||||
{
|
||||
vaapi_hevc_runtime_io_t native;
|
||||
bool frame = vp9 || av1;
|
||||
unsigned minimum_slots = av1 ? 9u : vp9 ? 10u : 2u;
|
||||
#if !VA_CHECK_VERSION(1, 8, 0)
|
||||
if (av1)
|
||||
return false;
|
||||
#endif
|
||||
if ((vp9 && av1) || !r || r->active || !path || !*path || !io
|
||||
|| !io->open_node
|
||||
|| !io->close_node || !io->load || !io->symbol || !io->unload
|
||||
|| width < (frame ? 1u : 8u)
|
||||
|| width > (av1 ? 65536u : 65535u)
|
||||
|| height < (frame ? 1u : 8u)
|
||||
|| height > (av1 ? 65536u : 65535u)
|
||||
|| (!frame && ((width & 1) || (height & 1)))
|
||||
|| (depth != 8 && depth != 10)
|
||||
|| slots < minimum_slots || slots > 32
|
||||
|| (!frame && slice_mode != VAAPI_HEVC_SLICE_AUTO
|
||||
&& slice_mode != VA_DEC_SLICE_MODE_NORMAL
|
||||
&& slice_mode != VA_DEC_SLICE_MODE_BASE))
|
||||
return false;
|
||||
native = *io;
|
||||
memset(r, 0, sizeof(*r));
|
||||
r->io = native;
|
||||
r->fd = -1;
|
||||
r->active = true;
|
||||
r->error = VA_STATUS_ERROR_INVALID_DISPLAY;
|
||||
r->fd = r->io.open_node(path, major_id, minor_id);
|
||||
if (r->fd < 0)
|
||||
return false;
|
||||
r->error = VA_STATUS_ERROR_UNIMPLEMENTED;
|
||||
r->va = r->io.load("libva.so.2");
|
||||
if (!r->va)
|
||||
return false;
|
||||
r->va_drm = r->io.load("libva-drm.so.2");
|
||||
if (!r->va_drm)
|
||||
return false;
|
||||
r->drm = r->io.load("libdrm.so.2");
|
||||
if (!r->drm)
|
||||
return false;
|
||||
#define VAAPI_HEVC_LOAD(member, library, name) \
|
||||
do \
|
||||
{ \
|
||||
function_t symbol = r->io.symbol(r->library, name); \
|
||||
if (!symbol || sizeof(symbol) != sizeof(r->member)) \
|
||||
return false; \
|
||||
memcpy(&r->member, &symbol, sizeof(symbol)); \
|
||||
} while (0)
|
||||
VAAPI_HEVC_LOAD(get_display, va_drm, "vaGetDisplayDRM");
|
||||
VAAPI_HEVC_LOAD(initialize, va, "vaInitialize");
|
||||
VAAPI_HEVC_LOAD(terminate, va, "vaTerminate");
|
||||
VAAPI_HEVC_LOAD(node_type, drm, "drmGetNodeTypeFromFd");
|
||||
VAAPI_HEVC_LOAD(session.ops.max_profiles, va, "vaMaxNumProfiles");
|
||||
VAAPI_HEVC_LOAD(session.ops.profiles, va, "vaQueryConfigProfiles");
|
||||
VAAPI_HEVC_LOAD(session.ops.max_entrypoints, va, "vaMaxNumEntrypoints");
|
||||
VAAPI_HEVC_LOAD(session.ops.entrypoints, va, "vaQueryConfigEntrypoints");
|
||||
VAAPI_HEVC_LOAD(session.ops.attributes, va, "vaGetConfigAttributes");
|
||||
VAAPI_HEVC_LOAD(session.ops.create_config, va, "vaCreateConfig");
|
||||
VAAPI_HEVC_LOAD(session.ops.destroy_config, va, "vaDestroyConfig");
|
||||
VAAPI_HEVC_LOAD(
|
||||
session.ops.surface_attributes, va, "vaQuerySurfaceAttributes");
|
||||
VAAPI_HEVC_LOAD(session.ops.create_surfaces, va, "vaCreateSurfaces");
|
||||
VAAPI_HEVC_LOAD(session.ops.destroy_surfaces, va, "vaDestroySurfaces");
|
||||
VAAPI_HEVC_LOAD(session.ops.create_context, va, "vaCreateContext");
|
||||
VAAPI_HEVC_LOAD(session.ops.destroy_context, va, "vaDestroyContext");
|
||||
VAAPI_HEVC_LOAD(submit.create, va, "vaCreateBuffer");
|
||||
VAAPI_HEVC_LOAD(submit.destroy, va, "vaDestroyBuffer");
|
||||
VAAPI_HEVC_LOAD(submit.begin, va, "vaBeginPicture");
|
||||
VAAPI_HEVC_LOAD(submit.render, va, "vaRenderPicture");
|
||||
VAAPI_HEVC_LOAD(submit.end, va, "vaEndPicture");
|
||||
VAAPI_HEVC_LOAD(submit.sync, va, "vaSyncSurface2");
|
||||
#undef VAAPI_HEVC_LOAD
|
||||
r->error = VA_STATUS_ERROR_INVALID_DISPLAY;
|
||||
if (r->node_type(r->fd) != VAAPI_HEVC_RENDER_NODE)
|
||||
return false;
|
||||
r->display = r->get_display(r->fd);
|
||||
if (!r->display)
|
||||
return false;
|
||||
r->error = r->initialize(r->display, &r->major_version, &r->minor_version);
|
||||
if (r->error != VA_STATUS_SUCCESS)
|
||||
return false;
|
||||
if (r->major_version != VA_MAJOR_VERSION || r->minor_version < 0)
|
||||
{
|
||||
r->error = VA_STATUS_ERROR_UNIMPLEMENTED;
|
||||
return false;
|
||||
}
|
||||
if (!(av1 ? vaapi_av1_session_open(&r->session, r->display,
|
||||
&r->session.ops, width, height, depth, slots)
|
||||
: vp9 ? vaapi_vp9_session_open(&r->session, r->display,
|
||||
&r->session.ops, width, height, depth, slots)
|
||||
: vaapi_hevc_session_open(&r->session, r->display,
|
||||
&r->session.ops, width, height, depth, slots, slice_mode)))
|
||||
{
|
||||
r->error = r->session.error;
|
||||
return false;
|
||||
}
|
||||
r->ready = true;
|
||||
return true;
|
||||
}
|
||||
bool vaapi_hevc_runtime_open_with_io(vaapi_hevc_runtime_t *r,
|
||||
const char *path, unsigned major_id, unsigned minor_id, unsigned width,
|
||||
unsigned height, unsigned depth, unsigned slots, unsigned slice_mode,
|
||||
const vaapi_hevc_runtime_io_t *io)
|
||||
{
|
||||
return vaapi_runtime_open_with_io(r, path, major_id, minor_id, width,
|
||||
height, depth, slots, slice_mode, io, false, false);
|
||||
}
|
||||
|
||||
bool vaapi_vp9_runtime_open_with_io(vaapi_vp9_runtime_t *r,
|
||||
const char *path, unsigned major_id, unsigned minor_id, unsigned width,
|
||||
unsigned height, unsigned depth, unsigned slots,
|
||||
const vaapi_vp9_runtime_io_t *io)
|
||||
{
|
||||
return vaapi_runtime_open_with_io(r, path, major_id, minor_id, width,
|
||||
height, depth, slots, 0, io, true, false);
|
||||
}
|
||||
|
||||
bool vaapi_vp9_runtime_open(vaapi_vp9_runtime_t *r, const char *path,
|
||||
unsigned major_id, unsigned minor_id, unsigned width, unsigned height,
|
||||
unsigned depth, unsigned slots)
|
||||
{
|
||||
#ifdef VAAPI_HEVC_NATIVE_RUNTIME
|
||||
static const vaapi_vp9_runtime_io_t io = {vaapi_hevc_open_node,
|
||||
vaapi_hevc_close_node, dylib_load, dylib_proc, dylib_close};
|
||||
return vaapi_vp9_runtime_open_with_io(r, path, major_id, minor_id, width,
|
||||
height, depth, slots, &io);
|
||||
#else
|
||||
(void)r;
|
||||
(void)path;
|
||||
(void)major_id;
|
||||
(void)minor_id;
|
||||
(void)width;
|
||||
(void)height;
|
||||
(void)depth;
|
||||
(void)slots;
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
bool vaapi_av1_runtime_open_with_io(vaapi_av1_runtime_t *r,
|
||||
const char *path, unsigned major_id, unsigned minor_id, unsigned width,
|
||||
unsigned height, unsigned depth, unsigned slots,
|
||||
const vaapi_av1_runtime_io_t *io)
|
||||
{
|
||||
return vaapi_runtime_open_with_io(r, path, major_id, minor_id, width,
|
||||
height, depth, slots, 0, io, false, true);
|
||||
}
|
||||
|
||||
bool vaapi_av1_runtime_open(vaapi_av1_runtime_t *r, const char *path,
|
||||
unsigned major_id, unsigned minor_id, unsigned width, unsigned height,
|
||||
unsigned depth, unsigned slots)
|
||||
{
|
||||
#ifdef VAAPI_HEVC_NATIVE_RUNTIME
|
||||
static const vaapi_av1_runtime_io_t io = {vaapi_hevc_open_node,
|
||||
vaapi_hevc_close_node, dylib_load, dylib_proc, dylib_close};
|
||||
return vaapi_av1_runtime_open_with_io(r, path, major_id, minor_id, width,
|
||||
height, depth, slots, &io);
|
||||
#else
|
||||
(void)r;
|
||||
(void)path;
|
||||
(void)major_id;
|
||||
(void)minor_id;
|
||||
(void)width;
|
||||
(void)height;
|
||||
(void)depth;
|
||||
(void)slots;
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
bool vaapi_hevc_runtime_close(vaapi_hevc_runtime_t *r)
|
||||
{
|
||||
if(!r || !r->active)return true;
|
||||
r->ready=false;r->closing=true;
|
||||
if(r->display)
|
||||
{
|
||||
if(!r->terminating)
|
||||
{
|
||||
vaapi_hevc_session_close(&r->session);
|
||||
r->terminating=true;
|
||||
}
|
||||
if(!r->terminated)
|
||||
{
|
||||
r->error=r->terminate(r->display);
|
||||
if(r->error!=VA_STATUS_SUCCESS)return false;
|
||||
r->terminated=true;
|
||||
}
|
||||
if(r->session.initialized && !vaapi_hevc_session_terminated(&r->session,r->display))
|
||||
{r->error=VA_STATUS_ERROR_OPERATION_FAILED;return false;}
|
||||
r->display=NULL;
|
||||
}
|
||||
if(r->drm)r->io.unload(r->drm);
|
||||
if(r->va_drm)r->io.unload(r->va_drm);
|
||||
if(r->va)r->io.unload(r->va);
|
||||
if(r->fd>=0)r->io.close_node(r->fd);
|
||||
memset(r,0,sizeof(*r));return true;
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
/* Copyright (C) 2026 The RetroArch team. SPDX-License-Identifier: GPL-3.0-or-later */
|
||||
#ifndef RARCH_VAAPI_HEVC_RUNTIME_H
|
||||
#define RARCH_VAAPI_HEVC_RUNTIME_H
|
||||
#include "vaapi_hevc_submit.h"
|
||||
#include <dynamic/dylib.h>
|
||||
RETRO_BEGIN_DECLS
|
||||
enum { VAAPI_HEVC_RENDER_NODE=2 };
|
||||
typedef struct vaapi_hevc_runtime_io {
|
||||
/* Returns an owned CLOEXEC character-device FD matching the renderer IDs. */
|
||||
int (*open_node)(const char*,unsigned,unsigned);
|
||||
void (*close_node)(int);
|
||||
dylib_t (*load)(const char*);
|
||||
function_t (*symbol)(dylib_t,const char*);
|
||||
void (*unload)(dylib_t);
|
||||
} vaapi_hevc_runtime_io_t;
|
||||
typedef struct vaapi_hevc_runtime {
|
||||
vaapi_hevc_session_t session;
|
||||
vaapi_hevc_submit_ops_t submit;
|
||||
vaapi_hevc_runtime_io_t io;
|
||||
dylib_t va,va_drm,drm;
|
||||
VADisplay display;
|
||||
VADisplay (*get_display)(int);
|
||||
VAStatus (*initialize)(VADisplay,int*,int*);
|
||||
VAStatus (*terminate)(VADisplay);
|
||||
int (*node_type)(int);
|
||||
VAStatus error;
|
||||
int fd,major_version,minor_version;
|
||||
bool active,ready,closing,terminating,terminated;
|
||||
} vaapi_hevc_runtime_t;
|
||||
/* Cold owner only, initially zeroed storage. The renderer supplies its actual
|
||||
* DRM render-device major/minor identity. The path is borrowed during open.
|
||||
* A false result still requires close. No backend is registered or exported
|
||||
* frame compatibility advertised. Linux default; other platforms refuse. */
|
||||
bool vaapi_hevc_runtime_open(vaapi_hevc_runtime_t *r,const char *path,
|
||||
unsigned major_id,unsigned minor_id,unsigned width,unsigned height,
|
||||
unsigned depth,unsigned slots,unsigned slice_mode);
|
||||
/* Same contract with platform I/O supplied explicitly; copied before use. */
|
||||
bool vaapi_hevc_runtime_open_with_io(vaapi_hevc_runtime_t *r,const char *path,
|
||||
unsigned major_id,unsigned minor_id,unsigned width,unsigned height,
|
||||
unsigned depth,unsigned slots,unsigned slice_mode,const vaapi_hevc_runtime_io_t *io);
|
||||
/* Cold executor, after exports retire and native work completes, or to recover
|
||||
* a poisoned pool whose decoder has stopped. False retains the
|
||||
* libraries, display and FD. Once termination begins, only vaTerminate may be
|
||||
* retried: an unsuccessful termination can already invalidate the driver. */
|
||||
bool vaapi_hevc_runtime_close(vaapi_hevc_runtime_t *r);
|
||||
RETRO_END_DECLS
|
||||
#endif
|
||||
@@ -0,0 +1,285 @@
|
||||
/* Copyright (C) 2026 The RetroArch team. SPDX-License-Identifier: GPL-3.0-or-later */
|
||||
#include "vaapi_hevc_session.h"
|
||||
#include "vaapi_vp9_session.h"
|
||||
#include "vaapi_av1_session.h"
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
typedef union vaapi_hevc_query {
|
||||
VAProfile profiles[256];
|
||||
VAEntrypoint entrypoints[256];
|
||||
VASurfaceAttrib surfaces[64];
|
||||
} vaapi_hevc_query_t;
|
||||
static bool vaapi_hevc_session_surfaces(vaapi_hevc_session_t *s)
|
||||
{
|
||||
unsigned i,j;
|
||||
bool complete=true;
|
||||
for(i=0;i<s->slots;i++)
|
||||
{
|
||||
if(s->surfaces[i]==VA_INVALID_SURFACE){complete=false;continue;}
|
||||
for(j=0;j<i;j++)if(s->surfaces[i]==s->surfaces[j])
|
||||
{s->surfaces[i]=VA_INVALID_SURFACE;complete=false;break;}
|
||||
}
|
||||
return complete;
|
||||
}
|
||||
|
||||
static bool vaapi_session_open(vaapi_hevc_session_t *s, VADisplay display,
|
||||
const vaapi_hevc_session_ops_t *ops, unsigned width, unsigned height,
|
||||
unsigned depth, unsigned slots, unsigned slice_mode, bool vp9, bool av1)
|
||||
{
|
||||
vaapi_hevc_query_t *q;
|
||||
vaapi_hevc_session_ops_t native;
|
||||
VAConfigAttrib attrs[4];
|
||||
VASurfaceAttrib request[2];
|
||||
VAProfile profile = vp9
|
||||
? (depth == 10 ? VAProfileVP9Profile2 : VAProfileVP9Profile0)
|
||||
: (depth == 10 ? VAProfileHEVCMain10 : VAProfileHEVCMain);
|
||||
unsigned format = depth == 10 ? VA_RT_FORMAT_YUV420_10 : VA_RT_FORMAT_YUV420;
|
||||
unsigned fourcc = depth == 10 ? VA_FOURCC_P010 : VA_FOURCC_NV12;
|
||||
bool frame = vp9 || av1;
|
||||
unsigned minimum_slots = av1 ? 9u : vp9 ? 10u : 2u;
|
||||
unsigned i, count, requests = 1, config_attrs = frame ? 1 : 2;
|
||||
unsigned width_attr = frame ? 1 : 2, height_attr = frame ? 2 : 3;
|
||||
int maximum, n;
|
||||
bool found;
|
||||
if (av1)
|
||||
{
|
||||
#if VA_CHECK_VERSION(1, 8, 0)
|
||||
profile = VAProfileAV1Profile0;
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
if ((vp9 && av1) || !s || s->initialized || !display || !ops
|
||||
|| !ops->max_profiles
|
||||
|| !ops->profiles || !ops->max_entrypoints || !ops->entrypoints
|
||||
|| !ops->attributes || !ops->create_config || !ops->destroy_config
|
||||
|| !ops->surface_attributes || !ops->create_surfaces
|
||||
|| !ops->destroy_surfaces || !ops->create_context
|
||||
|| !ops->destroy_context || width < (frame ? 1u : 8u)
|
||||
|| width > (av1 ? 65536u : 65535u)
|
||||
|| height < (frame ? 1u : 8u)
|
||||
|| height > (av1 ? 65536u : 65535u)
|
||||
|| (!frame && ((width & 1) || (height & 1)))
|
||||
|| (depth != 8 && depth != 10) || slots < minimum_slots || slots > 32
|
||||
|| (slice_mode != VAAPI_HEVC_SLICE_AUTO
|
||||
&& slice_mode != VA_DEC_SLICE_MODE_NORMAL
|
||||
&& slice_mode != VA_DEC_SLICE_MODE_BASE))
|
||||
return false;
|
||||
native = *ops;
|
||||
memset(s, 0, sizeof(*s));
|
||||
s->ops = native;
|
||||
s->display = display;
|
||||
ops = &s->ops;
|
||||
s->width = width;
|
||||
s->height = height;
|
||||
s->depth = depth;
|
||||
s->profile = profile;
|
||||
s->slots = slots;
|
||||
s->slice_mode = slice_mode;
|
||||
s->initialized = true;
|
||||
s->config = VA_INVALID_ID;
|
||||
s->context = VA_INVALID_ID;
|
||||
for (i = 0; i < 32; i++)
|
||||
s->surfaces[i] = VA_INVALID_SURFACE;
|
||||
s->error = VA_STATUS_ERROR_ALLOCATION_FAILED;
|
||||
q = (vaapi_hevc_query_t *)calloc(1, sizeof(*q));
|
||||
if (!q)
|
||||
return false;
|
||||
s->error = VA_STATUS_ERROR_OPERATION_FAILED;
|
||||
maximum = ops->max_profiles(display);
|
||||
if (maximum < 1 || maximum > 256)
|
||||
goto done;
|
||||
n = maximum;
|
||||
s->error = ops->profiles(display, q->profiles, &n);
|
||||
if (s->error != VA_STATUS_SUCCESS || n < 0 || n > maximum)
|
||||
goto failed;
|
||||
found = false;
|
||||
for (i = 0; i < (unsigned)n; i++)
|
||||
if (q->profiles[i] == profile)
|
||||
found = true;
|
||||
if (!found)
|
||||
{
|
||||
s->error = VA_STATUS_ERROR_UNSUPPORTED_PROFILE;
|
||||
goto done;
|
||||
}
|
||||
maximum = ops->max_entrypoints(display);
|
||||
if (maximum < 1 || maximum > 256)
|
||||
goto failed;
|
||||
n = maximum;
|
||||
s->error = ops->entrypoints(display, profile, q->entrypoints, &n);
|
||||
if (s->error != VA_STATUS_SUCCESS || n < 0 || n > maximum)
|
||||
goto failed;
|
||||
found = false;
|
||||
for (i = 0; i < (unsigned)n; i++)
|
||||
if (q->entrypoints[i] == VAEntrypointVLD)
|
||||
found = true;
|
||||
if (!found)
|
||||
{
|
||||
s->error = VA_STATUS_ERROR_UNSUPPORTED_ENTRYPOINT;
|
||||
goto done;
|
||||
}
|
||||
memset(attrs, 0, sizeof(attrs));
|
||||
attrs[0].type = VAConfigAttribRTFormat;
|
||||
attrs[1].type = VAConfigAttribDecSliceMode;
|
||||
attrs[width_attr].type = VAConfigAttribMaxPictureWidth;
|
||||
attrs[height_attr].type = VAConfigAttribMaxPictureHeight;
|
||||
for (i = 0; i < 4; i++)
|
||||
attrs[i].value = VA_ATTRIB_NOT_SUPPORTED;
|
||||
s->error = ops->attributes(
|
||||
display, profile, VAEntrypointVLD, attrs, frame ? 3 : 4);
|
||||
if (s->error != VA_STATUS_SUCCESS)
|
||||
goto done;
|
||||
if (attrs[0].value == VA_ATTRIB_NOT_SUPPORTED || !(attrs[0].value & format))
|
||||
{
|
||||
s->error = VA_STATUS_ERROR_UNSUPPORTED_RT_FORMAT;
|
||||
goto done;
|
||||
}
|
||||
if (!frame && slice_mode == VAAPI_HEVC_SLICE_AUTO)
|
||||
{
|
||||
if (attrs[1].value == VA_ATTRIB_NOT_SUPPORTED
|
||||
|| (attrs[1].value & VA_DEC_SLICE_MODE_NORMAL))
|
||||
slice_mode = VA_DEC_SLICE_MODE_NORMAL;
|
||||
else
|
||||
slice_mode = VA_DEC_SLICE_MODE_BASE;
|
||||
s->slice_mode = slice_mode;
|
||||
}
|
||||
if (!frame && attrs[1].value == VA_ATTRIB_NOT_SUPPORTED
|
||||
&& slice_mode == VA_DEC_SLICE_MODE_NORMAL)
|
||||
config_attrs = 1;
|
||||
else if (!frame && (attrs[1].value == VA_ATTRIB_NOT_SUPPORTED
|
||||
|| !(attrs[1].value & slice_mode)))
|
||||
{
|
||||
s->error = VA_STATUS_ERROR_ATTR_NOT_SUPPORTED;
|
||||
goto done;
|
||||
}
|
||||
if ((attrs[width_attr].value != VA_ATTRIB_NOT_SUPPORTED
|
||||
&& width > attrs[width_attr].value)
|
||||
|| (attrs[height_attr].value != VA_ATTRIB_NOT_SUPPORTED
|
||||
&& height > attrs[height_attr].value))
|
||||
{
|
||||
s->error = VA_STATUS_ERROR_RESOLUTION_NOT_SUPPORTED;
|
||||
goto done;
|
||||
}
|
||||
attrs[0].value = format;
|
||||
if (!frame)
|
||||
attrs[1].value = slice_mode;
|
||||
s->error = ops->create_config(display, profile, VAEntrypointVLD, attrs,
|
||||
(int)config_attrs, &s->config);
|
||||
if (s->error != VA_STATUS_SUCCESS || s->config == VA_INVALID_ID)
|
||||
goto failed;
|
||||
count = 0;
|
||||
s->error = ops->surface_attributes(display, s->config, NULL, &count);
|
||||
if (s->error != VA_STATUS_SUCCESS || !count || count > 64)
|
||||
goto failed;
|
||||
memset(q, 0, sizeof(*q));
|
||||
s->error = ops->surface_attributes(display, s->config, q->surfaces, &count);
|
||||
if (s->error != VA_STATUS_SUCCESS || count > 64)
|
||||
goto failed;
|
||||
memset(request, 0, sizeof(request));
|
||||
found = false;
|
||||
for (i = 0; i < count; i++)
|
||||
{
|
||||
const VASurfaceAttrib *a = &q->surfaces[i];
|
||||
if (!(a->flags & VA_SURFACE_ATTRIB_SETTABLE)
|
||||
|| a->value.type != VAGenericValueTypeInteger)
|
||||
continue;
|
||||
if (a->type == VASurfaceAttribPixelFormat
|
||||
&& (unsigned)a->value.value.i == fourcc)
|
||||
found = true;
|
||||
if (a->type == VASurfaceAttribUsageHint
|
||||
&& (a->value.value.i & VA_SURFACE_ATTRIB_USAGE_HINT_DECODER))
|
||||
{
|
||||
request[1].type = VASurfaceAttribUsageHint;
|
||||
request[1].flags = VA_SURFACE_ATTRIB_SETTABLE;
|
||||
request[1].value.type = VAGenericValueTypeInteger;
|
||||
request[1].value.value.i = VA_SURFACE_ATTRIB_USAGE_HINT_DECODER;
|
||||
requests = 2;
|
||||
}
|
||||
}
|
||||
if (!found)
|
||||
{
|
||||
s->error = VA_STATUS_ERROR_UNSUPPORTED_RT_FORMAT;
|
||||
goto done;
|
||||
}
|
||||
request[0].type = VASurfaceAttribPixelFormat;
|
||||
request[0].flags = VA_SURFACE_ATTRIB_SETTABLE;
|
||||
request[0].value.type = VAGenericValueTypeInteger;
|
||||
request[0].value.value.i = (int)fourcc;
|
||||
s->error = ops->create_surfaces(
|
||||
display, format, width, height, s->surfaces, slots, request, requests);
|
||||
found = vaapi_hevc_session_surfaces(s);
|
||||
if (s->error != VA_STATUS_SUCCESS)
|
||||
goto done;
|
||||
if (!found)
|
||||
goto failed;
|
||||
s->error = ops->create_context(display, s->config, (int)width, (int)height,
|
||||
VA_PROGRESSIVE, s->surfaces, (int)slots, &s->context);
|
||||
if (s->error != VA_STATUS_SUCCESS || s->context == VA_INVALID_ID)
|
||||
goto failed;
|
||||
s->ready = true;
|
||||
goto done;
|
||||
failed:
|
||||
if (s->error == VA_STATUS_SUCCESS)
|
||||
s->error = VA_STATUS_ERROR_OPERATION_FAILED;
|
||||
done:
|
||||
free(q);
|
||||
return s->ready;
|
||||
}
|
||||
bool vaapi_hevc_session_open(vaapi_hevc_session_t *s, VADisplay display,
|
||||
const vaapi_hevc_session_ops_t *ops, unsigned width, unsigned height,
|
||||
unsigned depth, unsigned slots, unsigned slice_mode)
|
||||
{
|
||||
return vaapi_session_open(s, display, ops, width, height, depth, slots,
|
||||
slice_mode, false, false);
|
||||
}
|
||||
|
||||
bool vaapi_vp9_session_open(vaapi_vp9_session_t *s, VADisplay display,
|
||||
const vaapi_vp9_session_ops_t *ops, unsigned width, unsigned height,
|
||||
unsigned depth, unsigned slots)
|
||||
{
|
||||
return vaapi_session_open(s, display, ops, width, height, depth, slots,
|
||||
0, true, false);
|
||||
}
|
||||
|
||||
bool vaapi_av1_session_open(vaapi_av1_session_t *s, VADisplay display,
|
||||
const vaapi_av1_session_ops_t *ops, unsigned width, unsigned height,
|
||||
unsigned depth, unsigned slots)
|
||||
{
|
||||
return vaapi_session_open(s, display, ops, width, height, depth, slots,
|
||||
0, false, true);
|
||||
}
|
||||
|
||||
bool vaapi_hevc_session_close(vaapi_hevc_session_t *s)
|
||||
{
|
||||
unsigned i;
|
||||
bool failed=false;
|
||||
VAStatus result;
|
||||
if(!s || !s->initialized)return true;
|
||||
s->closing=true;s->ready=false;
|
||||
if(s->context!=VA_INVALID_ID)
|
||||
{
|
||||
result=s->ops.destroy_context(s->display,s->context);
|
||||
if(result!=VA_STATUS_SUCCESS){s->error=result;return false;}
|
||||
s->context=VA_INVALID_ID;
|
||||
}
|
||||
for(i=0;i<s->slots;i++)if(s->surfaces[i]!=VA_INVALID_SURFACE)
|
||||
{
|
||||
VASurfaceID surface=s->surfaces[i];
|
||||
result=s->ops.destroy_surfaces(s->display,&surface,1);
|
||||
if(result!=VA_STATUS_SUCCESS){s->error=result;failed=true;}
|
||||
else s->surfaces[i]=VA_INVALID_SURFACE;
|
||||
}
|
||||
if(failed)return false;
|
||||
if(s->config!=VA_INVALID_ID)
|
||||
{
|
||||
result=s->ops.destroy_config(s->display,s->config);
|
||||
if(result!=VA_STATUS_SUCCESS){s->error=result;return false;}
|
||||
s->config=VA_INVALID_ID;
|
||||
}
|
||||
memset(s,0,sizeof(*s));return true;
|
||||
}
|
||||
bool vaapi_hevc_session_terminated(vaapi_hevc_session_t *s,VADisplay display)
|
||||
{
|
||||
if(!s || !s->initialized || !s->closing || !display || s->display!=display)return false;
|
||||
memset(s,0,sizeof(*s));return true;
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
/* Copyright (C) 2026 The RetroArch team. SPDX-License-Identifier: GPL-3.0-or-later */
|
||||
#ifndef RARCH_VAAPI_HEVC_SESSION_H
|
||||
#define RARCH_VAAPI_HEVC_SESSION_H
|
||||
#include <va/va.h>
|
||||
#include <boolean.h>
|
||||
#include <retro_common_api.h>
|
||||
RETRO_BEGIN_DECLS
|
||||
#define VAAPI_HEVC_SLICE_AUTO 0
|
||||
typedef struct vaapi_hevc_session_ops {
|
||||
int (*max_profiles)(VADisplay);
|
||||
VAStatus (*profiles)(VADisplay,VAProfile*,int*);
|
||||
int (*max_entrypoints)(VADisplay);
|
||||
VAStatus (*entrypoints)(VADisplay,VAProfile,VAEntrypoint*,int*);
|
||||
VAStatus (*attributes)(VADisplay,VAProfile,VAEntrypoint,VAConfigAttrib*,int);
|
||||
VAStatus (*create_config)(VADisplay,VAProfile,VAEntrypoint,VAConfigAttrib*,int,VAConfigID*);
|
||||
VAStatus (*destroy_config)(VADisplay,VAConfigID);
|
||||
VAStatus (*surface_attributes)(VADisplay,VAConfigID,VASurfaceAttrib*,unsigned*);
|
||||
VAStatus (*create_surfaces)(VADisplay,unsigned,unsigned,unsigned,VASurfaceID*,unsigned,VASurfaceAttrib*,unsigned);
|
||||
VAStatus (*destroy_surfaces)(VADisplay,VASurfaceID*,int);
|
||||
VAStatus (*create_context)(VADisplay,VAConfigID,int,int,int,VASurfaceID*,int,VAContextID*);
|
||||
VAStatus (*destroy_context)(VADisplay,VAContextID);
|
||||
} vaapi_hevc_session_ops_t;
|
||||
typedef struct vaapi_hevc_session {
|
||||
vaapi_hevc_session_ops_t ops;
|
||||
VADisplay display;
|
||||
VASurfaceID surfaces[32];
|
||||
VAConfigID config;
|
||||
VAContextID context;
|
||||
VAStatus error;
|
||||
unsigned slots,width,height,depth,slice_mode;
|
||||
VAProfile profile;
|
||||
bool initialized,ready,closing;
|
||||
} vaapi_hevc_session_t;
|
||||
/* Cold decoder worker, initially zeroed storage. Display is initialized and
|
||||
* borrowed; native functions/library outlive retirement. Explicit NORMAL/BASE mode
|
||||
* is a caller implementation choice, not inferred from picture parameters.
|
||||
* AUTO prefers NORMAL, including the API default when the attribute is absent,
|
||||
* and selects BASE only if NORMAL is not advertised. The caller implements both.
|
||||
* Failure can retain partial native ownership: close even after false.
|
||||
* This does not prove surface export/import support or register a backend. */
|
||||
bool vaapi_hevc_session_open(vaapi_hevc_session_t *s,VADisplay display,
|
||||
const vaapi_hevc_session_ops_t *ops,unsigned width,unsigned height,
|
||||
unsigned depth,unsigned slots,unsigned slice_mode);
|
||||
/* Cold retirement executor only, after all native/GPU work and exports retire.
|
||||
* False retains failed handles for retry; true releases the display/library
|
||||
* borrow and permits storage reuse. Never closes or terminates the display. */
|
||||
bool vaapi_hevc_session_close(vaapi_hevc_session_t *s);
|
||||
/* Owner-only recovery after successful vaTerminate(display), with native/GPU
|
||||
* work and exports retired. For a matching closing session; no native calls.
|
||||
* A failed vaTerminate does not permit this release. */
|
||||
bool vaapi_hevc_session_terminated(vaapi_hevc_session_t *s,VADisplay display);
|
||||
RETRO_END_DECLS
|
||||
#endif
|
||||
@@ -0,0 +1,227 @@
|
||||
/* Copyright (C) 2026 The RetroArch team.
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later */
|
||||
#ifndef RARCH_VAAPI_HEVC_SLICE_H
|
||||
#define RARCH_VAAPI_HEVC_SLICE_H
|
||||
|
||||
#include "vaapi_hevc.h"
|
||||
#include <limits.h>
|
||||
|
||||
/* Parsed, independent Main/Main10 slice and a picture already prepared by
|
||||
* vaapi_hevc_picture. Reference indices address that picture's ReferenceFrames,
|
||||
* never surface IDs. No allocation, native operation or DPB advancement.
|
||||
* Failure preserves out. Outputs must be disjoint from the inputs. Substream
|
||||
* lengths are supplied separately by the caller when packing a NORMAL AU. */
|
||||
static INLINE bool vaapi_hevc_long_slice(VASliceParameterBufferHEVC *out,
|
||||
const rh265_video_params_t *params, const rh265_slice_decode_t *slice,
|
||||
const VAPictureParameterBufferHEVC *picture, size_t size, unsigned offset,
|
||||
bool last)
|
||||
{
|
||||
VASliceParameterBufferHEVC v;
|
||||
const rh265_slice_info_t *h;
|
||||
uint8_t groups[3][16], counts[3], order[16];
|
||||
unsigned group, i, j, list, total = 0, seen = 0;
|
||||
size_t header;
|
||||
int qp_delta;
|
||||
if (!out || !params || !slice || !picture || size < 3 || size > UINT_MAX
|
||||
|| offset > UINT_MAX - size
|
||||
|| (params->bit_depth != 8 && params->bit_depth != 10)
|
||||
|| params->log2_max_poc_lsb < 4 || params->log2_max_poc_lsb > 16
|
||||
|| params->width <= 0 || params->width > 65535 || params->height <= 0
|
||||
|| params->height > 65535 || params->log2_ctb < 3
|
||||
|| params->log2_ctb > 6)
|
||||
return false;
|
||||
h = &slice->picture;
|
||||
if (h->slice_type < 0 || h->slice_type > 2 || h->pps_id != params->pps_id
|
||||
|| h->slice_segment_address < 0 || h->num_negative < 0
|
||||
|| h->num_negative > 16 || h->num_positive < 0 || h->num_positive > 16
|
||||
|| h->long_term.count > 16 || h->header_bits % 8
|
||||
|| h->header_bits / 8 > size - 2 || h->header_bytes > size
|
||||
|| slice->num_entry_points > 65535 || slice->luma_log2_denom < 0
|
||||
|| slice->luma_log2_denom > 7 || slice->chroma_log2_denom < 0
|
||||
|| slice->chroma_log2_denom > 7
|
||||
|| slice->qp < -(params->bit_depth - 8) * 6 || slice->qp > 51
|
||||
|| params->init_qp < -(params->bit_depth - 8) * 6
|
||||
|| params->init_qp > 51 || slice->cb_qp_offset < -12
|
||||
|| slice->cb_qp_offset > 12 || slice->cr_qp_offset < -12
|
||||
|| slice->cr_qp_offset > 12 || slice->beta_offset_div2 < -6
|
||||
|| slice->beta_offset_div2 > 6 || slice->tc_offset_div2 < -6
|
||||
|| slice->tc_offset_div2 > 6)
|
||||
return false;
|
||||
header = 2 + h->header_bits / 8;
|
||||
if (h->header_bytes < header || h->header_bytes - header > 65535)
|
||||
return false;
|
||||
if ((unsigned)h->slice_segment_address
|
||||
>= (((unsigned)params->width + (1u << params->log2_ctb) - 1)
|
||||
>> params->log2_ctb)
|
||||
* (((unsigned)params->height + (1u << params->log2_ctb) - 1)
|
||||
>> params->log2_ctb))
|
||||
return false;
|
||||
memset(counts, 0, sizeof(counts));
|
||||
for (group = 0; group < 3; group++)
|
||||
{
|
||||
unsigned count =
|
||||
group == 2 ? h->long_term.count
|
||||
: (unsigned)(group ? h->num_positive : h->num_negative);
|
||||
for (i = 0; i < count; i++)
|
||||
{
|
||||
unsigned active, found = 15;
|
||||
uint32_t flag = group == 2 ? VA_PICTURE_HEVC_RPS_LT_CURR
|
||||
: group ? VA_PICTURE_HEVC_RPS_ST_CURR_AFTER
|
||||
: VA_PICTURE_HEVC_RPS_ST_CURR_BEFORE;
|
||||
int64_t poc;
|
||||
uint32_t mask = (1u << params->log2_max_poc_lsb) - 1;
|
||||
if (group == 2)
|
||||
{
|
||||
if (h->long_term.used[i] > 1 || h->long_term.msb_present[i] > 1
|
||||
|| h->long_term.poc_lsb[i] > mask)
|
||||
return false;
|
||||
active = h->long_term.used[i];
|
||||
poc = (int64_t)picture->CurrPic.pic_order_cnt - h->poc_lsb
|
||||
+ h->long_term.poc_lsb[i]
|
||||
- (int64_t)h->long_term.msb_cycle[i] * (mask + 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
const int32_t *delta =
|
||||
group ? h->delta_poc_after : h->delta_poc_before;
|
||||
const uint8_t *used = group ? h->used_after : h->used_before;
|
||||
if (used[i] > 1 || (group ? delta[i] <= 0 : delta[i] >= 0)
|
||||
|| (i
|
||||
&& (group ? delta[i] <= delta[i - 1]
|
||||
: delta[i] >= delta[i - 1])))
|
||||
return false;
|
||||
active = used[i];
|
||||
poc = (int64_t)picture->CurrPic.pic_order_cnt + delta[i];
|
||||
}
|
||||
if (!active)
|
||||
continue;
|
||||
for (j = 0; j < 15; j++)
|
||||
{
|
||||
const VAPictureHEVC *ref = &picture->ReferenceFrames[j];
|
||||
bool match = group == 2 && !h->long_term.msb_present[i]
|
||||
? ((uint32_t)ref->pic_order_cnt & mask)
|
||||
== h->long_term.poc_lsb[i]
|
||||
: (int64_t)ref->pic_order_cnt == poc;
|
||||
if (!(ref->flags & VA_PICTURE_HEVC_INVALID) && (ref->flags & flag)
|
||||
&& match
|
||||
&& !!(ref->flags & VA_PICTURE_HEVC_LONG_TERM_REFERENCE)
|
||||
== (group == 2))
|
||||
{
|
||||
if (found != 15 || ref->picture_id == VA_INVALID_SURFACE)
|
||||
return false;
|
||||
found = j;
|
||||
}
|
||||
}
|
||||
if (found == 15 || total >= 8 || (seen & (1u << found)))
|
||||
return false;
|
||||
seen |= 1u << found;
|
||||
total++;
|
||||
groups[group][counts[group]++] = (uint8_t)found;
|
||||
}
|
||||
}
|
||||
memset(&v, 0, sizeof(v));
|
||||
memset(v.RefPicList, 0xff, sizeof(v.RefPicList));
|
||||
v.collocated_ref_idx = 0xff;
|
||||
for (list = 0; list < (h->slice_type == 0 ? 2u
|
||||
: h->slice_type == 1 ? 1u
|
||||
: 0u);
|
||||
list++)
|
||||
{
|
||||
unsigned n = 0;
|
||||
int refs = slice->num_refs[list];
|
||||
if (!total || refs < 1 || refs > 15 || slice->list_modified[list] < 0
|
||||
|| slice->list_modified[list] > 1)
|
||||
return false;
|
||||
for (group = 0; group < 3; group++)
|
||||
{
|
||||
unsigned g = group == 2 ? 2 : group ^ list;
|
||||
for (i = 0; i < counts[g]; i++)
|
||||
order[n++] = groups[g][i];
|
||||
}
|
||||
for (i = 0; i < (unsigned)refs; i++)
|
||||
{
|
||||
unsigned index = slice->list_modified[list]
|
||||
? slice->list_entry[list][i]
|
||||
: i % total;
|
||||
int dw = slice->luma_weight[list][i] - (1 << slice->luma_log2_denom);
|
||||
int lo = slice->luma_offset[list][i];
|
||||
int8_t *luma_weight =
|
||||
list ? v.delta_luma_weight_l1 : v.delta_luma_weight_l0;
|
||||
int8_t *luma_offset = list ? v.luma_offset_l1 : v.luma_offset_l0;
|
||||
if (index >= total || dw < -128 || dw > 127 || lo < -128 || lo > 127)
|
||||
return false;
|
||||
v.RefPicList[list][i] = order[index];
|
||||
luma_weight[i] = (int8_t)dw;
|
||||
luma_offset[i] = (int8_t)lo;
|
||||
for (j = 0; j < 2; j++)
|
||||
{
|
||||
int cw = slice->chroma_weight[list][i][j]
|
||||
- (1 << slice->chroma_log2_denom);
|
||||
int co = slice->chroma_offset[list][i][j];
|
||||
if (cw < -128 || cw > 127 || co < -128 || co > 127)
|
||||
return false;
|
||||
if (list)
|
||||
{
|
||||
v.delta_chroma_weight_l1[i][j] = (int8_t)cw;
|
||||
v.ChromaOffsetL1[i][j] = (int8_t)co;
|
||||
}
|
||||
else
|
||||
{
|
||||
v.delta_chroma_weight_l0[i][j] = (int8_t)cw;
|
||||
v.ChromaOffsetL0[i][j] = (int8_t)co;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (h->slice_type != 2)
|
||||
{
|
||||
if (slice->max_merge < 1 || slice->max_merge > 5)
|
||||
return false;
|
||||
v.num_ref_idx_l0_active_minus1 = (uint8_t)(slice->num_refs[0] - 1);
|
||||
if (h->slice_type == 0)
|
||||
v.num_ref_idx_l1_active_minus1 = (uint8_t)(slice->num_refs[1] - 1);
|
||||
v.five_minus_max_num_merge_cand = (uint8_t)(5 - slice->max_merge);
|
||||
if (slice->temporal_mvp)
|
||||
{
|
||||
unsigned cl = slice->collocated_from_l0 ? 0 : 1;
|
||||
if ((h->slice_type == 1 && cl) || slice->collocated_ref_idx < 0
|
||||
|| slice->collocated_ref_idx >= slice->num_refs[cl])
|
||||
return false;
|
||||
v.collocated_ref_idx = (uint8_t)slice->collocated_ref_idx;
|
||||
}
|
||||
}
|
||||
qp_delta = slice->qp - params->init_qp;
|
||||
v.slice_data_size = (uint32_t)size;
|
||||
v.slice_data_offset = offset;
|
||||
v.slice_data_flag = VA_SLICE_DATA_FLAG_ALL;
|
||||
v.slice_data_byte_offset = (uint32_t)header;
|
||||
v.slice_data_num_emu_prevn_bytes = (uint16_t)(h->header_bytes - header);
|
||||
v.slice_segment_address = (uint32_t)h->slice_segment_address;
|
||||
v.num_entry_point_offsets = (uint16_t)slice->num_entry_points;
|
||||
v.slice_qp_delta = (int8_t)qp_delta;
|
||||
v.slice_cb_qp_offset = (int8_t)slice->cb_qp_offset;
|
||||
v.slice_cr_qp_offset = (int8_t)slice->cr_qp_offset;
|
||||
v.slice_beta_offset_div2 = (int8_t)slice->beta_offset_div2;
|
||||
v.slice_tc_offset_div2 = (int8_t)slice->tc_offset_div2;
|
||||
v.luma_log2_weight_denom = (uint8_t)slice->luma_log2_denom;
|
||||
v.delta_chroma_log2_weight_denom =
|
||||
(int8_t)(slice->chroma_log2_denom - slice->luma_log2_denom);
|
||||
v.LongSliceFlags.fields.LastSliceOfPic = last;
|
||||
v.LongSliceFlags.fields.slice_type = (unsigned)h->slice_type;
|
||||
#define VAAPI_HEVC_LONG_FLAG(dst, src) \
|
||||
v.LongSliceFlags.fields.dst = !!slice->src
|
||||
VAAPI_HEVC_LONG_FLAG(slice_sao_luma_flag, sao_luma);
|
||||
VAAPI_HEVC_LONG_FLAG(slice_sao_chroma_flag, sao_chroma);
|
||||
VAAPI_HEVC_LONG_FLAG(mvd_l1_zero_flag, mvd_l1_zero);
|
||||
VAAPI_HEVC_LONG_FLAG(cabac_init_flag, cabac_init);
|
||||
VAAPI_HEVC_LONG_FLAG(slice_temporal_mvp_enabled_flag, temporal_mvp);
|
||||
VAAPI_HEVC_LONG_FLAG(
|
||||
slice_deblocking_filter_disabled_flag, deblocking_filter_disabled);
|
||||
VAAPI_HEVC_LONG_FLAG(collocated_from_l0_flag, collocated_from_l0);
|
||||
VAAPI_HEVC_LONG_FLAG(slice_loop_filter_across_slices_enabled_flag,
|
||||
loop_filter_across_slices);
|
||||
#undef VAAPI_HEVC_LONG_FLAG
|
||||
*out = v;
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,181 @@
|
||||
/* Copyright (C) 2026 The RetroArch team. SPDX-License-Identifier: GPL-3.0-or-later */
|
||||
#include "vaapi_hevc_submit.h"
|
||||
#include <string.h>
|
||||
|
||||
static bool vaapi_hevc_submit_buffers(vaapi_hevc_submit_t *t,
|
||||
const vaapi_hevc_session_t *s, const vaapi_hevc_submit_ops_t *ops,
|
||||
const VAPictureParameterBufferHEVC *picture,
|
||||
const VAIQMatrixBufferHEVC *matrix, const void *slices, unsigned count,
|
||||
const uint8_t *data, unsigned size, const uint32_t *entries,
|
||||
unsigned num_entries)
|
||||
{
|
||||
vaapi_hevc_submit_ops_t native;
|
||||
unsigned i, j, element, used_entries = 0;
|
||||
bool found = false;
|
||||
if (!t || t->active || !s || !s->ready || s->closing || !ops || !ops->create
|
||||
|| !ops->destroy || !ops->begin || !ops->render || !ops->end
|
||||
|| !ops->sync || !s->display || s->context == VA_INVALID_ID
|
||||
|| !s->slots || s->slots > 32 || !picture || !slices || !count
|
||||
|| count > 256 || !data || !size || num_entries > 65535
|
||||
|| (num_entries && !entries)
|
||||
|| (num_entries && s->slice_mode != VA_DEC_SLICE_MODE_NORMAL)
|
||||
|| (s->slice_mode != VA_DEC_SLICE_MODE_BASE
|
||||
&& s->slice_mode != VA_DEC_SLICE_MODE_NORMAL))
|
||||
return false;
|
||||
for (i = 0; i < s->slots; i++)
|
||||
if (s->surfaces[i] == picture->CurrPic.picture_id)
|
||||
found = true;
|
||||
if (!found || picture->CurrPic.picture_id == VA_INVALID_SURFACE)
|
||||
return false;
|
||||
if (picture->pic_width_in_luma_samples != s->width
|
||||
|| picture->pic_height_in_luma_samples != s->height
|
||||
|| picture->bit_depth_luma_minus8 + 8u != s->depth
|
||||
|| picture->bit_depth_chroma_minus8 + 8u != s->depth
|
||||
|| !!matrix != !!picture->pic_fields.bits.scaling_list_enabled_flag)
|
||||
return false;
|
||||
element = s->slice_mode == VA_DEC_SLICE_MODE_BASE
|
||||
? sizeof(VASliceParameterBufferBase)
|
||||
: sizeof(VASliceParameterBufferHEVC);
|
||||
for (i = 0; i < count; i++)
|
||||
{
|
||||
VASliceParameterBufferBase base;
|
||||
memcpy(&base, (const uint8_t *)slices + i * element, sizeof(base));
|
||||
if (!base.slice_data_size || base.slice_data_offset > size
|
||||
|| base.slice_data_size > size - base.slice_data_offset
|
||||
|| base.slice_data_flag != VA_SLICE_DATA_FLAG_ALL)
|
||||
return false;
|
||||
if (s->slice_mode == VA_DEC_SLICE_MODE_NORMAL)
|
||||
{
|
||||
const VASliceParameterBufferHEVC *slice =
|
||||
&((const VASliceParameterBufferHEVC *)slices)[i];
|
||||
unsigned n = slice->num_entry_point_offsets;
|
||||
size_t bytes = slice->slice_data_byte_offset;
|
||||
if (bytes > base.slice_data_size
|
||||
|| slice->slice_data_num_emu_prevn_bytes
|
||||
> base.slice_data_size - bytes)
|
||||
return false;
|
||||
bytes += slice->slice_data_num_emu_prevn_bytes;
|
||||
if (n > num_entries - used_entries
|
||||
|| (n && slice->entry_offset_to_subset_array != used_entries))
|
||||
return false;
|
||||
for (j = 0; j < n; j++)
|
||||
{
|
||||
uint32_t entry = entries[used_entries + j];
|
||||
if (entry >= base.slice_data_size - bytes)
|
||||
return false;
|
||||
bytes += (size_t)entry + 1;
|
||||
}
|
||||
if (n && bytes >= base.slice_data_size)
|
||||
return false;
|
||||
used_entries += n;
|
||||
}
|
||||
}
|
||||
if (used_entries != num_entries)
|
||||
return false;
|
||||
native = *ops;
|
||||
memset(t, 0, sizeof(*t));
|
||||
t->ops = native;
|
||||
t->display = s->display;
|
||||
t->surface = picture->CurrPic.picture_id;
|
||||
t->active = true;
|
||||
for (i = 0; i < 5; i++)
|
||||
t->buffers[i] = VA_INVALID_ID;
|
||||
#define VAAPI_HEVC_BUFFER(type, bytes, elements, pointer) \
|
||||
do \
|
||||
{ \
|
||||
t->error = t->ops.create(t->display, s->context, type, bytes, elements, \
|
||||
(void *)(pointer), &t->buffers[t->count]); \
|
||||
t->count++; \
|
||||
for (j = 0; j + 1 < t->count; j++) \
|
||||
if (t->buffers[j] == t->buffers[t->count - 1]) \
|
||||
{ \
|
||||
t->buffers[t->count - 1] = VA_INVALID_ID; \
|
||||
t->error = VA_STATUS_ERROR_OPERATION_FAILED; \
|
||||
return false; \
|
||||
} \
|
||||
if (t->error != VA_STATUS_SUCCESS) \
|
||||
return false; \
|
||||
if (t->buffers[t->count - 1] == VA_INVALID_ID) \
|
||||
{ \
|
||||
t->error = VA_STATUS_ERROR_OPERATION_FAILED; \
|
||||
return false; \
|
||||
} \
|
||||
} while (0)
|
||||
VAAPI_HEVC_BUFFER(
|
||||
VAPictureParameterBufferType, sizeof(*picture), 1, picture);
|
||||
if (matrix)
|
||||
VAAPI_HEVC_BUFFER(VAIQMatrixBufferType, sizeof(*matrix), 1, matrix);
|
||||
VAAPI_HEVC_BUFFER(VASliceParameterBufferType, element, count, slices);
|
||||
if (num_entries)
|
||||
VAAPI_HEVC_BUFFER(VASubsetsParameterBufferType, sizeof(*entries),
|
||||
num_entries, entries);
|
||||
VAAPI_HEVC_BUFFER(VASliceDataBufferType, size, 1, data);
|
||||
#undef VAAPI_HEVC_BUFFER
|
||||
t->poisoned = true;
|
||||
t->error = t->ops.begin(t->display, s->context, t->surface);
|
||||
if (t->error != VA_STATUS_SUCCESS)
|
||||
return false;
|
||||
t->error = t->ops.render(t->display, s->context, t->buffers, (int)t->count);
|
||||
if (t->error != VA_STATUS_SUCCESS)
|
||||
return false;
|
||||
t->error = t->ops.end(t->display, s->context);
|
||||
if (t->error != VA_STATUS_SUCCESS)
|
||||
return false;
|
||||
t->submitted = true;
|
||||
t->poisoned = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool vaapi_hevc_submit_start(vaapi_hevc_submit_t *t,
|
||||
const vaapi_hevc_session_t *s, const vaapi_hevc_submit_ops_t *ops,
|
||||
const VAPictureParameterBufferHEVC *picture,
|
||||
const VAIQMatrixBufferHEVC *matrix, const void *slices, unsigned count,
|
||||
const uint8_t *data, unsigned size)
|
||||
{
|
||||
return vaapi_hevc_submit_buffers(
|
||||
t, s, ops, picture, matrix, slices, count, data, size, NULL, 0);
|
||||
}
|
||||
|
||||
bool vaapi_hevc_submit_start_entries(vaapi_hevc_submit_t *t,
|
||||
const vaapi_hevc_session_t *s, const vaapi_hevc_submit_ops_t *ops,
|
||||
const VAPictureParameterBufferHEVC *picture,
|
||||
const VAIQMatrixBufferHEVC *matrix,
|
||||
const VASliceParameterBufferHEVC *slices, unsigned count,
|
||||
const uint8_t *data, unsigned size, const uint32_t *entries,
|
||||
unsigned num_entries)
|
||||
{
|
||||
if (!s || s->slice_mode != VA_DEC_SLICE_MODE_NORMAL)
|
||||
return false;
|
||||
return vaapi_hevc_submit_buffers(t, s, ops, picture, matrix, slices, count,
|
||||
data, size, entries, num_entries);
|
||||
}
|
||||
int vaapi_hevc_submit_poll(vaapi_hevc_submit_t *t)
|
||||
{
|
||||
if(!t || !t->active || !t->submitted || t->poisoned)return -1;
|
||||
if(t->complete)return t->completion==VA_STATUS_SUCCESS?0:-1;
|
||||
t->error=t->ops.sync(t->display,t->surface,0);
|
||||
if(t->error==VA_STATUS_ERROR_TIMEDOUT)return 1;
|
||||
if(t->error==VA_STATUS_SUCCESS || t->error==VA_STATUS_ERROR_DECODING_ERROR)
|
||||
{t->complete=true;t->completion=t->error;return t->completion==VA_STATUS_SUCCESS?0:-1;}
|
||||
t->poisoned=true;return -1;
|
||||
}
|
||||
bool vaapi_hevc_submit_retire(vaapi_hevc_submit_t *t)
|
||||
{
|
||||
unsigned i;
|
||||
bool failed=false;
|
||||
if(!t || !t->active)return true;
|
||||
if(t->poisoned || (t->submitted && !t->complete))return false;
|
||||
for(i=0;i<t->count;i++)if(t->buffers[i]!=VA_INVALID_ID)
|
||||
{
|
||||
VAStatus status=t->ops.destroy(t->display,t->buffers[i]);
|
||||
if(status==VA_STATUS_SUCCESS)t->buffers[i]=VA_INVALID_ID;
|
||||
else {t->error=status;failed=true;}
|
||||
}
|
||||
if(failed)return false;
|
||||
memset(t,0,sizeof(*t));return true;
|
||||
}
|
||||
bool vaapi_hevc_submit_terminated(vaapi_hevc_submit_t *t,VADisplay display)
|
||||
{
|
||||
if(!t || !t->active || !display || t->display!=display)return false;
|
||||
memset(t,0,sizeof(*t));return true;
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
/* Copyright (C) 2026 The RetroArch team. SPDX-License-Identifier:
|
||||
* GPL-3.0-or-later */
|
||||
#ifndef RARCH_VAAPI_HEVC_SUBMIT_H
|
||||
#define RARCH_VAAPI_HEVC_SUBMIT_H
|
||||
#include "vaapi_hevc_session.h"
|
||||
RETRO_BEGIN_DECLS
|
||||
|
||||
typedef struct vaapi_hevc_submit_ops
|
||||
{
|
||||
VAStatus (*create)(VADisplay, VAContextID, VABufferType, unsigned, unsigned,
|
||||
void *, VABufferID *);
|
||||
VAStatus (*destroy)(VADisplay, VABufferID);
|
||||
VAStatus (*begin)(VADisplay, VAContextID, VASurfaceID);
|
||||
VAStatus (*render)(VADisplay, VAContextID, VABufferID *, int);
|
||||
VAStatus (*end)(VADisplay, VAContextID);
|
||||
VAStatus (*sync)(VADisplay, VASurfaceID, uint64_t);
|
||||
} vaapi_hevc_submit_ops_t;
|
||||
|
||||
typedef struct vaapi_hevc_submit
|
||||
{
|
||||
vaapi_hevc_submit_ops_t ops;
|
||||
VADisplay display;
|
||||
VABufferID buffers[5];
|
||||
VASurfaceID surface;
|
||||
VAStatus error, completion;
|
||||
unsigned count;
|
||||
bool active, submitted, complete, poisoned;
|
||||
} vaapi_hevc_submit_t;
|
||||
|
||||
/* Decoder owner only. Prepared picture/slices must match this session's codec
|
||||
* state and slice mode. Input is copied by create; no native buffer is mapped.
|
||||
* False may retain ownership: retire before reusing the transaction or surface.
|
||||
* A poisoned transaction requires successful display termination. */
|
||||
bool vaapi_hevc_submit_start(vaapi_hevc_submit_t *t,
|
||||
const vaapi_hevc_session_t *s, const vaapi_hevc_submit_ops_t *ops,
|
||||
const VAPictureParameterBufferHEVC *picture,
|
||||
const VAIQMatrixBufferHEVC *matrix, const void *slices, unsigned count,
|
||||
const uint8_t *data, unsigned size);
|
||||
/* NORMAL subset entries contain coded entry_point_offset_minus1 values in
|
||||
* slice order. Slice subset ranges must cover the array exactly. The optional
|
||||
* fifth native buffer follows the same transaction/retirement ownership. */
|
||||
bool vaapi_hevc_submit_start_entries(vaapi_hevc_submit_t *t,
|
||||
const vaapi_hevc_session_t *s, const vaapi_hevc_submit_ops_t *ops,
|
||||
const VAPictureParameterBufferHEVC *picture,
|
||||
const VAIQMatrixBufferHEVC *matrix,
|
||||
const VASliceParameterBufferHEVC *slices, unsigned count,
|
||||
const uint8_t *data, unsigned size, const uint32_t *entries,
|
||||
unsigned num_entries);
|
||||
/* One zero-timeout completion check: 0 complete, 1 pending, -1 error.
|
||||
* A completed error is still retired; uncertain failures poison ownership. */
|
||||
int vaapi_hevc_submit_poll(vaapi_hevc_submit_t *t);
|
||||
/* No wait. Pending/poisoned work refuses; failed buffer destruction is retried.
|
||||
*/
|
||||
bool vaapi_hevc_submit_retire(vaapi_hevc_submit_t *t);
|
||||
/* Only after actual successful vaTerminate of the matching display. */
|
||||
bool vaapi_hevc_submit_terminated(vaapi_hevc_submit_t *t, VADisplay display);
|
||||
RETRO_END_DECLS
|
||||
#endif
|
||||
@@ -0,0 +1,61 @@
|
||||
/* Copyright (C) 2026 The RetroArch team. SPDX-License-Identifier: GPL-3.0-or-later */
|
||||
#ifndef RARCH_VAAPI_HEVC_VULKAN_H
|
||||
#define RARCH_VAAPI_HEVC_VULKAN_H
|
||||
#include "vaapi_hevc_export.h"
|
||||
#include "vulkan_prime_format.h"
|
||||
|
||||
/* Both sampled layers must pass before the importer takes any object FD. */
|
||||
static INLINE bool vaapi_hevc_vulkan_query(vulkan_prime_format_t out[2],
|
||||
const vaapi_hevc_export_t *frame, VkPhysicalDevice gpu, bool prime_device,
|
||||
PFN_vkGetPhysicalDeviceFormatProperties2 get_format,
|
||||
PFN_vkGetPhysicalDeviceImageFormatProperties2 get_image)
|
||||
{
|
||||
vulkan_prime_format_t layers[2];
|
||||
const VADRMPRIMESurfaceDescriptor *d;
|
||||
unsigned i, j;
|
||||
bool ten;
|
||||
if (!out || !frame || !frame->active || !frame->ready || !prime_device)
|
||||
return false;
|
||||
d = &frame->descriptor;
|
||||
if (d->num_layers != 2 || !d->num_objects || d->num_objects > 4
|
||||
|| !d->width || !d->height || (d->width & 1) || (d->height & 1)
|
||||
|| (d->fourcc != VA_FOURCC_NV12 && d->fourcc != VA_FOURCC_P010))
|
||||
return false;
|
||||
ten = d->fourcc == VA_FOURCC_P010;
|
||||
for (i = 0; i < d->num_objects; i++)
|
||||
if (d->objects[i].fd < 0)
|
||||
return false;
|
||||
for (i = 0; i < 2; i++)
|
||||
{
|
||||
uint32_t first = d->layers[i].object_index[0];
|
||||
uint32_t planes = d->layers[i].num_planes;
|
||||
uint32_t drm_format = ten
|
||||
? (i ? VA_FOURCC('G','R','3','2') : VA_FOURCC('R','1','6',' '))
|
||||
: (i ? VA_FOURCC('G','R','8','8') : VA_FOURCC('R','8',' ',' '));
|
||||
VkFormat format = ten ? (i ? VK_FORMAT_R16G16_UNORM : VK_FORMAT_R16_UNORM)
|
||||
: (i ? VK_FORMAT_R8G8_UNORM : VK_FORMAT_R8_UNORM);
|
||||
bool disjoint = false;
|
||||
if (!planes || planes > 4 || first >= d->num_objects
|
||||
|| d->layers[i].drm_format != drm_format)
|
||||
return false;
|
||||
for (j = 0; j < planes; j++)
|
||||
{
|
||||
uint32_t object = d->layers[i].object_index[j];
|
||||
if (object >= d->num_objects || !d->layers[i].pitch[j]
|
||||
|| d->layers[i].offset[j] >= d->objects[object].size
|
||||
|| d->objects[object].drm_format_modifier
|
||||
!= d->objects[first].drm_format_modifier)
|
||||
return false;
|
||||
if (object != first)
|
||||
disjoint = true;
|
||||
}
|
||||
if (!vulkan_prime_format_query(&layers[i], gpu, format,
|
||||
i ? d->width / 2 : d->width, i ? d->height / 2 : d->height,
|
||||
d->objects[first].drm_format_modifier, planes, disjoint,
|
||||
get_format, get_image))
|
||||
return false;
|
||||
}
|
||||
memcpy(out, layers, sizeof(layers));
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,321 @@
|
||||
/* Copyright (C) 2026 The RetroArch team.
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later */
|
||||
#include "vaapi_hevc_vulkan_cache.h"
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
typedef struct vaapi_hevc_vulkan_cache_entry
|
||||
{
|
||||
vaapi_hevc_vulkan_frame_t frame;
|
||||
vaapi_hevc_vulkan_submission_t submission;
|
||||
vaapi_hevc_vulkan_lease_t lease;
|
||||
bool occupied;
|
||||
} vaapi_hevc_vulkan_cache_entry_t;
|
||||
|
||||
struct vaapi_hevc_vulkan_cache
|
||||
{
|
||||
vaapi_hevc_vulkan_cache_config_t config;
|
||||
VkPhysicalDeviceMemoryProperties memory;
|
||||
vaapi_hevc_vulkan_resources_t resources;
|
||||
vaapi_hevc_vulkan_cache_entry_t entries[32];
|
||||
video_hw_frame_t stranded;
|
||||
struct vaapi_hevc_vulkan_cache *retired_next;
|
||||
unsigned count, next;
|
||||
bool fp16, closed, deferred, children_destroyed;
|
||||
};
|
||||
|
||||
vaapi_hevc_vulkan_cache_t *vaapi_hevc_vulkan_cache_new(
|
||||
const vaapi_hevc_vulkan_cache_config_t *config, unsigned count, bool fp16)
|
||||
{
|
||||
vaapi_hevc_vulkan_cache_t *cache;
|
||||
if (!config || !config->memory || !config->gpu || !config->device
|
||||
|| !config->queue || !config->prime_device || !config->get_format
|
||||
|| !config->get_image || !config->submit.fence_status || !count
|
||||
|| count > 32)
|
||||
return NULL;
|
||||
cache = (vaapi_hevc_vulkan_cache_t *)calloc(1, sizeof(*cache));
|
||||
if (!cache)
|
||||
return NULL;
|
||||
cache->config = *config;
|
||||
cache->memory = *config->memory;
|
||||
cache->config.memory = &cache->memory;
|
||||
cache->count = count;
|
||||
cache->fp16 = fp16;
|
||||
if (!vaapi_hevc_vulkan_resources_create(&cache->resources, config->device,
|
||||
config->family, count, fp16, &config->resources))
|
||||
{
|
||||
free(cache);
|
||||
return NULL;
|
||||
}
|
||||
return cache;
|
||||
}
|
||||
|
||||
int vaapi_hevc_vulkan_cache_poll(vaapi_hevc_vulkan_cache_t *cache)
|
||||
{
|
||||
unsigned i;
|
||||
int result = 1;
|
||||
if (!cache || cache->children_destroyed)
|
||||
return -1;
|
||||
if (cache->stranded.owner)
|
||||
result = -1;
|
||||
for (i = 0; i < cache->count; i++)
|
||||
if (cache->entries[i].occupied)
|
||||
{
|
||||
int status = vaapi_hevc_vulkan_lease_poll(&cache->entries[i].lease);
|
||||
if (status < 0)
|
||||
{
|
||||
cache->closed = true;
|
||||
result = -1;
|
||||
}
|
||||
else if (!status && result > 0)
|
||||
result = 0;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
static bool vaapi_hevc_vulkan_cache_claim(
|
||||
const video_hw_frame_t *frame, vaapi_frame_view_t *view)
|
||||
{
|
||||
return frame && vaapi_frame_callback(frame->abandon)
|
||||
&& vaapi_frame_claim(frame->owner, frame->done, frame->done_value,
|
||||
frame->image, view);
|
||||
}
|
||||
|
||||
static bool vaapi_hevc_vulkan_cache_unclaim(
|
||||
vaapi_hevc_vulkan_cache_t *cache, const video_hw_frame_t *frame)
|
||||
{
|
||||
if (vaapi_frame_unclaim(frame->owner, frame->done, frame->done_value, NULL))
|
||||
return true;
|
||||
cache->stranded = *frame;
|
||||
cache->closed = true;
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool vaapi_hevc_vulkan_cache_populate(vaapi_hevc_vulkan_cache_t *cache,
|
||||
unsigned index, const vaapi_frame_view_t *view)
|
||||
{
|
||||
vaapi_hevc_vulkan_cache_entry_t *entry = &cache->entries[index];
|
||||
const vaapi_hevc_vulkan_cache_config_t *config = &cache->config;
|
||||
vaapi_hevc_vulkan_submission_t *submission = &entry->submission;
|
||||
if (!vaapi_hevc_vulkan_frame_prepare(&entry->frame, view->source,
|
||||
config->gpu, config->device, config->prime_device, config->memory,
|
||||
&config->image, config->get_format, config->get_image))
|
||||
return false;
|
||||
submission->frame = &entry->frame;
|
||||
submission->ops = config->submit;
|
||||
submission->device = config->device;
|
||||
submission->queue = config->queue;
|
||||
submission->family = config->family;
|
||||
submission->command = cache->resources.commands[index];
|
||||
submission->fence = cache->resources.fences[index];
|
||||
submission->set = cache->resources.sets[index];
|
||||
submission->sampler = cache->resources.sampler;
|
||||
submission->pipeline = cache->resources.pipeline;
|
||||
submission->layout = cache->resources.layout;
|
||||
if (!vaapi_hevc_vulkan_lease_bind(&entry->lease, submission, cache->fp16))
|
||||
{
|
||||
vaapi_hevc_export_t empty;
|
||||
memset(&empty, 0, sizeof(empty));
|
||||
vaapi_hevc_vulkan_frame_retire(&entry->frame, &empty);
|
||||
memset(entry, 0, sizeof(*entry));
|
||||
return false;
|
||||
}
|
||||
entry->occupied = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
int vaapi_hevc_vulkan_cache_prepare(
|
||||
vaapi_hevc_vulkan_cache_t *cache, const video_hw_frame_t *frame)
|
||||
{
|
||||
vaapi_frame_view_t view;
|
||||
vaapi_hevc_export_t empty;
|
||||
unsigned i, index;
|
||||
int result = 0;
|
||||
if (!cache || cache->closed || vaapi_hevc_vulkan_cache_poll(cache) < 0
|
||||
|| !vaapi_hevc_vulkan_cache_claim(frame, &view))
|
||||
return -1;
|
||||
for (i = 0; i < cache->count; i++)
|
||||
if (cache->entries[i].occupied
|
||||
&& vaapi_hevc_vulkan_frame_matches(
|
||||
&cache->entries[i].frame, view.source))
|
||||
{
|
||||
result = cache->entries[i].lease.owner ? 0 : 1;
|
||||
goto done;
|
||||
}
|
||||
memset(&empty, 0, sizeof(empty));
|
||||
for (i = 0; i < cache->count; i++)
|
||||
{
|
||||
vaapi_hevc_vulkan_cache_entry_t *entry;
|
||||
index = (cache->next + i) % cache->count;
|
||||
entry = &cache->entries[index];
|
||||
if (entry->occupied)
|
||||
{
|
||||
if (entry->lease.owner || entry->lease.returned.active)
|
||||
continue;
|
||||
if (!vaapi_hevc_vulkan_frame_retire(&entry->frame, &empty))
|
||||
continue;
|
||||
memset(entry, 0, sizeof(*entry));
|
||||
}
|
||||
result = vaapi_hevc_vulkan_cache_populate(cache, index, &view) ? 1 : -1;
|
||||
cache->next = (index + 1) % cache->count;
|
||||
break;
|
||||
}
|
||||
done:
|
||||
if (!vaapi_hevc_vulkan_cache_unclaim(cache, frame))
|
||||
return -2;
|
||||
return result;
|
||||
}
|
||||
|
||||
int vaapi_hevc_vulkan_cache_present(vaapi_hevc_vulkan_cache_t *cache,
|
||||
const video_hw_frame_t *frame, VkImage destination, VkImageView view,
|
||||
VkFormat format, unsigned width, unsigned height, VkImageLayout layout,
|
||||
bool *prepare_needed)
|
||||
{
|
||||
vaapi_frame_view_t source;
|
||||
unsigned i;
|
||||
int result;
|
||||
if (prepare_needed)
|
||||
*prepare_needed = false;
|
||||
if (!cache || cache->closed || vaapi_hevc_vulkan_cache_poll(cache) < 0
|
||||
|| !vaapi_hevc_vulkan_cache_claim(frame, &source))
|
||||
return -1;
|
||||
for (i = 0; i < cache->count; i++)
|
||||
if (cache->entries[i].occupied
|
||||
&& vaapi_hevc_vulkan_frame_matches(
|
||||
&cache->entries[i].frame, source.source))
|
||||
break;
|
||||
if (!vaapi_hevc_vulkan_cache_unclaim(cache, frame))
|
||||
return -2;
|
||||
if (i == cache->count)
|
||||
{
|
||||
if (prepare_needed)
|
||||
*prepare_needed = true;
|
||||
return 0;
|
||||
}
|
||||
result = vaapi_hevc_vulkan_lease_present(&cache->entries[i].lease, frame,
|
||||
destination, view, format, width, height, layout);
|
||||
if (result == -2)
|
||||
cache->closed = true;
|
||||
return result;
|
||||
}
|
||||
|
||||
void vaapi_hevc_vulkan_cache_close(vaapi_hevc_vulkan_cache_t *cache)
|
||||
{
|
||||
if (cache)
|
||||
cache->closed = true;
|
||||
}
|
||||
|
||||
bool vaapi_hevc_vulkan_cache_free(vaapi_hevc_vulkan_cache_t *cache)
|
||||
{
|
||||
vaapi_hevc_export_t empty;
|
||||
unsigned i;
|
||||
if (!cache)
|
||||
return true;
|
||||
if (!cache->closed || cache->deferred
|
||||
|| vaapi_hevc_vulkan_cache_poll(cache) != 1)
|
||||
return false;
|
||||
vaapi_hevc_vulkan_resources_destroy(&cache->resources);
|
||||
memset(&empty, 0, sizeof(empty));
|
||||
for (i = 0; i < cache->count; i++)
|
||||
if (cache->entries[i].occupied)
|
||||
{
|
||||
if (!vaapi_hevc_vulkan_frame_retire(&cache->entries[i].frame, &empty))
|
||||
return false;
|
||||
cache->entries[i].occupied = false;
|
||||
}
|
||||
free(cache);
|
||||
return true;
|
||||
}
|
||||
|
||||
static vaapi_hevc_vulkan_cache_t *vaapi_hevc_vulkan_retired_caches;
|
||||
|
||||
void vaapi_hevc_vulkan_cache_defer(vaapi_hevc_vulkan_cache_t *cache)
|
||||
{
|
||||
if (!cache || cache->deferred)
|
||||
return;
|
||||
cache->closed = true;
|
||||
cache->deferred = true;
|
||||
cache->retired_next = vaapi_hevc_vulkan_retired_caches;
|
||||
vaapi_hevc_vulkan_retired_caches = cache;
|
||||
}
|
||||
|
||||
bool vaapi_hevc_vulkan_cache_device_pending(VkDevice device)
|
||||
{
|
||||
vaapi_hevc_vulkan_cache_t *cache;
|
||||
if (!device)
|
||||
return false;
|
||||
for (cache = vaapi_hevc_vulkan_retired_caches; cache;
|
||||
cache = cache->retired_next)
|
||||
if (cache->config.device == device)
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
void vaapi_hevc_vulkan_cache_device_destroying(VkDevice device)
|
||||
{
|
||||
vaapi_hevc_vulkan_cache_t *cache;
|
||||
if (!device)
|
||||
return;
|
||||
for (cache = vaapi_hevc_vulkan_retired_caches; cache;
|
||||
cache = cache->retired_next)
|
||||
if (cache->config.device == device && !cache->children_destroyed)
|
||||
{
|
||||
unsigned i, j;
|
||||
vaapi_hevc_vulkan_resources_destroy(&cache->resources);
|
||||
for (i = 0; i < cache->count; i++)
|
||||
if (cache->entries[i].occupied)
|
||||
{
|
||||
vaapi_hevc_vulkan_frame_t *frame = &cache->entries[i].frame;
|
||||
for (j = 0; j < 2; j++)
|
||||
vulkan_prime_image_destroy(
|
||||
device, &frame->ops, &frame->layers[j]);
|
||||
}
|
||||
if (cache->config.destroy_deferred_destination)
|
||||
cache->config.destroy_deferred_destination(
|
||||
device, cache->config.deferred_destination);
|
||||
cache->children_destroyed = true;
|
||||
}
|
||||
}
|
||||
|
||||
void vaapi_hevc_vulkan_cache_device_destroyed(VkDevice device)
|
||||
{
|
||||
vaapi_hevc_vulkan_cache_t **link = &vaapi_hevc_vulkan_retired_caches;
|
||||
if (!device)
|
||||
return;
|
||||
while (*link)
|
||||
{
|
||||
vaapi_hevc_vulkan_cache_t *cache = *link;
|
||||
unsigned i;
|
||||
bool complete = true;
|
||||
if (cache->config.device != device || !cache->children_destroyed)
|
||||
{
|
||||
link = &cache->retired_next;
|
||||
continue;
|
||||
}
|
||||
for (i = 0; i < cache->count; i++)
|
||||
if (cache->entries[i].occupied)
|
||||
{
|
||||
if (vaapi_hevc_vulkan_lease_after_device_destroy(
|
||||
&cache->entries[i].lease, device))
|
||||
cache->entries[i].occupied = false;
|
||||
else
|
||||
complete = false;
|
||||
}
|
||||
if (cache->stranded.owner)
|
||||
{
|
||||
video_hw_frame_t *frame = &cache->stranded;
|
||||
if (vaapi_frame_finish(
|
||||
frame->owner, frame->done, frame->done_value, NULL))
|
||||
memset(frame, 0, sizeof(*frame));
|
||||
else
|
||||
complete = false;
|
||||
}
|
||||
if (!complete)
|
||||
{
|
||||
link = &cache->retired_next;
|
||||
continue;
|
||||
}
|
||||
*link = cache->retired_next;
|
||||
free(cache);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
/* Copyright (C) 2026 The RetroArch team.
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later */
|
||||
#ifndef RARCH_VAAPI_HEVC_VULKAN_CACHE_H
|
||||
#define RARCH_VAAPI_HEVC_VULKAN_CACHE_H
|
||||
#include "vaapi_hevc_vulkan_lease.h"
|
||||
#include "vaapi_hevc_vulkan_resources.h"
|
||||
RETRO_BEGIN_DECLS
|
||||
|
||||
typedef struct vaapi_hevc_vulkan_cache vaapi_hevc_vulkan_cache_t;
|
||||
|
||||
typedef struct vaapi_hevc_vulkan_cache_config
|
||||
{
|
||||
const VkPhysicalDeviceMemoryProperties *memory;
|
||||
vulkan_prime_image_ops_t image;
|
||||
vaapi_hevc_vulkan_submit_ops_t submit;
|
||||
vaapi_hevc_vulkan_resource_ops_t resources;
|
||||
PFN_vkGetPhysicalDeviceFormatProperties2 get_format;
|
||||
PFN_vkGetPhysicalDeviceImageFormatProperties2 get_image;
|
||||
void (*destroy_deferred_destination)(VkDevice, void *);
|
||||
void *deferred_destination;
|
||||
VkPhysicalDevice gpu;
|
||||
VkDevice device;
|
||||
VkQueue queue;
|
||||
unsigned family;
|
||||
bool prime_device;
|
||||
} vaapi_hevc_vulkan_cache_config_t;
|
||||
|
||||
/* Cold renderer owner; copies memory properties and operation tables.
|
||||
* Owns 1..32 conversion slots, borrowing the device and serialized queue. */
|
||||
vaapi_hevc_vulkan_cache_t *vaapi_hevc_vulkan_cache_new(
|
||||
const vaapi_hevc_vulkan_cache_config_t *config, unsigned count,
|
||||
bool fp16);
|
||||
/* Cold preparation: 1 ready, 0 pending/full, -1 refusal, -2 broken handoff.
|
||||
* Ordinary results preserve the caller's pending frame. No GPU wait. */
|
||||
int vaapi_hevc_vulkan_cache_prepare(
|
||||
vaapi_hevc_vulkan_cache_t *cache, const video_hw_frame_t *frame);
|
||||
/* Warm renderer tick, including paused playback: one status query per occupied
|
||||
* slot; 1 all idle, 0 pending, -1 retained error. Never closes native FDs. */
|
||||
int vaapi_hevc_vulkan_cache_poll(vaapi_hevc_vulkan_cache_t *cache);
|
||||
/* Warm path, no allocation/resource creation/destruction. Lease-present result
|
||||
* contract; a miss returns 0 and prepare_needed=true with the input untouched.
|
||||
* Destination must outlive its submission and any deferred device retirement.
|
||||
*/
|
||||
int vaapi_hevc_vulkan_cache_present(vaapi_hevc_vulkan_cache_t *cache,
|
||||
const video_hw_frame_t *frame, VkImage destination, VkImageView view,
|
||||
VkFormat format, unsigned width, unsigned height, VkImageLayout layout,
|
||||
bool *prepare_needed);
|
||||
void vaapi_hevc_vulkan_cache_close(vaapi_hevc_vulkan_cache_t *cache);
|
||||
/* Cold owner after close; false retains the cache for a later retry/defer. */
|
||||
bool vaapi_hevc_vulkan_cache_free(vaapi_hevc_vulkan_cache_t *cache);
|
||||
/* Existing serialized renderer/context lifecycle only. A deferred device must
|
||||
* not be cached. No frame/producer thread may access the deferred list. */
|
||||
void vaapi_hevc_vulkan_cache_defer(vaapi_hevc_vulkan_cache_t *cache);
|
||||
bool vaapi_hevc_vulkan_cache_device_pending(VkDevice device);
|
||||
/* After existing idle/device-lost proof, before actual device destruction:
|
||||
* retire children but retain external leases. No polling or waits here. */
|
||||
void vaapi_hevc_vulkan_cache_device_destroying(VkDevice device);
|
||||
/* Only after destruction of this exact device; returns retained leases without
|
||||
* any Vulkan/VA call. Failed publication remains retained for another retry. */
|
||||
void vaapi_hevc_vulkan_cache_device_destroyed(VkDevice device);
|
||||
RETRO_END_DECLS
|
||||
#endif
|
||||
@@ -0,0 +1,251 @@
|
||||
/* Copyright (C) 2026 The RetroArch team.
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later */
|
||||
#include "vaapi_hevc_vulkan_frame.h"
|
||||
|
||||
bool vaapi_hevc_vulkan_frame_prepare(vaapi_hevc_vulkan_frame_t *out,
|
||||
const vaapi_hevc_export_t *source, VkPhysicalDevice gpu, VkDevice device,
|
||||
bool prime_device, const VkPhysicalDeviceMemoryProperties *memory,
|
||||
const vulkan_prime_image_ops_t *ops,
|
||||
PFN_vkGetPhysicalDeviceFormatProperties2 get_format,
|
||||
PFN_vkGetPhysicalDeviceImageFormatProperties2 get_image)
|
||||
{
|
||||
vulkan_prime_format_t plans[2];
|
||||
vulkan_prime_plane_t planes[4];
|
||||
unsigned i, j;
|
||||
if (!out || out->active || out->source.active || out->device || !source
|
||||
|| source == &out->source || !device || !ops)
|
||||
return false;
|
||||
for (i = 0; i < 2; i++)
|
||||
{
|
||||
if (out->layers[i].image || out->layers[i].view)
|
||||
return false;
|
||||
for (j = 0; j < 4; j++)
|
||||
if (out->layers[i].memory[j])
|
||||
return false;
|
||||
}
|
||||
if (!vaapi_hevc_vulkan_query(
|
||||
plans, source, gpu, prime_device, get_format, get_image))
|
||||
return false;
|
||||
for (i = 0; i < 2; i++)
|
||||
{
|
||||
memset(planes, 0, sizeof(planes));
|
||||
for (j = 0; j < plans[i].memory_planes; j++)
|
||||
{
|
||||
unsigned object = source->descriptor.layers[i].object_index[j];
|
||||
planes[j].fd = source->descriptor.objects[object].fd;
|
||||
planes[j].allocation_size = source->descriptor.objects[object].size;
|
||||
planes[j].offset = source->descriptor.layers[i].offset[j];
|
||||
planes[j].pitch = source->descriptor.layers[i].pitch[j];
|
||||
}
|
||||
if (!vulkan_prime_image_create(
|
||||
device, ops, memory, &plans[i], planes, &out->layers[i]))
|
||||
{
|
||||
for (j = 0; j < 2; j++)
|
||||
vulkan_prime_image_destroy(device, ops, &out->layers[j]);
|
||||
memset(out, 0, sizeof(*out));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
out->descriptor = source->descriptor;
|
||||
out->pool = vaapi_export_owner(source);
|
||||
out->slot = source->slot;
|
||||
for (i = 0; i < 4; i++)
|
||||
{
|
||||
out->descriptor.objects[i].fd = -1;
|
||||
out->object_device[i] = source->object_device[i];
|
||||
out->object_inode[i] = source->object_inode[i];
|
||||
}
|
||||
out->ops = *ops;
|
||||
out->device = device;
|
||||
out->active = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool vaapi_hevc_vulkan_frame_create(vaapi_hevc_vulkan_frame_t *out,
|
||||
vaapi_hevc_export_t *source, VkPhysicalDevice gpu, VkDevice device,
|
||||
bool prime_device, const VkPhysicalDeviceMemoryProperties *memory,
|
||||
const vulkan_prime_image_ops_t *ops,
|
||||
PFN_vkGetPhysicalDeviceFormatProperties2 get_format,
|
||||
PFN_vkGetPhysicalDeviceImageFormatProperties2 get_image)
|
||||
{
|
||||
if (!vaapi_hevc_vulkan_frame_prepare(out, source, gpu, device, prime_device,
|
||||
memory, ops, get_format, get_image))
|
||||
return false;
|
||||
out->source = *source;
|
||||
memset(source, 0, sizeof(*source));
|
||||
return true;
|
||||
}
|
||||
|
||||
bool vaapi_hevc_vulkan_frame_barrier(const vaapi_hevc_vulkan_frame_t *frame,
|
||||
VkCommandBuffer command, PFN_vkCmdPipelineBarrier barrier,
|
||||
unsigned graphics_family, VkPipelineStageFlags stage, bool acquire)
|
||||
{
|
||||
VkImageMemoryBarrier images[2];
|
||||
unsigned i;
|
||||
if (!frame || !frame->active || !frame->source.active || frame->pending
|
||||
|| frame->broken || !command || !barrier || !frame->layers[0].image
|
||||
|| !frame->layers[1].image
|
||||
|| graphics_family >= VK_QUEUE_FAMILY_FOREIGN_EXT
|
||||
|| (stage != VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT
|
||||
&& stage != VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT))
|
||||
return false;
|
||||
for (i = 0; i < 2; i++)
|
||||
{
|
||||
images[i].sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
|
||||
images[i].pNext = NULL;
|
||||
images[i].subresourceRange.baseMipLevel = 0;
|
||||
images[i].subresourceRange.baseArrayLayer = 0;
|
||||
images[i].image = frame->layers[i].image;
|
||||
images[i].subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
|
||||
images[i].subresourceRange.levelCount =
|
||||
images[i].subresourceRange.layerCount = 1;
|
||||
images[i].srcQueueFamilyIndex =
|
||||
acquire ? VK_QUEUE_FAMILY_FOREIGN_EXT : graphics_family;
|
||||
images[i].dstQueueFamilyIndex =
|
||||
acquire ? graphics_family : VK_QUEUE_FAMILY_FOREIGN_EXT;
|
||||
images[i].oldLayout = acquire ? VK_IMAGE_LAYOUT_GENERAL
|
||||
: VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
|
||||
images[i].newLayout = acquire ? VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL
|
||||
: VK_IMAGE_LAYOUT_GENERAL;
|
||||
images[i].srcAccessMask = acquire ? 0 : VK_ACCESS_SHADER_READ_BIT;
|
||||
images[i].dstAccessMask = acquire ? VK_ACCESS_SHADER_READ_BIT : 0;
|
||||
}
|
||||
barrier(command, acquire ? VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT : stage,
|
||||
acquire ? stage : VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, 0, 0, NULL, 0,
|
||||
NULL, 2, images);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool vaapi_hevc_vulkan_frame_track(
|
||||
vaapi_hevc_vulkan_frame_t *frame, VkFence fence)
|
||||
{
|
||||
if (!frame || !frame->active || !frame->source.active || frame->pending
|
||||
|| frame->broken || !fence)
|
||||
return false;
|
||||
frame->fence = fence;
|
||||
frame->pending = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool vaapi_hevc_vulkan_frame_detach(
|
||||
vaapi_hevc_vulkan_frame_t *frame, vaapi_hevc_export_t *returned)
|
||||
{
|
||||
if (!frame || !frame->active || !frame->source.active || frame->pending
|
||||
|| frame->broken || !returned || returned->active
|
||||
|| returned == &frame->source)
|
||||
return false;
|
||||
*returned = frame->source;
|
||||
memset(&frame->source, 0, sizeof(frame->source));
|
||||
return true;
|
||||
}
|
||||
|
||||
bool vaapi_hevc_vulkan_frame_matches(
|
||||
const vaapi_hevc_vulkan_frame_t *frame, const vaapi_hevc_export_t *source)
|
||||
{
|
||||
const VADRMPRIMESurfaceDescriptor *a, *b;
|
||||
unsigned i, j;
|
||||
if (!frame || !frame->active || frame->broken || !source || !source->active
|
||||
|| !source->ready || source == &frame->source
|
||||
|| vaapi_export_owner(source) != frame->pool || source->slot != frame->slot
|
||||
|| !vaapi_export_owner(source) || source->slot >= 32 || !source->ticket)
|
||||
return false;
|
||||
a = &frame->descriptor;
|
||||
b = &source->descriptor;
|
||||
if (!a->num_objects || a->num_objects > 4 || a->num_layers != 2
|
||||
|| a->fourcc != b->fourcc || a->width != b->width
|
||||
|| a->height != b->height || a->num_objects != b->num_objects
|
||||
|| a->num_layers != b->num_layers)
|
||||
return false;
|
||||
for (i = 0; i < a->num_objects; i++)
|
||||
if (b->objects[i].fd < 0 || !frame->object_inode[i]
|
||||
|| frame->object_device[i] != source->object_device[i]
|
||||
|| frame->object_inode[i] != source->object_inode[i]
|
||||
|| a->objects[i].size != b->objects[i].size
|
||||
|| a->objects[i].drm_format_modifier
|
||||
!= b->objects[i].drm_format_modifier)
|
||||
return false;
|
||||
for (i = 0; i < 2; i++)
|
||||
{
|
||||
if (!a->layers[i].num_planes || a->layers[i].num_planes > 4
|
||||
|| a->layers[i].drm_format != b->layers[i].drm_format
|
||||
|| a->layers[i].num_planes != b->layers[i].num_planes)
|
||||
return false;
|
||||
for (j = 0; j < a->layers[i].num_planes; j++)
|
||||
if (a->layers[i].object_index[j] != b->layers[i].object_index[j]
|
||||
|| a->layers[i].offset[j] != b->layers[i].offset[j]
|
||||
|| a->layers[i].pitch[j] != b->layers[i].pitch[j])
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool vaapi_hevc_vulkan_frame_adopt(
|
||||
vaapi_hevc_vulkan_frame_t *frame, vaapi_hevc_export_t *source)
|
||||
{
|
||||
if (!frame || frame->source.active || frame->pending
|
||||
|| !vaapi_hevc_vulkan_frame_matches(frame, source))
|
||||
return false;
|
||||
frame->source = *source;
|
||||
memset(source, 0, sizeof(*source));
|
||||
return true;
|
||||
}
|
||||
|
||||
int vaapi_hevc_vulkan_frame_poll(
|
||||
vaapi_hevc_vulkan_frame_t *frame, PFN_vkGetFenceStatus status)
|
||||
{
|
||||
VkResult result;
|
||||
if (!frame || !frame->active || frame->broken || !status)
|
||||
return -1;
|
||||
if (!frame->pending)
|
||||
return 1;
|
||||
result = status(frame->device, frame->fence);
|
||||
if (result == VK_NOT_READY)
|
||||
return 0;
|
||||
if (result != VK_SUCCESS)
|
||||
{
|
||||
frame->broken = true;
|
||||
return -1;
|
||||
}
|
||||
frame->pending = false;
|
||||
frame->fence = VK_NULL_HANDLE;
|
||||
return 1;
|
||||
}
|
||||
|
||||
static bool vaapi_hevc_vulkan_frame_returnable(
|
||||
const vaapi_hevc_vulkan_frame_t *frame,
|
||||
const vaapi_hevc_export_t *returned)
|
||||
{
|
||||
return frame && frame->active && returned && !returned->active
|
||||
&& returned != &frame->source;
|
||||
}
|
||||
|
||||
static void vaapi_hevc_vulkan_frame_return(
|
||||
vaapi_hevc_vulkan_frame_t *frame, vaapi_hevc_export_t *returned)
|
||||
{
|
||||
*returned = frame->source;
|
||||
memset(frame, 0, sizeof(*frame));
|
||||
}
|
||||
|
||||
bool vaapi_hevc_vulkan_frame_retire(
|
||||
vaapi_hevc_vulkan_frame_t *frame, vaapi_hevc_export_t *returned)
|
||||
{
|
||||
unsigned i;
|
||||
if (!vaapi_hevc_vulkan_frame_returnable(frame, returned) || frame->pending
|
||||
|| frame->broken)
|
||||
return false;
|
||||
for (i = 0; i < 2; i++)
|
||||
vulkan_prime_image_destroy(frame->device, &frame->ops, &frame->layers[i]);
|
||||
vaapi_hevc_vulkan_frame_return(frame, returned);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool vaapi_hevc_vulkan_frame_after_device_destroy(
|
||||
vaapi_hevc_vulkan_frame_t *frame, VkDevice device,
|
||||
vaapi_hevc_export_t *returned)
|
||||
{
|
||||
if (!vaapi_hevc_vulkan_frame_returnable(frame, returned) || !device
|
||||
|| frame->device != device)
|
||||
return false;
|
||||
vaapi_hevc_vulkan_frame_return(frame, returned);
|
||||
return true;
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
/* Copyright (C) 2026 The RetroArch team.
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later */
|
||||
#ifndef RARCH_VAAPI_HEVC_VULKAN_FRAME_H
|
||||
#define RARCH_VAAPI_HEVC_VULKAN_FRAME_H
|
||||
#include "vaapi_hevc_vulkan.h"
|
||||
#include "vulkan_prime_image.h"
|
||||
RETRO_BEGIN_DECLS
|
||||
|
||||
typedef struct vaapi_hevc_vulkan_frame
|
||||
{
|
||||
vaapi_hevc_export_t source;
|
||||
VADRMPRIMESurfaceDescriptor descriptor;
|
||||
uint64_t object_device[4], object_inode[4];
|
||||
void *pool;
|
||||
vulkan_prime_image_ops_t ops;
|
||||
vulkan_prime_image_t layers[2];
|
||||
VkDevice device;
|
||||
VkFence fence;
|
||||
unsigned slot;
|
||||
bool active, pending, broken;
|
||||
} vaapi_hevc_vulkan_frame_t;
|
||||
|
||||
/* Cold cache preparation borrowing an intact claimed source. Imports retain
|
||||
* their own references; no export or decoder ticket is taken. */
|
||||
bool vaapi_hevc_vulkan_frame_prepare(vaapi_hevc_vulkan_frame_t *out,
|
||||
const vaapi_hevc_export_t *source, VkPhysicalDevice gpu, VkDevice device,
|
||||
bool prime_device, const VkPhysicalDeviceMemoryProperties *memory,
|
||||
const vulkan_prime_image_ops_t *ops,
|
||||
PFN_vkGetPhysicalDeviceFormatProperties2 get_format,
|
||||
PFN_vkGetPhysicalDeviceImageFormatProperties2 get_image);
|
||||
/* Cold cache population, zeroed out. Both imports must succeed before moving
|
||||
* source's export/FDs/ticket into this owner. Failure consumes nothing. VA has
|
||||
* completed the source picture; matching physical-device identity is required.
|
||||
* VA pool/session/runtime outlive its held export and eventual return handoff.
|
||||
*/
|
||||
bool vaapi_hevc_vulkan_frame_create(vaapi_hevc_vulkan_frame_t *out,
|
||||
vaapi_hevc_export_t *source, VkPhysicalDevice gpu, VkDevice device,
|
||||
bool prime_device, const VkPhysicalDeviceMemoryProperties *memory,
|
||||
const vulkan_prime_image_ops_t *ops,
|
||||
PFN_vkGetPhysicalDeviceFormatProperties2 get_format,
|
||||
PFN_vkGetPhysicalDeviceImageFormatProperties2 get_image);
|
||||
/* Record outside a render pass. FOREIGN/GENERAL <-> graphics/SHADER_READ_ONLY.
|
||||
* Caller records acquire before sampling, release after its last sample. This
|
||||
* neither submits nor waits; never discard imported content via UNDEFINED. */
|
||||
bool vaapi_hevc_vulkan_frame_barrier(const vaapi_hevc_vulkan_frame_t *frame,
|
||||
VkCommandBuffer command, PFN_vkCmdPipelineBarrier barrier,
|
||||
unsigned graphics_family, VkPipelineStageFlags stage, bool acquire);
|
||||
/* One in-flight use on the renderer owner. Poll must return 1 before accepting
|
||||
* another use. Track immediately after accepted or possibly accepted GPU use.
|
||||
* Borrow the existing submission fence; it must stay valid/unreset until poll
|
||||
* succeeds. Uncertain submission failure must retain the frame through device
|
||||
* destruction if fence completion cannot be proven. */
|
||||
bool vaapi_hevc_vulkan_frame_track(
|
||||
vaapi_hevc_vulkan_frame_t *frame, VkFence fence);
|
||||
/* Renderer owner, after GPU and descriptor references to the use retire.
|
||||
* Return only its export; cached imports retain their independent BO
|
||||
* references. No native VA/FD call, allocation, query or wait. */
|
||||
bool vaapi_hevc_vulkan_frame_detach(
|
||||
vaapi_hevc_vulkan_frame_t *frame, vaapi_hevc_export_t *returned);
|
||||
/* Reuse imports only for the exact worker-captured buffer identities, layout
|
||||
* and decoder slot. Refusal consumes nothing. Caller reconfigures crop/color
|
||||
* and submission ticket for each newly adopted picture. */
|
||||
bool vaapi_hevc_vulkan_frame_adopt(
|
||||
vaapi_hevc_vulkan_frame_t *frame, vaapi_hevc_export_t *source);
|
||||
/* Immutable cache-key comparison; source remains borrowed and unchanged. */
|
||||
bool vaapi_hevc_vulkan_frame_matches(const vaapi_hevc_vulkan_frame_t *frame,
|
||||
const vaapi_hevc_export_t *source);
|
||||
/* One zero-timeout status query. 1 complete, 0 pending, -1 sticky error. */
|
||||
int vaapi_hevc_vulkan_frame_poll(
|
||||
vaapi_hevc_vulkan_frame_t *frame, PFN_vkGetFenceStatus status);
|
||||
/* Renderer owner, after final GPU and descriptor references retire. Destroy
|
||||
* Vulkan resources, then move any still-held export into empty returned.
|
||||
* An idle cache entry leaves returned empty.
|
||||
* Deliver returned through the existing lease-return mechanism; ONLY the VA
|
||||
* owner calls vaapi_hevc_export_close to release its pool ticket. */
|
||||
bool vaapi_hevc_vulkan_frame_retire(
|
||||
vaapi_hevc_vulkan_frame_t *frame, vaapi_hevc_export_t *returned);
|
||||
/* ONLY after vkDestroyDevice has returned for the exact device. No Vulkan
|
||||
* call; invalidate handles and move the export for the same VA-owner return. */
|
||||
bool vaapi_hevc_vulkan_frame_after_device_destroy(
|
||||
vaapi_hevc_vulkan_frame_t *frame, VkDevice device,
|
||||
vaapi_hevc_export_t *returned);
|
||||
RETRO_END_DECLS
|
||||
#endif
|
||||
@@ -0,0 +1,163 @@
|
||||
/* Copyright (C) 2026 The RetroArch team.
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later */
|
||||
#include "vaapi_hevc_vulkan_lease.h"
|
||||
|
||||
bool vaapi_hevc_vulkan_lease_bind(vaapi_hevc_vulkan_lease_t *l,
|
||||
vaapi_hevc_vulkan_submission_t *e, bool fp16)
|
||||
{
|
||||
if (!l || l->entry || l->owner || l->returned.active || l->broken || !e
|
||||
|| !e->frame || !e->frame->active || e->frame->source.active
|
||||
|| e->frame->pending || e->frame->broken)
|
||||
return false;
|
||||
l->entry = e;
|
||||
l->fp16 = fp16;
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool vaapi_hevc_vulkan_lease_put(
|
||||
vaapi_hevc_vulkan_lease_t *l, bool finish)
|
||||
{
|
||||
bool result;
|
||||
if (!l->returned.active
|
||||
&& !vaapi_hevc_vulkan_frame_detach(l->entry->frame, &l->returned))
|
||||
return false;
|
||||
result = finish ? vaapi_frame_finish(
|
||||
l->owner, l->index, l->generation, &l->returned)
|
||||
: vaapi_frame_unclaim(
|
||||
l->owner, l->index, l->generation, &l->returned);
|
||||
if (!result)
|
||||
return false;
|
||||
l->owner = NULL;
|
||||
l->index = l->generation = 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
int vaapi_hevc_vulkan_lease_poll(vaapi_hevc_vulkan_lease_t *l)
|
||||
{
|
||||
int result;
|
||||
if (!l || !l->entry || l->broken)
|
||||
return -1;
|
||||
if (!l->owner)
|
||||
return 1;
|
||||
result = vaapi_hevc_vulkan_frame_poll(
|
||||
l->entry->frame, l->entry->ops.fence_status);
|
||||
if (result <= 0)
|
||||
return result;
|
||||
if (!vaapi_hevc_vulkan_lease_put(l, true))
|
||||
{
|
||||
l->broken = true;
|
||||
return -1;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
int vaapi_hevc_vulkan_lease_present(vaapi_hevc_vulkan_lease_t *l,
|
||||
const video_hw_frame_t *frame, VkImage destination, VkImageView view,
|
||||
VkFormat format, unsigned width, unsigned height, VkImageLayout layout)
|
||||
{
|
||||
vaapi_frame_view_t source;
|
||||
unsigned i, yuv;
|
||||
int result = vaapi_hevc_vulkan_lease_poll(l);
|
||||
if (result <= 0)
|
||||
return result;
|
||||
if (!frame || !vaapi_frame_callback(frame->abandon)
|
||||
|| !vaapi_frame_claim(frame->owner, frame->done, frame->done_value,
|
||||
frame->image, &source))
|
||||
return -1;
|
||||
if (!frame->width || !frame->height || frame->width > source.width
|
||||
|| frame->height > source.height || frame->crop_x != source.x
|
||||
|| frame->crop_y != source.y || frame->pixfmt != source.pixfmt
|
||||
|| width != frame->width || height != frame->height
|
||||
|| ((frame->yuv | source.yuv)
|
||||
& ~(IMAGE_YUV_FLAG_FULL_RANGE | IMAGE_YUV_FLAG_BT709))
|
||||
|| !vaapi_hevc_vulkan_frame_matches(l->entry->frame, source.source))
|
||||
{
|
||||
if (vaapi_frame_unclaim(
|
||||
frame->owner, frame->done, frame->done_value, NULL))
|
||||
return -1;
|
||||
l->owner = frame->owner;
|
||||
l->index = frame->done;
|
||||
l->generation = frame->done_value;
|
||||
l->broken = true;
|
||||
return -2;
|
||||
}
|
||||
yuv = source.yuv | frame->yuv;
|
||||
if (!source.color.primaries || source.color.primaries == 2)
|
||||
source.color.primaries = frame->color.primaries;
|
||||
if (!source.color.transfer || source.color.transfer == 2)
|
||||
source.color.transfer = frame->color.transfer;
|
||||
if (!source.color.matrix || source.color.matrix == 2)
|
||||
source.color.matrix = frame->color.matrix;
|
||||
if (!source.color.range)
|
||||
source.color.range = frame->color.range;
|
||||
for (i = 0; i < 8; i++)
|
||||
if (frame->color.mastering.present & (1u << i))
|
||||
source.color.mastering.chromaticity[i] =
|
||||
frame->color.mastering.chromaticity[i];
|
||||
if (frame->color.mastering.present & 0x100u)
|
||||
source.color.mastering.luminance_max =
|
||||
frame->color.mastering.luminance_max;
|
||||
if (frame->color.mastering.present & 0x200u)
|
||||
source.color.mastering.luminance_min =
|
||||
frame->color.mastering.luminance_min;
|
||||
source.color.mastering.present |= frame->color.mastering.present;
|
||||
if (frame->color.max_cll)
|
||||
source.color.max_cll = frame->color.max_cll;
|
||||
if (frame->color.max_fall)
|
||||
source.color.max_fall = frame->color.max_fall;
|
||||
l->owner = frame->owner;
|
||||
l->index = frame->done;
|
||||
l->generation = frame->done_value;
|
||||
if (!vaapi_frame_take(l->owner, l->index, l->generation, &l->returned))
|
||||
{
|
||||
if (vaapi_frame_unclaim(l->owner, l->index, l->generation, NULL))
|
||||
{
|
||||
l->owner = NULL;
|
||||
l->index = l->generation = 0;
|
||||
return -1;
|
||||
}
|
||||
l->broken = true;
|
||||
return -2;
|
||||
}
|
||||
if (!vaapi_hevc_vulkan_frame_adopt(l->entry->frame, &l->returned)
|
||||
|| !vaapi_hevc_vulkan_submission_configure_position(l->entry,
|
||||
l->entry->frame, source.x, source.y, frame->width, frame->height,
|
||||
yuv, source.chroma_position, &source.color, l->fp16))
|
||||
result = -1;
|
||||
else
|
||||
result = vaapi_hevc_vulkan_submission_present(
|
||||
l->entry, destination, view, format, width, height, layout);
|
||||
if (result == 1)
|
||||
return 1;
|
||||
if (result == -2 || !vaapi_hevc_vulkan_lease_put(l, false))
|
||||
{
|
||||
l->broken = true;
|
||||
return -2;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
bool vaapi_hevc_vulkan_lease_after_device_destroy(
|
||||
vaapi_hevc_vulkan_lease_t *l, VkDevice device)
|
||||
{
|
||||
vaapi_hevc_vulkan_frame_t *frame;
|
||||
vaapi_hevc_export_t empty;
|
||||
if (!l || !l->entry || !device || l->entry->device != device)
|
||||
return false;
|
||||
frame = l->entry->frame;
|
||||
if (frame && frame->active)
|
||||
{
|
||||
if (l->returned.active && frame->source.active)
|
||||
return false;
|
||||
memset(&empty, 0, sizeof(empty));
|
||||
if (!vaapi_hevc_vulkan_frame_after_device_destroy(
|
||||
frame, device, l->returned.active ? &empty : &l->returned))
|
||||
return false;
|
||||
}
|
||||
if (l->owner
|
||||
&& !vaapi_frame_finish(l->owner, l->index, l->generation,
|
||||
l->returned.active ? &l->returned : NULL))
|
||||
return false;
|
||||
memset(l, 0, sizeof(*l));
|
||||
return true;
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
/* Copyright (C) 2026 The RetroArch team.
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later */
|
||||
#ifndef RARCH_VAAPI_HEVC_VULKAN_LEASE_H
|
||||
#define RARCH_VAAPI_HEVC_VULKAN_LEASE_H
|
||||
#include "vaapi_frame.h"
|
||||
#include "vaapi_hevc_vulkan_submit.h"
|
||||
RETRO_BEGIN_DECLS
|
||||
|
||||
typedef struct vaapi_hevc_vulkan_lease
|
||||
{
|
||||
vaapi_hevc_vulkan_submission_t *entry;
|
||||
vaapi_hevc_export_t returned;
|
||||
void *owner;
|
||||
uint64_t index, generation;
|
||||
bool fp16, broken;
|
||||
} vaapi_hevc_vulkan_lease_t;
|
||||
|
||||
/* Cold renderer owner, zeroed lease and an idle prepared import entry.
|
||||
* All entry resources outlive this bridge and any outstanding frame. */
|
||||
bool vaapi_hevc_vulkan_lease_bind(vaapi_hevc_vulkan_lease_t *lease,
|
||||
vaapi_hevc_vulkan_submission_t *entry, bool fp16);
|
||||
/* One zero-timeout query; 1 idle/returned, 0 pending, -1 retained error.
|
||||
* Completion publishes the export to its decoder owner, preserving imports.
|
||||
* Decoder collect must run even while playback is paused or closing. */
|
||||
int vaapi_hevc_vulkan_lease_poll(vaapi_hevc_vulkan_lease_t *lease);
|
||||
/* Warm renderer owner. 1 consumes the opaque lease, 0 busy/-1 refusal leave
|
||||
* it pending, -2 retains a claimed lease after an uncertain failure. No native
|
||||
* FD/VA call, resource creation/destruction or wait. Track metadata supplies
|
||||
* unspecified coded color and present HDR light levels. The visible frame
|
||||
* stays within the claimed carrier at its original crop origin; destination
|
||||
* format/dimensions must match that visible frame. */
|
||||
int vaapi_hevc_vulkan_lease_present(vaapi_hevc_vulkan_lease_t *lease,
|
||||
const video_hw_frame_t *frame, VkImage destination, VkImageView view,
|
||||
VkFormat format, unsigned width, unsigned height, VkImageLayout layout);
|
||||
/* ONLY after actual destruction of entry's exact device. Invalidates imported
|
||||
* handles and publishes the retained source without calling Vulkan or VA.
|
||||
* False retains ownership for a later valid return; caller must not free it. */
|
||||
bool vaapi_hevc_vulkan_lease_after_device_destroy(
|
||||
vaapi_hevc_vulkan_lease_t *lease, VkDevice device);
|
||||
RETRO_END_DECLS
|
||||
#endif
|
||||
@@ -0,0 +1,207 @@
|
||||
/* Copyright (C) 2026 The RetroArch team.
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later */
|
||||
#include "vaapi_hevc_vulkan_resources.h"
|
||||
#include <string.h>
|
||||
static const uint32_t vaapi_hevc_rgba8_shader[] =
|
||||
#include "../drivers/vulkan_shaders/yuv_image_to_rgba8888.comp.inc"
|
||||
;
|
||||
static const uint32_t vaapi_hevc_fp16_shader[] =
|
||||
#include "../drivers/vulkan_shaders/yuv_image_to_rgba16f.comp.inc"
|
||||
;
|
||||
|
||||
void vaapi_hevc_vulkan_resources_destroy(vaapi_hevc_vulkan_resources_t *r)
|
||||
{
|
||||
unsigned i;
|
||||
if (!r || !r->device)
|
||||
return;
|
||||
for (i = 0; i < 32; i++)
|
||||
if (r->fences[i])
|
||||
r->ops.destroy_fence(r->device, r->fences[i], NULL);
|
||||
if (r->command_pool)
|
||||
r->ops.destroy_commands(r->device, r->command_pool, NULL);
|
||||
if (r->pipeline)
|
||||
r->ops.destroy_pipeline(r->device, r->pipeline, NULL);
|
||||
if (r->pool)
|
||||
r->ops.destroy_pool(r->device, r->pool, NULL);
|
||||
if (r->layout)
|
||||
r->ops.destroy_layout(r->device, r->layout, NULL);
|
||||
if (r->set_layout)
|
||||
r->ops.destroy_set_layout(r->device, r->set_layout, NULL);
|
||||
if (r->sampler)
|
||||
r->ops.destroy_sampler(r->device, r->sampler, NULL);
|
||||
memset(r, 0, sizeof(*r));
|
||||
}
|
||||
|
||||
bool vaapi_hevc_vulkan_resources_after_device_destroy(
|
||||
vaapi_hevc_vulkan_resources_t *r, VkDevice device)
|
||||
{
|
||||
if (!r || !device || r->device != device)
|
||||
return false;
|
||||
memset(r, 0, sizeof(*r));
|
||||
return true;
|
||||
}
|
||||
|
||||
bool vaapi_hevc_vulkan_resources_create(vaapi_hevc_vulkan_resources_t *r,
|
||||
VkDevice device, unsigned family, unsigned count, bool fp16,
|
||||
const vaapi_hevc_vulkan_resource_ops_t *ops)
|
||||
{
|
||||
VkSamplerCreateInfo sampler;
|
||||
VkDescriptorSetLayoutBinding bindings[3];
|
||||
VkDescriptorSetLayoutCreateInfo set;
|
||||
VkPipelineLayoutCreateInfo layout;
|
||||
VkPushConstantRange push;
|
||||
VkDescriptorPoolSize sizes[2];
|
||||
VkDescriptorPoolCreateInfo pool;
|
||||
VkDescriptorSetAllocateInfo allocation;
|
||||
VkDescriptorSetLayout layouts[32];
|
||||
VkShaderModuleCreateInfo shader;
|
||||
VkComputePipelineCreateInfo pipeline;
|
||||
VkCommandPoolCreateInfo commands;
|
||||
VkCommandBufferAllocateInfo buffers;
|
||||
VkFenceCreateInfo fence;
|
||||
VkShaderModule module = VK_NULL_HANDLE;
|
||||
vaapi_hevc_vulkan_resource_ops_t native;
|
||||
unsigned i;
|
||||
if (!r || r->device || !device || !count || count > 32
|
||||
|| family >= VK_QUEUE_FAMILY_FOREIGN_EXT || !ops
|
||||
|| !ops->create_sampler || !ops->destroy_sampler
|
||||
|| !ops->create_set_layout || !ops->destroy_set_layout
|
||||
|| !ops->create_layout || !ops->destroy_layout || !ops->create_pool
|
||||
|| !ops->destroy_pool || !ops->allocate_sets || !ops->create_shader
|
||||
|| !ops->destroy_shader || !ops->create_pipeline
|
||||
|| !ops->destroy_pipeline || !ops->create_commands
|
||||
|| !ops->destroy_commands || !ops->allocate_commands
|
||||
|| !ops->create_fence || !ops->destroy_fence)
|
||||
return false;
|
||||
native = *ops;
|
||||
memset(r, 0, sizeof(*r));
|
||||
r->ops = native;
|
||||
r->device = device;
|
||||
ops = &r->ops;
|
||||
sampler.pNext = NULL;
|
||||
sampler.flags = 0;
|
||||
sampler.magFilter = VK_FILTER_NEAREST;
|
||||
sampler.minFilter = VK_FILTER_NEAREST;
|
||||
sampler.mipmapMode = VK_SAMPLER_MIPMAP_MODE_NEAREST;
|
||||
sampler.mipLodBias = 0.0f;
|
||||
sampler.anisotropyEnable = VK_FALSE;
|
||||
sampler.maxAnisotropy = 0.0f;
|
||||
sampler.compareEnable = VK_FALSE;
|
||||
sampler.compareOp = VK_COMPARE_OP_NEVER;
|
||||
sampler.minLod = 0.0f;
|
||||
sampler.maxLod = 0.0f;
|
||||
sampler.borderColor = VK_BORDER_COLOR_FLOAT_TRANSPARENT_BLACK;
|
||||
sampler.unnormalizedCoordinates = VK_FALSE;
|
||||
sampler.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO;
|
||||
sampler.addressModeU = sampler.addressModeV = sampler.addressModeW =
|
||||
VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
|
||||
if (ops->create_sampler(device, &sampler, NULL, &r->sampler) != VK_SUCCESS)
|
||||
goto failed;
|
||||
|
||||
for (i = 0; i < 3; i++)
|
||||
{
|
||||
bindings[i].pImmutableSamplers = NULL;
|
||||
bindings[i].binding = i + 1;
|
||||
bindings[i].descriptorCount = 1;
|
||||
bindings[i].stageFlags = VK_SHADER_STAGE_COMPUTE_BIT;
|
||||
bindings[i].descriptorType =
|
||||
i < 2 ? VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER
|
||||
: VK_DESCRIPTOR_TYPE_STORAGE_IMAGE;
|
||||
}
|
||||
set.pNext = NULL;
|
||||
set.flags = 0;
|
||||
set.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO;
|
||||
set.bindingCount = 3;
|
||||
set.pBindings = bindings;
|
||||
if (ops->create_set_layout(device, &set, NULL, &r->set_layout) != VK_SUCCESS)
|
||||
goto failed;
|
||||
push.offset = 0;
|
||||
push.stageFlags = VK_SHADER_STAGE_COMPUTE_BIT;
|
||||
push.size = 80;
|
||||
layout.pNext = NULL;
|
||||
layout.flags = 0;
|
||||
layout.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO;
|
||||
layout.setLayoutCount = 1;
|
||||
layout.pSetLayouts = &r->set_layout;
|
||||
layout.pushConstantRangeCount = 1;
|
||||
layout.pPushConstantRanges = &push;
|
||||
if (ops->create_layout(device, &layout, NULL, &r->layout) != VK_SUCCESS)
|
||||
goto failed;
|
||||
|
||||
sizes[0].type = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
|
||||
sizes[0].descriptorCount = 2 * count;
|
||||
sizes[1].type = VK_DESCRIPTOR_TYPE_STORAGE_IMAGE;
|
||||
sizes[1].descriptorCount = count;
|
||||
pool.pNext = NULL;
|
||||
pool.flags = 0;
|
||||
pool.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO;
|
||||
pool.maxSets = count;
|
||||
pool.poolSizeCount = 2;
|
||||
pool.pPoolSizes = sizes;
|
||||
if (ops->create_pool(device, &pool, NULL, &r->pool) != VK_SUCCESS)
|
||||
goto failed;
|
||||
for (i = 0; i < count; i++)
|
||||
layouts[i] = r->set_layout;
|
||||
allocation.pNext = NULL;
|
||||
allocation.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO;
|
||||
allocation.descriptorPool = r->pool;
|
||||
allocation.descriptorSetCount = count;
|
||||
allocation.pSetLayouts = layouts;
|
||||
if (ops->allocate_sets(device, &allocation, r->sets) != VK_SUCCESS)
|
||||
goto failed;
|
||||
shader.pNext = NULL;
|
||||
shader.flags = 0;
|
||||
shader.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO;
|
||||
shader.pCode = fp16 ? vaapi_hevc_fp16_shader : vaapi_hevc_rgba8_shader;
|
||||
shader.codeSize = fp16 ? sizeof(vaapi_hevc_fp16_shader)
|
||||
: sizeof(vaapi_hevc_rgba8_shader);
|
||||
if (ops->create_shader(device, &shader, NULL, &module) != VK_SUCCESS)
|
||||
goto failed;
|
||||
pipeline.pNext = NULL;
|
||||
pipeline.flags = 0;
|
||||
pipeline.basePipelineHandle = VK_NULL_HANDLE;
|
||||
pipeline.basePipelineIndex = 0;
|
||||
pipeline.stage.pNext = NULL;
|
||||
pipeline.stage.flags = 0;
|
||||
pipeline.stage.pSpecializationInfo = NULL;
|
||||
pipeline.sType = VK_STRUCTURE_TYPE_COMPUTE_PIPELINE_CREATE_INFO;
|
||||
pipeline.layout = r->layout;
|
||||
pipeline.stage.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
|
||||
pipeline.stage.stage = VK_SHADER_STAGE_COMPUTE_BIT;
|
||||
pipeline.stage.module = module;
|
||||
pipeline.stage.pName = "main";
|
||||
if (ops->create_pipeline(
|
||||
device, VK_NULL_HANDLE, 1, &pipeline, NULL, &r->pipeline)
|
||||
!= VK_SUCCESS)
|
||||
goto failed;
|
||||
ops->destroy_shader(device, module, NULL);
|
||||
module = VK_NULL_HANDLE;
|
||||
commands.pNext = NULL;
|
||||
commands.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO;
|
||||
commands.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT;
|
||||
commands.queueFamilyIndex = family;
|
||||
if (ops->create_commands(device, &commands, NULL, &r->command_pool)
|
||||
!= VK_SUCCESS)
|
||||
goto failed;
|
||||
buffers.pNext = NULL;
|
||||
buffers.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
|
||||
buffers.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
|
||||
buffers.commandPool = r->command_pool;
|
||||
buffers.commandBufferCount = count;
|
||||
if (ops->allocate_commands(device, &buffers, r->commands) != VK_SUCCESS)
|
||||
goto failed;
|
||||
fence.pNext = NULL;
|
||||
fence.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO;
|
||||
fence.flags = VK_FENCE_CREATE_SIGNALED_BIT;
|
||||
for (i = 0; i < count; i++)
|
||||
if (ops->create_fence(device, &fence, NULL, &r->fences[i]) != VK_SUCCESS)
|
||||
goto failed;
|
||||
r->format = fp16 ? VK_FORMAT_R16G16B16A16_SFLOAT : VK_FORMAT_R8G8B8A8_UNORM;
|
||||
r->count = count;
|
||||
return true;
|
||||
failed:
|
||||
if (module)
|
||||
ops->destroy_shader(device, module, NULL);
|
||||
vaapi_hevc_vulkan_resources_destroy(r);
|
||||
return false;
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
/* Copyright (C) 2026 The RetroArch team.
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later */
|
||||
#ifndef RARCH_VAAPI_HEVC_VULKAN_RESOURCES_H
|
||||
#define RARCH_VAAPI_HEVC_VULKAN_RESOURCES_H
|
||||
#include <vulkan/vulkan_core.h>
|
||||
#include <boolean.h>
|
||||
#include <retro_common_api.h>
|
||||
RETRO_BEGIN_DECLS
|
||||
|
||||
typedef struct vaapi_hevc_vulkan_resource_ops
|
||||
{
|
||||
PFN_vkCreateSampler create_sampler;
|
||||
PFN_vkDestroySampler destroy_sampler;
|
||||
PFN_vkCreateDescriptorSetLayout create_set_layout;
|
||||
PFN_vkDestroyDescriptorSetLayout destroy_set_layout;
|
||||
PFN_vkCreatePipelineLayout create_layout;
|
||||
PFN_vkDestroyPipelineLayout destroy_layout;
|
||||
PFN_vkCreateDescriptorPool create_pool;
|
||||
PFN_vkDestroyDescriptorPool destroy_pool;
|
||||
PFN_vkAllocateDescriptorSets allocate_sets;
|
||||
PFN_vkCreateShaderModule create_shader;
|
||||
PFN_vkDestroyShaderModule destroy_shader;
|
||||
PFN_vkCreateComputePipelines create_pipeline;
|
||||
PFN_vkDestroyPipeline destroy_pipeline;
|
||||
PFN_vkCreateCommandPool create_commands;
|
||||
PFN_vkDestroyCommandPool destroy_commands;
|
||||
PFN_vkAllocateCommandBuffers allocate_commands;
|
||||
PFN_vkCreateFence create_fence;
|
||||
PFN_vkDestroyFence destroy_fence;
|
||||
} vaapi_hevc_vulkan_resource_ops_t;
|
||||
|
||||
typedef struct vaapi_hevc_vulkan_resources
|
||||
{
|
||||
vaapi_hevc_vulkan_resource_ops_t ops;
|
||||
VkDevice device;
|
||||
VkSampler sampler;
|
||||
VkDescriptorSetLayout set_layout;
|
||||
VkPipelineLayout layout;
|
||||
VkDescriptorPool pool;
|
||||
VkPipeline pipeline;
|
||||
VkCommandPool command_pool;
|
||||
VkDescriptorSet sets[32];
|
||||
VkCommandBuffer commands[32];
|
||||
VkFence fences[32];
|
||||
VkFormat format;
|
||||
unsigned count;
|
||||
} vaapi_hevc_vulkan_resources_t;
|
||||
|
||||
/* Cold renderer setup, zeroed storage; 1..32 independent descriptor/command/
|
||||
* fence slots sharing the existing sampled-YUV shader variant. Owns every
|
||||
* created Vulkan child. Refusal unwinds partial resources; no GPU submission.
|
||||
*/
|
||||
bool vaapi_hevc_vulkan_resources_create(vaapi_hevc_vulkan_resources_t *out,
|
||||
VkDevice device, unsigned family, unsigned count, bool fp16,
|
||||
const vaapi_hevc_vulkan_resource_ops_t *ops);
|
||||
/* Cold renderer owner AFTER all command/descriptor/GPU use has retired.
|
||||
* Does not poll or wait. Destroy before imported source views or destination.
|
||||
*/
|
||||
void vaapi_hevc_vulkan_resources_destroy(
|
||||
vaapi_hevc_vulkan_resources_t *resources);
|
||||
/* Only AFTER destruction of the exact device. No Vulkan calls. */
|
||||
bool vaapi_hevc_vulkan_resources_after_device_destroy(
|
||||
vaapi_hevc_vulkan_resources_t *resources, VkDevice device);
|
||||
RETRO_END_DECLS
|
||||
#endif
|
||||
@@ -0,0 +1,190 @@
|
||||
/* Copyright (C) 2026 The RetroArch team.
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later */
|
||||
#include "vaapi_hevc_vulkan_submit.h"
|
||||
|
||||
bool vaapi_hevc_vulkan_submission_configure(vaapi_hevc_vulkan_submission_t *e,
|
||||
vaapi_hevc_vulkan_frame_t *frame, unsigned x, unsigned y, unsigned width,
|
||||
unsigned height, unsigned yuv, const video_color_metadata_t *color,
|
||||
bool fp16)
|
||||
{
|
||||
return vaapi_hevc_vulkan_submission_configure_position(
|
||||
e, frame, x, y, width, height, yuv, 0, color, fp16);
|
||||
}
|
||||
|
||||
bool vaapi_hevc_vulkan_submission_configure_position(
|
||||
vaapi_hevc_vulkan_submission_t *e, vaapi_hevc_vulkan_frame_t *frame,
|
||||
unsigned x, unsigned y, unsigned width, unsigned height, unsigned yuv,
|
||||
unsigned chroma_position, const video_color_metadata_t *color, bool fp16)
|
||||
{
|
||||
vulkan_video_layout_t layout;
|
||||
unsigned pixfmt;
|
||||
if (chroma_position > 2 || !e || !frame || !frame->active
|
||||
|| !frame->source.active || frame->pending || frame->broken
|
||||
|| (e->frame && (e->frame->pending || e->frame->broken)) || !width
|
||||
|| !height || ((x | y) & 1) || x >= frame->source.descriptor.width
|
||||
|| y >= frame->source.descriptor.height
|
||||
|| width > frame->source.descriptor.width - x
|
||||
|| height > frame->source.descriptor.height - y)
|
||||
return false;
|
||||
pixfmt = frame->source.descriptor.fourcc == VA_FOURCC_P010
|
||||
? IMAGE_PIXFMT_P010
|
||||
: IMAGE_PIXFMT_NV12;
|
||||
if (!vulkan_video_layout(&layout, width, height, pixfmt, yuv)
|
||||
|| !vulkan_video_color(&layout, yuv, color))
|
||||
return false;
|
||||
layout.header[2] = x;
|
||||
layout.header[3] = y;
|
||||
layout.header[4] = chroma_position;
|
||||
if (fp16)
|
||||
{
|
||||
float linear = -1.0f;
|
||||
memcpy(&layout.header[19], &linear, sizeof(linear));
|
||||
}
|
||||
memcpy(e->header, layout.header, sizeof(e->header));
|
||||
e->frame = frame;
|
||||
e->pool = vaapi_export_owner(&frame->source);
|
||||
e->ticket = frame->source.ticket;
|
||||
e->slot = frame->source.slot;
|
||||
e->device = frame->device;
|
||||
e->output_format =
|
||||
fp16 ? VK_FORMAT_R16G16B16A16_SFLOAT : VK_FORMAT_R8G8B8A8_UNORM;
|
||||
return true;
|
||||
}
|
||||
|
||||
static void vaapi_hevc_vulkan_destination(vaapi_hevc_vulkan_submission_t *e,
|
||||
VkImage image, VkImageLayout old, bool before)
|
||||
{
|
||||
VkImageMemoryBarrier barrier;
|
||||
barrier.pNext = NULL;
|
||||
barrier.subresourceRange.baseMipLevel = 0;
|
||||
barrier.subresourceRange.baseArrayLayer = 0;
|
||||
barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
|
||||
barrier.image = image;
|
||||
barrier.oldLayout = before ? old : VK_IMAGE_LAYOUT_GENERAL;
|
||||
barrier.newLayout = before ? VK_IMAGE_LAYOUT_GENERAL
|
||||
: VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
|
||||
barrier.srcQueueFamilyIndex = barrier.dstQueueFamilyIndex =
|
||||
VK_QUEUE_FAMILY_IGNORED;
|
||||
barrier.srcAccessMask =
|
||||
before ? (old == VK_IMAGE_LAYOUT_UNDEFINED ? 0
|
||||
: old == VK_IMAGE_LAYOUT_GENERAL
|
||||
? VK_ACCESS_MEMORY_READ_BIT
|
||||
| VK_ACCESS_MEMORY_WRITE_BIT
|
||||
: VK_ACCESS_SHADER_READ_BIT)
|
||||
: VK_ACCESS_SHADER_WRITE_BIT;
|
||||
barrier.dstAccessMask =
|
||||
before ? VK_ACCESS_SHADER_WRITE_BIT : VK_ACCESS_SHADER_READ_BIT;
|
||||
barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
|
||||
barrier.subresourceRange.levelCount = barrier.subresourceRange.layerCount =
|
||||
1;
|
||||
e->ops.barrier(e->command,
|
||||
before ? VK_PIPELINE_STAGE_ALL_COMMANDS_BIT
|
||||
: VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT,
|
||||
before ? VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT
|
||||
: VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT,
|
||||
0, 0, NULL, 0, NULL, 1, &barrier);
|
||||
}
|
||||
|
||||
int vaapi_hevc_vulkan_submission_present(vaapi_hevc_vulkan_submission_t *e,
|
||||
VkImage destination, VkImageView view, VkFormat format, unsigned width,
|
||||
unsigned height, VkImageLayout layout)
|
||||
{
|
||||
VkDescriptorImageInfo images[3];
|
||||
VkWriteDescriptorSet writes[3];
|
||||
VkCommandBufferBeginInfo begin;
|
||||
VkSubmitInfo submit;
|
||||
unsigned i;
|
||||
int status;
|
||||
VkResult result;
|
||||
if (!e || !e->frame || !e->frame->active || !e->frame->source.active
|
||||
|| !destination || !view || !e->device || e->device != e->frame->device
|
||||
|| !e->queue || !e->command || !e->fence || !e->pipeline || !e->layout
|
||||
|| !e->set || !e->sampler || e->family >= VK_QUEUE_FAMILY_FOREIGN_EXT
|
||||
|| width != e->header[0] || height != e->header[1]
|
||||
|| format != e->output_format
|
||||
|| destination == e->frame->layers[0].image
|
||||
|| destination == e->frame->layers[1].image
|
||||
|| view == e->frame->layers[0].view || view == e->frame->layers[1].view
|
||||
|| (layout != VK_IMAGE_LAYOUT_UNDEFINED
|
||||
&& layout != VK_IMAGE_LAYOUT_GENERAL
|
||||
&& layout != VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL)
|
||||
|| e->pool != vaapi_export_owner(&e->frame->source) || e->slot != e->frame->source.slot
|
||||
|| e->ticket != e->frame->source.ticket || !e->ops.fence_status
|
||||
|| !e->ops.reset_fences || !e->ops.reset_command || !e->ops.begin
|
||||
|| !e->ops.end || !e->ops.update || !e->ops.barrier
|
||||
|| !e->ops.bind_pipeline || !e->ops.bind_sets || !e->ops.push
|
||||
|| !e->ops.dispatch || !e->ops.submit)
|
||||
return -1;
|
||||
status = vaapi_hevc_vulkan_frame_poll(e->frame, e->ops.fence_status);
|
||||
if (status <= 0)
|
||||
return status;
|
||||
for (i = 0; i < 3; i++)
|
||||
{
|
||||
images[i].imageView = i < 2 ? e->frame->layers[i].view : view;
|
||||
images[i].sampler = i < 2 ? e->sampler : VK_NULL_HANDLE;
|
||||
images[i].imageLayout = i < 2 ? VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL
|
||||
: VK_IMAGE_LAYOUT_GENERAL;
|
||||
writes[i].pNext = NULL;
|
||||
writes[i].dstArrayElement = 0;
|
||||
writes[i].pBufferInfo = NULL;
|
||||
writes[i].pTexelBufferView = NULL;
|
||||
writes[i].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
|
||||
writes[i].dstSet = e->set;
|
||||
writes[i].dstBinding = i + 1;
|
||||
writes[i].descriptorCount = 1;
|
||||
writes[i].descriptorType =
|
||||
i < 2 ? VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER
|
||||
: VK_DESCRIPTOR_TYPE_STORAGE_IMAGE;
|
||||
writes[i].pImageInfo = &images[i];
|
||||
}
|
||||
e->ops.update(e->device, 3, writes, 0, NULL);
|
||||
if (e->ops.reset_command(e->command, 0) != VK_SUCCESS)
|
||||
return -1;
|
||||
begin.pNext = NULL;
|
||||
begin.pInheritanceInfo = NULL;
|
||||
begin.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
|
||||
begin.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;
|
||||
if (e->ops.begin(e->command, &begin) != VK_SUCCESS)
|
||||
return -1;
|
||||
if (!vaapi_hevc_vulkan_frame_barrier(e->frame, e->command, e->ops.barrier,
|
||||
e->family, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, true))
|
||||
return -1;
|
||||
vaapi_hevc_vulkan_destination(e, destination, layout, true);
|
||||
e->ops.bind_pipeline(
|
||||
e->command, VK_PIPELINE_BIND_POINT_COMPUTE, e->pipeline);
|
||||
e->ops.bind_sets(e->command, VK_PIPELINE_BIND_POINT_COMPUTE, e->layout, 0, 1,
|
||||
&e->set, 0, NULL);
|
||||
e->ops.push(e->command, e->layout, VK_SHADER_STAGE_COMPUTE_BIT, 0,
|
||||
sizeof(e->header), e->header);
|
||||
e->ops.dispatch(e->command, (width + 7) / 8, (height + 7) / 8, 1);
|
||||
vaapi_hevc_vulkan_destination(e, destination, layout, false);
|
||||
if (!vaapi_hevc_vulkan_frame_barrier(e->frame, e->command, e->ops.barrier,
|
||||
e->family, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, false)
|
||||
|| e->ops.end(e->command) != VK_SUCCESS
|
||||
|| e->ops.reset_fences(e->device, 1, &e->fence) != VK_SUCCESS)
|
||||
return -1;
|
||||
submit.pNext = NULL;
|
||||
submit.waitSemaphoreCount = 0;
|
||||
submit.pWaitSemaphores = NULL;
|
||||
submit.pWaitDstStageMask = NULL;
|
||||
submit.signalSemaphoreCount = 0;
|
||||
submit.pSignalSemaphores = NULL;
|
||||
submit.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
|
||||
submit.commandBufferCount = 1;
|
||||
submit.pCommandBuffers = &e->command;
|
||||
if (!vaapi_hevc_vulkan_frame_track(e->frame, e->fence))
|
||||
return -1;
|
||||
result = e->ops.submit(e->ops.context, e->queue, 1, &submit, e->fence);
|
||||
if (result == VK_TIMEOUT)
|
||||
{
|
||||
e->frame->pending = false;
|
||||
e->frame->fence = VK_NULL_HANDLE;
|
||||
return 0;
|
||||
}
|
||||
if (result != VK_SUCCESS && result != VK_NOT_READY)
|
||||
{
|
||||
e->frame->broken = true;
|
||||
return -2;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
/* Copyright (C) 2026 The RetroArch team.
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later */
|
||||
#ifndef RARCH_VAAPI_HEVC_VULKAN_SUBMIT_H
|
||||
#define RARCH_VAAPI_HEVC_VULKAN_SUBMIT_H
|
||||
#include "vaapi_hevc_vulkan_frame.h"
|
||||
#include "vulkan_video_import.h"
|
||||
RETRO_BEGIN_DECLS
|
||||
|
||||
typedef struct vaapi_hevc_vulkan_submit_ops
|
||||
{
|
||||
PFN_vkGetFenceStatus fence_status;
|
||||
PFN_vkResetFences reset_fences;
|
||||
PFN_vkResetCommandBuffer reset_command;
|
||||
PFN_vkBeginCommandBuffer begin;
|
||||
PFN_vkEndCommandBuffer end;
|
||||
PFN_vkUpdateDescriptorSets update;
|
||||
PFN_vkCmdPipelineBarrier barrier;
|
||||
PFN_vkCmdBindPipeline bind_pipeline;
|
||||
PFN_vkCmdBindDescriptorSets bind_sets;
|
||||
PFN_vkCmdPushConstants push;
|
||||
PFN_vkCmdDispatch dispatch;
|
||||
/* Existing queue serialization. SUCCESS submitted; NOT_READY deferred and
|
||||
* owned; TIMEOUT refused without GPU use. Negative errors are ambiguous. */
|
||||
VkResult (*submit)(void *, VkQueue, uint32_t, const VkSubmitInfo *, VkFence);
|
||||
void *context;
|
||||
} vaapi_hevc_vulkan_submit_ops_t;
|
||||
|
||||
typedef struct vaapi_hevc_vulkan_submission
|
||||
{
|
||||
vaapi_hevc_vulkan_frame_t *frame;
|
||||
void *pool;
|
||||
vaapi_hevc_vulkan_submit_ops_t ops;
|
||||
uint64_t ticket;
|
||||
VkDevice device;
|
||||
VkQueue queue;
|
||||
VkCommandBuffer command;
|
||||
VkFence fence;
|
||||
VkPipeline pipeline;
|
||||
VkPipelineLayout layout;
|
||||
VkDescriptorSet set;
|
||||
VkSampler sampler;
|
||||
VkFormat output_format;
|
||||
uint32_t header[20];
|
||||
unsigned slot, family;
|
||||
} vaapi_hevc_vulkan_submission_t;
|
||||
|
||||
/* Cold immutable crop/color preparation. Borrow the renderer's sampled-YUV
|
||||
* pipeline (rgba8 or rgba16f as selected), 80-byte layout, nearest sampler,
|
||||
* unique descriptor set and command/fence. These outlive all pending use.
|
||||
* Does not move or release frame's source. */
|
||||
bool vaapi_hevc_vulkan_submission_configure(
|
||||
vaapi_hevc_vulkan_submission_t *entry, vaapi_hevc_vulkan_frame_t *frame,
|
||||
unsigned x, unsigned y, unsigned width, unsigned height, unsigned yuv,
|
||||
const video_color_metadata_t *color, bool fp16);
|
||||
/* Sampled-image header word 4 carries the private chroma position. */
|
||||
bool vaapi_hevc_vulkan_submission_configure_position(
|
||||
vaapi_hevc_vulkan_submission_t *entry, vaapi_hevc_vulkan_frame_t *frame,
|
||||
unsigned x, unsigned y, unsigned width, unsigned height, unsigned yuv,
|
||||
unsigned chroma_position, const video_color_metadata_t *color, bool fp16);
|
||||
/* Renderer owner, warm resources only. Destination is a matching
|
||||
* SAMPLED|STORAGE image and view; layout is its tracked current layout. Ends
|
||||
* SHADER_READ_ONLY after conversion. 1 queued, 0 busy, -1 refused before GPU
|
||||
* submission, -2 uncertain submission failure: source/destination must survive
|
||||
* exact device retirement. Poll/retire through frame helpers and the existing
|
||||
* decoder-owner return path. */
|
||||
int vaapi_hevc_vulkan_submission_present(vaapi_hevc_vulkan_submission_t *entry,
|
||||
VkImage destination, VkImageView view, VkFormat format, unsigned width,
|
||||
unsigned height, VkImageLayout layout);
|
||||
RETRO_END_DECLS
|
||||
#endif
|
||||
@@ -0,0 +1,327 @@
|
||||
/* Copyright (C) 2026 The RetroArch team.
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later */
|
||||
#include "vaapi_vp9_backend.h"
|
||||
#include <formats/rvp9_color.h>
|
||||
#include <limits.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#if defined(HAVE_THREADS) && defined(RETRO_ATOMIC_LOCK_FREE) \
|
||||
&& defined(RETRO_ATOMIC_HAS_PTR)
|
||||
#include <pthread.h>
|
||||
#include <rthreads/retro_eventcount.h>
|
||||
|
||||
#ifndef VAAPI_VP9_NODE_PATH
|
||||
#define VAAPI_VP9_NODE_PATH vaapi_vp9_backend_node_path
|
||||
#if defined(__linux__) && !defined(__ANDROID__) && defined(NEED_DYNAMIC)
|
||||
#include <sys/sysmacros.h>
|
||||
#include <xf86drm.h>
|
||||
|
||||
static bool vaapi_vp9_backend_node_path(
|
||||
unsigned major_id, unsigned minor_id, char *path, size_t capacity)
|
||||
{
|
||||
dylib_t library = dylib_load("libdrm.so.2");
|
||||
int (*get_device)(dev_t, uint32_t, drmDevicePtr *);
|
||||
void (*free_device)(drmDevicePtr *);
|
||||
drmDevicePtr device = NULL;
|
||||
function_t symbol;
|
||||
size_t length;
|
||||
bool result = false;
|
||||
if (!library)
|
||||
return false;
|
||||
symbol = dylib_proc(library, "drmGetDeviceFromDevId");
|
||||
if (!symbol || sizeof(symbol) != sizeof(get_device))
|
||||
goto finish;
|
||||
memcpy(&get_device, &symbol, sizeof(get_device));
|
||||
symbol = dylib_proc(library, "drmFreeDevice");
|
||||
if (!symbol || sizeof(symbol) != sizeof(free_device))
|
||||
goto finish;
|
||||
memcpy(&free_device, &symbol, sizeof(free_device));
|
||||
if (!get_device(makedev(major_id, minor_id), 0, &device) && device)
|
||||
{
|
||||
if ((device->available_nodes & (1 << DRM_NODE_RENDER)) && device->nodes
|
||||
&& device->nodes[DRM_NODE_RENDER])
|
||||
{
|
||||
length = strlen(device->nodes[DRM_NODE_RENDER]);
|
||||
if (length && length < capacity)
|
||||
{
|
||||
memcpy(path, device->nodes[DRM_NODE_RENDER], length + 1);
|
||||
result = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (device)
|
||||
free_device(&device);
|
||||
finish:
|
||||
dylib_close(library);
|
||||
return result;
|
||||
}
|
||||
#else
|
||||
static bool vaapi_vp9_backend_node_path(
|
||||
unsigned major_id, unsigned minor_id, char *path, size_t capacity)
|
||||
{
|
||||
(void)major_id;
|
||||
(void)minor_id;
|
||||
(void)path;
|
||||
(void)capacity;
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
|
||||
typedef struct vaapi_vp9_backend_session
|
||||
{
|
||||
rvp9_hw_ops_t native;
|
||||
vaapi_vp9_owner_t *retired;
|
||||
vaapi_vp9_backend_t *backend;
|
||||
retro_eventcount_t wake;
|
||||
retro_atomic_int_t state, refs;
|
||||
} vaapi_vp9_backend_session_t;
|
||||
|
||||
static void vaapi_vp9_backend_unref(vaapi_vp9_backend_session_t *session)
|
||||
{
|
||||
vaapi_vp9_backend_t *backend;
|
||||
if (retro_atomic_fetch_sub_int(&session->refs, 1) != 1)
|
||||
return;
|
||||
backend = session->backend;
|
||||
retro_eventcount_free(&session->wake);
|
||||
free(session);
|
||||
retro_atomic_fetch_sub_int(&backend->active, 1);
|
||||
}
|
||||
|
||||
static void vaapi_vp9_backend_release(vaapi_vp9_backend_session_t *session)
|
||||
{
|
||||
retro_atomic_store_release_int(&session->state, 1);
|
||||
retro_eventcount_notify(&session->wake);
|
||||
vaapi_vp9_backend_unref(session);
|
||||
}
|
||||
|
||||
static void *vaapi_vp9_backend_retire(void *opaque)
|
||||
{
|
||||
vaapi_vp9_backend_session_t *session = (vaapi_vp9_backend_session_t *)opaque;
|
||||
int key;
|
||||
for (;;)
|
||||
{
|
||||
key = retro_eventcount_prepare_wait(&session->wake);
|
||||
if (retro_atomic_load_acquire_int(&session->state))
|
||||
{
|
||||
retro_eventcount_cancel_wait(&session->wake);
|
||||
break;
|
||||
}
|
||||
retro_eventcount_commit_wait(&session->wake, key);
|
||||
}
|
||||
/* This is the cold retirement executor, never a frame/UI caller. */
|
||||
while (!vaapi_vp9_owner_retire(session->retired))
|
||||
{
|
||||
key = retro_eventcount_prepare_wait(&session->wake);
|
||||
retro_eventcount_commit_wait_timeout(&session->wake, key, 10000);
|
||||
}
|
||||
vaapi_vp9_backend_unref(session);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static int vaapi_vp9_backend_decode(void *opaque, const uint8_t *packet,
|
||||
size_t size, const rvp9_hw_picture_t *picture)
|
||||
{
|
||||
vaapi_vp9_backend_session_t *session = (vaapi_vp9_backend_session_t *)opaque;
|
||||
return session->native.decode(
|
||||
session->native.decoder, packet, size, picture);
|
||||
}
|
||||
|
||||
static int vaapi_vp9_backend_output(
|
||||
void *opaque, unsigned slot, video_hw_frame_t *frame)
|
||||
{
|
||||
vaapi_vp9_backend_session_t *session = (vaapi_vp9_backend_session_t *)opaque;
|
||||
return session->native.output(session->native.decoder, slot, frame);
|
||||
}
|
||||
|
||||
static void vaapi_vp9_backend_close(rvp9_hw_ops_t *ops)
|
||||
{
|
||||
vaapi_vp9_backend_session_t *session;
|
||||
if (!ops || ops->decode != vaapi_vp9_backend_decode || !ops->decoder)
|
||||
return;
|
||||
session = (vaapi_vp9_backend_session_t *)ops->decoder;
|
||||
session->retired = vaapi_vp9_owner_detach(&session->native);
|
||||
memset(ops, 0, sizeof(*ops));
|
||||
vaapi_vp9_backend_release(session);
|
||||
}
|
||||
|
||||
static bool vaapi_vp9_backend_open_config(void *opaque, const rvp9_hdr *header,
|
||||
const video_color_metadata_t *container, const rvp9_config_t *record,
|
||||
unsigned slots, rvp9_hw_ops_t *ops)
|
||||
{
|
||||
vaapi_vp9_backend_t *backend = (vaapi_vp9_backend_t *)opaque;
|
||||
vaapi_vp9_backend_session_t *session;
|
||||
video_color_metadata_t color, configured;
|
||||
pthread_attr_t attr;
|
||||
pthread_t thread;
|
||||
char path[1024];
|
||||
unsigned major_id, minor_id;
|
||||
int epoch, active, result;
|
||||
if (!ops || ops->decoder || !backend || !header || slots < 10 || slots > 32
|
||||
|| header->frame_type || header->show_existing_frame)
|
||||
return false;
|
||||
if (record)
|
||||
{
|
||||
if (record->profile != header->profile
|
||||
|| record->bit_depth != header->bit_depth || record->chroma != 0
|
||||
|| !rvp9_config_apply_color(record, container, &configured))
|
||||
return false;
|
||||
container = &configured;
|
||||
}
|
||||
if (!rvp9_color_resolve(header, container, &color))
|
||||
return false;
|
||||
epoch = retro_atomic_load_acquire_int(&backend->epoch);
|
||||
if (epoch <= 0 || !(epoch & 1))
|
||||
return false;
|
||||
major_id = (unsigned)retro_atomic_load_acquire_int(&backend->major_id);
|
||||
minor_id = (unsigned)retro_atomic_load_acquire_int(&backend->minor_id);
|
||||
if (retro_atomic_load_acquire_int(&backend->epoch) != epoch)
|
||||
return false;
|
||||
for (;;)
|
||||
{
|
||||
active = retro_atomic_load_acquire_int(&backend->active);
|
||||
if (active < 0 || (unsigned)active >= backend->max_sessions)
|
||||
return false;
|
||||
if (retro_atomic_cas_int(&backend->active, active, active + 1))
|
||||
break;
|
||||
}
|
||||
if (!VAAPI_VP9_NODE_PATH(major_id, minor_id, path, sizeof(path))
|
||||
|| retro_atomic_load_acquire_int(&backend->epoch) != epoch)
|
||||
{
|
||||
retro_atomic_fetch_sub_int(&backend->active, 1);
|
||||
return false;
|
||||
}
|
||||
session = (vaapi_vp9_backend_session_t *)calloc(1, sizeof(*session));
|
||||
if (!session)
|
||||
{
|
||||
retro_atomic_fetch_sub_int(&backend->active, 1);
|
||||
return false;
|
||||
}
|
||||
session->backend = backend;
|
||||
if (!retro_eventcount_init(&session->wake))
|
||||
{
|
||||
free(session);
|
||||
retro_atomic_fetch_sub_int(&backend->active, 1);
|
||||
return false;
|
||||
}
|
||||
retro_atomic_int_init(&session->state, 0);
|
||||
retro_atomic_int_init(&session->refs, 1);
|
||||
if (pthread_attr_init(&attr))
|
||||
{
|
||||
vaapi_vp9_backend_unref(session);
|
||||
return false;
|
||||
}
|
||||
if (pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED))
|
||||
{
|
||||
pthread_attr_destroy(&attr);
|
||||
vaapi_vp9_backend_unref(session);
|
||||
return false;
|
||||
}
|
||||
retro_atomic_store_release_int(&session->refs, 2);
|
||||
result = pthread_create(&thread, &attr, vaapi_vp9_backend_retire, session);
|
||||
pthread_attr_destroy(&attr);
|
||||
if (result)
|
||||
{
|
||||
retro_atomic_store_release_int(&session->refs, 1);
|
||||
vaapi_vp9_backend_unref(session);
|
||||
return false;
|
||||
}
|
||||
if (!vaapi_vp9_owner_open(&session->retired, path, major_id, minor_id,
|
||||
header, &color, slots, &session->native))
|
||||
{
|
||||
vaapi_vp9_backend_release(session);
|
||||
return false;
|
||||
}
|
||||
if (retro_atomic_load_acquire_int(&backend->epoch) != epoch)
|
||||
{
|
||||
session->retired = vaapi_vp9_owner_detach(&session->native);
|
||||
vaapi_vp9_backend_release(session);
|
||||
return false;
|
||||
}
|
||||
memset(ops, 0, sizeof(*ops));
|
||||
ops->decoder = session;
|
||||
ops->decode = vaapi_vp9_backend_decode;
|
||||
ops->output = vaapi_vp9_backend_output;
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool vaapi_vp9_backend_open_color(void *opaque, const rvp9_hdr *header,
|
||||
const video_color_metadata_t *color, unsigned slots, rvp9_hw_ops_t *ops)
|
||||
{
|
||||
return vaapi_vp9_backend_open_config(
|
||||
opaque, header, color, NULL, slots, ops);
|
||||
}
|
||||
|
||||
static bool vaapi_vp9_backend_open(
|
||||
void *opaque, const rvp9_hdr *header, unsigned slots, rvp9_hw_ops_t *ops)
|
||||
{
|
||||
return vaapi_vp9_backend_open_color(opaque, header, NULL, slots, ops);
|
||||
}
|
||||
#endif
|
||||
|
||||
bool vaapi_vp9_backend_init(vaapi_vp9_backend_t *backend, unsigned max_sessions)
|
||||
{
|
||||
#if defined(HAVE_THREADS) && defined(RETRO_ATOMIC_LOCK_FREE) \
|
||||
&& defined(RETRO_ATOMIC_HAS_PTR)
|
||||
if (!backend || !max_sessions || max_sessions > 8)
|
||||
return false;
|
||||
memset(backend, 0, sizeof(*backend));
|
||||
backend->max_sessions = max_sessions;
|
||||
retro_atomic_int_init(&backend->epoch, 0);
|
||||
retro_atomic_int_init(&backend->major_id, 0);
|
||||
retro_atomic_int_init(&backend->minor_id, 0);
|
||||
retro_atomic_int_init(&backend->active, 0);
|
||||
backend->offer.user = backend;
|
||||
backend->offer.open = vaapi_vp9_backend_open;
|
||||
backend->offer.close = vaapi_vp9_backend_close;
|
||||
backend->offer.open_color = vaapi_vp9_backend_open_color;
|
||||
backend->offer.open_config = vaapi_vp9_backend_open_config;
|
||||
return true;
|
||||
#else
|
||||
(void)backend;
|
||||
(void)max_sessions;
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
bool vaapi_vp9_backend_publish(vaapi_vp9_backend_t *backend, void *owner,
|
||||
unsigned major_id, unsigned minor_id)
|
||||
{
|
||||
int epoch;
|
||||
if (!backend || !backend->offer.open || !owner || major_id > INT_MAX
|
||||
|| minor_id > INT_MAX)
|
||||
return false;
|
||||
epoch = retro_atomic_load_acquire_int(&backend->epoch);
|
||||
if (epoch < 0)
|
||||
return false;
|
||||
if (epoch >= INT_MAX - 2)
|
||||
{
|
||||
retro_atomic_store_release_int(&backend->epoch, -1);
|
||||
backend->owner = NULL;
|
||||
rvp9_unset_hw_backend(&backend->offer);
|
||||
return false;
|
||||
}
|
||||
if (epoch & 1)
|
||||
epoch++;
|
||||
retro_atomic_store_release_int(&backend->epoch, epoch);
|
||||
retro_atomic_store_release_int(&backend->major_id, (int)major_id);
|
||||
retro_atomic_store_release_int(&backend->minor_id, (int)minor_id);
|
||||
backend->owner = owner;
|
||||
retro_atomic_store_release_int(&backend->epoch, epoch + 1);
|
||||
rvp9_set_hw_backend(&backend->offer);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool vaapi_vp9_backend_withdraw(vaapi_vp9_backend_t *backend, void *owner)
|
||||
{
|
||||
int epoch;
|
||||
if (!backend || !backend->offer.open || !owner || backend->owner != owner)
|
||||
return false;
|
||||
epoch = retro_atomic_load_acquire_int(&backend->epoch);
|
||||
retro_atomic_store_release_int(
|
||||
&backend->epoch, epoch == INT_MAX ? -1 : epoch + 1);
|
||||
backend->owner = NULL;
|
||||
rvp9_unset_hw_backend(&backend->offer);
|
||||
return true;
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
/* Copyright (C) 2026 The RetroArch team.
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later */
|
||||
#ifndef RARCH_VAAPI_VP9_BACKEND_H
|
||||
#define RARCH_VAAPI_VP9_BACKEND_H
|
||||
#include "vaapi_vp9_owner.h"
|
||||
#include <retro_atomic.h>
|
||||
RETRO_BEGIN_DECLS
|
||||
|
||||
typedef struct vaapi_vp9_backend
|
||||
{
|
||||
rvp9_hw_backend_t offer;
|
||||
void *owner;
|
||||
unsigned max_sessions;
|
||||
retro_atomic_int_t epoch, major_id, minor_id, active;
|
||||
} vaapi_vp9_backend_t;
|
||||
|
||||
/* Initialize once. Storage and immutable configuration outlive setup readers,
|
||||
* sessions and deferred retirement. Requires threads and lock-free atomics. */
|
||||
bool vaapi_vp9_backend_init(
|
||||
vaapi_vp9_backend_t *backend, unsigned max_sessions);
|
||||
/* One serialized renderer lifecycle owner. Publish only after native PRIME
|
||||
* import prerequisites and the actual render-device identity are verified. */
|
||||
bool vaapi_vp9_backend_publish(vaapi_vp9_backend_t *backend, void *owner,
|
||||
unsigned major_id, unsigned minor_id);
|
||||
bool vaapi_vp9_backend_withdraw(vaapi_vp9_backend_t *backend, void *owner);
|
||||
|
||||
RETRO_END_DECLS
|
||||
#endif
|
||||
@@ -0,0 +1,228 @@
|
||||
/* Copyright (C) 2026 The RetroArch team.
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later */
|
||||
#include "vaapi_vp9_decoder.h"
|
||||
#include <formats/image_yuv_blit.h>
|
||||
#include <formats/rvp9_color.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
struct vaapi_vp9_decoder
|
||||
{
|
||||
vaapi_vp9_export_t pending;
|
||||
vaapi_vp9_export_ops_t exports;
|
||||
vaapi_vp9_pool_t *pool;
|
||||
vaapi_frame_pool_t *frames;
|
||||
rvp9_hdr params;
|
||||
rvp9_hw_picture_t picture;
|
||||
int surfaces[32];
|
||||
video_color_metadata_t color;
|
||||
unsigned width, height, yuv;
|
||||
bool closed, failed;
|
||||
};
|
||||
|
||||
int vaapi_vp9_decoder_poll(vaapi_vp9_decoder_t *decoder)
|
||||
{
|
||||
unsigned i;
|
||||
int result = 1;
|
||||
if (!decoder)
|
||||
return -1;
|
||||
vaapi_frame_collect(decoder->frames);
|
||||
if (decoder->pending.active && !decoder->pending.ready
|
||||
&& !vaapi_vp9_export_close(&decoder->pending))
|
||||
result = -1;
|
||||
if (decoder->pool->active)
|
||||
for (i = 0; i < decoder->pool->session->slots; i++)
|
||||
if (decoder->pool->transactions[i].active)
|
||||
{
|
||||
int status = vaapi_vp9_pool_poll(decoder->pool, i);
|
||||
if (status < 0)
|
||||
{
|
||||
decoder->failed = true;
|
||||
result = -1;
|
||||
}
|
||||
else if (status > 0 && result > 0)
|
||||
result = 0;
|
||||
}
|
||||
if (decoder->failed)
|
||||
return -1;
|
||||
return result;
|
||||
}
|
||||
|
||||
static int vaapi_vp9_decoder_decode(void *opaque, const uint8_t *packet,
|
||||
size_t size, const rvp9_hw_picture_t *picture)
|
||||
{
|
||||
vaapi_vp9_decoder_t *decoder = (vaapi_vp9_decoder_t *)opaque;
|
||||
const rvp9_hdr *header;
|
||||
uint32_t occupied;
|
||||
unsigned i, slots;
|
||||
int result, destination;
|
||||
if (!decoder || decoder->closed || !picture
|
||||
|| vaapi_vp9_decoder_poll(decoder) < 0)
|
||||
return -1;
|
||||
header = &picture->header;
|
||||
if (header->w != decoder->params.w || header->h != decoder->params.h
|
||||
|| header->profile != decoder->params.profile
|
||||
|| header->bit_depth != decoder->params.bit_depth
|
||||
|| header->subsampling_x != 1 || header->subsampling_y != 1
|
||||
|| header->color_space != decoder->params.color_space
|
||||
|| header->color_range != decoder->params.color_range)
|
||||
return -1;
|
||||
slots = decoder->pool->session->slots;
|
||||
if (picture->slot < 0 || (unsigned)picture->slot >= slots)
|
||||
return -1;
|
||||
decoder->picture = *picture;
|
||||
/* Logical reuse must preserve retained native surfaces. */
|
||||
occupied = vaapi_vp9_pool_held(decoder->pool);
|
||||
for (i = 0; i < RVP9_REF_FRAMES + RVP9_REFS_PER_FRAME + 1; i++)
|
||||
{
|
||||
int logical, physical;
|
||||
int *mapped;
|
||||
if (i < RVP9_REF_FRAMES)
|
||||
mapped = &decoder->picture.reference_slots[i];
|
||||
else if (i < RVP9_REF_FRAMES + RVP9_REFS_PER_FRAME)
|
||||
mapped = &decoder->picture.named_slots[i - RVP9_REF_FRAMES];
|
||||
else
|
||||
mapped = &decoder->picture.previous_slot;
|
||||
logical = *mapped;
|
||||
if (logical == -1)
|
||||
continue;
|
||||
if (logical < 0 || (unsigned)logical >= slots || logical == picture->slot)
|
||||
return -1;
|
||||
physical = decoder->surfaces[logical];
|
||||
if (physical < 0)
|
||||
return -1;
|
||||
*mapped = physical;
|
||||
occupied |= UINT32_C(1) << physical;
|
||||
}
|
||||
destination = decoder->surfaces[picture->slot];
|
||||
if (destination < 0 || (occupied & (UINT32_C(1) << destination)))
|
||||
{
|
||||
for (i = 0; i < slots; i++)
|
||||
if (!(occupied & (UINT32_C(1) << i)))
|
||||
break;
|
||||
if (i == slots)
|
||||
return 1;
|
||||
destination = (int)i;
|
||||
}
|
||||
decoder->picture.slot = destination;
|
||||
result = vaapi_vp9_pool_decode(
|
||||
decoder->pool, packet, size, &decoder->picture);
|
||||
if (!result)
|
||||
{
|
||||
for (i = 0; i < slots; i++)
|
||||
if (decoder->surfaces[i] == destination)
|
||||
decoder->surfaces[i] = -1;
|
||||
decoder->surfaces[picture->slot] = destination;
|
||||
}
|
||||
if (result < 0)
|
||||
decoder->failed = true;
|
||||
return result;
|
||||
}
|
||||
|
||||
static int vaapi_vp9_decoder_output(
|
||||
void *opaque, unsigned slot, video_hw_frame_t *out)
|
||||
{
|
||||
vaapi_vp9_decoder_t *decoder = (vaapi_vp9_decoder_t *)opaque;
|
||||
if (!decoder || decoder->closed || !out || !decoder->pool->active
|
||||
|| slot >= decoder->pool->session->slots
|
||||
|| vaapi_vp9_decoder_poll(decoder) < 0)
|
||||
return -1;
|
||||
if (decoder->surfaces[slot] < 0)
|
||||
return -1;
|
||||
slot = (unsigned)decoder->surfaces[slot];
|
||||
if (decoder->pending.active && decoder->pending.slot != slot)
|
||||
return 1;
|
||||
if (!decoder->pending.active)
|
||||
{
|
||||
if (!(decoder->pool->ready & (UINT32_C(1) << slot)))
|
||||
return decoder->pool->transactions[slot].active ? 1 : -1;
|
||||
if (decoder->pool->exports & (UINT32_C(1) << slot))
|
||||
return 1;
|
||||
if (!vaapi_vp9_export_open(
|
||||
&decoder->pending, decoder->pool, slot, &decoder->exports))
|
||||
{
|
||||
decoder->failed = true;
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
return vaapi_frame_export(decoder->frames, &decoder->pending, 0, 0,
|
||||
decoder->width, decoder->height, decoder->yuv, &decoder->color, out);
|
||||
}
|
||||
|
||||
bool vaapi_vp9_decoder_open(vaapi_vp9_pool_t *pool, const rvp9_hdr *params,
|
||||
const video_color_metadata_t *color,
|
||||
const vaapi_vp9_export_ops_t *exports, rvp9_hw_ops_t *ops)
|
||||
{
|
||||
vaapi_vp9_decoder_t *decoder;
|
||||
const vaapi_vp9_session_t *session;
|
||||
video_color_metadata_t resolved;
|
||||
unsigned i;
|
||||
if (!pool || !pool->active || pool->broken || !params || !ops || ops->decoder
|
||||
|| !exports || !exports->surface || !exports->cloexec
|
||||
|| !exports->close || !exports->identity
|
||||
|| !rvp9_color_resolve(params, color, &resolved))
|
||||
return false;
|
||||
session = pool->session;
|
||||
if (!session || !session->ready || session->closing || session->slots < 10
|
||||
|| session->slots > 32 || params->w <= 0 || params->h <= 0
|
||||
|| (unsigned)params->w != session->width
|
||||
|| (unsigned)params->h != session->height
|
||||
|| ((params->w | params->h) & 1)
|
||||
|| (unsigned)params->bit_depth != session->depth
|
||||
|| params->profile != (params->bit_depth == 10 ? 2 : 0)
|
||||
|| params->subsampling_x != 1 || params->subsampling_y != 1
|
||||
|| params->frame_type || params->show_existing_frame)
|
||||
return false;
|
||||
decoder = (vaapi_vp9_decoder_t *)calloc(1, sizeof(*decoder));
|
||||
if (!decoder)
|
||||
return false;
|
||||
decoder->frames = vaapi_frame_pool_new(session->slots);
|
||||
if (!decoder->frames)
|
||||
{
|
||||
free(decoder);
|
||||
return false;
|
||||
}
|
||||
for (i = 0; i < 32; i++)
|
||||
decoder->surfaces[i] = (int)i;
|
||||
decoder->pool = pool;
|
||||
decoder->params = *params;
|
||||
decoder->color = resolved;
|
||||
decoder->yuv = (resolved.range == 2 ? IMAGE_YUV_FLAG_FULL_RANGE : 0)
|
||||
| (resolved.matrix == 1 ? IMAGE_YUV_FLAG_BT709 : 0);
|
||||
decoder->exports = *exports;
|
||||
decoder->width = (unsigned)params->w;
|
||||
decoder->height = (unsigned)params->h;
|
||||
memset(ops, 0, sizeof(*ops));
|
||||
ops->decoder = decoder;
|
||||
ops->decode = vaapi_vp9_decoder_decode;
|
||||
ops->output = vaapi_vp9_decoder_output;
|
||||
return true;
|
||||
}
|
||||
|
||||
vaapi_vp9_decoder_t *vaapi_vp9_decoder_detach(rvp9_hw_ops_t *ops)
|
||||
{
|
||||
vaapi_vp9_decoder_t *decoder;
|
||||
if (!ops || ops->decode != vaapi_vp9_decoder_decode
|
||||
|| ops->output != vaapi_vp9_decoder_output || !ops->decoder)
|
||||
return NULL;
|
||||
decoder = (vaapi_vp9_decoder_t *)ops->decoder;
|
||||
decoder->closed = true;
|
||||
vaapi_frame_pool_close(decoder->frames);
|
||||
memset(ops, 0, sizeof(*ops));
|
||||
return decoder;
|
||||
}
|
||||
|
||||
bool vaapi_vp9_decoder_retire(vaapi_vp9_decoder_t *decoder)
|
||||
{
|
||||
if (!decoder)
|
||||
return true;
|
||||
if (!decoder->closed)
|
||||
return false;
|
||||
vaapi_vp9_decoder_poll(decoder);
|
||||
if (decoder->pending.active && !vaapi_vp9_export_close(&decoder->pending))
|
||||
return false;
|
||||
if (!vaapi_frame_pool_free(decoder->frames))
|
||||
return false;
|
||||
free(decoder);
|
||||
return true;
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
/* Copyright (C) 2026 The RetroArch team.
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later */
|
||||
#ifndef RARCH_VAAPI_VP9_DECODER_H
|
||||
#define RARCH_VAAPI_VP9_DECODER_H
|
||||
#include "vaapi_frame.h"
|
||||
#include "vaapi_vp9_export.h"
|
||||
RETRO_BEGIN_DECLS
|
||||
|
||||
typedef struct vaapi_vp9_decoder vaapi_vp9_decoder_t;
|
||||
/* Cold decoder owner; borrowed pool/session/runtime outlive detach/retirement.
|
||||
* Initially empty ops. Copies metadata and creates its opaque carriers.
|
||||
* Failure leaves ops unchanged; no native context ownership is transferred. */
|
||||
bool vaapi_vp9_decoder_open(vaapi_vp9_pool_t *pool, const rvp9_hdr *params,
|
||||
const video_color_metadata_t *color,
|
||||
const vaapi_vp9_export_ops_t *exports, rvp9_hw_ops_t *ops);
|
||||
/* Decoder owner, including paused/closing ticks. One bounded pass over native
|
||||
* completions and renderer returns. 1 decode transactions idle, 0 pending, -1
|
||||
* error. A failed transaction does not prevent collection of other returned
|
||||
* leases. */
|
||||
int vaapi_vp9_decoder_poll(vaapi_vp9_decoder_t *decoder);
|
||||
/* Decoder owner: stop admission, clear only this adapter's ops, transfer the
|
||||
* adapter to the owner's existing deferred-retirement executor. No wait. */
|
||||
vaapi_vp9_decoder_t *vaapi_vp9_decoder_detach(rvp9_hw_ops_t *ops);
|
||||
/* Detached decoder owner. True consumes adapter after all frame leases return;
|
||||
* false retains it for retry. Pool/native transactions remain caller-owned. */
|
||||
bool vaapi_vp9_decoder_retire(vaapi_vp9_decoder_t *decoder);
|
||||
RETRO_END_DECLS
|
||||
#endif
|
||||
@@ -0,0 +1,28 @@
|
||||
/* Copyright (C) 2026 The RetroArch team. SPDX-License-Identifier:
|
||||
* GPL-3.0-or-later */
|
||||
#include "vaapi_vp9_export.h"
|
||||
static bool vaapi_vp9_export_acquire(void *owner, unsigned slot, uint64_t *ticket)
|
||||
{
|
||||
return vaapi_vp9_pool_acquire((vaapi_vp9_pool_t *)owner, slot, ticket);
|
||||
}
|
||||
static bool vaapi_vp9_export_current(void *owner, unsigned slot, uint64_t ticket)
|
||||
{
|
||||
const vaapi_vp9_pool_t *pool = (const vaapi_vp9_pool_t *)owner;
|
||||
return pool && pool->active && !pool->broken && slot < 32 && ticket
|
||||
&& (pool->exports & (UINT32_C(1) << slot))
|
||||
&& pool->generation[slot] == ticket;
|
||||
}
|
||||
static bool vaapi_vp9_export_release(void *owner, unsigned slot, uint64_t ticket)
|
||||
{
|
||||
return vaapi_vp9_pool_release((vaapi_vp9_pool_t *)owner, slot, ticket);
|
||||
}
|
||||
bool vaapi_vp9_export_open(vaapi_vp9_export_t *source, vaapi_vp9_pool_t *pool,
|
||||
unsigned slot, const vaapi_vp9_export_ops_t *ops)
|
||||
{
|
||||
static const vaapi_export_owner_ops_t ownership = {
|
||||
vaapi_vp9_export_acquire, vaapi_vp9_export_current,
|
||||
vaapi_vp9_export_release};
|
||||
if (!pool)
|
||||
return false;
|
||||
return vaapi_export_open(source, pool, pool->session, &ownership, slot, ops);
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
/* Copyright (C) 2026 The RetroArch team. SPDX-License-Identifier:
|
||||
* GPL-3.0-or-later */
|
||||
#ifndef RARCH_VAAPI_VP9_EXPORT_H
|
||||
#define RARCH_VAAPI_VP9_EXPORT_H
|
||||
#include "vaapi_vp9_pool.h"
|
||||
#include "vaapi_hevc_export.h"
|
||||
typedef vaapi_hevc_export_t vaapi_vp9_export_t;
|
||||
typedef vaapi_hevc_export_ops_t vaapi_vp9_export_ops_t;
|
||||
RETRO_BEGIN_DECLS
|
||||
/* Owner only. Completed picture, generation checked; source survives close.
|
||||
* Descriptor validation is not renderer import admission. False needs close. */
|
||||
bool vaapi_vp9_export_open(vaapi_vp9_export_t *source, vaapi_vp9_pool_t *pool,
|
||||
unsigned slot, const vaapi_vp9_export_ops_t *ops);
|
||||
static INLINE int vaapi_vp9_export_take(vaapi_vp9_export_t *source, unsigned object)
|
||||
{
|
||||
return vaapi_hevc_export_take(source, object);
|
||||
}
|
||||
static INLINE bool vaapi_vp9_export_close(vaapi_vp9_export_t *source)
|
||||
{
|
||||
return vaapi_hevc_export_close(source);
|
||||
}
|
||||
RETRO_END_DECLS
|
||||
#endif
|
||||
@@ -0,0 +1,154 @@
|
||||
/* Copyright (C) 2026 The RetroArch team.
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later */
|
||||
#include "vaapi_vp9_owner.h"
|
||||
#include "vaapi_vp9_runtime.h"
|
||||
#include <formats/rvp9_color.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
struct vaapi_vp9_owner
|
||||
{
|
||||
vaapi_vp9_runtime_t runtime;
|
||||
vaapi_vp9_pool_t pool;
|
||||
rvp9_hw_ops_t decoder;
|
||||
vaapi_vp9_decoder_t *retired;
|
||||
VADisplay terminated_display;
|
||||
bool published, closing, terminating;
|
||||
};
|
||||
|
||||
static int vaapi_vp9_owner_decode(void *opaque, const uint8_t *packet,
|
||||
size_t size, const rvp9_hw_picture_t *picture)
|
||||
{
|
||||
vaapi_vp9_owner_t *owner = (vaapi_vp9_owner_t *)opaque;
|
||||
if (!owner || owner->closing || !owner->decoder.decode)
|
||||
return -1;
|
||||
return owner->decoder.decode(owner->decoder.decoder, packet, size, picture);
|
||||
}
|
||||
|
||||
static int vaapi_vp9_owner_output(
|
||||
void *opaque, unsigned slot, video_hw_frame_t *frame)
|
||||
{
|
||||
vaapi_vp9_owner_t *owner = (vaapi_vp9_owner_t *)opaque;
|
||||
if (!owner || owner->closing || !owner->decoder.output)
|
||||
return -1;
|
||||
return owner->decoder.output(owner->decoder.decoder, slot, frame);
|
||||
}
|
||||
|
||||
bool vaapi_vp9_owner_open_with_io(vaapi_vp9_owner_t **out, const char *path,
|
||||
unsigned major_id, unsigned minor_id, const rvp9_hdr *header,
|
||||
const video_color_metadata_t *color, unsigned slots,
|
||||
const vaapi_vp9_runtime_io_t *io, const vaapi_vp9_export_ops_t *exports,
|
||||
rvp9_hw_ops_t *ops)
|
||||
{
|
||||
vaapi_vp9_owner_t *owner;
|
||||
vaapi_vp9_export_ops_t native_exports;
|
||||
video_color_metadata_t resolved;
|
||||
bool opened;
|
||||
if (!out || *out || !ops || ops->decoder || !path || !*path || !header
|
||||
|| slots < 10 || slots > 32 || header->w <= 0 || header->h <= 0
|
||||
|| header->w > 65535 || header->h > 65535
|
||||
|| ((header->w | header->h) & 1) || header->frame_type
|
||||
|| header->show_existing_frame
|
||||
|| !rvp9_color_resolve(header, color, &resolved))
|
||||
return false;
|
||||
owner = (vaapi_vp9_owner_t *)calloc(1, sizeof(*owner));
|
||||
if (!owner)
|
||||
return false;
|
||||
*out = owner;
|
||||
if (io)
|
||||
opened = vaapi_vp9_runtime_open_with_io(&owner->runtime, path, major_id,
|
||||
minor_id, (unsigned)header->w, (unsigned)header->h,
|
||||
(unsigned)header->bit_depth, slots, io);
|
||||
else
|
||||
opened = vaapi_vp9_runtime_open(&owner->runtime, path, major_id, minor_id,
|
||||
(unsigned)header->w, (unsigned)header->h,
|
||||
(unsigned)header->bit_depth, slots);
|
||||
if (!opened
|
||||
|| !vaapi_vp9_pool_init(
|
||||
&owner->pool, &owner->runtime.session, &owner->runtime.submit))
|
||||
return false;
|
||||
if (exports)
|
||||
native_exports = *exports;
|
||||
else if (!vaapi_hevc_export_bind(&native_exports, &owner->runtime))
|
||||
return false;
|
||||
if (!vaapi_vp9_decoder_open(
|
||||
&owner->pool, header, &resolved, &native_exports, &owner->decoder))
|
||||
return false;
|
||||
memset(ops, 0, sizeof(*ops));
|
||||
ops->decoder = owner;
|
||||
ops->decode = vaapi_vp9_owner_decode;
|
||||
ops->output = vaapi_vp9_owner_output;
|
||||
owner->published = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool vaapi_vp9_owner_open(vaapi_vp9_owner_t **out, const char *path,
|
||||
unsigned major_id, unsigned minor_id, const rvp9_hdr *header,
|
||||
const video_color_metadata_t *color, unsigned slots, rvp9_hw_ops_t *ops)
|
||||
{
|
||||
return vaapi_vp9_owner_open_with_io(
|
||||
out, path, major_id, minor_id, header, color, slots, NULL, NULL, ops);
|
||||
}
|
||||
|
||||
vaapi_vp9_owner_t *vaapi_vp9_owner_detach(rvp9_hw_ops_t *ops)
|
||||
{
|
||||
vaapi_vp9_owner_t *owner;
|
||||
if (!ops || ops->decode != vaapi_vp9_owner_decode
|
||||
|| ops->output != vaapi_vp9_owner_output || !ops->decoder)
|
||||
return NULL;
|
||||
owner = (vaapi_vp9_owner_t *)ops->decoder;
|
||||
owner->closing = true;
|
||||
owner->retired = vaapi_vp9_decoder_detach(&owner->decoder);
|
||||
memset(ops, 0, sizeof(*ops));
|
||||
return owner;
|
||||
}
|
||||
|
||||
bool vaapi_vp9_owner_retire(vaapi_vp9_owner_t *owner)
|
||||
{
|
||||
if (!owner)
|
||||
return true;
|
||||
if (owner->published && !owner->closing)
|
||||
return false;
|
||||
owner->closing = true;
|
||||
if (!owner->terminating)
|
||||
{
|
||||
unsigned i;
|
||||
bool polled = false;
|
||||
if (owner->decoder.decoder)
|
||||
owner->retired = vaapi_vp9_decoder_detach(&owner->decoder);
|
||||
if (owner->retired)
|
||||
{
|
||||
if (!vaapi_vp9_decoder_retire(owner->retired))
|
||||
return false;
|
||||
owner->retired = NULL;
|
||||
polled = true;
|
||||
}
|
||||
if (owner->pool.exports)
|
||||
return false;
|
||||
if (owner->pool.active && !owner->pool.broken)
|
||||
{
|
||||
bool pending = false;
|
||||
for (i = 0; i < owner->pool.session->slots; i++)
|
||||
if (owner->pool.transactions[i].active)
|
||||
{
|
||||
int status = polled ? 1 : vaapi_vp9_pool_poll(&owner->pool, i);
|
||||
if (status > 0)
|
||||
pending = true;
|
||||
else if (status < 0)
|
||||
owner->pool.broken = true;
|
||||
}
|
||||
if (!owner->pool.broken
|
||||
&& (pending || !vaapi_vp9_pool_close(&owner->pool)))
|
||||
return false;
|
||||
}
|
||||
owner->terminated_display = owner->runtime.display;
|
||||
owner->terminating = true;
|
||||
}
|
||||
if (!vaapi_vp9_runtime_close(&owner->runtime))
|
||||
return false;
|
||||
if (owner->pool.active
|
||||
&& !vaapi_vp9_pool_terminated(&owner->pool, owner->terminated_display))
|
||||
return false;
|
||||
free(owner);
|
||||
return true;
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
/* Copyright (C) 2026 The RetroArch team.
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later */
|
||||
#ifndef RARCH_VAAPI_VP9_OWNER_H
|
||||
#define RARCH_VAAPI_VP9_OWNER_H
|
||||
#include "vaapi_vp9_decoder.h"
|
||||
#include "vaapi_vp9_runtime.h"
|
||||
RETRO_BEGIN_DECLS
|
||||
typedef struct vaapi_vp9_owner vaapi_vp9_owner_t;
|
||||
/* Cold owner; empty out/ops. False can publish a partial owned context.
|
||||
* Transfer that context to retirement; borrowed metadata expires on return. */
|
||||
bool vaapi_vp9_owner_open(vaapi_vp9_owner_t **out, const char *path,
|
||||
unsigned major_id, unsigned minor_id, const rvp9_hdr *header,
|
||||
const video_color_metadata_t *color, unsigned slots, rvp9_hw_ops_t *ops);
|
||||
bool vaapi_vp9_owner_open_with_io(vaapi_vp9_owner_t **out, const char *path,
|
||||
unsigned major_id, unsigned minor_id, const rvp9_hdr *header,
|
||||
const video_color_metadata_t *color, unsigned slots,
|
||||
const vaapi_vp9_runtime_io_t *io, const vaapi_vp9_export_ops_t *exports,
|
||||
rvp9_hw_ops_t *ops);
|
||||
/* Decoder owner: stop admission and clear only this owner's callbacks. */
|
||||
vaapi_vp9_owner_t *vaapi_vp9_owner_detach(rvp9_hw_ops_t *ops);
|
||||
/* Cold executor only. Bounded completion/return pass; never wait for GPU.
|
||||
* False retains all resources. No termination before exports return. */
|
||||
bool vaapi_vp9_owner_retire(vaapi_vp9_owner_t *owner);
|
||||
RETRO_END_DECLS
|
||||
#endif
|
||||
@@ -0,0 +1,154 @@
|
||||
/* Copyright (C) 2026 The RetroArch team. SPDX-License-Identifier:
|
||||
* GPL-3.0-or-later */
|
||||
#ifndef RARCH_VAAPI_VP9_PICTURE_H
|
||||
#define RARCH_VAAPI_VP9_PICTURE_H
|
||||
#include <va/va.h>
|
||||
#include <formats/rvp9_hw.h>
|
||||
|
||||
typedef struct vaapi_vp9_picture
|
||||
{
|
||||
VADecPictureParameterBufferVP9 picture;
|
||||
VASliceParameterBufferVP9 slice;
|
||||
} vaapi_vp9_picture_t;
|
||||
|
||||
/* Decoder owner, effective inherited syntax and live surface IDs. No VA calls.
|
||||
* Whole coded frame, without a superframe index. Refusal leaves out unchanged.
|
||||
* show_existing_frame is served by the session's retained reference surface. */
|
||||
static INLINE bool vaapi_vp9_picture(vaapi_vp9_picture_t *out,
|
||||
const rvp9_hw_picture_t *source, size_t size, const VASurfaceID *surfaces,
|
||||
unsigned slots)
|
||||
{
|
||||
vaapi_vp9_picture_t value;
|
||||
const rvp9_hdr *h;
|
||||
unsigned i;
|
||||
if (!out || !source || !surfaces || slots < 10 || slots > 32)
|
||||
return false;
|
||||
h = &source->header;
|
||||
if (!((h->profile == 0 && h->bit_depth == 8)
|
||||
|| (h->profile == 2 && h->bit_depth == 10))
|
||||
|| h->show_existing_frame || h->subsampling_x != 1
|
||||
|| h->subsampling_y != 1 || h->w < 1 || h->w > 65535 || h->h < 1
|
||||
|| h->h > 65535 || source->slot < 0 || (unsigned)source->slot >= slots
|
||||
|| surfaces[source->slot] == VA_INVALID_SURFACE
|
||||
|| (unsigned)h->frame_type > 1 || (unsigned)h->show_frame > 1
|
||||
|| (unsigned)h->error_resilient > 1 || (unsigned)h->intra_only > 1
|
||||
|| (unsigned)h->allow_high_precision_mv > 1
|
||||
|| (unsigned)h->interp_filter > 4
|
||||
|| (unsigned)h->frame_parallel_decoding > 1
|
||||
|| (unsigned)h->reset_frame_context > 3
|
||||
|| (unsigned)h->refresh_frame_context > 1
|
||||
|| (unsigned)h->frame_context_idx > 3
|
||||
|| (unsigned)h->seg_temporal_update > 1
|
||||
|| (unsigned)h->seg_update_map > 1 || (unsigned)h->lf_sharpness > 7
|
||||
|| (unsigned)h->log2_tile_cols > 6 || (unsigned)h->log2_tile_rows > 2
|
||||
|| !h->uncomp_size || h->uncomp_size > 255
|
||||
|| h->first_partition_size < 1 || h->first_partition_size > 65535
|
||||
|| size > UINT32_MAX || h->uncomp_size >= size
|
||||
|| (size_t)h->first_partition_size >= size - h->uncomp_size)
|
||||
return false;
|
||||
memset(&value, 0, sizeof(value));
|
||||
for (i = 0; i < RVP9_REF_FRAMES; i++)
|
||||
{
|
||||
int slot = source->reference_slots[i];
|
||||
if (slot < -1 || slot == source->slot
|
||||
|| (slot >= 0
|
||||
&& ((unsigned)slot >= slots
|
||||
|| surfaces[slot] == VA_INVALID_SURFACE
|
||||
|| surfaces[slot] == surfaces[source->slot])))
|
||||
return false;
|
||||
value.picture.reference_frames[i]
|
||||
= slot < 0 ? VA_INVALID_SURFACE : surfaces[slot];
|
||||
}
|
||||
for (i = 0; i < RVP9_REFS_PER_FRAME; i++)
|
||||
if (h->frame_type && !h->intra_only)
|
||||
{
|
||||
if ((unsigned)h->ref_idx[i] >= RVP9_REF_FRAMES
|
||||
|| source->named_slots[i] < 0
|
||||
|| source->named_slots[i]
|
||||
!= source->reference_slots[h->ref_idx[i]])
|
||||
return false;
|
||||
}
|
||||
else if (source->named_slots[i] != -1)
|
||||
return false;
|
||||
for (i = 0; i < 4; i++)
|
||||
if ((unsigned)h->ref_sign_bias[i] > 1)
|
||||
return false;
|
||||
value.picture.frame_width = (uint16_t)h->w;
|
||||
value.picture.frame_height = (uint16_t)h->h;
|
||||
value.picture.pic_fields.bits.subsampling_x = 1;
|
||||
value.picture.pic_fields.bits.subsampling_y = 1;
|
||||
value.picture.pic_fields.bits.frame_type = h->frame_type;
|
||||
value.picture.pic_fields.bits.show_frame = h->show_frame;
|
||||
value.picture.pic_fields.bits.error_resilient_mode = h->error_resilient;
|
||||
value.picture.pic_fields.bits.intra_only = h->intra_only;
|
||||
value.picture.pic_fields.bits.allow_high_precision_mv
|
||||
= h->allow_high_precision_mv;
|
||||
value.picture.pic_fields.bits.mcomp_filter_type = h->interp_filter;
|
||||
value.picture.pic_fields.bits.frame_parallel_decoding_mode
|
||||
= h->frame_parallel_decoding;
|
||||
value.picture.pic_fields.bits.reset_frame_context = h->reset_frame_context;
|
||||
value.picture.pic_fields.bits.refresh_frame_context
|
||||
= h->refresh_frame_context;
|
||||
value.picture.pic_fields.bits.frame_context_idx = h->frame_context_idx;
|
||||
value.picture.pic_fields.bits.segmentation_enabled = h->seg_enabled;
|
||||
value.picture.pic_fields.bits.segmentation_temporal_update
|
||||
= h->seg_temporal_update;
|
||||
value.picture.pic_fields.bits.segmentation_update_map = h->seg_update_map;
|
||||
if (h->frame_type && !h->intra_only)
|
||||
{
|
||||
value.picture.pic_fields.bits.last_ref_frame = h->ref_idx[0];
|
||||
value.picture.pic_fields.bits.golden_ref_frame = h->ref_idx[1];
|
||||
value.picture.pic_fields.bits.alt_ref_frame = h->ref_idx[2];
|
||||
value.picture.pic_fields.bits.last_ref_frame_sign_bias
|
||||
= h->ref_sign_bias[1];
|
||||
value.picture.pic_fields.bits.golden_ref_frame_sign_bias
|
||||
= h->ref_sign_bias[2];
|
||||
value.picture.pic_fields.bits.alt_ref_frame_sign_bias
|
||||
= h->ref_sign_bias[3];
|
||||
}
|
||||
value.picture.pic_fields.bits.lossless_flag = !h->base_qindex
|
||||
&& !h->y_dc_delta_q && !h->uv_dc_delta_q && !h->uv_ac_delta_q;
|
||||
value.picture.filter_level = (uint8_t)h->lf_level;
|
||||
value.picture.sharpness_level = (uint8_t)h->lf_sharpness;
|
||||
value.picture.log2_tile_rows = (uint8_t)h->log2_tile_rows;
|
||||
value.picture.log2_tile_columns = (uint8_t)h->log2_tile_cols;
|
||||
value.picture.frame_header_length_in_bytes = (uint8_t)h->uncomp_size;
|
||||
value.picture.first_partition_size = (uint16_t)h->first_partition_size;
|
||||
value.picture.profile = (uint8_t)h->profile;
|
||||
value.picture.bit_depth = (uint8_t)h->bit_depth;
|
||||
memcpy(value.picture.mb_segment_tree_probs, h->seg_tree_probs,
|
||||
sizeof(value.picture.mb_segment_tree_probs));
|
||||
if (h->seg_temporal_update)
|
||||
memcpy(value.picture.segment_pred_probs, h->seg_pred_probs,
|
||||
sizeof(value.picture.segment_pred_probs));
|
||||
else
|
||||
memset(value.picture.segment_pred_probs, 255,
|
||||
sizeof(value.picture.segment_pred_probs));
|
||||
value.slice.slice_data_size = (uint32_t)size;
|
||||
value.slice.slice_data_flag = VA_SLICE_DATA_FLAG_ALL;
|
||||
for (i = 0; i < (h->seg_enabled ? RVP9_MAX_SEGMENTS : 1); i++)
|
||||
{
|
||||
rvp9_hw_segment_t segment;
|
||||
VASegmentParameterVP9 *native = &value.slice.seg_param[i];
|
||||
if (!rvp9_hw_segment(h, i, &segment))
|
||||
return false;
|
||||
if (h->seg_enabled)
|
||||
{
|
||||
native->segment_flags.fields.segment_reference_enabled
|
||||
= h->seg_feature_enabled[i][2];
|
||||
native->segment_flags.fields.segment_reference
|
||||
= h->seg_feature_data[i][2];
|
||||
native->segment_flags.fields.segment_reference_skipped
|
||||
= h->seg_feature_enabled[i][3];
|
||||
}
|
||||
native->luma_dc_quant_scale = segment.y_dc;
|
||||
native->luma_ac_quant_scale = segment.y_ac;
|
||||
native->chroma_dc_quant_scale = segment.uv_dc;
|
||||
native->chroma_ac_quant_scale = segment.uv_ac;
|
||||
memcpy(
|
||||
native->filter_level, segment.filter, sizeof(native->filter_level));
|
||||
}
|
||||
*out = value;
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,188 @@
|
||||
/* Copyright (C) 2026 The RetroArch team. SPDX-License-Identifier:
|
||||
* GPL-3.0-or-later */
|
||||
#include "vaapi_vp9_pool.h"
|
||||
#include <limits.h>
|
||||
#include <string.h>
|
||||
bool vaapi_vp9_pool_init(vaapi_vp9_pool_t *p,
|
||||
const vaapi_vp9_session_t *session, const vaapi_vp9_submit_ops_t *ops)
|
||||
{
|
||||
vaapi_vp9_submit_ops_t native;
|
||||
uint64_t generation;
|
||||
if (!p || p->active || !session || !session->ready || session->closing
|
||||
|| !session->display || session->context == VA_INVALID_ID
|
||||
|| session->slots < 10 || session->slots > 32
|
||||
|| (session->depth != 8 && session->depth != 10)
|
||||
|| session->profile
|
||||
!= (session->depth == 10 ? VAProfileVP9Profile2
|
||||
: VAProfileVP9Profile0)
|
||||
|| !ops || !ops->create || !ops->destroy || !ops->begin || !ops->render
|
||||
|| !ops->end || !ops->sync || p->next_generation == (uint64_t)-1)
|
||||
return false;
|
||||
native = *ops;
|
||||
generation = p->next_generation;
|
||||
memset(p, 0, sizeof(*p));
|
||||
p->next_generation = generation;
|
||||
p->session = session;
|
||||
p->display = session->display;
|
||||
p->ops = native;
|
||||
p->previous_slot = -1;
|
||||
p->active = true;
|
||||
return true;
|
||||
}
|
||||
uint32_t vaapi_vp9_pool_held(const vaapi_vp9_pool_t *p)
|
||||
{
|
||||
uint32_t held;
|
||||
unsigned i;
|
||||
if (!p || !p->active)
|
||||
return 0;
|
||||
held = p->exports;
|
||||
for (i = 0; i < p->session->slots; i++)
|
||||
{
|
||||
if (p->transactions[i].active)
|
||||
held |= UINT32_C(1) << i;
|
||||
held |= p->reads[i];
|
||||
}
|
||||
return held;
|
||||
}
|
||||
int vaapi_vp9_pool_decode(vaapi_vp9_pool_t *p, const uint8_t *packet,
|
||||
size_t size, const rvp9_hw_picture_t *picture)
|
||||
{
|
||||
uint32_t bit, reads = 0;
|
||||
unsigned i, slot;
|
||||
vaapi_vp9_submit_t *transaction;
|
||||
bool accepted;
|
||||
if (!p || !p->active || p->broken || !picture || !packet || !size
|
||||
|| size > UINT_MAX || picture->slot < 0
|
||||
|| (unsigned)picture->slot >= p->session->slots
|
||||
|| picture->previous_slot != p->previous_slot
|
||||
|| (p->previous_slot < 0 && picture->header.frame_type)
|
||||
|| p->next_generation == (uint64_t)-1)
|
||||
return -1;
|
||||
slot = (unsigned)picture->slot;
|
||||
bit = UINT32_C(1) << slot;
|
||||
if (vaapi_vp9_pool_held(p) & bit)
|
||||
return 1;
|
||||
for (i = 0; i < RVP9_REF_FRAMES; i++)
|
||||
{
|
||||
int reference = picture->reference_slots[i];
|
||||
if (reference == -1)
|
||||
continue;
|
||||
if (reference < 0 || (unsigned)reference >= p->session->slots
|
||||
|| reference == picture->slot
|
||||
|| !(p->valid & (UINT32_C(1) << reference)))
|
||||
return -1;
|
||||
reads |= UINT32_C(1) << reference;
|
||||
}
|
||||
if (picture->previous_slot >= 0)
|
||||
{
|
||||
if ((unsigned)picture->previous_slot >= p->session->slots
|
||||
|| picture->previous_slot == picture->slot
|
||||
|| !(p->valid & (UINT32_C(1) << picture->previous_slot)))
|
||||
return -1;
|
||||
reads |= UINT32_C(1) << picture->previous_slot;
|
||||
}
|
||||
if (!vaapi_vp9_picture(
|
||||
&p->work, picture, size, p->session->surfaces, p->session->slots))
|
||||
return -1;
|
||||
transaction = &p->transactions[slot];
|
||||
accepted = vaapi_vp9_submit_start(transaction, p->session, &p->ops, slot,
|
||||
&p->work, packet, (unsigned)size);
|
||||
if (transaction->active)
|
||||
{
|
||||
p->reads[slot] = reads;
|
||||
p->valid &= ~bit;
|
||||
p->ready &= ~bit;
|
||||
}
|
||||
if (!accepted)
|
||||
{
|
||||
if (transaction->active)
|
||||
p->broken = true;
|
||||
return -1;
|
||||
}
|
||||
p->valid |= bit;
|
||||
p->generation[slot] = ++p->next_generation;
|
||||
p->previous_slot = (int)slot;
|
||||
return 0;
|
||||
}
|
||||
int vaapi_vp9_pool_poll(vaapi_vp9_pool_t *p, unsigned slot)
|
||||
{
|
||||
vaapi_vp9_submit_t *transaction;
|
||||
uint32_t bit;
|
||||
int status;
|
||||
if (!p || !p->active || slot >= p->session->slots)
|
||||
return -1;
|
||||
bit = UINT32_C(1) << slot;
|
||||
transaction = &p->transactions[slot];
|
||||
if (!transaction->active)
|
||||
return (p->ready & bit) ? 0 : -1;
|
||||
if (!transaction->submitted)
|
||||
{
|
||||
if (vaapi_vp9_submit_retire(transaction))
|
||||
p->reads[slot] = 0;
|
||||
return -1;
|
||||
}
|
||||
status = vaapi_vp9_submit_poll(transaction);
|
||||
if (status == 1)
|
||||
return 1;
|
||||
if (status < 0)
|
||||
{
|
||||
p->broken = true;
|
||||
p->valid &= ~bit;
|
||||
p->ready &= ~bit;
|
||||
}
|
||||
else
|
||||
p->ready |= bit;
|
||||
if (transaction->complete)
|
||||
{
|
||||
p->reads[slot] = 0;
|
||||
vaapi_vp9_submit_retire(transaction);
|
||||
}
|
||||
return status;
|
||||
}
|
||||
bool vaapi_vp9_pool_acquire(
|
||||
vaapi_vp9_pool_t *p, unsigned slot, uint64_t *ticket)
|
||||
{
|
||||
uint32_t bit;
|
||||
if (!p || !p->active || p->broken || !ticket || slot >= p->session->slots)
|
||||
return false;
|
||||
bit = UINT32_C(1) << slot;
|
||||
if (!(p->ready & p->valid & bit) || (p->exports & bit)
|
||||
|| !p->generation[slot])
|
||||
return false;
|
||||
p->exports |= bit;
|
||||
*ticket = p->generation[slot];
|
||||
return true;
|
||||
}
|
||||
bool vaapi_vp9_pool_release(vaapi_vp9_pool_t *p, unsigned slot, uint64_t ticket)
|
||||
{
|
||||
uint32_t bit;
|
||||
if (!p || !p->active || slot >= p->session->slots || !ticket)
|
||||
return false;
|
||||
bit = UINT32_C(1) << slot;
|
||||
if (!(p->exports & bit) || p->generation[slot] != ticket)
|
||||
return false;
|
||||
p->exports &= ~bit;
|
||||
return true;
|
||||
}
|
||||
bool vaapi_vp9_pool_close(vaapi_vp9_pool_t *p)
|
||||
{
|
||||
uint64_t generation;
|
||||
if (!p || !p->active)
|
||||
return true;
|
||||
if (p->broken || vaapi_vp9_pool_held(p))
|
||||
return false;
|
||||
generation = p->next_generation;
|
||||
memset(p, 0, sizeof(*p));
|
||||
p->next_generation = generation;
|
||||
return true;
|
||||
}
|
||||
bool vaapi_vp9_pool_terminated(vaapi_vp9_pool_t *p, VADisplay display)
|
||||
{
|
||||
uint64_t generation;
|
||||
if (!p || !p->active || !display || p->display != display || p->exports)
|
||||
return false;
|
||||
generation = p->next_generation;
|
||||
memset(p, 0, sizeof(*p));
|
||||
p->next_generation = generation;
|
||||
return true;
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
/* Copyright (C) 2026 The RetroArch team. SPDX-License-Identifier:
|
||||
* GPL-3.0-or-later */
|
||||
#ifndef RARCH_VAAPI_VP9_POOL_H
|
||||
#define RARCH_VAAPI_VP9_POOL_H
|
||||
#include "vaapi_vp9_submit.h"
|
||||
RETRO_BEGIN_DECLS
|
||||
typedef struct vaapi_vp9_pool
|
||||
{
|
||||
const vaapi_vp9_session_t *session;
|
||||
VADisplay display;
|
||||
vaapi_vp9_submit_ops_t ops;
|
||||
vaapi_vp9_submit_t transactions[32];
|
||||
vaapi_vp9_picture_t work;
|
||||
uint64_t generation[32], next_generation;
|
||||
uint32_t reads[32], valid, ready, exports;
|
||||
int previous_slot;
|
||||
bool active, broken;
|
||||
} vaapi_vp9_pool_t;
|
||||
/* Cold owner, initially zeroed storage. Session/functions outlive retirement.
|
||||
* Shared rvp9 state selects logical DPB slots; this pool protects native use.
|
||||
*/
|
||||
bool vaapi_vp9_pool_init(vaapi_vp9_pool_t *pool,
|
||||
const vaapi_vp9_session_t *session, const vaapi_vp9_submit_ops_t *ops);
|
||||
uint32_t vaapi_vp9_pool_held(const vaapi_vp9_pool_t *pool);
|
||||
/* Owner only. 0 accepted, 1 busy without submission, -1 refused/broken.
|
||||
* Native create copies input before acceptance; no heap allocation here. */
|
||||
int vaapi_vp9_pool_decode(vaapi_vp9_pool_t *pool, const uint8_t *packet,
|
||||
size_t size, const rvp9_hw_picture_t *picture);
|
||||
/* At most one zero-timeout native completion query for this slot.
|
||||
* 0 decoded, 1 pending, -1 failure. Cleanup failures remain held for retry. */
|
||||
int vaapi_vp9_pool_poll(vaapi_vp9_pool_t *pool, unsigned slot);
|
||||
/* One export per surface incarnation. Ordinary failures leave ticket intact. */
|
||||
bool vaapi_vp9_pool_acquire(
|
||||
vaapi_vp9_pool_t *pool, unsigned slot, uint64_t *ticket);
|
||||
bool vaapi_vp9_pool_release(
|
||||
vaapi_vp9_pool_t *pool, unsigned slot, uint64_t ticket);
|
||||
bool vaapi_vp9_pool_close(vaapi_vp9_pool_t *pool);
|
||||
/* Only after matching successful vaTerminate and all renderer exports retire.
|
||||
*/
|
||||
bool vaapi_vp9_pool_terminated(vaapi_vp9_pool_t *pool, VADisplay display);
|
||||
RETRO_END_DECLS
|
||||
#endif
|
||||
@@ -0,0 +1,24 @@
|
||||
/* Copyright (C) 2026 The RetroArch team.
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later */
|
||||
#ifndef RARCH_VAAPI_VP9_RUNTIME_H
|
||||
#define RARCH_VAAPI_VP9_RUNTIME_H
|
||||
#include "vaapi_hevc_runtime.h"
|
||||
#include "vaapi_vp9_session.h"
|
||||
typedef vaapi_hevc_runtime_t vaapi_vp9_runtime_t;
|
||||
typedef vaapi_hevc_runtime_io_t vaapi_vp9_runtime_io_t;
|
||||
RETRO_BEGIN_DECLS
|
||||
/* Cold owner; renderer DRM identity must match the opened render node.
|
||||
* Close even after failed startup, retaining storage when close refuses. */
|
||||
bool vaapi_vp9_runtime_open(vaapi_vp9_runtime_t *runtime, const char *path,
|
||||
unsigned major_id, unsigned minor_id, unsigned width, unsigned height,
|
||||
unsigned depth, unsigned slots);
|
||||
bool vaapi_vp9_runtime_open_with_io(vaapi_vp9_runtime_t *runtime,
|
||||
const char *path, unsigned major_id, unsigned minor_id, unsigned width,
|
||||
unsigned height, unsigned depth, unsigned slots,
|
||||
const vaapi_vp9_runtime_io_t *io);
|
||||
static INLINE bool vaapi_vp9_runtime_close(vaapi_vp9_runtime_t *runtime)
|
||||
{
|
||||
return vaapi_hevc_runtime_close(runtime);
|
||||
}
|
||||
RETRO_END_DECLS
|
||||
#endif
|
||||
@@ -0,0 +1,26 @@
|
||||
/* Copyright (C) 2026 The RetroArch team. SPDX-License-Identifier:
|
||||
* GPL-3.0-or-later */
|
||||
#ifndef RARCH_VAAPI_VP9_SESSION_H
|
||||
#define RARCH_VAAPI_VP9_SESSION_H
|
||||
#include "vaapi_hevc_session.h"
|
||||
#include <retro_inline.h>
|
||||
/* Share native VA operations and surface retirement, not codec parameters. */
|
||||
typedef vaapi_hevc_session_t vaapi_vp9_session_t;
|
||||
typedef vaapi_hevc_session_ops_t vaapi_vp9_session_ops_t;
|
||||
RETRO_BEGIN_DECLS
|
||||
/* Cold decoder owner, initialized borrowed display. Profile 0/2, 8/10-bit 420.
|
||||
* Failed startup can retain native handles; close even after false. */
|
||||
bool vaapi_vp9_session_open(vaapi_vp9_session_t *session, VADisplay display,
|
||||
const vaapi_vp9_session_ops_t *ops, unsigned width, unsigned height,
|
||||
unsigned depth, unsigned slots);
|
||||
static INLINE bool vaapi_vp9_session_close(vaapi_vp9_session_t *session)
|
||||
{
|
||||
return vaapi_hevc_session_close(session);
|
||||
}
|
||||
static INLINE bool vaapi_vp9_session_terminated(
|
||||
vaapi_vp9_session_t *session, VADisplay display)
|
||||
{
|
||||
return vaapi_hevc_session_terminated(session, display);
|
||||
}
|
||||
RETRO_END_DECLS
|
||||
#endif
|
||||
@@ -0,0 +1,97 @@
|
||||
/* Copyright (C) 2026 The RetroArch team. SPDX-License-Identifier:
|
||||
* GPL-3.0-or-later */
|
||||
#include "vaapi_vp9_submit.h"
|
||||
#include <string.h>
|
||||
|
||||
bool vaapi_vp9_submit_start(vaapi_vp9_submit_t *t, const vaapi_vp9_session_t *s,
|
||||
const vaapi_vp9_submit_ops_t *ops, unsigned slot,
|
||||
const vaapi_vp9_picture_t *picture, const uint8_t *packet, unsigned size)
|
||||
{
|
||||
vaapi_vp9_submit_ops_t native;
|
||||
const void *data[3];
|
||||
unsigned bytes[3], i, j;
|
||||
static const VABufferType types[3] = {VAPictureParameterBufferType,
|
||||
VASliceParameterBufferType, VASliceDataBufferType};
|
||||
if (!t || t->active || !s || !s->ready || s->closing || !s->display
|
||||
|| s->context == VA_INVALID_ID || s->slots < 10 || s->slots > 32
|
||||
|| slot >= s->slots || s->surfaces[slot] == VA_INVALID_SURFACE || !ops
|
||||
|| !ops->create || !ops->destroy || !ops->begin || !ops->render
|
||||
|| !ops->end || !ops->sync || !picture || !packet || !size
|
||||
|| (s->depth != 8 && s->depth != 10)
|
||||
|| s->profile
|
||||
!= (s->depth == 10 ? VAProfileVP9Profile2 : VAProfileVP9Profile0)
|
||||
|| picture->picture.profile != (s->depth == 10 ? 2 : 0)
|
||||
|| picture->picture.bit_depth != s->depth
|
||||
|| picture->picture.frame_width != s->width
|
||||
|| picture->picture.frame_height != s->height
|
||||
|| picture->slice.slice_data_size != size
|
||||
|| picture->slice.slice_data_offset
|
||||
|| picture->slice.slice_data_flag != VA_SLICE_DATA_FLAG_ALL
|
||||
|| !picture->picture.frame_header_length_in_bytes
|
||||
|| picture->picture.frame_header_length_in_bytes >= size
|
||||
|| !picture->picture.first_partition_size
|
||||
|| picture->picture.first_partition_size
|
||||
>= size - picture->picture.frame_header_length_in_bytes)
|
||||
return false;
|
||||
for (i = 0; i < 8; i++)
|
||||
{
|
||||
VASurfaceID reference = picture->picture.reference_frames[i];
|
||||
bool found = false;
|
||||
if (reference == VA_INVALID_SURFACE)
|
||||
continue;
|
||||
if (reference == s->surfaces[slot])
|
||||
return false;
|
||||
for (j = 0; j < s->slots; j++)
|
||||
if (reference == s->surfaces[j])
|
||||
found = true;
|
||||
if (!found)
|
||||
return false;
|
||||
}
|
||||
data[0] = &picture->picture;
|
||||
data[1] = &picture->slice;
|
||||
data[2] = packet;
|
||||
bytes[0] = sizeof(picture->picture);
|
||||
bytes[1] = sizeof(picture->slice);
|
||||
bytes[2] = size;
|
||||
native = *ops;
|
||||
memset(t, 0, sizeof(*t));
|
||||
t->ops = native;
|
||||
t->display = s->display;
|
||||
t->surface = s->surfaces[slot];
|
||||
t->active = true;
|
||||
for (i = 0; i < 5; i++)
|
||||
t->buffers[i] = VA_INVALID_ID;
|
||||
for (i = 0; i < 3; i++)
|
||||
{
|
||||
t->error = t->ops.create(t->display, s->context, types[i], bytes[i], 1,
|
||||
(void *)data[i], &t->buffers[t->count]);
|
||||
t->count++;
|
||||
for (j = 0; j + 1 < t->count; j++)
|
||||
if (t->buffers[j] == t->buffers[t->count - 1])
|
||||
{
|
||||
t->buffers[t->count - 1] = VA_INVALID_ID;
|
||||
t->error = VA_STATUS_ERROR_OPERATION_FAILED;
|
||||
return false;
|
||||
}
|
||||
if (t->error != VA_STATUS_SUCCESS)
|
||||
return false;
|
||||
if (t->buffers[t->count - 1] == VA_INVALID_ID)
|
||||
{
|
||||
t->error = VA_STATUS_ERROR_OPERATION_FAILED;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
t->poisoned = true;
|
||||
t->error = t->ops.begin(t->display, s->context, t->surface);
|
||||
if (t->error != VA_STATUS_SUCCESS)
|
||||
return false;
|
||||
t->error = t->ops.render(t->display, s->context, t->buffers, (int)t->count);
|
||||
if (t->error != VA_STATUS_SUCCESS)
|
||||
return false;
|
||||
t->error = t->ops.end(t->display, s->context);
|
||||
if (t->error != VA_STATUS_SUCCESS)
|
||||
return false;
|
||||
t->submitted = true;
|
||||
t->poisoned = false;
|
||||
return true;
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
/* Copyright (C) 2026 The RetroArch team. SPDX-License-Identifier:
|
||||
* GPL-3.0-or-later */
|
||||
#ifndef RARCH_VAAPI_VP9_SUBMIT_H
|
||||
#define RARCH_VAAPI_VP9_SUBMIT_H
|
||||
#include "vaapi_hevc_submit.h"
|
||||
#include "vaapi_vp9_session.h"
|
||||
#include "vaapi_vp9_picture.h"
|
||||
typedef vaapi_hevc_submit_t vaapi_vp9_submit_t;
|
||||
typedef vaapi_hevc_submit_ops_t vaapi_vp9_submit_ops_t;
|
||||
RETRO_BEGIN_DECLS
|
||||
/* Decoder owner. Native create copies the borrowed packet and parameters.
|
||||
* False may retain handles or uncertain native work; retire before reuse. */
|
||||
bool vaapi_vp9_submit_start(vaapi_vp9_submit_t *transaction,
|
||||
const vaapi_vp9_session_t *session, const vaapi_vp9_submit_ops_t *ops,
|
||||
unsigned slot, const vaapi_vp9_picture_t *picture, const uint8_t *packet,
|
||||
unsigned size);
|
||||
static INLINE int vaapi_vp9_submit_poll(vaapi_vp9_submit_t *transaction)
|
||||
{
|
||||
return vaapi_hevc_submit_poll(transaction);
|
||||
}
|
||||
static INLINE bool vaapi_vp9_submit_retire(vaapi_vp9_submit_t *transaction)
|
||||
{
|
||||
return vaapi_hevc_submit_retire(transaction);
|
||||
}
|
||||
/* Only after matching successful vaTerminate, as for shared VA transactions. */
|
||||
static INLINE bool vaapi_vp9_submit_terminated(
|
||||
vaapi_vp9_submit_t *transaction, VADisplay display)
|
||||
{
|
||||
return vaapi_hevc_submit_terminated(transaction, display);
|
||||
}
|
||||
RETRO_END_DECLS
|
||||
#endif
|
||||
@@ -0,0 +1,227 @@
|
||||
/* Copyright (C) 2026 The RetroArch team.
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later */
|
||||
#ifndef RETROARCH_VIDEOTOOLBOX_AV1_H
|
||||
#define RETROARCH_VIDEOTOOLBOX_AV1_H
|
||||
#if defined(__APPLE__) && !defined(__cplusplus) \
|
||||
&& (!defined(__STDC_VERSION__) || __STDC_VERSION__ < 199901L) \
|
||||
&& !defined(inline)
|
||||
#define inline __inline__
|
||||
#define VIDEOTOOLBOX_AV1_UNDEF_INLINE
|
||||
#endif
|
||||
#include <CoreMedia/CoreMedia.h>
|
||||
#include <CoreVideo/CoreVideo.h>
|
||||
#include <VideoToolbox/VideoToolbox.h>
|
||||
#ifdef VIDEOTOOLBOX_AV1_UNDEF_INLINE
|
||||
#undef inline
|
||||
#undef VIDEOTOOLBOX_AV1_UNDEF_INLINE
|
||||
#endif
|
||||
#include "videotoolbox_av1_config.h"
|
||||
#include <stdlib.h>
|
||||
|
||||
#define VIDEOTOOLBOX_AV1_CODEC ((CMVideoCodecType)0x61763031)
|
||||
/* The lifecycle owner resolves supplemental registration when available.
|
||||
* Keeping it optional avoids a newer/macOS-only API link on older SDKs/iOS. */
|
||||
typedef void (*videotoolbox_av1_register_t)(CMVideoCodecType codec);
|
||||
|
||||
/* Cold setup only. A valid format is not evidence of hardware support. */
|
||||
static INLINE OSStatus videotoolbox_av1_format_create(
|
||||
const rav1_sequence_t *header, const video_color_metadata_t *color,
|
||||
const rav1_obu_t *sequence, CMVideoFormatDescriptionRef *out)
|
||||
{
|
||||
videotoolbox_av1_config_t config;
|
||||
bool (*volatile configure)(const rav1_sequence_t *, const rav1_obu_t *,
|
||||
const video_color_metadata_t *, videotoolbox_av1_config_t *)
|
||||
= videotoolbox_av1_configure;
|
||||
CFDataRef data = NULL;
|
||||
uint8_t *configuration = NULL;
|
||||
CFDictionaryRef atoms = NULL, extensions = NULL;
|
||||
CFNumberRef depth = NULL;
|
||||
const void *keys[6], *values[6];
|
||||
CFStringRef primaries = NULL, transfer = NULL, matrix = NULL;
|
||||
unsigned count = 3;
|
||||
int32_t bits;
|
||||
OSStatus status = kCMFormatDescriptionError_AllocationFailed;
|
||||
if (!out)
|
||||
return kCMFormatDescriptionError_InvalidParameter;
|
||||
*out = NULL;
|
||||
if (!configure(header, sequence, color, &config))
|
||||
return kCMFormatDescriptionError_InvalidParameter;
|
||||
bits = header->bit_depth;
|
||||
configuration = (uint8_t *)malloc(4 + config.sequence_size);
|
||||
if (!configuration)
|
||||
goto done;
|
||||
memcpy(configuration, config.av1c, 4);
|
||||
memcpy(configuration + 4, config.sequence_obu, config.sequence_size);
|
||||
data = CFDataCreate(
|
||||
kCFAllocatorDefault, configuration, (long)(4 + config.sequence_size));
|
||||
free(configuration);
|
||||
configuration = NULL;
|
||||
if (!data)
|
||||
goto done;
|
||||
keys[0] = CFSTR("av1C");
|
||||
values[0] = data;
|
||||
atoms = CFDictionaryCreate(kCFAllocatorDefault, keys, values, 1,
|
||||
&kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
|
||||
if (!atoms)
|
||||
goto done;
|
||||
depth = CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &bits);
|
||||
if (!depth)
|
||||
goto done;
|
||||
keys[0] = kCMFormatDescriptionExtension_SampleDescriptionExtensionAtoms;
|
||||
keys[1] = CFSTR("BitsPerComponent");
|
||||
values[0] = atoms;
|
||||
values[1] = depth;
|
||||
/* av1C alone does not prevent size-based CoreVideo color defaults. */
|
||||
keys[2] = kCMFormatDescriptionExtension_FullRangeVideo;
|
||||
values[2] = config.color.range == 2 ? kCFBooleanTrue : kCFBooleanFalse;
|
||||
if (config.color.primaries == 1)
|
||||
primaries = kCVImageBufferColorPrimaries_ITU_R_709_2;
|
||||
else if (config.color.primaries == 9)
|
||||
primaries = kCVImageBufferColorPrimaries_ITU_R_2020;
|
||||
switch (config.color.transfer)
|
||||
{
|
||||
case 1:
|
||||
transfer = kCVImageBufferTransferFunction_ITU_R_709_2;
|
||||
break;
|
||||
case 13:
|
||||
transfer = kCVImageBufferTransferFunction_sRGB;
|
||||
break;
|
||||
case 16:
|
||||
transfer = kCVImageBufferTransferFunction_SMPTE_ST_2084_PQ;
|
||||
break;
|
||||
case 18:
|
||||
transfer = kCVImageBufferTransferFunction_ITU_R_2100_HLG;
|
||||
break;
|
||||
}
|
||||
if (config.color.matrix == 1)
|
||||
matrix = kCVImageBufferYCbCrMatrix_ITU_R_709_2;
|
||||
else if (config.color.matrix == 9)
|
||||
matrix = kCVImageBufferYCbCrMatrix_ITU_R_2020;
|
||||
if (primaries)
|
||||
{
|
||||
keys[count] = kCMFormatDescriptionExtension_ColorPrimaries;
|
||||
values[count++] = primaries;
|
||||
}
|
||||
if (transfer)
|
||||
{
|
||||
keys[count] = kCMFormatDescriptionExtension_TransferFunction;
|
||||
values[count++] = transfer;
|
||||
}
|
||||
if (matrix)
|
||||
{
|
||||
keys[count] = kCMFormatDescriptionExtension_YCbCrMatrix;
|
||||
values[count++] = matrix;
|
||||
}
|
||||
extensions = CFDictionaryCreate(kCFAllocatorDefault, keys, values, count,
|
||||
&kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
|
||||
if (!extensions)
|
||||
goto done;
|
||||
status = CMVideoFormatDescriptionCreate(kCFAllocatorDefault,
|
||||
VIDEOTOOLBOX_AV1_CODEC, (int32_t)config.width, (int32_t)config.height,
|
||||
extensions, out);
|
||||
if (!status && !*out)
|
||||
status = kCMFormatDescriptionError_AllocationFailed;
|
||||
if (status && *out)
|
||||
{
|
||||
CFRelease(*out);
|
||||
*out = NULL;
|
||||
}
|
||||
done:
|
||||
if (extensions)
|
||||
CFRelease(extensions);
|
||||
if (depth)
|
||||
CFRelease(depth);
|
||||
if (atoms)
|
||||
CFRelease(atoms);
|
||||
if (data)
|
||||
CFRelease(data);
|
||||
free(configuration);
|
||||
return status;
|
||||
}
|
||||
|
||||
/* Caller owns successful format/session references and callback storage.
|
||||
* Hardware is required; the output pool requests Metal-compatible IOSurface
|
||||
* NV12/P010 in the coded range. Registration alone proves no capability.
|
||||
* No packets, callbacks, waits or per-frame resources are created here. */
|
||||
static INLINE OSStatus videotoolbox_av1_session_create(
|
||||
const rav1_sequence_t *header, const video_color_metadata_t *color,
|
||||
const rav1_obu_t *sequence, videotoolbox_av1_register_t register_decoder,
|
||||
const VTDecompressionOutputCallbackRecord *callback,
|
||||
CMVideoFormatDescriptionRef *format_out, VTDecompressionSessionRef *out)
|
||||
{
|
||||
CMVideoFormatDescriptionRef format = NULL;
|
||||
CFDictionaryRef specification = NULL, surfaces = NULL, attributes = NULL;
|
||||
CFNumberRef number = NULL;
|
||||
const void *keys[3], *values[3];
|
||||
OSStatus status;
|
||||
int32_t pixel;
|
||||
if (format_out)
|
||||
*format_out = NULL;
|
||||
if (out)
|
||||
*out = NULL;
|
||||
if (!format_out || !out || (void *)format_out == (void *)out || !callback
|
||||
|| !callback->decompressionOutputCallback)
|
||||
return kCMFormatDescriptionError_InvalidParameter;
|
||||
status = videotoolbox_av1_format_create(header, color, sequence, &format);
|
||||
if (status)
|
||||
return status;
|
||||
if (register_decoder)
|
||||
register_decoder(VIDEOTOOLBOX_AV1_CODEC);
|
||||
pixel = header->bit_depth == 10
|
||||
? (header->color_range ? 0x78663230 : 0x78343230)
|
||||
: (header->color_range
|
||||
? kCVPixelFormatType_420YpCbCr8BiPlanarFullRange
|
||||
: kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange);
|
||||
status = kCMFormatDescriptionError_AllocationFailed;
|
||||
keys[0]
|
||||
= kVTVideoDecoderSpecification_RequireHardwareAcceleratedVideoDecoder;
|
||||
values[0] = kCFBooleanTrue;
|
||||
specification = CFDictionaryCreate(kCFAllocatorDefault, keys, values, 1,
|
||||
&kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
|
||||
if (!specification)
|
||||
goto done;
|
||||
number = CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &pixel);
|
||||
if (!number)
|
||||
goto done;
|
||||
surfaces = CFDictionaryCreate(kCFAllocatorDefault, NULL, NULL, 0,
|
||||
&kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
|
||||
if (!surfaces)
|
||||
goto done;
|
||||
keys[0] = kCVPixelBufferPixelFormatTypeKey;
|
||||
keys[1] = kCVPixelBufferMetalCompatibilityKey;
|
||||
keys[2] = kCVPixelBufferIOSurfacePropertiesKey;
|
||||
values[0] = number;
|
||||
values[1] = kCFBooleanTrue;
|
||||
values[2] = surfaces;
|
||||
attributes = CFDictionaryCreate(kCFAllocatorDefault, keys, values, 3,
|
||||
&kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
|
||||
if (!attributes)
|
||||
goto done;
|
||||
status = VTDecompressionSessionCreate(
|
||||
kCFAllocatorDefault, format, specification, attributes, callback, out);
|
||||
if (!status && !*out)
|
||||
status = kCMFormatDescriptionError_AllocationFailed;
|
||||
done:
|
||||
if (attributes)
|
||||
CFRelease(attributes);
|
||||
if (surfaces)
|
||||
CFRelease(surfaces);
|
||||
if (number)
|
||||
CFRelease(number);
|
||||
if (specification)
|
||||
CFRelease(specification);
|
||||
if (status)
|
||||
{
|
||||
if (*out)
|
||||
{
|
||||
VTDecompressionSessionInvalidate(*out);
|
||||
CFRelease(*out);
|
||||
*out = NULL;
|
||||
}
|
||||
CFRelease(format);
|
||||
}
|
||||
else
|
||||
*format_out = format;
|
||||
return status;
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,110 @@
|
||||
/* Copyright (C) 2026 The RetroArch team.
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later */
|
||||
#ifndef RETROARCH_VIDEOTOOLBOX_AV1_BACKEND_H
|
||||
#define RETROARCH_VIDEOTOOLBOX_AV1_BACKEND_H
|
||||
#include "videotoolbox_av1_dispatch.h"
|
||||
#include "metal_frame_convert.h"
|
||||
#include "metal_decoder_snapshot.h"
|
||||
#ifdef VIDEOTOOLBOX_AV1_OWNER
|
||||
|
||||
typedef metal_decoder_snapshot_view_t videotoolbox_av1_device_t;
|
||||
|
||||
typedef struct videotoolbox_av1_backend
|
||||
{
|
||||
rav1_hw_backend_t offer;
|
||||
void *user;
|
||||
bool (*acquire)(void *, videotoolbox_av1_device_t *);
|
||||
void (*release)(void *);
|
||||
bool (*current)(void *, uintptr_t);
|
||||
bool (*enabled)(void *);
|
||||
videotoolbox_av1_register_t registration;
|
||||
size_t capacity;
|
||||
} videotoolbox_av1_backend_t;
|
||||
|
||||
static bool videotoolbox_av1_backend_open_config(void *opaque,
|
||||
const rav1_sequence_t *header, const rav1_obu_t *sequence,
|
||||
const video_color_metadata_t *color, unsigned count, rav1_hw_ops_t *ops)
|
||||
{
|
||||
videotoolbox_av1_backend_t *b = (videotoolbox_av1_backend_t *)opaque;
|
||||
videotoolbox_av1_device_t snapshot;
|
||||
videotoolbox_av1_config_t config;
|
||||
metal_frame_view_t view;
|
||||
metal_frame_sdr_params_t conversion;
|
||||
metal_frame_cv_pool_t *pool;
|
||||
bool opened;
|
||||
if (!ops)
|
||||
return false;
|
||||
memset(ops, 0, sizeof(*ops));
|
||||
if (!b || !b->acquire || !b->release || !b->current || count < 9
|
||||
|| count > 32 || b->capacity < 3 || b->capacity > 64u * 1024 * 1024
|
||||
|| (b->enabled && !b->enabled(b->user)))
|
||||
return false;
|
||||
if (count < 10)
|
||||
count = 10;
|
||||
if (b->capacity > 256u * 1024 * 1024 / count
|
||||
|| !videotoolbox_av1_configure(header, sequence, color, &config))
|
||||
return false;
|
||||
memset(&snapshot, 0, sizeof(snapshot));
|
||||
if (!b->acquire(b->user, &snapshot) || !snapshot.device)
|
||||
{
|
||||
if (snapshot.device)
|
||||
b->release(snapshot.device);
|
||||
return false;
|
||||
}
|
||||
memset(&view, 0, sizeof(view));
|
||||
view.color = config.color;
|
||||
view.width = config.width;
|
||||
view.height = config.height;
|
||||
view.pixfmt = config.pixfmt;
|
||||
view.chroma_position = config.chroma_position;
|
||||
if ((!metal_frame_sdr_params(&view, &conversion)
|
||||
&& !(snapshot.hdr && metal_frame_hdr_params(&view, &conversion)))
|
||||
|| !b->current(b->user, snapshot.generation))
|
||||
{
|
||||
b->release(snapshot.device);
|
||||
return false;
|
||||
}
|
||||
pool = metal_frame_cv_pool_create(snapshot.device, 32);
|
||||
b->release(snapshot.device);
|
||||
if (!pool)
|
||||
return false;
|
||||
opened = videotoolbox_av1_dispatch_open(header, color, sequence, count,
|
||||
b->capacity, pool, b->registration, ops);
|
||||
if (!opened)
|
||||
metal_frame_cv_pool_free(pool);
|
||||
else if (!b->current(b->user, snapshot.generation))
|
||||
{
|
||||
videotoolbox_av1_decoder_close(ops);
|
||||
return false;
|
||||
}
|
||||
return opened;
|
||||
}
|
||||
|
||||
static INLINE bool videotoolbox_av1_backend_init(videotoolbox_av1_backend_t *b,
|
||||
void *user, bool (*acquire)(void *, videotoolbox_av1_device_t *),
|
||||
void (*release)(void *), bool (*current)(void *, uintptr_t),
|
||||
bool (*enabled)(void *), videotoolbox_av1_register_t registration,
|
||||
size_t capacity)
|
||||
{
|
||||
if (!b)
|
||||
return false;
|
||||
memset(b, 0, sizeof(*b));
|
||||
if (!acquire || !release || !current || capacity < 3
|
||||
|| capacity > 64u * 1024 * 1024)
|
||||
return false;
|
||||
b->user = user;
|
||||
b->acquire = acquire;
|
||||
b->release = release;
|
||||
b->current = current;
|
||||
b->enabled = enabled;
|
||||
b->registration = registration;
|
||||
b->capacity = capacity;
|
||||
b->offer.user = b;
|
||||
b->offer.close = videotoolbox_av1_decoder_close;
|
||||
b->offer.drain = videotoolbox_av1_decoder_finish;
|
||||
b->offer.open_config = videotoolbox_av1_backend_open_config;
|
||||
b->offer.packet_decoder = true;
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
@@ -0,0 +1,106 @@
|
||||
/* Copyright (C) 2026 The RetroArch team.
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later */
|
||||
#ifndef RETROARCH_VIDEOTOOLBOX_AV1_CONFIG_H
|
||||
#define RETROARCH_VIDEOTOOLBOX_AV1_CONFIG_H
|
||||
#include <formats/rav1_sequence.h>
|
||||
#include <formats/image.h>
|
||||
|
||||
typedef struct videotoolbox_av1_config
|
||||
{
|
||||
const uint8_t *sequence_obu;
|
||||
size_t sequence_size;
|
||||
video_color_metadata_t color;
|
||||
unsigned width, height, pixfmt, chroma_position;
|
||||
uint8_t av1c[4];
|
||||
} videotoolbox_av1_config_t;
|
||||
|
||||
static INLINE bool videotoolbox_av1_color(const rav1_sequence_t *s,
|
||||
const video_color_metadata_t *container, video_color_metadata_t *out)
|
||||
{
|
||||
video_color_metadata_t color;
|
||||
unsigned coded[3], *fields[3], i, range;
|
||||
if (!s || !out || s->color_range > 1 || !s->color_primaries
|
||||
|| !s->transfer_characteristics || !s->matrix_coefficients
|
||||
|| s->color_primaries > 255 || s->transfer_characteristics > 255
|
||||
|| s->matrix_coefficients > 255)
|
||||
return false;
|
||||
memset(&color, 0, sizeof(color));
|
||||
if (container)
|
||||
color = *container;
|
||||
range = s->color_range ? 2 : 1;
|
||||
if (color.range > 2 || (color.range && color.range != range))
|
||||
return false;
|
||||
coded[0] = s->color_primaries;
|
||||
coded[1] = s->transfer_characteristics;
|
||||
coded[2] = s->matrix_coefficients;
|
||||
fields[0] = &color.primaries;
|
||||
fields[1] = &color.transfer;
|
||||
fields[2] = &color.matrix;
|
||||
for (i = 0; i < 3; i++)
|
||||
if (coded[i] != 2)
|
||||
{
|
||||
if (*fields[i] && *fields[i] != 2 && *fields[i] != coded[i])
|
||||
return false;
|
||||
*fields[i] = coded[i];
|
||||
}
|
||||
if (color.primaries == 3 || (color.primaries > 12 && color.primaries != 22)
|
||||
|| color.transfer == 3 || color.transfer > 18 || color.matrix == 3
|
||||
|| color.matrix > 14)
|
||||
return false;
|
||||
color.range = range;
|
||||
*out = color;
|
||||
return true;
|
||||
}
|
||||
|
||||
/* Cold av1C prefix plus a borrowed, sized sequence OBU. No native capability
|
||||
* is implied. The format owner copies the bytes before returning admission. */
|
||||
static INLINE bool videotoolbox_av1_configure(const rav1_sequence_t *header,
|
||||
const rav1_obu_t *sequence, const video_color_metadata_t *container,
|
||||
videotoolbox_av1_config_t *out)
|
||||
{
|
||||
rav1_sequence_t parsed;
|
||||
rav1_obu_t obu;
|
||||
video_color_metadata_t color;
|
||||
size_t offset = 0;
|
||||
if (!out)
|
||||
return false;
|
||||
if (!header || !sequence || !sequence->data || !sequence->size
|
||||
|| sequence->size > 1024u * 1024u
|
||||
|| rav1_obu_next(&obu, sequence->data, sequence->size, &offset, false)
|
||||
!= 1
|
||||
|| offset != sequence->size || obu.type != 1 || obu.temporal_id
|
||||
|| obu.spatial_id
|
||||
|| !rav1_sequence_parse(&parsed, obu.payload, obu.payload_size)
|
||||
|| memcmp(&parsed, header, sizeof(parsed)) || header->profile
|
||||
|| header->monochrome
|
||||
|| (header->bit_depth != 8 && header->bit_depth != 10)
|
||||
|| header->subsampling_x != 1 || header->subsampling_y != 1
|
||||
|| header->chroma_sample_position > 2 || !header->max_width
|
||||
|| !header->max_height || header->max_width > 16384
|
||||
|| header->max_height > 16384 || header->operating_point_count != 1
|
||||
|| header->operating_points[0].idc
|
||||
|| !videotoolbox_av1_color(header, container, &color))
|
||||
{
|
||||
memset(out, 0, sizeof(*out));
|
||||
return false;
|
||||
}
|
||||
memset(out, 0, sizeof(*out));
|
||||
out->sequence_obu = obu.data;
|
||||
out->sequence_size = obu.size;
|
||||
out->width = header->max_width;
|
||||
out->height = header->max_height;
|
||||
out->pixfmt
|
||||
= header->bit_depth == 10 ? IMAGE_PIXFMT_P010 : IMAGE_PIXFMT_NV12;
|
||||
out->chroma_position = header->chroma_sample_position;
|
||||
out->color = color;
|
||||
out->av1c[0] = 0x81;
|
||||
out->av1c[1] = (uint8_t)((header->profile << 5)
|
||||
| header->operating_points[0].level);
|
||||
out->av1c[2] = (uint8_t)((header->operating_points[0].tier << 7)
|
||||
| ((header->bit_depth == 10) << 6) | (header->monochrome << 4)
|
||||
| (header->subsampling_x << 3) | (header->subsampling_y << 2)
|
||||
| header->chroma_sample_position);
|
||||
/* Sample presentation delay cannot be inferred from frame display delay. */
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,189 @@
|
||||
/* Copyright (C) 2026 The RetroArch team.
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later */
|
||||
#ifndef RETROARCH_VIDEOTOOLBOX_AV1_DECODER_H
|
||||
#define RETROARCH_VIDEOTOOLBOX_AV1_DECODER_H
|
||||
#include <formats/rav1_hw.h>
|
||||
#include "videotoolbox_av1_owner.h"
|
||||
#include "metal_frame.h"
|
||||
#ifdef VIDEOTOOLBOX_AV1_OWNER
|
||||
|
||||
typedef struct videotoolbox_av1_decoder videotoolbox_av1_decoder_t;
|
||||
|
||||
struct videotoolbox_av1_decoder
|
||||
{
|
||||
rav1_sequence_t header;
|
||||
rav1_grain_t grain;
|
||||
metal_frame_view_t view;
|
||||
videotoolbox_av1_owner_t *owner;
|
||||
metal_frame_cv_pool_t *pool;
|
||||
CVPixelBufferRef image;
|
||||
void (*retire)(void *, videotoolbox_av1_decoder_t *);
|
||||
void *user;
|
||||
unsigned output_slot;
|
||||
};
|
||||
|
||||
static int videotoolbox_av1_decoder_decode(void *opaque, const uint8_t *packet,
|
||||
size_t size, const rav1_sequence_t *sequence,
|
||||
const rav1_hw_picture_t *picture)
|
||||
{
|
||||
videotoolbox_av1_decoder_t *d = (videotoolbox_av1_decoder_t *)opaque;
|
||||
bool show;
|
||||
int status;
|
||||
if (!d || !sequence || !picture || picture->slot < 0
|
||||
|| (unsigned)picture->slot >= d->owner->count
|
||||
|| memcmp(sequence, &d->header, sizeof(*sequence))
|
||||
|| !picture->setup.size.upscaled_width || !picture->setup.size.height
|
||||
|| picture->setup.size.upscaled_width > sequence->max_width
|
||||
|| picture->setup.size.height > sequence->max_height)
|
||||
return -1;
|
||||
show = picture->header.show_frame || picture->header.show_existing;
|
||||
status = videotoolbox_av1_owner_append(
|
||||
d->owner, packet, size, show, picture->slot);
|
||||
if (!status && show)
|
||||
{
|
||||
if (d->image)
|
||||
CVPixelBufferRelease(d->image);
|
||||
d->image = NULL;
|
||||
d->output_slot = (unsigned)picture->slot;
|
||||
d->view.width = picture->setup.size.upscaled_width;
|
||||
d->view.height = picture->setup.size.height;
|
||||
d->grain = picture->grain.params;
|
||||
videotoolbox_av1_owner_discard_previous(d->owner);
|
||||
}
|
||||
return status;
|
||||
}
|
||||
|
||||
static int videotoolbox_av1_decoder_output(void *opaque, unsigned slot,
|
||||
const rav1_grain_t *grain, video_hw_frame_t *out)
|
||||
{
|
||||
videotoolbox_av1_decoder_t *d = (videotoolbox_av1_decoder_t *)opaque;
|
||||
videotoolbox_av1_result_t result;
|
||||
int status;
|
||||
if (!d || !out || !grain || slot != d->output_slot
|
||||
|| memcmp(grain, &d->grain, sizeof(*grain)))
|
||||
return -1;
|
||||
if (!d->image)
|
||||
{
|
||||
status = videotoolbox_av1_owner_take(d->owner, slot, &result);
|
||||
if (status)
|
||||
return status;
|
||||
if (result.status || !result.image
|
||||
|| (result.flags & kVTDecodeInfo_FrameDropped))
|
||||
{
|
||||
if (result.image)
|
||||
CVPixelBufferRelease(result.image);
|
||||
return -1;
|
||||
}
|
||||
d->image = result.image;
|
||||
}
|
||||
status = metal_frame_cv_pool_export(d->pool, &d->view, d->image, out);
|
||||
if (!status)
|
||||
{
|
||||
CVPixelBufferRelease(d->image);
|
||||
d->image = NULL;
|
||||
}
|
||||
return status;
|
||||
}
|
||||
|
||||
static INLINE int videotoolbox_av1_decoder_finish(void *opaque, bool discard)
|
||||
{
|
||||
videotoolbox_av1_decoder_t *d = (videotoolbox_av1_decoder_t *)opaque;
|
||||
if (!d)
|
||||
return -1;
|
||||
if (d->image)
|
||||
{
|
||||
if (!discard)
|
||||
return 1;
|
||||
CVPixelBufferRelease(d->image);
|
||||
d->image = NULL;
|
||||
}
|
||||
return videotoolbox_av1_owner_finish(d->owner, discard);
|
||||
}
|
||||
|
||||
static INLINE int videotoolbox_av1_decoder_restart(void *opaque)
|
||||
{
|
||||
videotoolbox_av1_decoder_t *d = (videotoolbox_av1_decoder_t *)opaque;
|
||||
int result;
|
||||
if (!d)
|
||||
return -1;
|
||||
if (d->image)
|
||||
CVPixelBufferRelease(d->image);
|
||||
d->image = NULL;
|
||||
result = videotoolbox_av1_owner_restart(d->owner);
|
||||
if (!result)
|
||||
d->output_slot = (unsigned)-1;
|
||||
return result;
|
||||
}
|
||||
|
||||
static INLINE bool videotoolbox_av1_decoder_retire_poll(
|
||||
videotoolbox_av1_decoder_t **handle)
|
||||
{
|
||||
videotoolbox_av1_decoder_t *d;
|
||||
if (!handle || !(d = *handle))
|
||||
return true;
|
||||
if (!videotoolbox_av1_owner_retire_poll(&d->owner))
|
||||
return false;
|
||||
free(d);
|
||||
*handle = NULL;
|
||||
return true;
|
||||
}
|
||||
|
||||
static INLINE void videotoolbox_av1_decoder_close(rav1_hw_ops_t *ops)
|
||||
{
|
||||
videotoolbox_av1_decoder_t *d;
|
||||
if (!ops || !(d = (videotoolbox_av1_decoder_t *)ops->decoder))
|
||||
return;
|
||||
memset(ops, 0, sizeof(*ops));
|
||||
videotoolbox_av1_owner_close(d->owner);
|
||||
if (d->image)
|
||||
CVPixelBufferRelease(d->image);
|
||||
d->image = NULL;
|
||||
metal_frame_cv_pool_free(d->pool);
|
||||
d->pool = NULL;
|
||||
d->retire(d->user, d);
|
||||
}
|
||||
|
||||
/* Cold worker setup. Success takes pool ownership. wake/retire use a stable
|
||||
* external executor; neither may run service/retirement inline on playback. */
|
||||
static INLINE bool videotoolbox_av1_decoder_open(const rav1_sequence_t *header,
|
||||
const video_color_metadata_t *color, const rav1_obu_t *sequence,
|
||||
unsigned count, size_t capacity, metal_frame_cv_pool_t *pool,
|
||||
videotoolbox_av1_register_t registration, void (*wake)(void *),
|
||||
void (*retire)(void *, videotoolbox_av1_decoder_t *), void *user,
|
||||
rav1_hw_ops_t *ops)
|
||||
{
|
||||
videotoolbox_av1_decoder_t *d;
|
||||
videotoolbox_av1_config_t config;
|
||||
if (!ops)
|
||||
return false;
|
||||
memset(ops, 0, sizeof(*ops));
|
||||
if (!pool || !retire || !header
|
||||
|| !videotoolbox_av1_configure(header, sequence, color, &config))
|
||||
return false;
|
||||
d = (videotoolbox_av1_decoder_t *)calloc(1, sizeof(*d));
|
||||
if (!d)
|
||||
return false;
|
||||
d->owner = videotoolbox_av1_owner_new(header, color, sequence, count,
|
||||
capacity, registration, wake, user, NULL);
|
||||
if (!d->owner)
|
||||
{
|
||||
free(d);
|
||||
return false;
|
||||
}
|
||||
d->header = *header;
|
||||
d->view.color = config.color;
|
||||
d->view.width = config.width;
|
||||
d->view.height = config.height;
|
||||
d->view.pixfmt = config.pixfmt;
|
||||
d->view.chroma_position = config.chroma_position;
|
||||
d->pool = pool;
|
||||
d->retire = retire;
|
||||
d->user = user;
|
||||
d->output_slot = count;
|
||||
ops->decoder = d;
|
||||
ops->decode = videotoolbox_av1_decoder_decode;
|
||||
ops->output = videotoolbox_av1_decoder_output;
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
@@ -0,0 +1,182 @@
|
||||
/* Copyright (C) 2026 The RetroArch team.
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later */
|
||||
#ifndef RETROARCH_VIDEOTOOLBOX_AV1_DISPATCH_H
|
||||
#define RETROARCH_VIDEOTOOLBOX_AV1_DISPATCH_H
|
||||
#include "videotoolbox_av1_decoder.h"
|
||||
#ifdef VIDEOTOOLBOX_AV1_OWNER
|
||||
#if defined(__APPLE__) && !defined(__cplusplus) && !defined(inline) \
|
||||
&& (!defined(__STDC_VERSION__) || __STDC_VERSION__ < 199901L)
|
||||
#define inline __inline__
|
||||
#define VIDEOTOOLBOX_AV1_DISPATCH_INLINE
|
||||
#endif
|
||||
#include <dispatch/dispatch.h>
|
||||
#ifdef VIDEOTOOLBOX_AV1_DISPATCH_INLINE
|
||||
#undef inline
|
||||
#undef VIDEOTOOLBOX_AV1_DISPATCH_INLINE
|
||||
#endif
|
||||
#if defined(__has_feature)
|
||||
#if __has_feature(objc_arc)
|
||||
#define VIDEOTOOLBOX_AV1_DISPATCH_ARC 1
|
||||
#endif
|
||||
#endif
|
||||
|
||||
typedef struct videotoolbox_av1_dispatch
|
||||
{
|
||||
dispatch_queue_t queue;
|
||||
dispatch_source_t signal, timer;
|
||||
videotoolbox_av1_decoder_t *decoder;
|
||||
retro_atomic_int_t closing;
|
||||
unsigned cancellations;
|
||||
} videotoolbox_av1_dispatch_t;
|
||||
|
||||
static void videotoolbox_av1_dispatch_dispose(void *opaque)
|
||||
{
|
||||
videotoolbox_av1_dispatch_t *e = (videotoolbox_av1_dispatch_t *)opaque;
|
||||
if (--e->cancellations)
|
||||
return;
|
||||
#ifdef VIDEOTOOLBOX_AV1_DISPATCH_ARC
|
||||
e->signal = NULL;
|
||||
e->timer = NULL;
|
||||
e->queue = NULL;
|
||||
#else
|
||||
if (e->signal)
|
||||
dispatch_release(e->signal);
|
||||
if (e->timer)
|
||||
dispatch_release(e->timer);
|
||||
dispatch_release(e->queue);
|
||||
#endif
|
||||
free(e);
|
||||
}
|
||||
|
||||
static void videotoolbox_av1_dispatch_cancel(videotoolbox_av1_dispatch_t *e)
|
||||
{
|
||||
dispatch_source_t signal = e->signal, timer = e->timer;
|
||||
#ifndef VIDEOTOOLBOX_AV1_DISPATCH_ARC
|
||||
if (signal)
|
||||
dispatch_retain(signal);
|
||||
if (timer)
|
||||
dispatch_retain(timer);
|
||||
#endif
|
||||
if (signal)
|
||||
dispatch_source_cancel(signal);
|
||||
if (timer)
|
||||
dispatch_source_cancel(timer);
|
||||
#ifndef VIDEOTOOLBOX_AV1_DISPATCH_ARC
|
||||
if (signal)
|
||||
dispatch_release(signal);
|
||||
if (timer)
|
||||
dispatch_release(timer);
|
||||
#endif
|
||||
}
|
||||
|
||||
static void videotoolbox_av1_dispatch_service(void *opaque)
|
||||
{
|
||||
videotoolbox_av1_dispatch_t *e = (videotoolbox_av1_dispatch_t *)opaque;
|
||||
videotoolbox_av1_decoder_t *d = e->decoder;
|
||||
if (!d)
|
||||
return;
|
||||
if (retro_atomic_load_acquire_int(&e->closing))
|
||||
{
|
||||
if (videotoolbox_av1_decoder_retire_poll(&e->decoder))
|
||||
videotoolbox_av1_dispatch_cancel(e);
|
||||
else
|
||||
dispatch_source_set_timer(e->timer,
|
||||
dispatch_time(DISPATCH_TIME_NOW, 16 * NSEC_PER_MSEC),
|
||||
DISPATCH_TIME_FOREVER, 0);
|
||||
return;
|
||||
}
|
||||
videotoolbox_av1_owner_worker(d->owner);
|
||||
if (retro_spsc_read_avail(&d->owner->queue) >= sizeof(unsigned))
|
||||
dispatch_source_merge_data(e->signal, 1);
|
||||
}
|
||||
|
||||
static void videotoolbox_av1_dispatch_wake(void *opaque)
|
||||
{
|
||||
videotoolbox_av1_dispatch_t *e = (videotoolbox_av1_dispatch_t *)opaque;
|
||||
dispatch_source_merge_data(e->signal, 1);
|
||||
}
|
||||
|
||||
/* Retain the source before publishing close: service may finish immediately.
|
||||
* After publishing, the executor context is no longer accessed here. */
|
||||
static void videotoolbox_av1_dispatch_retire(
|
||||
void *opaque, videotoolbox_av1_decoder_t *d)
|
||||
{
|
||||
videotoolbox_av1_dispatch_t *e = (videotoolbox_av1_dispatch_t *)opaque;
|
||||
dispatch_source_t signal = e->signal;
|
||||
(void)d;
|
||||
#ifndef VIDEOTOOLBOX_AV1_DISPATCH_ARC
|
||||
dispatch_retain(signal);
|
||||
#endif
|
||||
retro_atomic_store_release_int(&e->closing, 1);
|
||||
dispatch_source_merge_data(signal, 1);
|
||||
#ifndef VIDEOTOOLBOX_AV1_DISPATCH_ARC
|
||||
dispatch_release(signal);
|
||||
#endif
|
||||
}
|
||||
|
||||
/* Cold worker setup; successful construction takes pool ownership. */
|
||||
static INLINE bool videotoolbox_av1_dispatch_open(const rav1_sequence_t *header,
|
||||
const video_color_metadata_t *color, const rav1_obu_t *sequence,
|
||||
unsigned count, size_t capacity, metal_frame_cv_pool_t *pool,
|
||||
videotoolbox_av1_register_t registration, rav1_hw_ops_t *ops)
|
||||
{
|
||||
videotoolbox_av1_dispatch_t *e;
|
||||
dispatch_source_t sources[2];
|
||||
unsigned i;
|
||||
if (!ops)
|
||||
return false;
|
||||
memset(ops, 0, sizeof(*ops));
|
||||
e = (videotoolbox_av1_dispatch_t *)calloc(1, sizeof(*e));
|
||||
if (!e)
|
||||
return false;
|
||||
retro_atomic_int_init(&e->closing, 0);
|
||||
e->queue = dispatch_queue_create(
|
||||
"org.libretro.metal.av1", DISPATCH_QUEUE_SERIAL);
|
||||
if (!e->queue)
|
||||
{
|
||||
free(e);
|
||||
return false;
|
||||
}
|
||||
e->signal = dispatch_source_create(
|
||||
DISPATCH_SOURCE_TYPE_DATA_ADD, 0, 0, e->queue);
|
||||
e->timer
|
||||
= dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, e->queue);
|
||||
sources[0] = e->signal;
|
||||
sources[1] = e->timer;
|
||||
for (i = 0; i < 2; i++)
|
||||
if (sources[i])
|
||||
{
|
||||
e->cancellations++;
|
||||
dispatch_set_context(sources[i], e);
|
||||
dispatch_source_set_event_handler_f(
|
||||
sources[i], videotoolbox_av1_dispatch_service);
|
||||
dispatch_source_set_cancel_handler_f(
|
||||
sources[i], videotoolbox_av1_dispatch_dispose);
|
||||
if (i == 1)
|
||||
dispatch_source_set_timer(
|
||||
sources[i], DISPATCH_TIME_FOREVER, DISPATCH_TIME_FOREVER, 0);
|
||||
dispatch_resume(sources[i]);
|
||||
}
|
||||
if (!e->cancellations)
|
||||
{
|
||||
#ifdef VIDEOTOOLBOX_AV1_DISPATCH_ARC
|
||||
e->queue = NULL;
|
||||
#else
|
||||
dispatch_release(e->queue);
|
||||
#endif
|
||||
free(e);
|
||||
return false;
|
||||
}
|
||||
if (!e->signal || !e->timer
|
||||
|| !videotoolbox_av1_decoder_open(header, color, sequence, count,
|
||||
capacity, pool, registration, videotoolbox_av1_dispatch_wake,
|
||||
videotoolbox_av1_dispatch_retire, e, ops))
|
||||
{
|
||||
videotoolbox_av1_dispatch_cancel(e);
|
||||
return false;
|
||||
}
|
||||
e->decoder = (videotoolbox_av1_decoder_t *)ops->decoder;
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
Loaded 100 of 456 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user