Compare commits

...
Author SHA1 Message Date
Codex 249ba6f2e3 vp8: retain decoded arenas instead of copying reference planes
Bind last, golden and altref to completed frame arenas and decode into an unreferenced fourth arena. Preserve reference-copy ordering and borrowed-plane lifetime, and release retained macroblock state if an arena allocation fails. Generated independent pixel references cover hidden frames, cropped geometry and resolution changes; counted large copies and injected allocation failures guard the ownership path.
2026-10-24 02:46:44 +02:00
Codex 59e6caa7bf menu: reload active file media after a window context reset
Ozone and XMB must reopen the selected fullscreen file even when ordinary thumbnails are disabled. Exercise application fullscreen transitions with real movie pixels and seeking across all four menus, with direct and threaded GL presentation. Windows tests can require the DXGI bridge.
2026-10-24 02:27:01 +02:00
Codex 7169fb191d vulkan: route NVIDIA 610.47 H.264 through shared D3D11 exports
The Windows driver corrupts H.264 reference pictures through Vulkan Video, including in independent FFmpeg decoding. Reuse the matched-adapter packed GPU export path without CPU readback. Keep other drivers on Vulkan Video and leave correct software fallback when GPU interop is unavailable. Check generated H.264 exports against software, including cropping.
2026-10-24 02:27:00 +02:00
Codex 37c2964a10 io: retain the reserved media window file cursor
Avoid restoring a cursor that no prefix fill consumes. Consecutive window reads no longer seek; metadata probes still leave the residency frontier untouched. Keep cursor restoration for prefix fills and whole-file fallback. Count reads and seeks in the generated window regression.
2026-10-24 02:26:35 +02:00
Codex ec8e234dc3 tests: supply the GL queue harness context-release dependency
Abort if the scripted fence queue unexpectedly reaches single-context handoff. Keep the production publication path linked after it began calling the context-release helper.
2026-10-24 01:58:22 +02:00
Codex f817e27bab tests: complete synchronous shader harness dependencies
Fail fast if deferred shader ownership reaches the synchronous cycle harness. Normalize fixture and expected path separators for native Windows, preserving preset identity and order checks.
2026-10-24 01:46:49 +02:00
Codex 704ba2c4a1 media: retain WebM sound metadata before seeking its window
Create the prepared sound while the initial header is resident, before seeking can decommit earlier cluster headers. Retain its window owner immediately so cancelled preparations release borrowed data safely. Exercise later WebM seeks and cancellation with float and s16 output against resident reference audio.
2026-10-24 01:45:26 +02:00
Codex fafc841034 Merge remote-tracking branch 'origin/master' into codex/builtin-media-baseline
# Conflicts:
#	.github/workflows/Linux-menu-media.yml
#	menu/menu_media_browser.c
#	menu/menu_media_browser.h
#	samples/menu/rgui_media/rgui_media_test.c
#	samples/menu/rgui_media/run.py
2026-10-24 01:09:33 +02:00
Codex 28912bfb19 media: disable the legacy imageviewer core in normal builds
Keep image browsing, settings and history available through the menu viewer without HAVE_IMAGEVIEWER. Build extension filters from the enabled image codecs instead of querying the legacy core. Preserve its sources and Griffin guard.
2026-10-24 00:55:53 +02:00
Codex 19b2ac1e53 media: own fullscreen refresh requests through the menu lifecycle
Add an opt-in Match Display Refresh Rate setting shared by all four menus. Keep one temporary display owner, release it on source/viewer/settings changes and display-server teardown, and respect CRT SwitchRes and SDL mode-server selection. Stable frames make no mode queries; output notifications validate ownership before rebinding.

Retain an applied mode across compatible cadence changes instead of cycling the display. Core AV information remains unchanged. Win32 backend support is connected, but X11 and other relevant backend parity, precise fractional inventory, physical acceptance and VRR remain incomplete; keep this development feature off master.

GCC/Clang ASan+UBSan threaded/threadless controls, TSan, Win32 backend profiles and native Windows models pass. Full Linux C89 and native Windows frontends link. Real four-menu source/settings lifecycle, settings reference/unity/capacity/order and MSVC6 standin pass. No physical mode switch or completed VRR acceptance is claimed.
2026-10-24 00:42:20 +02:00
Codex 1215f526b9 media: add temporary Win32 refresh ownership callbacks
Select an advertised progressive mode at the captured geometry and depth using the shared cadence policy. Keep a separate snapshot for temporary playback ownership so release returns to the immediately preceding mode, including a paused core's mode, while preserving later external changes.

The backend and optional display-server callbacks are implemented; menu lifecycle ownership and settings are not connected yet. Keep this development checkpoint off master until integration acceptance.

C89 legacy/current ANSI/Unicode models pass GCC/Clang sanitizers, Wine and native Windows, covering matching/current/unsupported modes, geometry/depth/interlace filters, failures and conditional release. Real Windows-header ANSI/Unicode builds and read-only queries pass; physical playback refresh and VRR remain unaccepted.
2026-10-24 00:31:03 +02:00
Codex 8b084b8f02 Merge remote-tracking branch 'origin/master' into codex/builtin-media-baseline 2026-10-24 00:26:15 +02:00
Codex a37d9e5025 win32: restore owned display modes on their original outputs
Keep each changed output's original DEVMODE and last successfully applied mode. Teardown queries that named output and restores only while the applied mode remains current; external changes and disconnected outputs end ownership. A later successful switch after an external change saves that newer desktop state.

Allocate bookkeeping before applying a first change, preserve the first original across repeated owned changes, and free records after failure or teardown. This replaces primary-screen geometry and current-window restoration.

Legacy/current ANSI/Unicode profiles pass GCC/Clang ASan+UBSan, Wine and native Windows, covering two outputs, repeat switches, external changes, disconnect and allocation failure. An unconditional-restore negative control fails. Real Windows-header/read-only checks and actual Wine/Xorg listed-mode switching pass; physical multi-monitor acceptance remains open.
2026-10-24 00:25:50 +02:00
Codex 7c5a1e511c Merge remote-tracking branch 'origin/master' into codex/builtin-media-baseline 2026-10-24 00:21:34 +02:00
Codex 5c91c6a297 win32: keep mode enumeration on the captured monitor
Capture the monitor once for a resolution change, then read current settings, enumerate candidates and apply the mode to that same device. A window move must not combine one output's mode with another output's name.

Legacy/modern ANSI/Unicode profiles pass GCC/Clang sanitizers, MinGW/Wine and native Windows. The monitor-recapture negative control fails. Real Windows-header builds and read-only display queries pass in ANSI and Unicode; no physical mode was changed.
2026-10-24 00:21:14 +02:00
Codex e13540d39b Merge remote-tracking branch 'origin/master' into codex/builtin-media-baseline 2026-10-24 00:18:01 +02:00
Codex cf5a318410 win32: report rejected display mode changes as failures
Stop if current mode discovery fails and only report success after Windows applies the requested mode. No matching advertised mode, failed validation, failed application and registry-not-updated results must not be accepted as a successful refresh change.

Seven outcome cases pass strict C89 in legacy/modern ANSI/Unicode profiles with GCC/Clang ASan+UBSan, MinGW/Wine and native Windows. The previous setter fails the result assertion. Tests mock the mode API and do not change a physical display.
2026-10-24 00:17:38 +02:00
Codex 6bc9be5d67 Merge remote-tracking branch 'origin/master' into codex/builtin-media-baseline 2026-10-24 00:14:24 +02:00
Codex 4781239614 x11: identify the output under the current window
Return the connected output name and geometry from the same CRTC under the window center. Before window creation use the first active output; clear failed queries rather than leave stale results. Handle disappeared output records and bound copies by the RandR name length.

GCC/Clang ASan+UBSan fixtures cover two outputs, movement, query failure, off-output coordinates and short names; the old getter fails the new test. C89 compilation passes with and without RandR. A real isolated Xorg dummy server passes output identity, modeline switching and restoration; physical multi-monitor acceptance remains open.
2026-10-24 00:13:48 +02:00
Codex e3a48f3a16 Merge remote-tracking branch 'origin/master' into codex/builtin-media-baseline 2026-10-24 00:09:59 +02:00
Codex 14a0a9102f win32: report output identity with its captured geometry
Capture one monitor for both the output name and current display settings. Clear failed queries, handle missing monitors, and convert Unicode device names to bounded UTF-8. This supplies the output identity needed to bind future media display restoration to the correct monitor.

Exercise legacy and modern ANSI/Unicode paths, monitor movement, failures and bounded buffers with GCC/Clang sanitizers and MinGW. Native Windows ANSI/Unicode read-only queries, full frontend startup and the MSVC6 standin passed. No native physical display mode was changed.
2026-10-24 00:08:47 +02:00
Codex eefe17dd5b tests: supply the deferred shader cancellation boundary
The directory-cycle harness links the real shader parser without the video driver. Supply its new cancellation symbol and abort if the synchronous harness unexpectedly enters that path.

Validated the five directory-cycle scenarios with GCC and Clang under AddressSanitizer and UndefinedBehaviorSanitizer.
2026-10-24 00:08:17 +02:00
Codex 60de19825c Merge remote-tracking branch 'origin/master' into codex/builtin-media-baseline 2026-10-23 23:54:33 +02:00
Codex c0657f9b9f Merge remote-tracking branch 'origin/master' into codex/builtin-media-baseline 2026-10-23 23:50:23 +02:00
Codex 67d9d2e704 media: open external files in the fullscreen menu viewer
Prefer the internal viewer for supported movie and image arguments without an explicit core. Wait for content loading to finish before selecting the file in its browser directory. Keep unsupported formats, no-menu builds and explicit core requests on their existing paths.

Image confirmation now uses the fullscreen thumbnail viewer in all four menus and cannot launch an image-viewer core from that view. Add real startup/browser coverage and regenerate label fallbacks for menu-disabled builds.
2026-10-23 23:48:22 +02:00
Codex 1f5682f3bd media: offer fullscreen movie timing to CRT SwitchRes
Add a default-off Media Player option and publish dimensions and measured cadence to the existing display owner. Preserve core timing and hold a stable rate across seeks and timestamp quantization; withdraw the request when fullscreen closes or settings open.

Cover all four menu adapters, settings restoration, fractional rate changes, and concurrent numeric publication. Physical CRT scanout and desktop VRR acceptance remain separate gates.
2026-10-23 23:41:45 +02:00
Codex eb2c94efd7 vulkan: initialize published capabilities in import fixtures
Exercise the shipping capability publishers before checking imported formats. Cover immutable device support and revocation of the published format mask.
2026-10-23 23:19:55 +02:00
Codex a7961703e6 modeline: bound scale selection for invalid timings
A listed server mode with zero refresh makes the incrementing scale search loop forever. Compute the first candidate directly, reject non-positive and unrepresentable inputs, and guard integer and floating products.

Cover zero-rate mode rejection, integer overflow, extreme scales and fractional rounding. GCC/Clang sanitizers, MinGW64/Wine and native Windows tests pass; the complete modeline golden matrix remains unchanged.
2026-10-23 23:04:30 +02:00
Codex 0adb2302a9 x11: contain errors from rejected modeline requests
Do not forward a modeline connection error to Xlib after trapping it: the default handler terminates the process when a server rejects a mode. Forward only errors from other connections, preserving their handler result without contaminating the modeline failure flags.

Exercise rejected additions and unrelated connections through the real backend. Run the production handler separately on Windows without Xlib, and require the original handler to fail the regression assertions.
2026-10-23 22:50:23 +02:00
Codex 63ea9b5ae0 Batch file refills during builtin movie seeking
Expose the next MP4 packet span without reading media or advancing its cursor. Refill forward seek windows within the existing four-millisecond parser slice, capped at eight bounded reads, instead of yielding to the menu after each byte frontier. Preserve backward-frontier yields and decoder waits.

On the 4K60 AV1 sample, a rendered Windows Vulkan seek to 20:00.375 falls from 16.59 to 2.39 seconds. Repeated alternating seeks, all four rendered menu adapters, ASan/UBSan/TSan preview checks and codec regressions pass.
2026-10-23 22:22:20 +02:00
Codex f36c998b6c Merge remote-tracking branch 'origin/master' into codex/builtin-media-baseline 2026-10-23 21:50:40 +02:00
Codex a58bb21a15 Measure movie cadence from accepted presentation timestamps
Publish a bounded rational-rate estimate after 32 near-uniform intervals, tolerating millisecond timestamp quantization. Invalidate evidence on seeks, stopped playback and timing discontinuities. Carry measured cadence through shared CPU and texture playback state without querying decoder-owned data.

Cover variable timing, fractional and quantized rates, hardware backpressure and all four menu adapters. Display switching remains unchanged.
2026-10-23 21:49:59 +02:00
Codex a9b70a16aa Publish movie timestamps with presented player frames
Pass the existing decoder-owned timestamp through the threaded and inline presentation paths. Sinks can observe fractional movie timing without reading decoder state or inferring cadence from rounded frame durations. Cover leading gaps, loop epochs, unavailable timestamps and fractional intervals.
2026-10-23 21:37:59 +02:00
Codex 0a9d017609 Open media settings from fullscreen playback controls
Pause through the shared transport and open the existing Media Player settings list. Hide each menu viewer only after navigation succeeds and preserve browser return selection. Expose the action through mouse and existing keyboard/gamepad focus without adding input mappings.

Cover all four menus, failed pause/navigation, and threaded and inline playback.
2026-10-23 21:31:52 +02:00
Codex a9a6b925a8 Merge remote-tracking branch 'origin/master' into codex/builtin-media-baseline 2026-10-23 21:20:18 +02:00
Codex 356d382c11 Add continuous folder playback to the builtin menu player
Keep a natural-order folder snapshot for opt-in continuous playback and shared previous/next file controls. Distinguish clean movie EOF from decoder failure and retain the final picture for transport actions. Apply repeat policy to video and mixer voices without restarting playback.

Cover all four menus, threaded and inline playback, folder boundaries, invalid next files, Stop and Back.
2026-10-23 21:19:18 +02:00
Codex b2bd15fe7d Choose Android recording levels from picture rate and bitrate limits
Replace the fixed 1080p level split with AVC and HEVC picture-size, processing-rate, bitrate and dimension limits. Prefer HEVC main tier when it fits, retain explicit video_level values and refuse automatic requests beyond known limits. Native inventory and session admission remain mandatory.

Validate 1080p, 4K, 8K, fractional rates, bitrate boundaries, extreme aspect ratios and explicit overrides in both frontend profiles. Recording suites pass Windows GCC and Linux GCC/Clang ASan/UBSan; sixteen Android ABI/API/file-offset/history profiles compile under strict C89 and the 2048-byte stack ceiling. Physical device acceptance remains outstanding.
2026-10-23 18:27:43 +02:00
Codex 12f968b9fc Capture no-core menu recording from final video and mixed audio
Use full-window Vulkan menu capture with monotonic presentation timestamps and a bounded post-mixer PCM tap. Attach and retire the tap only through nonblocking cold operations on the existing audio owner lock; retain all I/O and GPU owners until independent retirement. Keep core AV information unchanged and Android native recording behind its explicit acceptance flag.

Validate the recording and shipping hook contracts on Windows and Linux, GCC/Clang ASan/UBSan and TSan, both NDK workflow matrices, object inventories and a default C89 frontend. Physical Android A/V and recovery acceptance remains open.
2026-10-23 18:20:56 +02:00
Codex 008b672bac Include Android recording objects in the source inventory contracts
The capture registry added a record-directory implementation include to Griffin. The inventory preprocessor only normalized gfx/common includes, so the JNI-contract CI tried to open the new file relative to stdin and failed before executing the remaining contracts.

Normalize every captured C implementation include into an object token, include the recording/frontend registry sections, and compare both real Makefile and Griffin inventories. Cover 24 valid codec/thread/recording profiles, require each selected object once, and verify that all eight threadless recording profiles are refused.

Validate the complete JNI-contract make check under GCC and Clang ASan/UBSan after reproducing the hosted failure. Production source, budgets and native capability admission are unchanged. The failing Clang job and cancelled GCC job from Android run 39980257395 are not passes.
2026-10-23 17:43:20 +02:00
Codex 4ef797f16d Publish Android recording history after successful native collection
Retain the original bounded output path in the cold frontend owner, independently of caller storage. Add it to video history exactly once after nonempty successful mux/file completion and GPU/I/O collection, with no session, file or system errors. Respect disabled or unavailable history and settings teardown; failed or cancelled output is never published.

Exercise history-enabled and history-free frontend profiles, deferred collection, caller filename mutation, path bounds, empty output, cancellation, native errors and missing settings/history. Compile the history branch explicitly in Android CI. All 23 suites pass Windows GCC C89 and Linux GCC/Clang ASan/UBSan; the native frontend passes 16 ABI/API/file-offset/history profiles and the exact new workflow command passes all eight history configurations. Native Android hardware acceptance remains open; ADB currently lists no connected device.
2026-10-23 17:36:28 +02:00
Codex 99c07ab0e0 Connect the Android recording frontend behind native acceptance opt-in
Wire the MediaCodec backend into viewport capture, PCM production, deferred startup, finalization and collection. Arm both clocks after render-owner acknowledgment, retain rational microsecond cadence independently of movie seeking, split bounded PCM packets without waiting, and fail rather than silently losing audio. Preserve PQ and convert scRGB once in the existing worker; unknown mastering metadata stays unknown.

Separate reset admission from full resource collection. A cold render-owner quiescence acknowledgment lets the existing Vulkan teardown drain prove ambiguous native work instead of deadlocking before that drain. It frees no native resources and grants no completion or device-death proof. Retain queued bindings, partial startup, GLES and I/O owners through collection; collect cancelled streams whose source end was already fixed by renderer failure.

Keep default registration off. Explicit HAVE_RECORD_MEDIACODEC builds require Android/Vulkan/threads/configuration support and link each native module once, including encoder-only device prerequisites. Test full frontend lifecycles, clocks, audio backpressure, output changes, queued closure and reset ownership with modeled platform boundaries.

Validate 22 suites on Windows GCC C89 and Linux GCC/Clang ASan/UBSan, reset guards, both eight-configuration NDK workflow groups including the enabled frontend and encoder-only variant, native registry compilation, actual Makefile profiles, C89 and ASan/UBSan shipping threaded-video harnesses, fresh CI frontend and unchanged hot/wait limits, MSVC6 stand-in and a full threadless frontend forcing 39 ownership APIs. Native hardware A/V/pixel/recovery acceptance, no-core player recording and final history integration remain open.
2026-10-23 17:25:58 +02:00
Codex 553d650782 Build Android capture configuration from enabled Vulkan device state
Resolve required native operations and copy memory properties into a stable cold controller. Require the enabled AHB/FOREIGN/SYNC_FD capability before admission; procedure availability alone cannot enable recording. Preserve the existing queue serialization contract and fd ownership without allocating native resources.

Publish immutable Vulkan device capabilities at initialization and use the existing atomic format cache for get_device, avoiding reads of mutable renderer flags and pipelines. Exercise the shipping getter concurrently and test resolver faults, admission, memory validation, submission and fd lifetimes.

Validate 21 recording suites on Windows GCC C89 and Linux GCC/Clang ASan/UBSan, getter ASan/UBSan/TSan and Wine profiles, both eight-configuration NDK workflow groups, full C89 threadless frontend with 38 forced ownership APIs, unchanged wait limits and MSVC6 unity stand-in. Actual Android recording hardware acceptance and frontend registration remain open.
2026-10-23 16:53:21 +02:00
Codex cd1963571c Marshal Android capture setup and collection through asynchronous render jobs
Reuse the shipping caller-owned upload queue for explicit cold operations, preserving opaque results through completion and teardown without treating them as textures. The Android capture request consumes this path for EGL/pool setup, service, closure, child preparation and collection. It uses the current Vulkan queue lock, drains queued binding references before closure, and retains stale-device owners until actual destruction proof.

Keep setup away from frame capture, copy cold memory properties, and guard unused GPU closure with the I/O claim. Validate Windows C89 and Linux sanitizer ownership suites, both Android NDK compile groups with final request rechecks, the real GL cold-job lane, TSan and ASan/UBSan shipping frontend lanes, full ASan/UBSan threaded harness, unchanged hot/wait budgets, MSVC6 stand-in and a full threadless link forcing 35 APIs. Recorder settings/audio-clock wiring and Android device acceptance remain open.
2026-10-23 16:22:18 +02:00
Codex a2744181aa Retire Android capture pools around actual Vulkan device destruction
Register each pool on its serialized render owner before native allocation, covering partial startup and recordings that have not published a frame. The existing Vulkan teardown drain supplies completion or device-loss proof. Prepare matching capture children before device destruction, refuse device caching while an owner remains, and notify source leases only after actual vkDestroyDevice. Failed closure freezes submission and service; unproven children retain the device.

Exercise matching and unrelated devices, partial preparation, failed drains, cache refusal, post-destruction callbacks and retained GLES reads. Validate 19 Windows/Linux recording suites, both NDK compile groups with final pool/registry rechecks in all eight configurations, and a full C89 threadless frontend forcing all 29 owner APIs. Android hardware recording acceptance and frontend registration remain open.
2026-10-23 15:45:56 +02:00
Codex cd33fb9062 Merge remote-tracking branch 'origin/master' into codex/builtin-media-baseline 2026-10-23 15:42:38 +02:00
Codex 3af4ff288d Declare std::terminate through its owning exception header 2026-10-23 15:42:38 +02:00
Codex a36a066959 Merge remote-tracking branch 'origin/master' into codex/builtin-media-baseline 2026-10-23 15:30:36 +02:00
Codex 4f07a54e5e Prepare single GL context ownership during poke installation 2026-10-23 15:30:36 +02:00
Codex 23132fdf7d Merge remote-tracking branch 'origin/master' into codex/builtin-media-baseline 2026-10-23 15:21:22 +02:00
Codex b90e83ac00 Merge remote-tracking branch 'origin/master' into codex/builtin-media-baseline 2026-10-23 15:11:10 +02:00
Codex 5e1a160d7f Connect Android capture pools to Vulkan frame submissions
Record final-image capture before presentation and report actual submission results under the existing queue synchronization. Bind stable pool leases, reject changed mode or extent, suppress duplicate timestamps and retain ownership on native errors. Expose the borrowed render queue for cold setup.
2026-10-23 15:11:09 +02:00
Codex eb1c4c979e Retire capture Vulkan objects independently of GLES reads
Keep Android buffers and the GLES executor owned after Vulkan child retirement. Freeze relay service once device preparation begins so partial teardown cannot poll destroyed child objects or prematurely collect their shared buffers. Require actual device destruction and terminal GLES ownership before collection.
2026-10-23 14:44:52 +02:00
Codex 4beb10789e Merge commit 'cd005310ec2347c91092cc717e327cd705df30cb' into codex/builtin-media-baseline 2026-10-23 14:36:12 +02:00
Codex 8de9161c6e Merge commit '01a88010' into codex/builtin-media-baseline 2026-10-23 14:22:06 +02:00
Codex 8995fac08a Merge commit 'c0e4be82' into codex/builtin-media-baseline 2026-10-23 14:20:24 +02:00
Codex 3b1631163e Defer video reinit until native recording resources retire
Finish native GPU recording before resetting its video device. Coalesce pending reinit flags and apply them once through the existing main-thread recorder poll after all native users retire. Drop superseded requests during content or quit teardown, with no waits, locks, frame allocations or new runloop call.
2026-10-23 14:18:52 +02:00
Codex c0e4be8251 Defer video reinit until native recording resources retire
Finish native GPU recording before resetting its video device. Coalesce pending reinit flags and apply them once through the existing main-thread recorder poll after all native users retire. Drop superseded requests during content or quit teardown, with no waits, locks, frame allocations or new runloop call.
2026-10-23 14:18:52 +02:00
Codex 4c3cf90d73 Merge commit '255f4be8' into codex/builtin-media-baseline
# Conflicts:
#	libretro-common/samples/formats/h265/Makefile
2026-10-23 14:03:48 +02:00
Codex fae6ebc8eb Route renderer capture ownership through the Android handoff
Keep uncommitted renderer work out of the encoder queue and expose record, commit and proven abort through the joint resource owner. Preserve unresolved source leases across cancellation so accepted work can still obtain completion proof without replay, and forbid native bookkeeping changes after device preparation.
2026-10-23 14:02:34 +02:00
Codex ffd49befc6 Record Android capture copies in renderer commands
Separate recording a capture copy from committing its completion after the renderer submits the frame. A dedicated empty command signals the existing exportable semaphore and fence; busy completion retries never replay the image copy. Retain source ownership until completion or explicit proof that an unsubmitted renderer command cannot execute.
2026-10-23 13:51:57 +02:00
Codex bd33b2ab28 Merge remote-tracking branch 'origin/master' into codex/builtin-media-baseline 2026-10-23 13:43:54 +02:00
Codex 9f87fa089b Own Android recording EGL display and libraries
Hold a balanced Android system EGL initialization reference and keep both procedure libraries alive through recording collection. Select exact RGB8 or RGB10 recordable configurations without changing the controller context. Retain failed termination ownership without repeating a potentially consumed reference release.
2026-10-23 13:42:31 +02:00
Codex a23020bbd5 Merge commit '4b4c0a8b' into codex/builtin-media-baseline 2026-10-23 13:41:39 +02:00
Codex ccac4dc5b3 Load Android recording EGL and GLES operations atomically
Resolve the Surface, capture and draw procedures during cold setup. Missing procedures clear every operation table while preserving the borrowed display, buffers and format configuration, so startup cannot use a partially refreshed table. Capability checks remain with the native owners.
2026-10-23 13:35:49 +02:00
Codex 58363d892a Own Android capture resources through joint retirement
Construct the fixed capture images, Vulkan entries, GLES executor and handoff under one cold owner. Retain partial startup ownership, keep I/O borrowed, and collect only after copy/read retirement. Preserve two-phase Vulkan device teardown and source-only completion checks without adding frame-path allocation or waits.
2026-10-23 13:06:37 +02:00
Codex 12659387f1 Merge remote-tracking branch 'origin/master' into codex/builtin-media-baseline 2026-10-23 12:47:57 +02:00
Codex d2b5462b87 Verify recording PCM across device stop and resume
Keep the stopped-output regression tied to recording behavior: all core callback styles must still deliver complete PCM to the recorder while device writes are disabled and after output resumes. Verify both frame counts and sample values.
2026-10-23 12:46:15 +02:00
Codex 5668d9afc4 Merge remote-tracking branch 'origin/master' into codex/builtin-media-baseline 2026-10-23 12:42:06 +02:00
Codex 52c80ab0be Expose completed capture source leases to the renderer
Provide a cached source-only completion check for renderer resource changes after production stops. Keep capture-buffer and device retirement separate, and verify multiple slots, reuse and outstanding GLES ownership.
2026-10-23 12:39:51 +02:00
Codex 6640500010 Retire original capture sources during open recording
Poll one unfinished copy per renderer service step even while the GLES consumer still owns its capture slot. Cache copy retirement separately from slot ownership so source leases do not remain pinned until slot reuse or recording closure.
2026-10-23 12:33:30 +02:00
Codex ea35cd2973 Merge remote-tracking branch 'origin/master' into codex/builtin-media-baseline 2026-10-23 12:28:25 +02:00
Codex c5fe577bcf Add shared Media Player settings below File Browser
Expose fullscreen seek interval and controls auto-hide preferences through shared menu navigation. Preserve the existing five-second seek and three-second hide defaults, keep zero-delay controls visible, and publish the configured seek interval for matching button captions. Exercise actual navigation, editing, persistence and kiosk visibility in all four menu drivers.
2026-10-23 12:25:48 +02:00
Codex 6394afc9c3 Exercise capture relay with actual entry and slot queue
Cover direct and deferred submissions, cancellation before queued copy execution, normal worker drain and device-loss retirement using the shipping capture entry, deferred Vulkan queue and GPU slot queue. Mock only native Vulkan and executor boundaries.
2026-10-23 12:12:56 +02:00
Codex 2cbd669641 Relay Android Vulkan captures to the GLES executor
Retain deferred submissions without replay, transfer ready descriptors once, and require both Vulkan copy and GLES read retirement before slot reuse and final cleanup. Keep producer closure separate from GPU completion and preserve ambiguous submission ownership.
2026-10-23 12:07:27 +02:00
Codex 8b3296ee7f Merge remote-tracking branch 'origin/master' into codex/builtin-media-baseline 2026-10-23 11:58:48 +02:00
Codex 35899e65e4 Resolve GLES oracle recovery callbacks at runtime
Use the already loaded GLES runtime for recovery callbacks so the software oracle does not require a development-library linker symlink.
2026-10-23 11:53:56 +02:00
Codex c4f0967df7 Recover recording reads with a terminal GLES fence
A missing slot fence can leave recording slots permanently held. Stop drawing and fence the same context stream once, then query completion without waiting. Retire source reads only after that fence signals; failures keep ownership and no frame is replayed.

Test fence creation/query faults, exact sync ownership and threadless execution. Run the shipping recovery on actual software GLES commands before pixel readback, with GCC and Clang and mandatory assertion checks.
2026-10-23 11:37:05 +02:00
Codex bcb51f41de Run Android recording GLES work on a separate executor
Create the recording Surface, fixed imports and draw resources on their own thread while codec output drains independently. Publish read retirement separately from Surface release and retain resources when completion cannot be proven.

Enforce one GPU executor per input window and collect it before the I/O owner. Validate dimensions and SDR/PQ configuration before startup. Exercise real slot queues, partial startup, cleanup retries and cancellation under sanitizers and TSan; extend native ABI and threadless checks.
2026-10-23 11:16:53 +02:00
Codex dbfcccfc6d Run Android recording I/O on an independent worker
Copy startup configuration and attach JNI on the I/O thread. Publish source ends and GPU retirement separately, coalesce nonblocking eventfd notifications, and keep native startup and finalization off producer threads.

Exercise concurrent submissions, closure during startup, cancellation, notification failures and thread faults. Compile the dispatcher in both native ABI/private-link groups; unsupported and threadless builds retain stubs.
2026-10-23 10:49:39 +02:00
Codex c473ac1470 Coordinate Android recording session ownership
Own encoders, PCM queue, mux and exclusive output on the independent I/O worker. Publish immutable producer ends separately from GPU read and Surface retirement; cancel without freeing resources still used by the GPU.

Cover startup rollback, delayed producer closure, ordered cancellation and file failures with a real PCM queue and concurrent producer notifications. Include the session in native ABI and private-link checks.
2026-10-23 10:31:29 +02:00
Codex e06385a2cc Separate Android encoder stop from GPU resource release
Allow terminal cancellation on the I/O owner while retaining the input window and codec until GPU retirement. Cache the native stop result, invalidate packet access and avoid repeating stop during final teardown.

Exercise held output, prior codec errors, EOS, stop failure and AAC cancellation with resource retention checks.
2026-10-23 10:20:03 +02:00
Codex c29a5ef860 Merge remote-tracking branch 'origin/master' into codex/builtin-media-baseline
# Conflicts:
#	.github/workflows/Android.yml
2026-10-23 10:07:45 +02:00
Codex df2cfda5d8 Own fresh Android recording output descriptors
Create native paths exclusively and validate fresh adopted descriptors before transferring ownership. Preserve existing files and caller descriptors on refusal, and perform final sync and one-shot close only on the cold I/O owner.
2026-10-23 09:57:21 +02:00
Codex cf0dd22df8 Configure and verify Android encoder color metadata
Match hardware admission and native configuration for explicit profile/level, SDR or HDR10 color aspects and static HDR metadata. Copy managed metadata ownership and reject contradictory output formats before muxing. Leave transfer conversion in the capture shader and do not request codec tone mapping.
2026-10-23 09:28:09 +02:00
Codex 5460adac99 Service Android capture frames through the GLES worker
Connect the fixed capture queue to readiness import, drawing, source-read fences and Surface posts. Hold queue slots through GPU retirement and refuse encoder EOS until closed input fully retires. Preserve partial Surface cleanup ownership and permit terminal context scopes after posting failures.
2026-10-23 09:10:45 +02:00
Codex dacf2d7a9f Draw Android recording RGB inputs with explicit transfer modes
Preserve encoded SDR and PQ values, and convert linear scRGB to PQ once using the existing capture primaries and luminance scale. Validate shader output independently on software GLES across native source formats and both orientations, alongside portable ownership and failure tests.
2026-10-23 09:00:08 +02:00
Codex f180af2c5a Merge remote-tracking branch 'origin/master' into codex/builtin-media-baseline
# Conflicts:
#	.github/workflows/Android.yml
2026-10-23 08:51:00 +02:00
Codex e3cc552c34 Import fixed Android recording textures with GPU retirement
Keep readiness-fd consumption separate from completed texture reads. Retain partial cleanup ownership and refuse capture-slot reuse until a zero-timeout completion query succeeds.
2026-10-23 08:46:23 +02:00
Codex 6c35eb1b64 Submit Android capture copies with retained GPU source leases
Record fixed-slot Vulkan copies and foreign ownership transfers without warm allocation or CPU waits. Retain source leases through copy completion, transfer readiness fds once, and prevent binary semaphore reuse after an unconsumed export failure.

Separate child-object destruction from final source/native-buffer retirement so device teardown remains ordered. Exercise direct and deferred submission, cancellation, device loss and repeated reuse with the real deferred queue. Keep mock EGL tests independent of X11 headers.
2026-10-23 08:29:15 +02:00
Codex 781853a905 Allocate fixed Android RGB capture image pools
Query native RGB external-memory import and usage support before allocating AHardwareBuffers. Own dedicated Vulkan images and memory for RGB8, RGB10 and FP16 capture, preserve optimal native usage flags, and unwind partial resources without CPU mapping.

Exercise allocation faults, buffer identity and reference ownership with portable mocks, and compile standalone and combined Android recording units across both ABIs and file-offset modes. GPU readiness, foreign ownership transitions and worker reads remain with the capture producer/worker integration.
2026-10-23 07:50:13 +02:00
Codex 78cc0196d9 Own worker EGL surfaces and exact encoder presentation times
Create a validated recordable ES3 context and encoder window surface on the recording worker. Preserve zero-based source PTS, restore prior GLES bindings after posting, and retain failed restores or native destruction for explicit retry. Accept promoted EGL 1.5 context/colorspace support and require explicit PQ output capability.

Exercise context, format, timestamp and cleanup faults with portable mocks; compile the Surface and combined recording unit against every Android SDK ABI/file-offset configuration. GPU capture and recording registration remain separate work.
2026-10-23 07:30:58 +02:00
Codex 766a365f61 Merge remote-tracking branch 'origin/master' into codex/builtin-media-baseline 2026-10-23 07:17:59 +02:00
Codex 01a54a9fd2 Coordinate Android video, AAC and MP4 worker ownership
Drain both encoders without losing borrowed packets while mux tracks start. Consume PCM incrementally across backpressure and finalize tracks with explicit source ends only after encoder drain. Exercise real PCM queue ownership with delayed codec formats and failures, and link the worker dependencies in the Android SDK matrix.
2026-10-23 07:10:54 +02:00
Codex d09da7b20e Link threaded codec dependencies in the AV1 Metal fixture
Build the thread pool and event count before the first Metal fixture link, including when native AV1 decoding is unavailable.
2026-10-23 07:04:23 +02:00
Codex dd38c0dab3 Encode worker-owned AAC PCM with sample-clock timestamps
Reuse encoded-output ownership for platform AAC-LC input. Submit one bounded native buffer at zero timeout, retain unconsumed PCM across backpressure, fill sample-clock gaps with bounded silence and fix EOS before retries. Validate actual output rate/channels and preserve the Android queue offset ABI.
2026-10-23 07:00:03 +02:00
Codex 2b12a040c2 Own Android MP4 muxing and explicit track end timestamps
Bind native video/AAC formats before start, borrow compressed packets only during worker writes, and preserve source timestamps and encoder order. Require explicit source end markers before finalization and keep native errors sticky. Extend portable fault tests and NDK ABI/unity checks; recording frontend and device output acceptance remain open.
2026-10-23 06:45:07 +02:00
Codex 6e28729120 Own Android Surface encoding and borrowed output on the worker
Create an explicitly admitted native encoder and input Surface with ordered partial cleanup. Pump one zero-timeout output action, retain packet borrows until acknowledgement, and preserve format/EOS/error ownership. Add portable fault tests and native NDK ABI/unity compilation; GPU capture and mux integration remain separate.
2026-10-23 06:35:34 +02:00
Codex b71ce6e61d Require libaom in macOS AV1 acceptance tooling
Use the Homebrew full FFmpeg formula when the current binary lacks libaom-av1. Check the selected encoder before generating grain and chroma fixtures.
2026-10-23 06:31:19 +02:00
Codex f5acdc1722 Query complete Android Surface encoder input formats
Add cold hardware admission for bitrate, fractional frame rate and COLOR_FormatSurface alongside profile and dimensions. Preserve decoder selection and exception ownership; exercise encoder filtering, rejection and local-reference bounds through real JNI.
2026-10-23 06:25:29 +02:00
Codex a56e1b006a Compile AV1 native fixtures when hardware is unavailable
Keep unavailable decoder probes distinct from native SDK and linker validation. Build Metal and container fixtures even after an unavailable probe, while preserving compiler failures and exit 77.
2026-10-23 06:21:53 +02:00
Codex f901aa40d4 Keep Darwin loader declarations visible in AV1 acceptance
Remove the unnecessary POSIX feature restriction, which hides RTLD_DEFAULT on Apple and prevents the native AV1 fixture from compiling.
2026-10-23 06:18:53 +02:00
Codex 93b3114029 Merge remote-tracking branch 'origin/master' into codex/builtin-media-baseline 2026-10-23 06:14:21 +02:00
Codex e0c48b473d Exercise native AV1 MP4 and WebM Metal playback
Check independent decoded-plane and FrameView pixels through container streams, alternating warm seeks, reopen and retained pool exports. Require one native session per stream and explicit native completion; portable probes report unavailable.
2026-10-23 06:13:38 +02:00
Codex 33996131a1 Release the AV1 fixture lease before Metal submission
Avoid an extra fixture reference masking consumer ownership during asynchronous GPU completion. Check that completion has not advanced before submitting the command buffer.
2026-10-23 06:07:22 +02:00
Codex 44d4e9c5ac Exercise AV1 decoder exports through native Metal movie conversion
Use the published backend, retained pool leases and shipping FrameView path for non-square decoded images. Check GPU plane hashes and independent chroma-aware RGB references across warm seek, renderer teardown and reopen. Keep raw decode and Metal acceptance modes distinct.
2026-10-23 05:58:50 +02:00
Codex 54b9271633 Distinguish AV1 native setup failures from unavailable hardware
Only known decoder availability errors return 77. Require the native completion marker before recording a successful device case, so an empty-success executable cannot produce a false acceptance result.
2026-10-23 05:40:50 +02:00
Codex 51dce116c6 Add native VideoToolbox AV1 pixel and seek acceptance
Compare eight generated 8/10-bit chroma/grain cases with independent decoded references. Require hardware decoding, preserve a native image across warm keyframe seek and retirement, and report unavailable devices separately from passes. Metal import acceptance remains separate.
2026-10-23 05:37:51 +02:00
Codex 2f7e664d88 Publish AV1 decoding through the shared Metal lifecycle
Use the retained renderer snapshot for AV1 startup and withdraw only its offer on matching renderer teardown. Extend native lifecycle checks across AV1-only and combined-codec builds in both ARC modes.
2026-10-23 05:28:40 +02:00
Codex 599c35c247 Merge remote-tracking branch 'origin/master' into codex/builtin-media-baseline 2026-10-23 05:24:55 +02:00
Codex 731d95f52c Add serialized AV1 dispatch and Metal backend admission
Follow the existing VP9 executor pattern with preallocated signal/timer sources and deferred retirement. Admit AV1 using retained renderer snapshots, bounded session storage, supported conversion metadata and current device generations.
2026-10-23 05:20:23 +02:00
Codex 823de371e8 Carry explicit AV1 chroma placement through Metal conversion
Keep placement in private leases and reconstruct explicit vertical/top-left chroma with bounded taps. Preserve legacy unspecified sampling and core HDR ABI. Extend native HDR references and macOS AV1 configuration/ownership checks; native execution remains pending.
2026-10-23 05:15:10 +02:00
Codex 8c5e4473cc Adapt AV1 owner output to retained Metal frame leases
Snapshot accepted display dimensions and grain parameters, preserve images on export backpressure, and reuse native sessions on restart. Exercise sequence mismatch refusal, show-existing output and leases held through decoder retirement with the mock pool.
2026-10-23 05:11:04 +02:00
Codex bdad7a1acd Own bounded VideoToolbox AV1 service and completion slots
Follow the VP9 queue and retirement pattern for staged AV1 samples. Retain native output independently of owner shutdown and reject stale callback cookies before marking errors. Cover backpressure, hidden drain, restart, delayed release and concurrent callback publication.
2026-10-23 05:08:24 +02:00
Codex ce6d74fc2d Stage bounded AV1 samples for VideoToolbox ownership
Normalize sized OBUs into preallocated packet storage, seal hidden/display batches, and retain borrowed bytes through the CoreMedia release callback. Exercise busy retries, overlapping input, allocation errors and delayed native sample release.
2026-10-23 05:04:27 +02:00
Codex 151a67d06d Keep AV1 sample presentation delay unspecified in Android setup
The sequence display delay counts frames; av1C presentation delay counts samples and needs container grouping information. Preserve the coded sequence delay without copying it into the configuration prefix.
2026-10-23 05:00:27 +02:00
Codex bfd3f884d0 Add cold VideoToolbox AV1 format and session setup
Validate the sequence and container color before building av1C and requesting a hardware-only Metal-compatible session. Keep sample presentation delay unspecified when only frame display delay is known.

Exercise portable configuration, setup allocation failures and partial native creation errors. Native Apple format execution and decoder integration remain pending.
2026-10-23 04:58:11 +02:00
Codex d355ae237b Merge remote-tracking branch 'origin/master' into codex/builtin-media-baseline 2026-10-23 04:42:49 +02:00
Codex e89a508e9a Add VA AV1 native pixel and seek acceptance runner
Generate 8/10-bit streams and independent FFmpeg pixel references for chroma placement and film grain. Exercise keyframe seek/reopen cycles, retained exports, native Vulkan import and owner retirement.

Keep portable fixture checks distinct from native acceptance, reject mismatched binary modes, and report unavailable hardware without counting a pass. Extend the shared import oracle to validate chroma placement.
2026-10-23 04:39:43 +02:00
Codex 7e8587cbcf Carry AV1 chroma placement through VA Vulkan conversion
Preserve signaled vertical and colocated chroma positions in private frame leases and reconstruct sampled planes at their specified locations. Clamp interpolation to the coded extent so allocation padding cannot bleed into the picture. Unspecified positions retain existing sampling; reserved values refuse.

Validate metadata lifetime and admission, 8/10-bit SDR/PQ/HLG GPU pixels, padding/crop boundaries, and a negative control that drops placement.
2026-10-23 04:20:52 +02:00
Codex 12d11e8322 Terminate generated Vulkan oracle headers with a newline
Clang rejects freshly compiled shader initializers under the strict C89 oracle flags when the generated include has no final newline. Write an explicit LF on every host while keeping the shader payload and pixel assertions intact.
2026-10-23 03:59:42 +02:00
Codex e736b5aa75 Merge commit 'bf777848c63eee8dd84f5bb94df2245d221d3038' into codex/builtin-media-baseline 2026-10-23 03:47:19 +02:00
Codex 7cce0be1a2 Verify packed settings table keys
Resolve label offsets from preprocessed blob declarations instead of dropping generated settings rows. Refresh the reference for ten already-landed settings and microphone row ordering, with all existing keys and defaults preserved. Cover packed and literal keys, ordering and missing labels, and run table and unity checks in settings CI.
2026-10-23 03:46:30 +02:00
Codex d2a2fdc37e Merge remote-tracking branch 'origin/master' into codex/builtin-media-baseline 2026-10-23 03:34:47 +02:00
Codex ab554a3ab7 Offer VA AV1 decoding on eligible Vulkan devices
Keep native Vulkan Video AV1 preferred and publish VA only for the actual DRM PRIME import device. Reserve 32 physical surfaces independently of the nine logical stream slots, bound cold sessions and defer native retirement. Detect AV1 SDK headers separately so absent headers leave HEVC and VP9 available. Exercise admission, epoch changes, retirement, threadless refusal and notification lifetime under sanitizers and TSan.
2026-10-23 03:34:17 +02:00
Codex 54c3407bb0 Own native VA AV1 startup and deferred retirement
Combine AV1 runtime, physical pool and decoder lifetimes without terminating while renderer exports remain owned. Keep partial startup retireable and retry termination without repeating native cleanup. Share source color admission with the decoder and cover 8/10-bit runtime faults, retained grain exports and nine-surface non-grain admission.
2026-10-23 03:22:44 +02:00
Codex 6695043ce9 Map VA AV1 references and retained grain outputs
Keep physical reference and grain display pairs for each logical AV1 slot. Preserve cached display images across inter frames and exported leases across logical reuse, with bounded BUSY and identical-packet retry when surfaces are held. Snapshot output geometry, grain and source color metadata, and defer decoder retirement until renderer ownership returns. Explicit chroma placement still refuses pending renderer support.
2026-10-23 03:15:57 +02:00
Codex d7182ccea7 Export completed VA AV1 display surfaces
Reuse the shared DMA-BUF descriptor and opaque frame ownership path with AV1 pool generation checks. Cover 8/10-bit grain display export, pending completion refusal, retained renderer ownership, close retries and duplicate native descriptors.
2026-10-23 03:04:15 +02:00
Codex 6ddc16eff2 Protect AV1 VA reference and grain surface ownership
Track native reads and both write destinations through bounded completion and retirement. Retained exports use incarnation tickets, and grained output cannot become a reference. Allocate picture and tile work storage with the cold pool instead of on the frame stack.
2026-10-23 03:00:42 +02:00
Codex 1dbb4ecb04 Track both VA AV1 submission destinations
Submit bounded typed picture and tile buffers with the shared VA transaction operations. Poll reference and grain display surfaces without waiting, retain uncertain work until display termination, and retry failed buffer destruction. Validate surface membership, tile spans and session geometry before native ownership.
2026-10-23 02:56:57 +02:00
Codex 2dbc25edaf Build native VA AV1 picture parameters
Map effective AV1 references, filters, restoration, global motion and film grain into typed VA buffers. Reject malformed state without changing caller output and keep separate grain display ownership explicit. Cover parsed 8/10-bit fixtures, reference mapping and bounded refusal without claiming native hardware decode.
2026-10-23 02:52:10 +02:00
Codex 2f26f56198 Build bounded native VA AV1 tile records
Translate validated whole-frame tile spans into SDK-native slice records using caller-owned storage. Recheck every span before publishing output and preserve output on refusal. Validate reserved fields and maximum capacity, and compare native offsets and sizes against independent AV1 traces on real coded streams.
2026-10-23 02:39:26 +02:00
Codex 5e5ba8b90c Merge master Exynos and CI package-cache updates 2026-10-23 02:22:48 +02:00
Codex 9a697765f8 Add VA AV1 session admission and runtime ownership
Admit AV1 profile 0 with 8/10-bit render formats and nine to thirty-two surfaces through the existing native VA lifecycle. Preserve partial initialization and retryable cleanup, and refuse AV1 before native ownership on older SDKs. Cover startup faults, boundaries and compatibility without registering an incomplete decoder.
2026-10-23 02:21:55 +02:00
Codex 89364ba943 Keep retained VA VP9 surfaces independent of logical slots
Map logical DPB slots to native surfaces on accepted decode, including references, previous pictures and output. Select an unheld surface when a logical slot is reused so retained renderer exports cannot stall otherwise available decode capacity. Cover remapped output and references, full 32-surface backpressure, retry and stale ticket refusal.
2026-10-23 02:13:48 +02:00
Codex 37fb6dd13e Add native VA VP9 pixel and import acceptance runner
Check profile 0/2 decode against independent fixture hashes and retain an exported frame across later pictures and decoder detach. Reuse the Vulkan modifier import oracle for conversion and exact-device ownership checks. Build both native variants in the Linux sample lane; require an explicit render node to execute them and report unavailable without a device.
2026-10-23 02:02:35 +02:00
Codex 6fa9e490f0 Register VA VP9 decoding on eligible Vulkan devices
Publish the parsed VP9 backend only for matching PRIME import devices, preserving native Vulkan Video preference. Retire decoder contexts off the frame path and retain outstanding exports across renderer replacement. Add factory admission and lifetime coverage, plus the deferred queue dependency for the native import oracle.
2026-10-23 01:57:19 +02:00
Codex 6a9886de64 Merge master shader compiler changes 2026-10-23 01:41:16 +02:00
Codex ac35f9fa36 Keep VA queue inclusion outside the Android unity block
Preserve the Android source-matrix anchor while compiling the shared conversion queue for non-Android VA builds. The sixteen codec/thread configurations and renderer lifecycle variants pass locally.
2026-10-23 01:40:03 +02:00
Codex 6a6b492626 Build VA VP9 ownership and defer Vulkan conversion submits
Add profile 0/2 parameters, bounded submission, surface generations and decoder retirement using the existing VA runtime and opaque export path.

Prepare VA imports on the cold owner and drain conversions under existing renderer queue serialization. Preserve exports on deferred or ambiguous GPU use, and find the required libdrm headers independently of KMS.

Native VA playback acceptance and VP9 frontend registration remain pending.
2026-10-23 01:38:12 +02:00
Codex b9392ebf46 Test VP9 WebM playback through the display API
WebM skip intentionally discards display output. Use next for the native playback oracle so its frame, pixel, seek and retained-lease assertions exercise exported frames.
2026-10-23 01:17:30 +02:00
Codex 20ebf595cd Merge remote-tracking branch 'origin/master' into codex/builtin-media-baseline 2026-10-23 00:35:07 +02:00
Codex a4b20df274 vulkan: collect retired Android imports during cold setup 2026-10-23 00:35:07 +02:00
Codex 7744736d83 Merge remote-tracking branch 'origin/master' into codex/builtin-media-baseline 2026-10-23 00:29:26 +02:00
Codex ab25c98feb media: defer Android imports through the renderer submission queue
Drain conversions under the existing queue lock, retain queued leases until completion, and prepare native identities through owner resource work before warm imports.
2026-10-23 00:29:21 +02:00
Codex de524e63d9 media: reconcile public Vulkan Android warm path 2026-10-23 00:05:13 +02:00
Codex 4613d47bc0 vulkan: make Android warm-path ownership checks portable
Run the positive-FD ownership and retirement cases on Windows CRT pipes while retaining real POSIX paths on Linux and Android. Add a portable warm-path runner, strict NDK coverage and project formatting without changing the production ownership algorithm.
2026-10-23 00:05:11 +02:00
Codex f01298f246 media: reconcile public Vulkan Android resources 2026-10-22 23:54:11 +02:00
Codex 8465e993b4 vulkan: validate portable Android import resource contracts
Use portable model and pixel runners, execute embedded and fresh-source shader oracles, and extend strict Android ABI coverage. Apply the project style and distinguish legacy descriptor reservation from native admission.
2026-10-22 23:54:08 +02:00
Codex 2c21231c86 media: reconcile public Android color export 2026-10-22 23:44:09 +02:00
Codex 616b915dc3 android: align color export with shared VP9 metadata
Use the published libretro-common VP9 color resolver, add a portable three-codec export runner and extend Android ABI coverage. Apply the project style without changing the metadata or lease contract.
2026-10-22 23:44:06 +02:00
Codex f8ee96bc96 media: reconcile public Android frame leases 2026-10-22 23:40:18 +02:00
Codex 266761837b android: make opaque frame lease checks portable
Use RetroArch threads on Windows and Unix, preserve native Linux fence checks and exercise actual stale generation reuse. Add a portable lease/HDR runner and Android ABI coverage with the project code style.
2026-10-22 23:40:14 +02:00
Codex e8e2ff3396 media: reconcile public codec worker sessions 2026-10-22 23:36:06 +02:00
Codex fe199993a3 android: make worker session contracts portable
Add a temporary-output session runner for Windows and Unix, test codec-configuration output and timestamp overflow, and check session SDK signatures in the Android ABI matrix. Apply the project code style.
2026-10-22 23:35:38 +02:00
Codex 035e1b0a53 media: reconcile image ownership with current master 2026-10-22 23:31:33 +02:00
Codex be3e85bb5b android: reject empty native image acquisitions
Do not publish an image wrapper when the native API reports success without an AImage. Keep the reader fault test portable, apply the project style and give image ownership its own CI job.
2026-10-22 23:31:06 +02:00
Codex 95adc8c060 android: make image ownership tests portable
Use RetroArch threads for concurrent image returns and exercise no-fence lifecycle contracts on Windows while retaining native-fence polling tests on Linux. Apply the project style to the queue and its tests.
2026-10-22 23:28:30 +02:00
Codex 3cbfdbb91e android: make codec inventory contracts portable
Run the real JVM tests from temporary output directories on Windows and Unix, retain separate inventory CI coverage, and apply the project code style.
2026-10-22 23:25:03 +02:00
Codex f89b4a8fb7 vp9: reuse native sessions across key-frame seeks
Append an optional nonblocking restart callback. Drain discarded work before resetting reference state, preserving exported leases and avoiding repeated VideoToolbox session creation. Cover pending restart, repeated seeks and restart failure.
2026-10-22 23:20:53 +02:00
Codex 8218c1923a media: reconcile hardware migration with current master
Preserve the current shader and CI layout and published backend prefixes while integrating the pending platform decoder work. Keep device acceptance on the candidate branch.
2026-10-22 23:05:02 +02:00
Codex ff7cef3da1 av1: withdraw only the retiring backend offer 2026-10-22 22:52:36 +02:00
Codex 620405d736 test: distinguish VP9 reopen and output failures 2026-10-22 22:45:20 +02:00
Codex 1482dab102 av1: preserve the published backend prefix 2026-10-22 22:41:26 +02:00
Codex b316823902 media: add explicit play and pause actions 2026-10-22 22:33:08 +02:00
Codex d89cec4bdf vp9: match published calloc argument ordering 2026-10-22 22:28:21 +02:00
Codex a93d2c4663 test: identify VP9 seek output refusal 2026-10-22 22:21:36 +02:00
Codex 9a22d549c1 test: guard immutable CoreFoundation mock values
Assert non-null retained values and return before atomic object refcounts for mock CF booleans. This preserves the real constant-ownership contract and avoids GCC15 treating sentinel booleans as possible objects. Windows64 and Linux sanitizer mock suites pass without disabling warnings.
2026-10-22 22:13:44 +02:00
Codex 9fd92b3a68 video: declare VP9 color properties to VideoToolbox
Supply primaries, transfer, matrix and full-range CoreMedia format extensions in addition to vpcC. Native run39842936896 decoded BT709 content into buffers tagged SMPTE-C/BT601, causing strict Metal import to reject the first frame. Preserve coded/container metadata validation; do not override output attachments or weaken import checks. Verify explicit SDR/sRGB/PQ/HLG and range properties plus unchanged setup failure cleanup.
2026-10-22 22:05:33 +02:00
Codex a35a2b36da vp9: correct Matroska vertical chroma mapping
Map left/half-row siting to VPCC vertical 4:2:0 and top-left to colocated 4:2:0. Reject horizontal-half descriptions that neither supported VPCC value represents. Fix the swapped-axis candidate logic and cover both valid locations plus horizontal-half software fallback against the published container/binding definitions.
2026-10-22 22:05:33 +02:00
Codex 300be42cea vp9: preserve replacement offers during renderer retirement
Use conditional registry withdrawal for Vulkan and D3D11 shutdown. Verify that a stale backend cannot withdraw a newer offer and that a subsequent open snapshots the replacement. This is cold lifecycle work; decoder frame processing is unchanged.
2026-10-22 21:52:35 +02:00
Codex f1ddffb95a test: diagnose native VP9 CoreVideo export refusal
Instrument the extracted acceptance-only Metal export failure branch with native pixel format and borrowed color attachments. Require an exact shipping-source anchor; preserve production code and export return behavior.
2026-10-22 21:42:22 +02:00
Codex 1f53fc376c test: identify native VP9 stream output failures
Print the active container/color/repeat case and stopped output count before the existing strict assertion. Keep diagnostics in the acceptance fixture; do not change native decoder behavior or weaken expected output.
2026-10-22 21:37:02 +02:00
Codex 166835d878 vp9: preserve published backend prefix for color extensions
Keep user/open/close/packet_decoder offsets when appending color, drain and configuration callbacks. Update positional test offers and verify mode offsets plus callback append placement with C89 compile-time checks. This aligns the pending native backends with the incrementally landed master API.
2026-10-22 21:35:50 +02:00
Codex a4ef3b5f4a h264: remove unused variadic dispatch macro
The bit-depth wrappers already call the typed C89 functions directly. Remove the unused RH264_K2 definition and undef so Apple Clang can compile the pipeline support objects with strict C89 warnings as errors. No decoder call sites or generated code change.
2026-10-22 21:25:20 +02:00
Codex 6a2aa0b6e9 vp8: retain legacy MSVC support in stack helper
Match the existing VP9 noinline compiler guard: declspec(noinline) requires MSVC 1300 or newer. Older compilers keep the portable helper definition. Apple/GCC/Clang paths are unchanged.
2026-10-22 21:24:18 +02:00
Codex 725afac1f5 vp8: bound picture-header setup stack usage
Keep the inherited probability/reference snapshot in a separate non-inlined reset helper, outside picture-header parser temporaries. Preserve the same state and arena without new allocation. Apple Clang reported a 2240-byte rvp8_begin frame under the 2048-byte LRC budget. GCC/Clang sanitizer pixel comparisons across repeated key/inter frames and four ABI stack builds pass; native acceptance remains required.
2026-10-22 21:18:57 +02:00
Codex 5bb8d13270 test: cover hidden VP9 pictures and native repeat packets
Add valid hidden-keyframe/show-existing superframes to every MP4/WebM depth and color fixture. Independently verify with FFmpeg that visible output remains byte-identical. Carry the cases through native Metal plane/color, repeated-seek and retained-export acceptance. Host software pixel and seek checks pass; native execution remains pending.
2026-10-22 21:14:23 +02:00
Codex 1e1bc0d3e4 test: exercise native VP9 Metal playback and repeated seeks
Run the shipping Metal decoder factory, MP4/WebM streams and FrameView conversion against independent decoded-plane CRC and color references. Cover 8/10-bit SDR, PQ and HLG, alternating seeks and an exported frame retained through stream and pool close. Keep GPU waits and readbacks confined to acceptance fixtures; require native execution and report missing devices separately.
2026-10-22 21:08:30 +02:00
Codex d74c08de2c test: execute native VP9 hardware pixels and retained output
Decode fixed lossless 8/10-bit pictures through the shipping owner and a separate serialized worker. Compare native planar output against FFmpeg-derived references, reopen sessions and retain decoded images through retirement. Report unavailable hardware explicitly without claiming pixel acceptance.
2026-10-22 20:34:29 +02:00
Codex 6d418fd6a5 ci: recover stalled VA oracle dependency downloads
Apply bounded package download and mirror recovery to the VA matrix job after its dependency installation exhausted the deadline before any tests ran. Preserve all test commands and reserve time beyond dependency recovery.
2026-10-22 20:22:47 +02:00
Codex 48c17098a6 mp4: connect VP9 hardware sessions to stream playback
Use configuration-aware cold admission and bounded superframe submission with retained input on BUSY. Preserve output leases, drain terminal packet decoders, and reopen from retained initial headers during seeks. Keep software fallback and reject coded color changes before native submission.
2026-10-22 20:22:47 +02:00
Codex 4d9642f844 mp4: resolve VP9 configuration and coded color metadata
Interpret retained vpcC before decoder setup and reconcile coded profile, depth, subsampling and range with container color. Preserve mastering and light metadata across rewind, and refuse contradictory descriptions. Add portable metadata and configuration ownership regressions.
2026-10-22 20:09:24 +02:00
Codex 4891a5de59 ci: recover stalled Metal mock dependency downloads
The Metal mock job exhausted its ten-minute deadline while installing packages, before any test ran. Use the established bounded download and mirror recovery recipe, and allow its existing twenty-minute dependency bound plus test execution. Preserve every test command.
2026-10-22 20:02:08 +02:00
Codex e02fc1a467 ci: budget graphics job for dependency recovery and full regressions
A healthy preview pass took 8m40s in the prior successful job. After mirror recovery, the next run reached that step with only 7m28s before its 30-minute job cutoff. Allow the existing 20-minute dependency bound plus the measured regression workload and setup overhead; preserve every test command and per-command timeout.
2026-10-22 19:31:14 +02:00
Codex ef4956ee82 vp9: carry container chroma through hardware admission
Add an append-only cold configuration callback while preserving legacy callback selection. Carry Matroska siting to VideoToolbox codec setup, reject truncated or unrepresentable descriptions, and retain resolved coded/container color for presentation. Share the existing renderer color policy with the stream layer without changing the core HDR ABI.
2026-10-22 19:30:27 +02:00
Codex ee5eed049b vp9: parse container codec configuration records
Validate vpcC FullBox bounds and profile, depth, chroma and level constraints while preserving raw CICP values for later admission. Register the portable configuration regression in the common sample runner.
2026-10-22 13:44:54 +02:00
Codex 415735875e ci: bound shader dependency downloads and retry stalled mirrors
Use the existing formats-job package download and mirror fallback pattern in the two shader jobs that stalled before checkout. Fetch under a bound before installation, retain visible package output, and preserve all shader steps and the existing overall job deadline.
2026-10-22 13:39:49 +02:00
Codex b8b6fca0b2 mp4: preserve VP8 and VP9 codec configuration metadata
Retain the complete vpcC box for later color and chroma admission even when codecInitializationData is empty. Cover configuration ownership after input release and allocation failure for both sample entries.
2026-10-22 13:36:52 +02:00
Codex 2891916ad7 tests: keep combined Metal codec declarations C89 compatible 2026-10-22 13:32:55 +02:00
Codex eea612586b metal: publish VP9 and HEVC decoder offers from shared lifecycle
Reuse the existing retained setup snapshot and lifecycle lock for both codecs. Keep VP9 independent of MP4 support, resolve optional macOS registration at setup, and preserve compare-withdrawal of foreign offers. Exercise native lifecycle builds with each codec and both ARC modes.
2026-10-22 13:29:26 +02:00
Codex c7622782cc video: share retained Metal decoder capability snapshots
Reuse the HEVC setup lock and publication ordering for device, HDR capability and nonwrapping generation snapshots. Keep owner queries lock-free and expose the same snapshot contract to VP9 without a HEVC dependency.
2026-10-22 13:10:45 +02:00
Codex f036a9c764 video: admit VP9 sessions with stable Metal capability snapshots
Build the packet-decoder backend around an owned device/HDR snapshot and validate its generation before and after native setup. Balance failed setup resources and retire stale sessions through the existing executor.
2026-10-22 12:52:17 +02:00
Codex c047f15028 video: schedule VP9 native work with preallocated dispatch sources
Use coalesced source wakes for serialized native submission and deferred retirement. Retain the close-notification source across concurrent cancellation and use a setup-time timer for outstanding native block retirement. Check native C, ARC and MRC SDK builds separately from host dispatch models.
2026-10-22 12:40:17 +02:00
Codex f90e2de655 queues: bound POSIX SPSC shared-memory name formatting
Use snprintf and reject formatting failures before shm_open. This keeps the shared-memory setup branch buildable with modern Apple SDK deprecation warnings treated as errors.
2026-10-22 12:26:38 +02:00
Codex bda2fbea3f video: export VideoToolbox VP9 output through Metal pools
Connect the VP9 packet-decoder contract to the bounded native owner and retain images across busy Metal exports. Discard superseded display results by generation so reused DPB slots cannot return stale pictures, while exported leases survive deferred retirement.
2026-10-22 12:24:37 +02:00
Codex e20ba583e4 video: connect bounded VideoToolbox VP9 owner service
Queue startup-owned compressed batches for serialized native submission, preserve final visible output and recycle storage only after submission and native block release. Close admission before deferred retirement and keep owners alive while native blocks remain retained.
2026-10-22 12:16:18 +02:00
Codex 9c3e97f739 video: guard VP9 completion slots against stale callbacks
Match callbacks with nonrepeating generation cookies and claim completion atomically. Transfer retained images to the owner and keep null-image success separate from visible output; retiring owners keep slot pools until native callbacks quiesce.
2026-10-22 12:04:08 +02:00
Codex 77ef1c7d95 video: pin VP9 sample storage through native block release
Create and submit CoreMedia samples on the serialized worker. Keep staged input pinned until CoreMedia releases its block, independently of decode callbacks, and suppress display output for hidden-only batches.
2026-10-22 11:57:00 +02:00
Codex 0f0ee60703 video: stage bounded VP9 batches for VideoToolbox
Own accepted packet bytes in startup-provided storage and emit Annex B indexes. Seal on display, capacity or terminal input, retaining unaccepted input for retry without growing buffers.
2026-10-22 11:51:52 +02:00
Codex bcc0d9bdcb video: create hardware-only VideoToolbox VP9 sessions
Build CoreMedia VP9 formats and request Metal-compatible IOSurface pools. Release partial format/session state on setup failures and keep supplemental decoder registration optional for older platforms.
2026-10-22 11:47:32 +02:00
Codex cbbf6d2a43 video: share VP9 color admission and build VideoToolbox configuration
Reuse the existing coded/container color intersection for Android and the new VideoToolbox configuration helper. Build the versioned vpcC payload for 8/10-bit 4:2:0 with explicit chroma position, unspecified level and preserved metadata. Reject reserved color fields, conflicts and unsupported output layouts rather than guessing HDR.

Add portable strict-C89 configuration conformance to Linux and macOS CI. GCC/Clang configuration and full MediaCodec sanitizers, Windows64 runtime and sixteen Android compile objects pass. Twenty measured frames remain below2048 bytes. Win32 compiles but Windows security blocked copying its executable, so Win32 runtime is not claimed. Native VideoToolbox session/packet/Metal integration remains separate work.
2026-10-22 11:34:57 +02:00
Codex 1d862204fa av1: retain admitted startup sequences for container reopen
Keep the actual sequence bytes after compatible pre-frame admission changes, so MP4 and WebM seeks reopen with the accepted decoding flags even when original packet input is temporarily unavailable. Bound owned configuration to 1 MiB, reuse identical storage, and free it with the outer stream. Retention runs only during cold sequence admission; later sequence changes remain rejected.

Preserve configured level/tier, close cleanly if retention fails, and cover borrowed-input overwrite and ownership. Reproduce stale reopen state in MP4 and both WebM startup modes, then validate GCC/Clang sanitizers, ten Windows32/64 runtime cases, C89 frontends, seventy-two strict stack objects and sixteen Android root links.
2026-10-22 11:24:26 +02:00
Codex e8733ab179 android: publish AV1 MediaCodec through Vulkan renderer admission
Select the AV1 native owner and backend in Makefile and Griffin builds, including AV1-only configurations. Extend shared device/import/cache gates, publish only after Android image admission, preserve Vulkan Video preference and withdraw with renderer lifecycle ownership.

Exercise all sixteen codec/thread source and renderer lifecycle combinations, including init retry and stale teardown. Validate GCC/Clang sanitizers, thirty-two Windows runtime cases, both C89 frontends and sixteen combined/AV1-only native Android root links. Vulkan continues to require threads; threadless source/registry exclusion is tested separately.
2026-10-22 11:10:53 +02:00
Codex f2512de8b5 av1: drain native output across terminal hidden packets
Let packet decoders accept hidden input while retaining one visible output, then drain only at real container EOF. Preserve presentation timestamps, HDR metadata and native format snapshots across delayed output. Keep surface backends on their existing contract.

Cover partial input, terminal BUSY and failure, discard and rewind in MP4/WebM, plus native format changes while an image is pending. Validate GCC/Clang sanitizers, Windows32/64 runtime cases, two C89 frontends, sixty strict stack builds and eight Android native root links.
2026-10-22 11:01:49 +02:00
Codex a8286d93fa media: add bounded MediaCodec AV1 backend and registry
Normalize admitted sequence OBUs into validated av1C during cold startup and export native-grained NV12/P010 with reconciled color metadata. Bound active and retiring sessions, retain notifier ownership through deferred cleanup, and reject stale opens and foreign withdrawals by publication epoch.

Cover configuration normalization, admission failures, retained exports, color conflicts, pool reuse and concurrent renderer replacement. Factory publication and container terminal drain remain separate integration work.
2026-10-22 10:21:13 +02:00
Codex 19f37ff656 av1: admit hardware with sequence and container metadata
Pass borrowed sequence OBUs and known color/HDR metadata through an optional factory admission callback. Preserve legacy surface factories and snapshot the admitted packet mode.

Retain the initial WebM sequence bytes during cold admission so decoder reopens do not depend on the feeder keeping startup bytes available. Exercise both admission paths through MP4/WebM seek, HDR and reduced-availability cases.
2026-10-22 10:05:30 +02:00
Codex 1758ffd469 media: add the MediaCodec AV1 Surface decoder owner
Validate av1C sequence admission and reconcile coded/container color before native startup. Correlate hidden and repeated-display packets with Surface images using bounded local timestamps, preserve grain and crop snapshots, and defer destruction until exported leases retire.

Cover malformed configuration, input and output BUSY, PQ/HLG, grain mismatch, cropped output, delayed EOS delivery and retained leases. Include the owner in native Android signature and root-link checks; factory registration remains separate.
2026-10-22 09:50:41 +02:00
Codex 4f7abb7734 av1: submit repeated display to opaque packet decoders
Snapshot an explicit packet-decoder mode at stream admission. Submit show-existing header OBUs before committing reference refresh, preserving immutable BUSY retries, cancellation and independent output leases. Existing surface factories keep output-only repeats.

Exercise both modes against generated 8/10-bit reordered and grain streams, including invalid repeats, and verify packet acceptance before keyframe reference refresh.
2026-10-22 09:41:54 +02:00
Codex dcfdc099d6 vp9: bound reconstruction and probability decoder stacks
Keep predictor and coefficient-band expansion out of reconstruction and header frames. Reuse inverse-transform scratch after its previous use has ended for coefficient token decoding, without changing decoder size or allocating per frame.

Check whole-decoder C89 stack budgets with and without workers, compile both Android ABIs, and compare 8/10-bit cropped and tiled output against FFmpeg.
2026-10-22 09:32:28 +02:00
Codex 939c5ed088 media: publish admitted Android decoders from Vulkan
Publish HEVC and VP9 MediaCodec registries only for frontend devices with Android image import enabled, preserving working Vulkan Video codecs. Withdraw offers before renderer teardown and link shared imports independently of MP4 so VP9-only builds work. Keep the worker JNIEnv declaration available without legacy JNI macros and check Makefile/Griffin feature gates.
2026-10-22 08:53:09 +02:00
Codex bdb9f5a3ee media: drain terminal VP9 packets before ending WebM playback
Preserve the last visible output across trailing hidden packets and signal terminal input only after confirmed demux EOF. Poll EOS and delayed Surface delivery without waiting, retain exported leases, and keep the existing timestamp-table shortcut. Leave surface backends on their existing output path.
2026-10-22 08:34:54 +02:00
Codex d0bd247a9a media: bound MediaCodec VP9 backend retirement and registry
Own exporter pools and deferred decoder cleanup through bounded session admission. Preserve color-aware packet decoding, keep retained frame leases alive across close, and reject opens whose registry generation changes during native startup. Add conditional VP9 offer withdrawal so stale owners cannot remove a replacement backend.
2026-10-22 08:22:04 +02:00
Codex c11aae0554 media: pass WebM color metadata into VP9 admission
Allow color-aware factories to inspect container metadata before native startup. Keep legacy factories and their close snapshots, and clean up refused or incomplete opens without disabling software playback.
2026-10-22 08:14:44 +02:00
Codex 27c38530e2 media: preserve VP9 container color through MediaCodec export
Intersect VP9 color-space/range syntax with container metadata without inferring a transfer curve. Supply explicit Android input color hints and select the ten-bit HDR profile for PQ/HLG. Carry the immutable metadata to the output adapter and reuse the shared frame exporter with native-color conflict refusal. Cover input hints, PQ/HLG leases, mastering/light refinement, depth mismatches and retirement while retaining HEVC behavior.
2026-10-22 08:04:56 +02:00
Codex bc1cdfa00d media: own MediaCodec VP9 Surface output and retirement
Add a bounded VP9 decoder owner using the existing MediaCodec session and Android image queue. Match visible and repeated output by local timestamps, discard late hidden or dropped images, and retire codec resources only after leased images return. Cover input backpressure, EOS, startup faults and deferred release. Include the owner in native Android signature/link checks and update the optional-extension fixture for Linux PRIME and XR.
2026-10-22 07:44:24 +02:00
Codex 63f30c9569 media: preserve VP9 repeat packets for opaque decoders
Add an explicit packet-decoder factory mode so opaque codecs receive show-existing packets without committing new DPB state. Retain output-only repeats for surface backends. Cover hidden references, repeat backpressure and cancellation, WebM superframe/lacing dispatch and output surviving close.
2026-10-22 07:25:52 +02:00
Codex a153d185ff test: compare real VA imports through Vulkan conversion
Extend the explicit native modifier gate through production submission, fence retirement and RGBA readback against an independent SDR reference. Restore claimed exports after GPU retirement to retain the parser-close lifetime checks. Exercise the new readback oracle with synthetic Vulkan planes in host CI and reject corrupted reference pixels; native VA device acceptance remains separate.
2026-10-22 07:17:04 +02:00
Codex 6e1b2318c8 test: import real VA exports on the matching Vulkan device
Add an explicit Linux modifier allocation gate to the native decode oracle. Require reported render-node identity, import both planes through production helpers, and retire each import before returning its VA claim, including retained outputs after parser close. Keep GPU sampling and pixel acceptance separate from allocation success.
2026-10-22 07:17:04 +02:00
Codex 4874c400c7 test: cover real weighted WPP HEVC streams in VA acceptance
Add four reproducible Main8/Main10 full/limited weighted-B streams with multi-row WPP and independent FFmpeg CRCs. Preserve the reference range explicitly and exercise every picture through software decode and NORMAL subset submission.

Extend the required-device oracle to 192 pictures and verify WPP entries in NORMAL mode. Native hardware success remains unexecuted without a DRM render node.
2026-10-22 07:17:04 +02:00
Codex b9198c8886 test: add required-device VA HEVC decode and export oracle
Build a Linux native oracle against the production decoder owner. Require hardware admission, compare display-order NV12/P010 output to the existing FFmpeg fixture CRCs, retain an exported surface after source close, and check FD retirement across reopened sequences.

Keep CPU image transfer and bounded polling inside the test executable. Independently validate cropped/padded image checksums and reject unavailable devices or assertions-disabled acceptance builds. The hardware success path remains unexecuted on this host without a DRM render node.
2026-10-22 07:17:04 +02:00
Codex 9d12250174 vaapi: connect NORMAL HEVC decoding to the native owner
Select NORMAL when advertised or when the API default applies, retaining BASE-only support. Preallocate both packet workspaces on the cold owner and release the parser borrow at decoder detach before deferred native retirement.

Exercise both pool modes, parsed owner decode and held exports after parser destruction, four allocation failures and partial-context termination retry. Share parser objects across the host fixtures, with separate TSan instrumentation.
2026-10-22 07:17:03 +02:00
Codex bfff7f90df vaapi: pack NORMAL HEVC slices and own WPP subset buffers
Pack escaped NALs and coded entry offsets into a preallocated owner workspace after validating every header and extent. Extend native submission to retain a fifth subset buffer through failure, completion and retirement.

Exercise Main8/Main10 I/P/B WPP packets, parsed sequence submission and all five buffer failures under sanitizers and strict C89 builds. Shipping pool/owner selection remains separate work.
2026-10-22 07:17:03 +02:00
Codex 42b32da45e vaapi: map parsed HEVC long-slice parameters
Map coded short- and long-term reference order, list modifications and effective prediction weights into VA long-slice metadata. Preserve output on refusal and account for escaped header bytes without allocation or native operations.

Exercise independent boundary/refusal cases and 64 parsed Main8/Main10 I/P/B pictures. NORMAL packet packing and native owner integration remain separate work.
2026-10-22 07:17:03 +02:00
Codex 87224b14b4 rh265: expose parsed long-slice metadata
Publish slice flags, reference-list modifications, effective prediction weights and WPP entry lengths without pixel or DPB advancement. Keep output storage separate from existing access-unit state and exercise the query through the byte-exact decode oracle.
2026-10-22 07:17:03 +02:00
Codex 90c70f94fc video: register the VA HEVC fallback factory
Keep a stable offer with atomic DRM identity and renderer epochs. Resolve the actual render node through libdrm, preserve Vulkan Video preference, and transfer closed or partially opened owners to bounded cold retirement workers. Refuse admission without threads or lock-free atomics.
2026-10-22 07:17:03 +02:00
Codex 61db8ae149 video: preserve track HDR metadata in VA imports
Accept the supported YUV flag bits, carry container HDR metadata into conversion, and allow track-visible dimensions within the decoded surface. Preserve specified coded color while using track metadata for unspecified values and light levels.
2026-10-22 07:17:03 +02:00
Codex b75a7f373c video: own cold VA session setup and deferred cleanup
Keep the runtime, native pool, packet workspace and decoder adapter under one owner. Preserve partial setup failures and renderer-held exports, poll pending work once per retirement pass, and retain libraries and context through termination retries. Registration and executor policy remain with the factory.
2026-10-22 07:17:03 +02:00
Codex fdd27c04ce video: adapt VA HEVC pool completion to opaque frame leases
Connect parsed-picture submission and bounded completion polling to opaque output carriers. Keep crop/color metadata immutable and detach without freeing renderer-held frames; the decoder owner retries native returns before retiring the adapter. The native pool, session and runtime stay explicitly borrowed.
2026-10-22 07:17:03 +02:00
Codex d4932e8f25 video: connect optional VA cache to Vulkan renderer lifetime
Detect Linux HEVC/PRIME2 headers without a link-time VA dependency. Route opaque leases through the existing queue callbacks, poll returns on renderer ticks, and retain failed source/destination use through exact-device retirement. Cover native resolution and adapter transport with portable typed fixtures.
2026-10-22 07:17:02 +02:00
Codex 3eff27b52c video: own bounded VA Vulkan cache and deferred retirement
Reuse immutable imported-image identities with the opaque lease bridge and owned conversion resources. Poll completion during paused ticks, retain uncertain submissions through exact-device teardown, and retry failed owner returns. Cover reuse, eviction, allocation failures and retirement with portable fixtures.
2026-10-22 07:17:02 +02:00
Codex 28221097e0 style: follow project conventions in recent VA migration code
Use Allman braces, three-space indentation, separate statements and readable alignment. Initialize fixed Vulkan resource, barrier and submission structs through explicit member stores while retaining clearing for project-owned state.
2026-10-22 07:17:02 +02:00
Codex de28925f7b vulkan: own cold VA conversion resources
Create bounded sampler, descriptor, pipeline, command and fence resources for persistent VA conversion entries. Unwind partial creation in dependency order and preserve exact-device teardown. Exercise the production owner in the native pixel oracle across all 32 slots.
2026-10-22 07:17:02 +02:00
Codex 60834a9628 vulkan: bridge opaque VA leases through conversion fence retirement
Borrow completed exports for cold import preparation, then claim and adopt matching opaque frames for warm conversion. Restore refused submissions and publish completed exports to decoder collection without native calls on the renderer. Retain uncertain submissions and failed returns through exact-device teardown.
2026-10-22 07:17:02 +02:00
Codex 0a69266bc2 vulkan: reuse imported VA images across returned frame leases
Capture unique DMA-BUF identities on the decoder owner. Return completed export tickets independently of cached imports, and re-adopt only matching buffers and layouts without renderer-side native calls. Require per-picture submission reconfiguration and retain pending or uncertain GPU ownership.
2026-10-22 07:17:02 +02:00
Codex 2fd1326943 tests: stub software fallback in GPU format transport fixture
Keep the format-only harness independent of the player worker while satisfying callback references retained by the MinGW linker. Count either fallback call as a test failure.
2026-10-22 07:17:02 +02:00
Codex 22de713719 video: carry VA exports through opaque owner-return leases
Publish completed exports through the existing internal hardware-frame callback contract. Bounded lock-free claim/take/restore/return states retain tickets through GPU use; only the decoder owner collects and closes returned exports. Add stale/abandonment and 10000 SPSC handoff tests with an isolated TSan target; refuse unsupported atomics without changing frame ABI.
2026-10-22 07:17:02 +02:00
Codex 0beecd021c vulkan: execute VA conversion submission pixel oracle
Exercise real exportable plane images, foreign ownership, destination layouts and fences through the production command helper under Vulkan validation. Compare cropped 8/10-bit SDR and PQ/HLG RGBA8/FP16 pixels with independent references. This proves conversion commands, not native VA DMA-BUF decode/import.
2026-10-22 07:17:02 +02:00
Codex 3ad02e2935 vulkan: record warm VA plane conversion submissions
Use borrowed sampled-YUV pipeline resources and immutable crop/color uniforms. Preserve source foreign ownership, tracked destination layout and existing queue serialization. Busy or stale entries submit nothing; uncertain GPU submission retains the source through device retirement.
2026-10-22 07:17:01 +02:00
Codex 3076c7e936 vulkan: retain VA export tickets through imported frame use
Move a VA export only after both sampled layer imports succeed. Record paired foreign-queue barriers and retain resources across pending or failed renderer fences. Retire Vulkan resources before returning the owned export through the decoder-owner handoff; device-destruction recovery makes no invalid Vulkan calls.
2026-10-22 07:17:01 +02:00
Codex b97e28a05b vulkan: own DRM modifier image imports and memory
Create explicit sampled images, validate FD size and image/FD memory-type intersections, then import CLOEXEC duplicates and bind memory planes. Honor dedicated requirements and unwind view, image, memory and unconsumed FDs on refusal. This remains private resource-cache groundwork; producer completion, foreign ownership and VA ticket retirement are separate admission gates.
2026-10-22 07:17:01 +02:00
Codex 9eb993f011 vulkan: preflight exported VA layer formats and modifiers
Query sampled DRM modifier planes and external DMA-BUF image limits before taking FDs. Admit both NV12/P010 layers atomically and reject dedicated-only disjoint imports. This prepares the private VA importer; no renderer capability or decoder registration is enabled.
2026-10-22 07:17:01 +02:00
Codex d247c3b353 vulkan: negotiate optional PRIME device prerequisites
Enable the complete DMA-BUF/modifier/foreign-queue bundle only on frontend-owned flat Linux devices with a render identity and compatible API dependencies. Keep core and XR device requests unchanged, preserve the actual enabled state across cache reuse, and do not advertise surface import support. On creation refusal, retry without PRIME using a copied enable list while retaining video queues/features. Validate version/missing-extension gates, candidate fallback, local Vulkan playback/validation, native ABI checks and Android root links.
2026-10-22 07:17:01 +02:00
Codex be479b7b6e vulkan: cache the selected GPU render-node identity
Query advertised DRM properties during device initialization and expose bounded render-node major/minor metadata through the internal device handoff. Retain the actual instance API/properties2 capability through cached-instance reuse and refuse queries for an unverified instance. Device reads copy cached metadata without queries on playback paths. Validate fault gates, native context/cache lifecycle, builtin media Vulkan playback, legacy syntax, Android roots and production stack budgets. This does not advertise PRIME import support or activate VA decoding.
2026-10-22 07:17:01 +02:00
Codex 727936f331 video: own VA PRIME exports through surface retirement
Retain completed pool tickets while importing or using exported PRIME objects. Validate bounded NV12/P010 descriptors, preserve modifiers and auxiliary planes, seal FDs close-on-exec and transfer each FD only once. Close partial exports without duplicate FD closes, including descriptor zero. Bind the real export API and test metadata/fault ownership on Linux and Windows; hardware export and renderer import remain unvalidated.
2026-10-22 07:17:01 +02:00
Codex 631d770472 video: retain VA surfaces across reads and exported frames
Track bounded in-flight submissions separately from shared DPB selection. Prevent surface reuse while a write, later GPU reader or exported incarnation retains it; validate reference picture identity and decode serials. Preserve ticket generations across cold reset, allow exactly one frame ticket per incarnation and retain broken owners until display termination. Exercise real parsed batches, delayed/out-of-order completion, stale returns, cleanup retries and failure recovery on Linux and Windows.
2026-10-22 07:17:01 +02:00
Codex 54909c62d6 video: pack parsed HEVC pictures for VA short submission
Build BASE-mode start-code slice buffers with escaped payloads and aligned zero padding using preallocated storage. Connect native picture/matrix translation and bounded submission without advancing the shared DPB until acceptance. Refuse unsupported modes, malformed metadata, overlapping storage and capacity failures before writing output. Roundtrip 64 real Main/Main10 I/P/B packets and exercise failure boundaries on Linux and Windows; native VA pixels remain untested.
2026-10-22 07:17:00 +02:00
Codex 89a310a69a video: retain VA decode submissions through completion
Keep bounded picture, matrix, slice and data buffers until zero-timeout surface synchronization completes. Refuse reuse while pending, retain failed destruction for retry and poison uncertain native failures until display termination. Bind the six native submission entrypoints and preserve explicit short/long slice mode. Exercise both modes and depths, partial creation, duplicate IDs, native failure, polling and retirement on Linux and Windows.
2026-10-22 07:17:00 +02:00
Codex b0c79b4b01 video: own the VA-API DRM runtime lifecycle
Bind the actual VA/DRM ABI, validate the renderer node identity, and retain display, libraries and FD until termination succeeds. Once termination starts, retry only vaTerminate because a failed call may already unload its driver. Exercise partial initialization and teardown on Linux and Windows; verify real library binding and rejected Linux devices without claiming hardware decode.
2026-10-22 07:17:00 +02:00
Codex 389516d408 video: own VA-API HEVC context admission and retirement
Validate the requested profile, decode entrypoint, slice mode and surface format before creating a bounded context. Preserve partial native ownership across setup errors and retry failed destruction in dependency order. A closing owner can release remaining IDs only after successful display termination. Verify the real SDK function signatures and exercise allocation, creation and retirement faults without registering a decoder backend.
2026-10-22 07:17:00 +02:00
Codex b9d5dd0460 video: map prepared HEVC pictures to VA-API parameter buffers
Translate shared HEVC picture/reference metadata and effective raster scaling lists to the native libva ABI without allocating or taking a frame lease. Track slice types across the access unit so mixed-slice pictures set NoBiPredFlag correctly. Cover parsed Main/Main10 I/P/B streams and invalid reference/surface descriptions. This is buffer preparation only; no VA backend is registered.
2026-10-22 07:17:00 +02:00
Codex 29f05b9f4a mp4: keep software decoding after hardware admission fails
Clear the hardware preference when H.264 or HEVC admission fails. A later rewind must not silently enable hardware after the player selected its software sink; an explicit admission request can retry. Cover refusal followed by rewinds and an explicit retry.
2026-10-22 07:17:00 +02:00
Codex 026ec76bde player: schedule cold hardware admission on the decoder worker
Queue initial hardware admission before starting the sink, reusing its job block for normal decoding. Preserve polling-thread sink callbacks and stream ownership through queued or running cancellation. Keep inline frame decoding and the no-thread path, and only query existing hardware when the worker is unavailable.
2026-10-22 07:17:00 +02:00
Codex 9b12d82ef5 media: complete HEVC drain without losing delayed Surface frames
Invoke the backend drain hook even when no parser display slots remain. Report confirmed missing codec output at EOS instead of waiting indefinitely, while retaining BUSY for a rendered Surface image that has not arrived yet. Preserve pressure-only output handling and make drain failures sticky.
2026-10-22 07:17:00 +02:00
Codex 5ccfb36f1d media: guard Android decoder admission across driver replacement
Keep registry offers and decoder configuration stable while a generation gate controls new sessions. Reject and asynchronously close opens that overlap withdrawal or replacement. Existing sessions retain their callbacks; stale owners cannot withdraw a replacement. Stop admission permanently before generation reuse.
2026-10-22 07:17:00 +02:00
Codex a43188f642 media: retire Android decoders without caller locks or joins
Publish retirement through an eventcount to a detached worker. Keep publisher and worker references until notification and cleanup both finish, including failed startup. Exercise delayed workers and notifier lifetime under sanitizers and provide a no-native-fence host mode for the Windows lifecycle fixture.
2026-10-22 07:16:59 +02:00
Codex 8bff865216 media: preserve container HDR metadata in Android imports
Resolve container mastering and light levels against the native frame before cache lookup and submission. Inherit unspecified fields and reject explicit CICP or range conflicts without consuming the lease.
2026-10-22 07:16:59 +02:00
Codex 344648123e player: recover in software when the first hardware frame is refused
Quiesce queued decode work and restart on its exclusive owner before initializing the software sink. Preserve the movie clock and prevent reselection of the failing backend. Keep inline queued frame ownership until sink release. Hardware-only codecs still require an available software decoder to recover.

Validate startup, decode and import refusal; bounded restart, failed/canceled recovery, held slots and old exported leases. GCC/Clang sanitizers, TSan, native MinGW thread/no-thread tests, C89 application link, MSVC6 stand-in, stack checks and expanded NDK root/player links pass. Environment-dependent compile-matrix skips remain unverified.
2026-10-22 07:16:59 +02:00
Codex e411036f18 media: add bounded Android HEVC backend lifecycle and native link checks
Compose the Surface decoder and opaque frame exporter with pre-created cold retirement workers. Bound admission across live and retiring sessions, preserve old leases across seek epochs, and roll back JNI/native/thread startup failures. Registration remains disabled pending native playback acceptance.

Scope C89 static-inline compatibility to the Android native-window SDK header. NDK r29 otherwise emits duplicate ANativeWindow_clearFrameRate definitions and an unused newer-API dependency under GNU89 inline semantics. Validate helper and shipping renderer/context objects together with relocatable links.

Validation: GCC/Clang C89 ASan+UBSan and TSan lifecycle tests; held real exporter lease; native/thread/detach failure injection; 136 NDK individual compiles, eight unity builds, sixteen shipping root compiles and eight relocatable links. Actual Android playback is not executed.
2026-10-22 07:16:59 +02:00
Codex 010683ca31 vulkan: integrate Android frame imports with renderer cache and staged teardown
Recognize opaque Android leases before raw VkImage interpretation, validate source geometry and color, prepare immutable per-buffer conversion resources on first use, and route submissions through the existing queue lock. Preserve the previous destination on busy or failed conversion.

Destroy deferred Vulkan children before vkDestroyDevice as required by VUID-vkDestroyDevice-device-05137, while retaining external AImage leases until device destruction returns. Transfer dedicated command-pool and deferred destination ownership with the cache. Decoder registration remains disabled pending native Android validation.

Validation: GCC/Clang C89 ASan/UBSan component and shipping-helper tests; eight real NDK renderer/context/unity configurations; GCC/Clang non-Android shipping renderer/context compilation. Native Android playback is not yet executed.
2026-10-22 07:16:59 +02:00
Codex 0dfc3b8124 vulkan: retire deferred Android image caches after actual device destruction 2026-10-22 07:16:59 +02:00
Codex b1bf9ee6b1 samples: verify Android cache holds failed submissions until device destruction 2026-10-22 07:16:59 +02:00
Codex 8cd312ba2f vulkan: route Android imports through renderer queue submission callback 2026-10-22 07:16:58 +02:00
Codex 3ae1b5ad3b vulkan: negotiate Android import features only on frontend-owned devices 2026-10-22 07:16:58 +02:00
Codex 746b5d3db2 media: prefix Android helpers and validate native unity compilation 2026-10-22 07:16:58 +02:00
Codex dbcd658b9c media: retain Android decoder owner until import context retires 2026-10-22 07:16:58 +02:00
Codex 94b995efc7 video: bound Android Vulkan import cache and evict only retired entries 2026-10-22 07:16:58 +02:00
Codex 720fbf5056 video: create and retire cold Android Vulkan import entries 2026-10-22 07:16:58 +02:00
Codex e0ca7b3b0d video: submit warm Android image conversions through GPU fence retirement 2026-10-22 07:16:58 +02:00
Codex 202ec8e8f6 video: scope Android image ownership barriers to compute or fragment reads 2026-10-22 07:16:58 +02:00
Codex 26069ee954 media: export HEVC Android image leases with verified color metadata 2026-10-22 07:16:58 +02:00
Codex 2bc182435e video: retain Android image tickets through opaque renderer frame leases 2026-10-22 07:16:57 +02:00
Codex a83d3c997a test: execute external RGB shader crop and HDR pixel oracle 2026-10-22 07:16:57 +02:00
Codex ac2eb32e7a video: build immutable external-sampler compute pipeline resources 2026-10-22 07:16:57 +02:00
Codex a0d7749922 video: convert external sampler RGB through the shared HDR shader 2026-10-22 07:16:57 +02:00
Codex fa43677c9a video: record Android foreign image acquire and release barriers 2026-10-22 07:16:57 +02:00
Codex 83579a04d8 media: validate native Android buffer allocation before GPU import 2026-10-22 07:16:57 +02:00
Codex ff1ceafe1d video: transfer Android readiness and completion through Vulkan sync fds 2026-10-22 07:16:57 +02:00
Codex b77d37a65d video: import Android Vulkan external image resources with dedicated memory 2026-10-22 07:16:57 +02:00
Codex 954539d87e media: return Android pictures excluded from display immediately 2026-10-22 07:16:57 +02:00
Codex 275eafab01 test: cover Android HEVC startup refusal and malformed timestamps 2026-10-22 07:16:57 +02:00
Codex b3789ce352 test: keep the Android HEVC fixture whitespace clean 2026-10-22 07:16:57 +02:00
Codex f8444c1d98 media: own HEVC Surface decoding through Android image retirement 2026-10-22 07:16:57 +02:00
Codex 256a5f48f9 media: expose image retirement before stopping the Android codec 2026-10-22 07:16:56 +02:00
Codex 82be334b42 h265: let asynchronous hardware backends submit explicit EOF 2026-10-22 07:16:56 +02:00
Codex e81d767dae media: validate Android native ABI with the pinned NDK
Scope the modern native_window.h inline spelling to its SDK includes for strict C89. Compare every loaded media function pointer with its SDK declaration without introducing direct symbol references. Cover both Android file-offset ABIs in cross-compile CI.

All 40 actual NDK r29 combinations pass locally: ARM32/64, API 21/29, five sources and default/64-bit offsets. Undefined-symbol inspection confirms no newer media APIs became mandatory load dependencies. Decoder/GPU/device acceptance remains pending.
2026-10-22 07:16:56 +02:00
Codex 6aa0e45313 test: roundtrip real HEVC parser output through Android packet packing
Generate Main/Main10 test samples with test-only libx265, feed packed parameter sets through rh265, verify retained bytes and access-unit metadata, and roundtrip every escaped VCL NAL through Annex-B packing. Exercise the shipping parser under GCC/Clang C89 ASan/UBSan.
2026-10-22 07:16:56 +02:00
Codex 11e76e7abf media: pack validated HEVC NALs for Android Surface decoding
Construct Annex-B VPS/SPS/PPS csd-0 and complete VCL access units from existing rh265 parser views. Preserve escaped bytes, validate type/temporal consistency and exact coded sizes, bound staging needs, and reject overlapping input before modifying output. No allocation or decoder capability activation.

GCC and Clang C89 ASan/UBSan tests cover exact lengths, emulation-prevention bytes, insufficient storage, mismatched units and alias refusal. Actual native MediaCodec decode and GPU import remain pending.
2026-10-22 07:16:56 +02:00
Codex 4f39c8a27a media: add runtime MediaCodec Surface decoder session
Create only an explicitly admitted hardware component and verify its actual name. Configure bounded codec-specific CSD and whole access-unit input, use zero-timeout input/output dequeue, track output format/crop/color without invented metadata, and deliver Surface buffers with explicit presentation timestamps. Teardown belongs to the decoder worker after GPU image retirement.

GCC/Clang C89 ASan/UBSan stand-in tests cover missing symbols, partial creation, identity mismatch, input backpressure/overflow, format changes, EOS and sticky failures. Actual NDK compilation, native decoding and renderer integration remain pending.
2026-10-22 07:16:56 +02:00
Codex 42f0cc2075 media: retain Android reader images through GPU fence retirement
Add bounded cold image ownership and runtime-loaded AImageReader PRIVATE/GPU-sampled output. Keep native images, reader and media library alive through renderer leases; perform only zero-timeout fence polls and native image destruction on the worker. Renderer returns transfer a completion fence without allocation or native image calls.

GCC/Clang C89 ASan/UBSan tests cover delayed ready/done fences, stale tickets, malformed metadata, missing symbols and partial startup. A 10000-handoff worker/render test passes ThreadSanitizer. Actual NDK compilation and Android decoder/GPU integration remain pending.
2026-10-22 07:16:56 +02:00
Codex 5c2db2becf media: add cold Android hardware codec inventory admission
Query explicit API 29 hardware identity, direction, aliases and profile/size support through bounded JNI local frames. Reject unknown/software/contradictory identities and preserve caller exceptions. No decoder or renderer capability is enabled.

Exercise real JNI with synthetic Android codec classes, vendor/metadata faults and 10000 local-frame iterations under GCC/Clang C89 and UBSan. Add actual NDK 32/64-bit API 21/29 cross-compile gates; native device/session/import acceptance remains pending.
2026-10-22 07:16:56 +02:00
456 changed files with 68933 additions and 1207 deletions

No files matched your search

+234 -4
View File
@@ -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() }}
+56
View File
@@ -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
+62
View File
@@ -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
+64 -2
View File
@@ -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
View File
@@ -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
+85
View File
@@ -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
+10
View File
@@ -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
+3
View File
@@ -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 */
+23 -7
View File
@@ -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
+4
View File
@@ -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);
+266
View File
@@ -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;
}
+33
View File
@@ -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
+343
View File
@@ -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;
}
+34
View File
@@ -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
+105
View File
@@ -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
+134
View File
@@ -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;
}
+27
View File
@@ -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
+207
View File
@@ -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;
}
+33
View File
@@ -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
+52
View File
@@ -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);
}
+21
View File
@@ -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
+70
View File
@@ -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
+218
View File
@@ -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;
}
+33
View File
@@ -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
+322
View File
@@ -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);
}
+38
View File
@@ -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
+105
View File
@@ -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
+148
View File
@@ -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
-68
View File
@@ -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
+195
View File
@@ -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(&registration, &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
+319
View File
@@ -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;
}
+28
View File
@@ -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
+261
View File
@@ -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;
}
+31
View File
@@ -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
+30
View File
@@ -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);
}
+24
View File
@@ -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
+160
View File
@@ -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;
}
+25
View File
@@ -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
+467
View File
@@ -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
+187
View File
@@ -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;
}
+38
View File
@@ -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
+24
View File
@@ -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
+27
View File
@@ -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
+184
View File
@@ -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;
}
+29
View File
@@ -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
+81
View File
@@ -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
+269
View File
@@ -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;
}
+55
View File
@@ -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
+198
View File
@@ -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
+330
View File
@@ -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;
}
+29
View File
@@ -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
+185
View File
@@ -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;
}
+29
View File
@@ -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
+240
View File
@@ -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;
}
+83
View File
@@ -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
+146
View File
@@ -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
+238
View File
@@ -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;
}
+47
View File
@@ -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
+88
View File
@@ -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
+173
View File
@@ -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;
}
+68
View File
@@ -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
+246
View File
@@ -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;
}
+47
View File
@@ -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
+285
View File
@@ -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;
}
+53
View File
@@ -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
+227
View File
@@ -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
+181
View File
@@ -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;
}
+58
View File
@@ -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
+61
View File
@@ -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
+321
View File
@@ -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);
}
}
+62
View File
@@ -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
+251
View File
@@ -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;
}
+84
View File
@@ -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
+163
View File
@@ -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;
}
+41
View File
@@ -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
+207
View File
@@ -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;
}
+65
View File
@@ -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
+190
View File
@@ -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;
}
+70
View File
@@ -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
+327
View File
@@ -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;
}
+28
View File
@@ -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
+228
View File
@@ -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;
}
+28
View File
@@ -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
+28
View File
@@ -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);
}
+23
View File
@@ -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
+154
View File
@@ -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;
}
+25
View File
@@ -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
+154
View File
@@ -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
+188
View File
@@ -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;
}
+42
View File
@@ -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
+24
View File
@@ -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
+26
View File
@@ -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
+97
View File
@@ -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;
}
+32
View File
@@ -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
+227
View File
@@ -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
+110
View File
@@ -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
+106
View File
@@ -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
+189
View File
@@ -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
+182
View File
@@ -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