Commit Graph
68903 Commits
Author SHA1 Message Date
LibretroAdmin 211263f008 rpng: real SSE2 kernels for the scanline conversions
The _sse2-suffixed conversion helpers contained no intrinsics - they
were 4x-unrolled scalar loops. Replace both with genuine SSE2:

RGB->32bpp expands 4 pixels per 16-byte load. SSE2 has no byte
shuffle (pshufb is SSSE3), but the fixed 3->4 byte expansion falls
out of whole-register byte shifts plus dword masks, placing triple k
at output byte 4k with alpha OR'd in; the ARGB order additionally
swaps R and B inside each word with the usual mask/shift trick. The
load reads 4 bytes past the 12 consumed, so the vector loop requires
6 remaining pixels (18 bytes) and hands the tail to the scalar loop.

RGBA->32bpp: the supports_rgba case is the input bytes verbatim on
LE (implied by SSE2/x86), so it is a straight row copy, matching the
dispatcher-level fast path; the ARGB case is a vectorized R/B swap
within each word.

Output is byte-identical to the previous code for all color types at
both supports_rgba values. 1920x1080 RGB full decode: ~9% faster at
either output order.
2026-07-16 17:15:06 +02:00
LibretroAdmin 154e920b82 rwebp/rvp8: select channel order at YUV->RGB store time
The lossy decode paths produced ARGB words and then ran a whole-image
read-modify-write pass to swap R/B whenever the caller wanted memory
R,G,B,A order (supports_rgba, i.e. every GL/Vulkan/Metal frontend,
and every animation frame). The swap is free at store time instead:
thread a swap_rb flag through vp8_yuv2rgb_row (SSE2: pack operand
swap; NEON: exchange px.val[0]/px.val[2]; scalar tails including the
no-scratch fancy-pair fallback: branchless shift selection) and
expose it as rvp8_dec::swap_rb plus a parameter on the one-shot
rvp8_decode.

With that in place:
- the sliced lossy path in rwebp_process_image emits the requested
  order directly from RWEBP_PHASE_UPSAMPLE and no longer enters the
  RWEBP_PHASE_SWIZZLE post-pass (which is now VP8L-only);
- rwebp_do lossy decodes come out pre-swapped, with the ALPH
  composite unchanged since alpha is the top byte of the word in
  both layouts;
- animation lossy frames decode straight into canvas order, so the
  per-frame conversion pass reduces to the opaque-mask accumulation.

Output is byte-identical for lossy (including odd dimensions and
lossy+ALPH), lossless, and animated content at both supports_rgba
values. 1919x1077 lossy full decode with supports_rgba: ~11% faster
(one full-image pass over cold memory eliminated); supports_rgba=0
unchanged.
2026-07-16 16:56:33 +02:00
LibretroAdmin 0ca7a52abb rtga: include retro_endianness.h so the MSB_FIRST guard works
The 32-bit BGRA fast path is guarded with #ifndef MSB_FIRST, but
nothing in this translation unit defined the macro - MSB_FIRST only
comes from retro_endianness.h, which was not included, and no build
target defines it on the command line. The memcpy fast path was
therefore compiled unconditionally, producing wrong channel order on
big-endian hosts (Wii, PS3). Include the header so the guard actually
engages.
2026-07-16 16:56:33 +02:00
LibretroAdmin b6a68caa95 rpng: fix dead SIMD conversion dispatch, memcpy fast path for ABGR RGBA
The SSE2/NEON scanline conversion helpers were gated on bpp == 24
(RGB) / bpp == 32 (RGBA), but the only call site passes ihdr->depth,
which for PNG is bits per SAMPLE (8 or 16), never 24 or 32 - so the
fast paths were unreachable and every PNG converted through the
scalar per-channel loop. The scalar tail confirms the intended unit:
it strides bpp/8 bytes per channel, which is only correct when bpp is
the sample depth. Gate on bpp == 8 and document the semantics.

While here, add an identity fast path for the common case: unfiltered
8-bit RGBA scanline bytes are already R,G,B,A in memory order, which
on a little-endian host is exactly the ABGR32 layout the
supports_rgba output wants, so the whole row is a single memcpy
(guarded on !MSB_FIRST via retro_endianness.h, now included).

Output is byte-identical to the previous scalar path for all color
types (RGB/RGBA 8-bit, RGB/RGBA 16-bit, palette, gray, gray+alpha) at
both supports_rgba values. 1920x1080 RGBA full decode: ~10% faster
with supports_rgba, ~4% without.
2026-07-16 16:56:33 +02:00
Craig 281faf8267 android: remove x86 from play store variant / update gitignore (#19212) 2026-07-16 13:20:07 +02:00
Ryunam 9300d7b63e menu: When renaming playlist entries, allow CTRL movement between space-delimited text (#19213) 2026-07-16 13:19:46 +02:00
libretroadmin f81493266a qt: hold pending-thumbnail queue in QList to silence -Wclass-memaccess
m_pendingPlaylistThumbnails is a QVector<QHash<QString, QString>> drained
with takeAt(0). QVector::takeAt -> remove -> erase relocates the tail with
memmove(T*, T*, n * sizeof(T)); since QHash is not trivially copyable
(ref-counted d-pointer), GCC flags this via -Wclass-memaccess deep in
Qt5's qvector.h:

  writing to an object of type 'class QHash<QString, QString>' with no
  trivial copy-assignment; use copy-assignment or copy-initialization

Switch the member to QList<QHash<QString, QString>>. QList relocates its
void* node slots (a trivial type), storing the movable, pointer-sized
QHash inline, so the erase path no longer memmoves QHash objects and the
warning goes away. The queue is used only via clear()/count()/append()/
takeAt(), all QList-compatible, and is never cross-assigned with a
QVector.

The other QVector<QHash<...>> locals/return are left as-is: they only
append and iterate, and QVector::append constructs complex types rather
than memmoving them, so they do not trip the warning.
2026-07-16 02:12:58 +02:00
libretroadmin a580a50cf8 android: define HAVE_THREAD_STORAGE (fix ndk-build after platform_unix TLS migration)
Routing platform_unix.c's JNI thread-local storage through rthreads made
it depend on the sthread_tls_* API, which is gated behind
HAVE_THREAD_STORAGE in both rthreads.h and rthreads.c. The griffin
ndk-build (pkg/android/phoenix-common/jni/Android.mk) never defined that
flag -- the previous raw pthread_key_* code did not need it -- so the
whole TLS API compiled out and the Android build failed:

  platform_unix.c:125: unknown type name 'sthread_tls_t'
  platform_unix.c:425: call to undeclared function 'sthread_tls_set'
  platform_unix.c:573: call to undeclared function 'sthread_tls_create_with_dtor'

Android/Bionic fully supports pthread thread-local storage (including
pthread_key_create destructors), so declare the capability alongside
-DHAVE_THREADS. This also activates retroarch.c's rarch_tls, which uses
the same API and is purely additive (no #else path). The configure-based
Linux/Unix builds autodetect HAVE_THREAD_STORAGE and were unaffected.
2026-07-16 02:10:14 +02:00
libretroadmin 93f47ac416 platform_unix: route JNI thread-local storage through rthreads
The Android JNI glue in frontend/drivers/platform_unix.c held the
per-thread JNIEnv* in a raw pthread_key_t, created with
pthread_key_create(..., jni_thread_destruct) so the JVM is detached when a
thread exits, and updated with pthread_setspecific().

rthreads already wraps TLS (sthread_tls_*), but sthread_tls_create()
passes a NULL destructor, so it could not preserve the detach-on-exit
behaviour. Add sthread_tls_create_with_dtor(), which forwards a
pthread_key_create() destructor on pthread backends (Win32 TlsAlloc has no
destructor callback and ignores it -- documented; current users are
POSIX). Route the driver through it plus sthread_tls_set(), change
thread_key to sthread_tls_t, and drop <pthread.h> for
<rthreads/rthreads.h>.

The destructor (jni_thread_destruct) is preserved, so JVM detach on thread
teardown is unchanged. platform_unix.c no longer makes direct pthread
calls. No functional change on Android.
2026-07-16 01:47:55 +02:00
LibretroAdmin 2846817a04 rbmp: accept plain BI_RGB 16bpp/32bpp BMPs
The BITMAPINFOHEADER (hsz 40/56) path unconditionally returned "Bad
BMP" for bpp == 16/32 after its switch on the compression field - the
BI_RGB (compress == 0) case fell through the switch straight into the
error return, so every plain 16bpp or 32bpp BMP was rejected. Only
BI_BITFIELDS files with explicit masks could reach the pixel loop.

Set the standard mask defaults for BI_RGB instead, mirroring
stb_image's stbi__bmp_set_mask_defaults: X1R5G5B5 for 16bpp, BGRA for
32bpp (4th byte treated as alpha). Since BI_RGB 32bpp writers
routinely leave that pad byte zeroed - which would decode fully
transparent - also track the OR of all alpha values read and force the
image opaque when every alpha was 0, again matching stb_image's all_a
fixup. Real alpha (e.g. RGBA BMPs out of Pillow/GIMP) passes through
untouched, and unsupported compression values now return the error the
fall-through was presumably meant for.

Verified against a 32bpp RGBA BMP (alpha preserved byte-exact vs the
PNG reference), a 24bpp BMP, and a hand-built zero-alpha BI_RGB 32bpp
BMP (decodes opaque).
2026-07-16 01:08:36 +02:00
libretroadmin fb993da590 linux_common: route thread cancellation through rthreads
The illuminance-sensor code in input/common/linux_common.c used raw
pthreads: pthread_setcancelstate() around the file-reading critical
section, and pthread_cancel() on close via a pthread_t reconstructed by
casting sthread_get_thread_id() -- a fragile round-trip through the
opaque id.

Add cancellation primitives to rthreads and route the driver through
them:

  void sthread_set_cancel_enable(bool enable);
  bool sthread_cancel(sthread_t *thread);

sthread_cancel() takes the sthread_t directly and uses its backing handle,
dropping the pthread_t cast. Both are implemented with
pthread_setcancelstate()/pthread_cancel() on pthread backends that provide
them, gated by RTHREADS_HAVE_CANCEL which excludes Win32, GEKKO, 3DS and
Android/Bionic (Bionic has no pthread_cancel; rthreads.c is compiled there
even though linux_common.c is not). Where unsupported they are a no-op /
return false, and the existing cooperative sensor->done flag still stops
the thread.

linux_common.c no longer includes <pthread.h> or references pthread
directly. No functional change on Linux.
2026-07-16 00:46:51 +02:00
libretroadmin c2d1a02b3e cocoa_common: use sthread_is_main_thread() instead of raw pthread_main_np()
Follow-up to routing the Cocoa GL/VK context drivers through
sthread_is_main_thread(): migrate the three remaining direct
pthread_main_np() calls in cocoa_common.m (cocoa_main_thread_sync(),
cocoa_main_thread_cond_wait_pump(), and the resize/notification helper)
to the same rthreads predicate, and drop the now-unused <pthread.h>
include. The == 0 site becomes !sthread_is_main_thread(). rthreads.h is
already included here. No functional change.
2026-07-16 00:02:15 +02:00
libretroadmin 2a2bf863c7 cocoa_gl/cocoa_vk: use sthread_is_main_thread() instead of raw pthread_main_np()
The Cocoa GL and Vulkan context drivers included <pthread.h> and called
pthread_main_np() directly in their threaded get_video_size path (refresh
the published backing size from UIKit only when on the main thread, else
read the cached value). That is a raw platform primitive in a gfx context
driver, and pthread_main_np() is a BSD/Darwin extension absent on other
pthread platforms.

Add sthread_is_main_thread() to rthreads and route both drivers through
it. The predicate is implemented with pthread_main_np() on Apple (where
these drivers run) and returns false on backends without a native
main-thread test; the header documents the portable captured-id
alternative (cf. task_is_on_main_thread()) for any future non-Apple
caller. The drivers now include <rthreads/rthreads.h> instead of
<pthread.h> and contain no direct pthread references. No functional
change.
2026-07-15 23:43:20 +02:00
libretroadmin 848a5cbdc7 video_thread_wrapper: move cocoa trampoline pump out of the wrapper
video_thread_wait_reply() and the threaded frame path pumped the cocoa
main-thread trampoline runloop mode inline with raw pthread_main_np(),
CFRunLoopRunInMode() and a hardcoded mode string, forcing <pthread.h> and
<CoreFoundation/CoreFoundation.h> into the otherwise platform-neutral
wrapper. The mode string was also duplicated here and in
cocoa_main_thread_sync(), with a comment warning the two must be kept in
sync.

The main-thread check plus runloop pump is cocoa-runloop integration, not
a generic threading primitive (unlike the QoS override, which went to
rthreads), so move it to the cocoa layer as
cocoa_main_thread_cond_wait_pump() in cocoa_common.m, next to
cocoa_main_thread_sync() that shares its trampoline mode string -- now a
single source of truth. It does one wait iteration: on the main thread a
bounded timed wait pumping the private mode on timeout, otherwise it
returns false so the caller does a plain scond_wait().

The wrapper now calls it through a small static video_thread_pump_wait()
shim (a no-op returning false off Apple), so both wait loops are uniform
and the wrapper no longer references pthread, CoreFoundation, or the mode
string -- only a single #ifdef __APPLE__ selecting the strategy remains,
with no raw platform primitives. No functional change.
2026-07-15 23:27:37 +02:00
libretroadmin 267083e056 rthreads: abstract thread priority override; drop pthread from video_thread_wrapper
The QoS priority-inversion mitigation for synchronous CMD_CUSTOM_COMMAND
work was implemented inline in gfx/video_thread_wrapper.c with raw
pthread_override_qos_class_start_np/_end_np, <pthread/qos.h>, and Apple
deployment-target gating. That is platform-specific threading detail and
does not belong in the video wrapper, which should speak only to the
sthread abstraction.

Move it behind rthreads:

  void *sthread_priority_override_begin(void);
  void  sthread_priority_override_end(void *ovr);

begin() raises the calling thread's scheduling priority to match a
higher-priority thread synchronously waiting on it (sthread's lock/condvar
do not propagate priority), returning an opaque handle; end() restores it.
The Apple pthread-QoS implementation and its 10.10+/iOS 8+ deployment gate
now live in rthreads.c's existing Apple section; every other platform gets
a no-op returning NULL, so callers need no conditionals.

video_thread_wrapper.c now just brackets the synchronous method call with
begin()/end() and no longer includes <pthread/qos.h>, <TargetConditionals.h>,
or <AvailabilityMacros.h>. (<pthread.h>/CoreFoundation stay for the
existing pthread_main_np()/CFRunLoop trampoline code.) No functional
change on any platform.
2026-07-15 23:08:59 +02:00
libretroadmin 24ad2a2498 video: force non-threaded video for iOS GLKit GL backends
The iOS Cocoa GL/GLES backends ("gl", "glcore") render through a GLKView.
Its drawable and backing CAEAGLLayer may only be touched on the main
thread, but with threaded video the swap and the per-frame backbuffer
bind (glkitview_bind_fbo -> -[GLKView bindDrawable], and -[GLKView
display]) run on the video worker thread. The GLKView realizes/resizes
its renderbuffer from the layer lazily on whichever thread first binds
it, so that first off-main bind mutates the layer and UIKit aborts:

  Modifying properties of a view's layer off the main thread is not
  allowed: <GLKView ...> ... swap_buffers -> gl2_frame -> video_thread_loop

The render context is created on the main thread but made current on the
worker (make_current is NULL), so the layer work cannot simply be
marshaled back to the main thread without either stealing the context
from the worker (freezing rendering) or a full per-frame handoff.

Rather than fight GLKView's main-thread model, veto threaded video for
these backends. Add video_driver_render_context_is_main_thread_only() and
fold it into VIDEO_DRIVER_IS_THREADED_INTERNAL next to the existing
!video_driver_is_hw_context() term, so the effective decision is
non-threaded at every call site (init, texture-load marshal, etc.) while
the user's video_threaded setting is left untouched -- exactly how
hardware-rendered cores are already handled. The predicate reads the
underlying driver ident via VIDEO_FLAG_THREAD_WRAPPER_ACTIVE rather than
video_driver_get_ident() to avoid recursing back through the macro. Metal
and Vulkan (CAMetalLayer via MoltenVK) do not use GLKView and keep
threaded video.

With the path now always main-thread, restore the canonical
-[GLKView display] in swap_buffers (present + layer-bounds resize, both
main-thread-legal), reverting the earlier bind+presentRenderbuffer
workaround that had dropped the resize handling.
2026-07-15 22:45:29 +02:00
libretroadmin f9975468c2 cocoa/ios: present renderbuffer on the render thread instead of -[GLKView display]
Under threaded video, gl2_frame -> cocoa_gl_gfx_ctx_swap_buffers runs on
the video worker thread (video_thread_loop), and the iOS swap path called
-[GLKView display]. Besides presenting, -display resizes the view's
drawable to its layer bounds and mutates CALayer state; that is a UIView
layer mutation off the main thread, which UIKit traps:

  Modifying properties of a view's layer off the main thread is not
  allowed: <GLKView ...> ... cocoa_gl_gfx_ctx_swap_buffers -> gl2_frame
  -> video_thread_loop

The context is made current once and stays on the render thread
(make_current is NULL), so marshaling -display to the main thread is not
an option: it would move GL currency off the worker and, with no
per-frame re-make-current, freeze rendering.

Instead bind the view's drawable and present directly with
-[EAGLContext presentRenderbuffer:], both pure EAGL/GL calls on the
already-current context, so they are legal on the worker thread and touch
no layer state. The bindDrawable immediately before the present pins the
colour renderbuffer to GL_RENDERBUFFER regardless of what the final gl2
pass left bound; it is the same call gl2 already issues every frame via
glkitview_bind_fbo(), which runs off-main without tripping the checker --
confirming the layer mutation was specific to -display's resize, not to
binding. Drawable resize on rotation/reinit remains on the main thread
through set_video_mode (cocoa_main_thread_sync). The OSX path
(flushBuffer) is unchanged.
2026-07-15 22:24:24 +02:00
libretroadmin f562181625 Cleanup 2026-07-15 21:45:25 +02:00
libretroadmin 078a3dca7e Cleanup 2026-07-15 21:42:57 +02:00
libretroadmin 2509e679b4 video/threaded: lift QoS during synchronous custom commands (Apple)
Under threaded video a texture load from the main thread runs
video_driver_texture_load -> gl3_load_texture (threaded branch) ->
video_thread_texture_handle, which posts a CMD_CUSTOM_COMMAND and blocks
the caller in video_thread_wait_reply. The video thread then executes the
method (video_texture_load_gl3 -> glGenerateMipmap) while the main thread
waits on it.

On Darwin the main/UI thread is user-interactive QoS and the video thread
is the default class, so this synchronous wait is a priority inversion --
Thread Performance Checker reports it as a hang risk at the
glGenerateMipmap frame. RetroArch's scond/slock (pthread condvar/mutex)
do not propagate QoS the way dispatch_sync / os_unfair_lock do, so the
video thread is never boosted to the waiter's class.

Wrap the synchronous CMD_CUSTOM_COMMAND method call in a self-targeted
pthread_override_qos_class_start_np / _end_np pair, raising the video
thread to user-interactive for the duration of the work only and
restoring the prior class afterwards. This is hand-rolled priority
inheritance scoped exactly to the inversion window: no steady-state QoS
change, no cross-thread handle, no rthreads-core plumbing.

The override API is macOS 10.10+ / iOS 8.0+, but RetroArch still ships
deployment targets below that (OS X 10.5, iOS 6) where the symbol is
absent in both SDK and runtime. Gate on the deployment-target version
(RARCH_HAVE_PTHREAD_QOS_OVERRIDE) rather than __APPLE__: TARGET_OS_*
separates the macOS and iOS checks, and numeric literals are used because
the MAC_OS_X_VERSION_10_10 / __IPHONE_8_0 constants are themselves absent
on old SDKs. Below the threshold the path compiles to the plain method
call, identical to non-Apple builds -- and those OSes predate both the
QoS scheduler and Thread Performance Checker, so there is nothing to fix
there anyway.

This is the path that carries the heavy GPU uploads (texture and font
resource commands routed through video_thread_texture_handle); lighter
synchronous pokes were not flagged and are left alone.
2026-07-15 21:37:14 +02:00
libretroadmin 9d677ca9e7 cocoa: gate title-update GCD path behind MAC_OS_X_VERSION_10_6
The main-thread marshal in ui_window_cocoa_set_title uses
dispatch_get_main_queue/dispatch_async, which are 10.6+. This file is
still compiled against pre-10.6 SDKs (see the MAC_OS_X_VERSION_10_6 guard
around the Carbon TransformProcessType include), and prior to the title
fix it pulled in no GCD at all, so the unconditional dispatch was the
first thing here that would fail to build on those SDKs.

Gate the GCD marshal behind the same MAC_OS_X_VERSION_10_6 macro the file
already uses; pre-10.6 SDKs fall back to the direct -setTitle:, which is
the legacy behaviour (and predates Main Thread Checker anyway). No change
on 10.6+ / Metal builds, where the macro is always defined.
2026-07-15 21:22:44 +02:00
libretroadmin cf768e65aa Update 2026-07-15 21:21:39 +02:00
libretroadmin 2dede2684d cocoa: marshal window title updates onto the main thread
ui_window_cocoa_set_title reaches AppKit (-[NSView window]/-[NSWindow
setTitle:]) directly. Under threaded video it is invoked from the video
thread via gl3_frame -> video_driver_update_title, which trips the Main
Thread Checker (UI API called on a background thread) and is undefined
behaviour since AppKit is main-thread-only.

Build the NSString on the calling thread first, since buf aliases the
shared video_st->window_title buffer that the next frame may overwrite,
then dispatch_async the -setTitle: onto the main queue. When already on
the main thread the call is issued inline to avoid a needless one-frame
deferral on the non-threaded path. dispatch_async rather than sync keeps
this clear of the video thread lock.
2026-07-15 21:13:47 +02:00
libretroadmin 15f239eae8 vulkan: recycle framebuffer memory across resizes
Follow-up to the framebuffer resize-crash fix. That change correctly stopped
reusing a framebuffer's own live allocation for a new image (reusing one that
is still bound to an in-flight image aliases two live optimal-tiled images,
invalid on some drivers), but as a result every resize now pairs a
vkAllocateMemory with a deferred vkFreeMemory. On drivers with slow or
fragmenting allocators, rapid resizes (e.g. toggling integer scale) churn
device memory harder than before.

Add a small chain-scoped best-fit recycle pool. Freed blocks are parked in
the pool from the deferred disposer callback -- i.e. only after the image that
aliased the memory has been destroyed and the referencing GPU work has retired
-- so a recycled block never aliases a live image. init() consults the pool
before allocating; a block is reused only when its type matches and its size
fits.

The pool is owned by CommonResources so its address is stable and it is alive
at every deferred_calls drain point, including the destructor-body flush().
The disposer callback therefore captures a pool pointer, never 'this' (the
Framebuffer may be reset() on a pass rebuild before the callback runs). The
pool is bounded to a handful of blocks, evicting the smallest to keep the
larger, more reusable allocations; it is drained in ~CommonResources.

For sizes that oscillate between two values, steady-state allocations drop to
zero after warmup.
2026-07-15 19:58:39 +02:00
libretroadmin 9decc0fde4 Silence some warnings 2026-07-15 19:48:55 +02:00
Matt Akins 3dbb2830e2 vulkan: fix framebuffer resize crashes (#19194) 2026-07-15 19:31:10 +02:00
Poly 76796ee6d0 feat: Added urlscheme export functionality (#19167) 2026-07-15 18:41:06 +02:00
LibretroAdmin 2050622c21 gfx_thumbnail: optional preview audio for animated WebM thumbnails
New setting 'Animated Thumbnail Audio' (menu_thumbnail_preview_audio,
default off): while an animated WebM thumbnail is shown, its audio
track is decoded and looped through the audio mixer, and stops when
the thumbnail closes.

New libretro-common module formats/webm/rwebm_audio.c decodes the
first supported audio track of an in-memory WebM file to interleaved
s16 PCM (plus a convenience wrapper producing a complete in-memory
RIFF/WAVE):

- Opus goes straight through ropus: the CodecPrivate is the OpusHead,
  packets decode independently, pre-skip is dropped from the stream
  start and DiscardPadding trimmed from the packets carrying it.
- Vorbis has no packet-level decoder here, so the track is wrapped
  into a well-formed in-memory Ogg stream - Xiph-laced CodecPrivate
  split into the three header packets, one packet per page, correct
  page CRCs, granules derived from container timestamps with a small
  surplus on the final page - and handed to rvorbis.

Correctness: PCM output matches ffmpeg's decode of the same files at
the exact same sample count with a maximum difference of 1 LSB
(Vorbis) / 3 LSB, 100% within +-2 (Opus) - the fixed-point vs float
pipeline residue - and the Ogg synthesis is proven byte-exact by
decoding ffmpeg's own -c copy remux of the track through the same
rvorbis and comparing PCM. 300-case truncation/corruption fuzz over
both codecs runs clean under ASan/UBSan/LSan. The duration cap is
honoured exactly (5 s request yields exactly 240,000 frames at
48 kHz) and the generated WAV parses as valid 16-bit PCM.

Integration reuses the animated-thumbnail decode worker: the audio
decode runs as a job on the shared worker thread (a synchronous
fallback covers HAVE_THREADS=0 builds, a one-shot cost when the
preview opens), and the finished WAV is handed to the mixer from
gfx_thumbnail_animate (AUDIO_STREAM_TYPE_SYSTEM, WAV, looped; the
mixer copies the buffer, so the job frees its own). A single preview
stream exists at a time, identified by a distinctive stream name and
verified by name again before removal so an unrelated stream that
replaced it is never touched. Decoded PCM is capped at 90 s. The
extended concurrency hammer - the verbatim worker code with audio
jobs mixed into the video job load and hostile release timing -
passes 20,000 iterations under both TSan and ASan, and the video
thumbnail pipeline is regression-checked hash-identical on the
1080p VP8 and VP9 corpus files.

The setting row lives in the settings_def single-source system
(settings_def_menu_thumbnail_background.h, generalized to thumbnail
display settings), which generates the enum, labels, sublabel,
config persistence and menu entry; manual sites are the struct field,
the default, the displaylist row and the sublabel binding.
2026-07-15 03:44:48 +02:00
LibretroAdmin 1c837e4619 rvp8/rvp9: bool reader micro-optimizations
The boolean arithmetic decoders are serial by nature, so this is
about per-symbol cost, not parallelism. Two mechanical changes:

- rvp8: replace the data-dependent normalization loop
  (while (range < 128) { range <<= 1; }) that ran on every single
  bool read with the same lookup table construction rvp9 already
  uses (vp8b_norm[range] gives exactly the shift the loop computed).

- rvp8 + rvp9: when at least 8 bytes remain, refill the 64-bit value
  register with one big-endian load instead of the byte-at-a-time
  loop. Byte k of the load sits at bit 56 - 8k, and >> (count + 8)
  lands it at 48 - count - 8k, precisely the loop's shift for byte k;
  the load is masked to the bytes the loop would have consumed, since
  an unmasked shift would leak the tail bytes' high bits into the low
  bits of the value register. Sub-8-byte tails and the rvp9
  LOTS_OF_BITS end-of-buffer inflation keep the original loop.

Verified state-exact - not just the returned bit, but value, range,
count and the buffer pointer compared after every single read -
against the pre-change readers over every buffer size 0..64 (covering
all bulk/tail boundary interactions) x 300 random probability
streams per size per codec, plain and under ASan/UBSan. Full-stream
oracles unchanged: the real-content 1080p VP8 and VP9 files remain
hash-identical to ffmpeg, the animated-WebP corpus identical,
corruption fuzz clean. aarch64-gcc compile-checked (the byte-swap
falls back to a portable expression off GNU C, and big-endian targets
skip it entirely).

Measured on the 1-core container (interleaved pairs, real-content
1080p): VP8 improves ~1.5% (the normalization loop ran per read; its
elimination is the bulk of the win). VP9 is statistically neutral
across eight pairs - its reader already had LUT normalization, and
refills only occur every ~7 bytes; the hunk is kept so both readers
share the same refill construction.
2026-07-15 02:31:16 +02:00
LibretroAdmin cd43104bee rvp8: merge equal-MV SPLITMV sub-blocks into rectangles
SPLITMV prediction issued 16 unconditional 4x4 luma calls plus 8
chroma calls per split macroblock, even though the SPLITMV partition
patterns (2/4/8 partitions) express 16x8, 8x16 and 8x8 regions
through per-4x4 syntax - most split MBs carry only 2-4 distinct
motion vectors. On a real-content 1080p stream this put
rvp8_mc_block at ~48,700 calls per frame.

Merge equal-MV neighbouring sub-blocks into maximal rectangles
greedily (raster scan; extend right, then down; extension probes skip
already-covered cells) and compensate each rectangle with one call;
the derived 2x2 chroma grid is merged the same way. This is bit-exact
by construction - every output pixel of a motion-compensated block
depends only on its own source window and the shared motion vector,
never on block extents - and merging vertically removes real work:
the six-tap first pass overlaps by five rows at every seam between
vertically adjacent blocks.

The first version of the chroma merge had exactly the bug the
extension-probe rule prevents: with vectors [A,B,B,B], (0,1) merges
down over (1,1) and (1,0) then merged right over the already-covered
cell. The output oracle cannot catch this class of defect (the
duplicate prediction writes identical bytes), so the sweep asserts
disjoint and complete coverage - every luma MB covers exactly 256
pixels and chroma 2x64, no cell twice - in addition to byte-equality.

Verified against the pre-change per-4x4 driver over 18,000 cases:
uniform, 16x8, 8x16, 8x8 and fully random per-4x4 MV patterns x
edge-adjacent macroblocks x full_pixel and bilinear modes, byte-exact
with the coverage assertions, plain and under ASan/UBSan. Full-stream
oracle unchanged (the real-content 1080p file stays hash-identical to
ffmpeg, synthetic streams byte-exact, animated-WebP corpus
untouched); per-category motion-compensated pixel counts are exactly
conserved. aarch64-gcc compile-checked.

Numbers on the real-content 1080p stream (1-core container):
mc_block calls 48.7k -> 28.2k per frame (-42%), six-tap first-pass
rows -28%; wall time improves a consistent but modest ~1-1.5% here
(all five interleaved measurement pairs positive) - the vectorized
kernels already made the eliminated calls cheap on this core. The
reduction weighs more on streams with heavier SPLITMV use and on
lower-IPC cores where per-call overhead is proportionally larger.
2026-07-15 00:52:34 +02:00
LibretroAdmin 51aa91c20d rvp8: interior fast path + SSE2/NEON six-tap motion compensation
Instrumented region timing (gprof on the build host misattributes
symbols) on a real-content 1080p VP8 WebM: rvp8_mc_block is 78.9% of
total decode time, called ~48,700 times per frame (VP8 SPLITMV
reduces motion compensation to 4x4/8x8 sub-blocks), 94% of the calls
through the six-tap path - and every single pixel went through
rvp8_clampc frame-edge clamping even for blocks nowhere near an edge.
The IDCT, loop filter and output stages were already vectorized; this
was the scalar holdout.

Two layers:

- Interior fast path: when the block's whole source footprint
  (columns sx-2 .. sx+bw+2, rows sy-2 .. sy+bh+2 for six-tap; the
  exact block for full-pel) lies inside the reference frame, take a
  clamp-free path - plain row memcpy for full-pel, clamp-free
  separable six-tap otherwise, with the intermediate held as u8 (the
  scalar stores the same clamped 0..255 values in an int temp).
  Border blocks (motion vectors reaching off-frame) keep the existing
  clamped scalar path.

- SSE2 and NEON kernels for the interior six-tap passes: pmaddwd /
  vmlal_lane tap pairs accumulating in int32, (acc + 64) >> 7 via
  arithmetic shift (vqrshrn_n_s32(acc, 7) on NEON), saturating packs
  as the 0..255 clamp - the exact scalar expressions. 8-wide and
  4-wide groups with a scalar tail; sample loads composed so nothing
  outside the scalar footprint is touched (13 bytes for 8 outputs via
  loads at +0 and +5; the NEON filter-tail load goes through a padded
  copy because the [8][6] tap table would otherwise be over-read by
  its final row). A scalar clamp-free interior path serves non-SIMD
  builds.

Verified byte-exact against the pre-change rvp8_mc_block over 13,824
cases: all block sizes {4,8,16}^2 x all 64 subpel phases x bilinear
and six-tap modes x motion vectors from deep interior to far
off-frame, on exact-bounds heap planes, plain and under ASan/UBSan
(which also proves the interior predicate never admits an
out-of-bounds footprint). Full-stream oracle: the real-content 1080p
VP8 file decodes hash-identical to ffmpeg, the synthetic streams
byte-exact, and the animated-WebP corpus (intra-only, mc unused) is
unchanged. aarch64-gcc compile-checked for NEON; corruption fuzz
clean.

Interleaved harness numbers (1-core container): real-content 1080p
60.3 -> 20.1 ms/frame (3.0x whole-pipeline; the mc region itself
~5.4x, from 48.5 to 8.9 ms/frame), 720p synthetic 6.2 -> 4.2 (1.5x).
Post-change breakdown: mc 41.6% (dominated by per-tiny-block call
overhead), loop filter 22.8%.
2026-07-14 23:54:07 +02:00
LibretroAdmin bdc933bbe9 rvp9: SSE2/NEON loop filter
With the convolve vectorized, instrumented region timing puts the
loop filter at ~36% of real-content 1080p decode. Add 8-lane SSE2
implementations of all six edge appliers (horizontal/vertical x
4/8/16) plus a full NEON translation, with the scalar code kept as
the fallback.

Every step mirrors the scalar functions exactly, with no shortcut
whose equivalence depends on input ranges:
- the six 'limit' terms use max-of-absdiffs (max(a,b) > L is
  identical to (a > L) | (b > L)) compared exactly via
  subs_epu8 == 0 / vcle_u8;
- the blimit term abs(p0-q0)*2 + abs(p1-q1)/2 is evaluated in 16-bit
  lanes, so it is exact for ALL thresholds, not just legal loop
  filter levels;
- filter4 runs in saturating int8 (adds/subs_epi8, vqadd/vqsub ==
  signed_char_clamp); the three successive saturating adds of
  (qs0 - ps0) equal the scalar's single clamp of filter + 3*d for
  all inputs: with |d| <= 127 every partial sum lies between two
  in-range endpoints, and with |d| >= 128 the true result is already
  at the rail the saturated steps reach;
- filter8/filter16 smoothing runs as u16 sliding sums (max 4080)
  with the scalar's rounding, and the scalar cascade
  filter16 -> filter8 -> filter4 -> unchanged is reproduced with
  per-lane mask blends; the 15-tap sums consume the ORIGINAL samples,
  widened before the inner cascade mutates the registers, exactly as
  the scalar's else-branch structure implies;
- vertical edges go through 8x8 byte transposes (punpck / vtrn
  cascades); every load and store is the scalar footprint (the
  vertical-16 writes rewrite the read-only p7/q7 columns with their
  unchanged values, inside memory the scalar already reads).

Verified byte-exact against the pre-change scalar appliers over
144,000 cases: all six appliers x six pixel patterns (random, flat,
vertical/horizontal step edges, checkerboard, mild gradients - flat
and flat2 need biased inputs to trigger at all) x legal, full-range,
and boundary thresholds, on exact-bounds heap blocks, plain and under
ASan/UBSan. The full-stream oracle is unchanged: the 911-frame
real-world 1080p file remains hash-identical to ffmpeg, and the
tiled/altref/odd-dimension corpus byte-exact. aarch64-gcc
compile-checked for NEON; corruption fuzz clean.

Interleaved harness numbers (1-core container): real-content 1080p
38.1 -> 26.9 ms/frame (1.42x), 720p synthetic 5.95 -> 3.66 (1.63x).
Cumulative with the convolve work, real 1080p WebM decode is ~3x
faster than at the start of the campaign.
2026-07-14 22:32:27 +02:00
LibretroAdmin f3f2ccf606 rvp9: SSE2/NEON 8-tap convolve
Instrumented region timing on a real-world 1080p WebM (gprof on the
build host misattributed symbols badly - it blamed the loop-filter
mask construction, which actually measures 0.5%) shows the scalar
8-tap inter-prediction convolve at 58.8% of total decode time.

rvp9 only implements the unscaled path (x/y_step_q4 == 16), so the
subpel phase is constant across each call and every call is a fixed
8-tap FIR. Add SSE2 kernels for the horizontal and vertical passes
(the 2D path composes them through the existing clipped u8 temp,
unchanged) with 8-wide and 4-wide groups plus the scalar tail, and
mechanical NEON translations. Bit-exactness is by construction: the
SIMD paths evaluate the same integer expressions - pmaddwd /
vmlal_lane tap pairs accumulating in int32, +64 and an arithmetic
shift (vqrshrn_n_s32(sum, 7) is exactly (sum + 64) >> 7), saturating
packs as the 0..255 clip - and the averaging variant's
ROUND_POWER_OF_TWO(dst + res, 1) is exactly pavgb / vrhadd_u8.
Sample loads are composed from 8-byte (and 4-byte) loads so not one
byte outside the scalar footprint is touched: the source can be the
exact-bounds build_mc_border temp.

Verified bit-exact against an independent scalar reference over all
four filter families x 16 subpel phases x random sizes (w 1..67,
h 1..19, padded strides) x both averaging modes with exact-bounds
heap buffers under ASan/UBSan; the full decode oracle is unchanged -
the entire 911-frame real-world file remains hash-identical to
ffmpeg's decoder, and the tiled/altref/odd-dimension corpus stays
byte-exact. aarch64-gcc compile-checked for NEON.

Real-content 1080p decode: 82-89 -> ~46 ms/frame on the 1-core
container (about 1.8x whole-pipeline); low-motion synthetic content
improves less, as expected. The instrumented breakdown after this
change puts the loop filter at ~36% - that is the next candidate.
2026-07-14 22:06:27 +02:00
Craig e283339a07 android: set core package as per 2026 spec (#19207) 2026-07-14 22:04:11 +02:00
Craig c5422481f0 android: fix storage permission acceptance on Android 8 (#19206) 2026-07-14 21:56:02 +02:00
LibretroAdmin a1f164e214 rvp8: SSE2/NEON fancy chroma upsampling
The YUV->RGB row conversion in the output stage was already
vectorized, but the 9-3-3-1 fancy chroma interpolation feeding it was
scalar, and profiling animated-WebP decode showed it as the largest
remaining output-stage cost (~14% of total at 1080p, attributed to
vp8_fancy_pair with the row helpers inlined).

The interpolation looks serial (carried top-left/left samples) but is
a pure 2x2 stencil, and the bottom-row variant is exactly the top-row
variant with the chroma rows swapped - the scheme is symmetric under
the swap (d12 and d03 exchange roles) including the edge and even-
length tail samples. Collapse both variants onto one per-channel
function with an SSE2 kernel (8 output pairs per iteration) and a
mechanical NEON translation. The SIMD paths compute the identical
16-bit integer expressions as the scalar loop - no averaging-
instruction shortcuts - so they are bit-exact by construction (all
intermediates fit u16); the scalar loop remains as the fallback,
prologue and tail.

Verified bit-exact against the original scalar implementations over
every length 1..131 x 200 random row pairs including aliased top==cur
rows (the mirrored first/last-row case), ASan/UBSan clean; the full
animated-WebP corpus (dispose/blend/alpha/sub-rect files plus
full-frame streams) hash-identical frame for frame, and the WebM VP8
path byte-identical. aarch64-gcc compile-checked for NEON.

Kernel microbenchmark (1920 px rows, 1-core container): 4.1x.
Whole-pipeline 1080p animated WebP: ~6-7% faster (after beat before
in all five interleaved measurement pairs on a noisy host); the
remaining decode time is the VP8 entropy/IDCT core itself.
2026-07-14 20:35:13 +02:00
LibretroAdmin e8026115d5 Menu: decode animated thumbnails on a worker thread
A single animated-thumbnail frame decode is atomic on the menu thread,
so a stream whose per-frame cost exceeds the vsync interval drags the
whole menu down regardless of the per-vsync budget accounting (which
only limits cumulative time, after the fact). In practice a 1080p VP9
WebM preview pinned a 120 Hz menu at ~47 fps even on very fast
hardware.

In HAVE_THREADS builds, move the decode to a shared worker thread
(one per process, created lazily on the first animated thumbnail).
Each active animation owns a job; the worker pops jobs from a FIFO,
decodes one displayed frame - including end-of-pass loop/rewind
handling and the ARGB swizzle for non-RGBA video drivers - into the
job's own upload-ready buffer, and marks it READY. The main thread's
gfx_thumbnail_animate() only inspects job state under the lock,
uploads READY frames, and re-enqueues, pipelining the next frame while
the current one is displayed; a frame that is not ready when due is
uploaded a vsync or two later, so the menu never blocks on the
decoder and holds its native refresh rate.

Ownership and teardown: while a job is QUEUED or RUNNING its stream
belongs exclusively to the worker. gfx_thumbnail_anim_close() unlinks
QUEUED jobs and waits out RUNNING ones before freeing, so teardown can
never free a stream under the worker. The worker is torn down when
the menu driver is deinitialised (after every thumbnail has been
reset) and recreated lazily; deinit orphans anything still queued
without deadlocking their owners' close path.

Without HAVE_THREADS - or if thread-primitive allocation fails at
runtime - the original synchronous, budget-limited decode path is
used unchanged; the upload and frame-pacing logic is factored into
helpers shared by both paths so they cannot drift.

Verified with the shipped worker code extracted verbatim into a
concurrency hammer (six simulated thumbnails over real WebM/WebP
streams, randomised animate cadence, hostile close/reopen and
mid-flight worker teardown, 20k iterations): ThreadSanitizer and
ASan/LeakSanitizer clean. Both HAVE_THREADS=1 and =0 configurations
compile and link.
2026-07-14 18:38:47 +02:00
LibretroAdmin b791390e9c rvp9: decode tiled VP9 streams
Multi-tile streams were rejected (rvp9_decode_frame returned -3),
which in practice meant most real-world VP9 WebM files at 720p and
above showed no thumbnail preview: encoders (ffmpeg/libvpx included)
enable tile columns automatically whenever threading is on at those
resolutions.

Implement tiles per the spec / libvpx semantics. The decoder was
already structured so that almost everything flows through the
above_mi/left_mi neighbour pointers, so the tile-sensitive surface is
small:

- the frame driver parses the tile grid (raster order, every tile but
  the last prefixed with its 32-bit big-endian size, edges aligned to
  64x64 superblocks via get_tile_offset) and runs one bool decoder per
  tile over its byte span
- left never crosses a vertical tile edge: the left_mi derivation, the
  intra have_left test, and the MV candidate scan bounds (is_inside)
  are gated on the current tile's column range, exactly as libvpx's
  set_mi_row_col and is_inside do
- above stays frame-based (also across tile rows), above contexts are
  cleared once per frame, and left entropy contexts are cleared at the
  start of every superblock row of every tile, which is what confines
  them to the tile
- the loop filter is untouched: it runs whole-frame after decode and
  VP9 filters across tile boundaries

Intra above-right needs no tile handling because vp9_predict_intra_
block only permits above-right pixels from within the current
prediction block ((aoff + txw) < bw), which can never cross a tile.
Single-tile behaviour is bit-for-bit unchanged (tile bounds degenerate
to 0..mi_cols).

Verified byte-identical to ffmpeg's VP9 decoder over: a real-world
30 s 1080p 4-tile-column WebM (911 frames, streaming hash equal), a
4-column encode with an uneven superblock split (1100 px wide), a
2-column 1080p encode, a 4-tile-row 720p encode, and the four
pre-existing single-tile regression streams. ASan/UBSan clean over
truncation and random-corruption sweeps of the tiled files (the tile
size parsing is new attack surface and is bounds-checked).

Harness perf (1-core container): the ffmpeg-default 720p 4-column
stream that previously failed now decodes at 8.05 ms/frame - slightly
faster than its single-tile twin (8.39) thanks to smaller entropy
ranges and no cross-tile left dependencies. Real-content 1080p runs
~81 ms/frame here; on such hardware the thumbnail animation budget
lowers the animation rate rather than stalling the menu.
2026-07-14 18:13:19 +02:00
LibretroAdmin 13fc6369a2 rwebm_video: SSE2/NEON YUV->RGB blit
Profiling WebM thumbnail decode showed rwebm_video_blit_i420 taking
~40% of total time for both VP8 and VP9 at 720p (~3.95 ms/frame): the
scalar per-pixel BT.601 conversion with three clamps outweighed
everything but the codecs' own hottest loops.

Add an SSE2 row kernel (8 pixels/iteration, pmaddwd coefficient
pairs, saturating packs as the clamp) and a mechanical NEON
translation (widening multiply-accumulate, saturating narrows), both
engineered to be bit-exact with the scalar path: psrad/vshrq match
the arithmetic shift, and the saturation chain is exactly the 0..255
clamp since the pre-clamp channel range (~-223..481) fits int16. The
scalar path remains as the fallback and handles row tails.

Verified bit-exact against an independent scalar reference over the
full y cube x u,v stride-5 grid through every SIMD lane position,
plus 300 random plane configurations (widths 1..67, odd heights,
padded strides); ASan/UBSan clean, and end-to-end frame dumps match
the pre-SIMD output byte for byte. NEON is compile-checked with
aarch64-gcc per the usual mechanical-translation policy.

Harness numbers (1-core container, whole-pipeline ms/frame):
320x240 VP8 0.95 -> 0.69, VP9 1.26 -> 0.97;
1280x720 VP8 9.78 -> 6.30, VP9 11.80 -> 8.39.
2026-07-14 17:46:22 +02:00
LibretroAdmin 147c479124 rwebp: cut per-frame canvas traffic in the animation stream
Every emitted frame paid two full-canvas memcpys (restore from the
'disposed' buffer, then save back into it) plus a rect memset,
regardless of how small the ANMF sub-rectangle was - 16 MB of memory
traffic per frame at 1080p before any decoding. But the disposal state
only ever differs from the displayed canvas inside the previous
frame's rectangle, so the second buffer is unnecessary: remember the
rectangle and apply dispose-to-background lazily as a rect clear when
the next frame is prepared. This also halves the stream's memory (one
canvas instead of two). The key-frame memset is additionally skipped
when a full-canvas opaque copy is about to overwrite it anyway.

Second, encoders (ffmpeg/libwebp among them) routinely emit an ALPH
chunk whose bytes are all 0xFF; such frames were treated as
non-opaque, sending every pixel through rwebp_blend_px (which contains
a per-pixel integer division) just to take its src_a == 0xFF early
out. The R,B-swap pass already touches every decoded pixel, so
accumulate an AND-mask of the alpha bytes there and mark the frame
opaque when it is - converting the whole compositing step to a plain
row copy. This also covers VP8L frames with an unused alpha channel.

Frame output is byte-identical to the previous code across a corpus
covering dispose-to-background on/off, blend on/off, sub-rectangles,
odd offsets, genuine alpha, and multiple rewind passes (hash-compared
old vs new build, 330 frame emissions). Harness numbers, 1-core
container: 1080p full-frame stream 37.0 -> 34.3 ms/frame; the
remaining cost is the VP8/VP8L decode itself.
2026-07-14 17:46:22 +02:00
Ryunam 36bf97f01c menu: Quality-of-life changes when renaming Playlist Entries (#19201)
* menu: When renaming entries, copy existing entry name in text box

* menu: When renaming entries, show caret and allow arrow movement
2026-07-14 17:31:53 +02:00
Craig 53021d40b1 android: update as per 2026 requirement for play store (#19205) 2026-07-14 17:12:57 +02:00
LibretroAdmin a415ebddf7 Menu: raise thumbnail animation file cap to 256 MiB
GFX_THUMB_ANIM_MAX_FILE was sized for animated WebP, where 64 MiB is
already enormous. Since 7b2b711 the same constant also gates the
file-browser WebM preview (both the static first frame in
gfx_thumbnail_update_path and the animation in
gfx_thumbnail_anim_open), and real-world WebM videos routinely exceed
64 MiB - an 85 MB file gets no preview at all.

Raise it to 256 MiB. The memory cost is transient and opt-in: the
buffer is only resident while the highlighted entry's animation is on
screen. The cap continues to exist so hovering a multi-GiB movie in
the browser cannot trigger a matching allocation.
2026-07-14 16:03:39 +02:00
LibretroAdmin 7b2b7116a1 Menu: WebM previews in the file browser (Ozone/XMB)
1316b60 made .webm decodable by the thumbnail pipeline, but only wired
it up for playlist thumbnails. Highlighting a .webm in the file browser
still showed nothing, because the 'file is its own thumbnail' preview
path that images use is gated four times on image-ness, in series:

- ozone_selection_changed / xmb selection handling only trigger the
  imageviewer preview for FILE_TYPE_IMAGEVIEWER/FILE_TYPE_IMAGE, and
  .webm entries are typed FILE_TYPE_WEBM / FILE_TYPE_MOVIE (with a
  media player compiled in) or FILE_TYPE_PLAIN (without)
- xmb_set_thumbnail_content's imageviewer branch re-checks
  FILE_TYPE_IMAGEVIEWER
- gfx_thumbnail_set_content_image rejects anything whose
  path_is_media_type is not RARCH_CONTENT_IMAGE
- gfx_thumbnail_update_path's imageviewer special case requires
  RARCH_CONTENT_IMAGE before using the file itself as the thumbnail

Open all four for WebM. The menu-driver checks use
image_texture_get_type(entry.path) == IMAGE_TYPE_WEBM rather than the
entry type, so they work regardless of how the file was typed (and
compile away to a false constant without HAVE_RWEBM, since the type
can then never be returned). File typing itself is untouched: OK on a
.webm still launches the media player as before, and the movie icon
is kept.

Unlike an image, the whole WebM must be read to decode a frame, and
file-browser content is arbitrarily large, so gfx_thumbnail_update_path
additionally refuses files past GFX_THUMB_ANIM_MAX_FILE (64 MiB), the
same cap the animation decoder enforces.

MaterialUI has no filebrowser preview mechanism for images either, so
there is nothing to extend there; its playlist thumbnails already
animate via 1316b60.
2026-07-14 15:41:10 +02:00
LibretroAdmin 1316b60f05 Menu: animated WebM thumbnails (Ozone/XMB/MaterialUI)
Let .webm files serve as menu thumbnails, decoded and animated exactly
like animated WebP: the still-image pipeline uploads the first displayed
frame, and gfx_thumbnail_animate() then streams frames under the
existing per-vsync decode budget with durations taken from the
container's block timestamps.

New glue module formats/webm/rwebm_video.c combines the rwebm demuxer
with the rvp8/rvp9 decoders behind the two contracts image_transfer.c
dispatches on:
- rwebm_video_alloc/set_buf_ptr/process_image: rwebp_t-style still
  image (first displayed frame), honouring supports_rgba
- rwebm_video_stream_open/get_info/next/rewind/close: the
  anim-stream contract (ABGR words, per-frame duration in ms,
  loop_count 0 = infinite)

It picks the first video track with a compiled-in codec (VP8 via rvp8;
VP9 via rvp9 when HAVE_RVP9), pre-scans block timestamps so frame
durations need no lookahead decode, splits VP9 superframes, skips
non-shown frames (VP8 show_frame bit / VP9 show_fb), and resets the
decoder on rewind since the stream restarts at a key frame.

Plumbing: IMAGE_TYPE_WEBM through image_texture_get_type and every
image_transfer dispatch, NBIO_TYPE_WEBM through the image load task,
the gfx_thumbnail anim gate, and .webp/.webm appended to the
playlist_allow_non_png extension probe order (webp animation existed
but was previously undiscoverable there). HAVE_RWEBM/HAVE_RVP9 default
to yes in qb builds; console Makefiles are unchanged.

Verified against libvpx via ffmpeg-encoded streams (VP8, VP9,
VP9+altref superframes, odd 317x239 dims): frame counts and container
durations exact, rewind byte-deterministic, final inter frame within
YUV->RGB rounding (max diff 3) of ffmpeg's decode; ASan/UBSan clean
over truncation and random-corruption sweeps. Multi-tile VP9 streams
are rejected gracefully (documented rvp9 limitation). Decode cost in
harness: ~1ms/frame at 320x240, well inside the 8ms anim budget;
oversized streams degrade to a lower frame rate by design.
2026-07-14 15:06:34 +02:00
libretroadmin a4c583e01c Cleanups 2026-07-13 22:03:33 +02:00
Craig 839bacb3f5 Android: Build Google Play compatible version / update NDK / JDK (#19202)
* android: depreciate phoenix as phoenix-jelly-bean

* android: use latest NDK (LTS), raise required minimums for play store, otherwise require Android 5.0+
2026-07-13 19:31:46 +02:00
LibretroAdmin 0a8b5dad74 metal: marshal driver init AppKit work to the main thread
Hardware testing: starting with threaded video enabled and the metal
driver crashes with SIGABRT.  The crash report shows the video worker
thread inside metal_init -> -[RetroArch_OSX setViewType:] ->
-[NSWindow setContentView:], where AppKit raises
NSInternalInconsistencyException (via
_postWindowNeedsToResetDragMarginsUnlessPostingDisabled) because the
window hierarchy is being mutated off the main thread.  The metal
driver is self-contained (no gfx_ctx driver), so the earlier ctx-level
marshaling never covered it - it was the remaining known-uncovered
item from the original series.

metal_init: the whole body is main-thread work - setViewType replaces
the window's contentView, and MetalDriver's -initWithVideo: attaches
to the render view (view.device / view.delegate / view.frame) and
drives [apple_platform setVideoMode:].  Run it via
cocoa_main_thread_sync(); the trampoline calls straight through on
the main thread, so the non-threaded path is unchanged.

metal_focus: apple_platform.hasFocus is [NSApp isActive] (AppKit,
main-thread-only) and is queried from the worker every frame; route
it through cocoa_has_focus(), which reads the value the main thread
last published, lock-free.

metal_show_mouse: setCursorVisible is NSCursor; marshal.

Deliberately NOT marshaled, with reasons:
 - metal_frame: presents via CAMetalLayer.nextDrawable, the
   sanctioned off-main path - verified free of view/AppKit access;
 - metal_free: MetalDriver's dealloc is AppKit-free (viewport free,
   font teardown, GPU-list teardown; MTKView.delegate is weak and
   zeroes without AppKit involvement);
 - metal_suppress_screensaver: NSProcessInfo
   beginActivityWithOptions: is thread-safe.

Known residual risk (pre-existing, unchanged by this commit):
-mtkView:drawableSizeWillChange: fires on the main thread during live
resize while the worker is mid-frame; viewport state is written
unsynchronised.  Worth a look if threaded metal shows resize
glitches, but it is not the reported crash.

RUNTIME-UNTESTED: authored without Apple hardware against the
provided crash report; needs device validation.  Test: launch with
threaded video ON + metal driver (the reported crash), then resize,
fullscreen toggle, focus loss, content reload, clean exit.
2026-07-13 18:35:50 +02:00
LibretroAdmin 4b079f49f6 cocoa/vk: marshal Vulkan ctx driver work to the main thread
Hardware testing confirmed that toggling threaded video with the
vulkan driver on macOS locks up.  Root cause is one layer deeper than
the AppKit marshaling the GL driver received: the MoltenVK binary
itself contains dispatch_sync and internally marshals some
CAMetalLayer work to the GCD MAIN QUEUE when called off the main
thread.  The thread wrapper's main-thread wait pumps only the private
trampoline CFRunLoop mode, which does NOT drain the GCD main queue -
so worker -> vulkan_surface_create -> MoltenVK dispatch_sync(main)
-> main blocked in video_thread_wait_reply() deadlocks.  MoltenVK's
internal dispatch short-circuits when already on the main thread, so
the fix is to run every MoltenVK-reaching ctx path there:

 - init: [apple_platform setViewType:] (AppKit) + vulkan_context_init;
 - set_video_mode: g_view.layer / metalLayer access (AppKit/UIKit),
   vulkan_surface_create, [apple_platform setVideoMode:], NSCursor;
   also seeds the published backing size so the worker never reads
   0x0;
 - set_resize: vulkan_create_swapchain (CAMetalLayer sizing), reached
   from the worker's frame processing on resize;
 - destroy: vulkan_context_destroy (surface teardown).
All via cocoa_main_thread_sync(); direct call when already on the
main thread, so the non-threaded path is unchanged.  Failure results
propagate through explicit args structs rather than assumptions about
partially-initialised vk state.

Queue-concurrency note: the worker is blocked inside
cocoa_main_thread_sync() for the duration of each marshaled call, so
main-thread vkQueue usage inside set_resize/destroy is serialised
with the worker's per-frame queue usage by construction (and
vulkan_common's queue_lock still applies).

video_thread_wrapper.c: the HAVE_MENU texture-sync wait in
video_thread_frame() is unbounded and runs on the main thread; a
worker-side set_resize marshaled mid-frame while the menu is open
would deadlock against it.  Give it the same pump-the-private-mode
treatment as video_thread_wait_reply().  The timed frame-pacing wait
above it needs no change: it breaks after at most one frame period
and the main runloop then drains common modes.  The non-Apple path
is untouched and verified to compile under -std=c89 -pedantic.

RUNTIME-UNTESTED beyond the reported lockup analysis: authored
without Apple hardware.  Re-test vulkan threaded toggle first; if it
still locks up, attach 'sample RetroArch 5' output taken during the
hang - the stalled stack will show whether a dispatch_sync remains on
some other MoltenVK path.
2026-07-13 18:08:41 +02:00
LibretroAdmin 4a3b7b4dcd gfx: ungate threaded video on Apple platforms
Remove the force-off in video_driver_get_threaded()/set_threaded()
("until NSWindow/UIWindow concurrency issues are taken care of") and
expose the Threaded Video menu entry on Apple.  The concurrency issues
in question are addressed by the two preceding commits: cross-thread
geometry/focus publication and main-thread marshaling of AppKit/UIKit
driver init work.

RUNTIME-UNTESTED: authored without Apple hardware.  Validation plan on
real hardware, in order:
 1. non-threaded regression pass first (macOS GL + Metal, iOS):
    resize, fullscreen toggle, rotation, content load/unload - the
    two preceding commits must be behaviourally inert here;
 2. macOS + gl/glcore, threaded video ON: launch (the init handshake
    is the historical deadlock), then resize, fullscreen toggle,
    focus loss, content reload, clean exit;
 3. iOS + gl, threaded video ON: expect the per-frame
    [glk_view display] on the worker to be the first thing to
    misbehave - it is knowingly not yet marshaled;
 4. vulkan (MoltenVK) threaded: ctx init path not yet marshaled;
    treat as expected-broken.
If any step 2 item fails, this commit is the one to revert; the two
groundwork commits are safe to keep.
2026-07-13 18:08:41 +02:00