mirror of
https://github.com/libretro/RetroArch.git
synced 2026-11-08 08:10:30 -10:00
Compare commits
4
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bb4be745e5 | ||
|
|
2fbd665566 | ||
|
|
9dd810c96e | ||
|
|
8d61347d46 |
No files matched your search
@@ -21,12 +21,11 @@ jobs:
|
||||
timeout-minutes: 30
|
||||
steps:
|
||||
- name: Dependencies
|
||||
timeout-minutes: 20
|
||||
timeout-minutes: 5
|
||||
run: |
|
||||
# The gl2 arms compile against the desktop GL and EGL headers,
|
||||
# and the Dingux and RS90 video drivers against SDL 1.2's.
|
||||
# The Direct3D and WASAPI lanes cross-compile with mingw-w64.
|
||||
pkgs="build-essential libgl-dev libegl-dev libgles-dev libsdl1.2-dev gcc-mingw-w64-x86-64 clang gcc-aarch64-linux-gnu gcc-mipsel-linux-gnu"
|
||||
pkgs="build-essential libgl-dev libegl-dev libgles-dev libsdl1.2-dev"
|
||||
if dpkg -s $pkgs >/dev/null 2>&1; then
|
||||
echo "All packages already present on runner image; skipping apt."
|
||||
exit 0
|
||||
@@ -37,23 +36,6 @@ jobs:
|
||||
echo "apt-get update failed (attempt $i); retrying in 10s"
|
||||
sleep 10
|
||||
done
|
||||
# Fetch first, under a bound: a download is safe to stop and
|
||||
# resumes, an install is not. A mirror that stalls is left for
|
||||
# the main archive on the next attempt.
|
||||
for i in 1 2 3; do
|
||||
sudo timeout 240 apt-get install -y --download-only \
|
||||
-o Acquire::Retries=3 -o Acquire::http::Timeout=30 \
|
||||
$pkgs && break
|
||||
[ $i = 3 ] && exit 1
|
||||
echo "package download failed or stalled (attempt $i); retrying from archive.ubuntu.com"
|
||||
for f in /etc/apt/sources.list /etc/apt/sources.list.d/*; do
|
||||
[ -f "$f" ] || continue
|
||||
sudo sed -i -e 's#mirror+file:/etc/apt/apt-mirrors.txt#http://archive.ubuntu.com/ubuntu/#g' \
|
||||
-e 's#http://azure\.archive\.ubuntu\.com/ubuntu#http://archive.ubuntu.com/ubuntu#g' "$f"
|
||||
done
|
||||
sudo timeout 90 apt-get update -y \
|
||||
-o Acquire::Retries=3 -o Acquire::http::Timeout=30 || true
|
||||
done
|
||||
sudo apt-get install -y \
|
||||
-o Acquire::Retries=3 -o Acquire::http::Timeout=30 \
|
||||
$pkgs
|
||||
|
||||
@@ -35,9 +35,6 @@ on:
|
||||
- 'libretro-common/include/vfs/**'
|
||||
- 'libretro-common/include/streams/file_stream.h'
|
||||
- 'libretro-common/samples/file/vfs/**'
|
||||
- 'libretro-common/fastjmp/**'
|
||||
- 'libretro-common/include/fastjmp.h'
|
||||
- 'libretro-common/samples/fastjmp/**'
|
||||
- '.github/workflows/Cross-libc-vfs-samples.yml'
|
||||
pull_request:
|
||||
paths:
|
||||
@@ -46,9 +43,6 @@ on:
|
||||
- 'libretro-common/include/vfs/**'
|
||||
- 'libretro-common/include/streams/file_stream.h'
|
||||
- 'libretro-common/samples/file/vfs/**'
|
||||
- 'libretro-common/fastjmp/**'
|
||||
- 'libretro-common/include/fastjmp.h'
|
||||
- 'libretro-common/samples/fastjmp/**'
|
||||
- '.github/workflows/Cross-libc-vfs-samples.yml'
|
||||
workflow_dispatch:
|
||||
|
||||
@@ -175,15 +169,3 @@ jobs:
|
||||
# Exercises the Win32 CreateFileMapping/MapViewOfFile
|
||||
# backend, which no other job in the tree reaches.
|
||||
./vfs_mapped_ptr_test.exe
|
||||
|
||||
# The Win64 fastjmp_set stores xmm6-xmm15 with movaps and faults
|
||||
# on a misaligned buffer. Linux has no such stores, so the Linux
|
||||
# samples job cannot see a fastjmp_buf that lost its alignment;
|
||||
# this is the only lane that can. Under a second to build and run.
|
||||
- name: fastjmp on Win64
|
||||
working-directory: libretro-common/samples/fastjmp/fastjmp_test
|
||||
run: |
|
||||
set -euo pipefail
|
||||
make clean
|
||||
make
|
||||
./fastjmp_test.exe
|
||||
@@ -1,48 +0,0 @@
|
||||
name: CI extern "C" prototype parity
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
pull_request:
|
||||
branches:
|
||||
- master
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
|
||||
cancel-in-progress: ${{ github.ref != 'refs/heads/master' }}
|
||||
|
||||
jobs:
|
||||
extern-c-prototype-check:
|
||||
name: extern "C" definitions match their C prototypes
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 10
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v5
|
||||
|
||||
- name: Checker self-test
|
||||
shell: bash
|
||||
run: |
|
||||
set -eu
|
||||
# Fixture pass: a matching definition is clean, a definition
|
||||
# with a parameter more than its prototype (the shape of the
|
||||
# 0b2613d Xbox boot crash) is flagged, and a parameter of
|
||||
# another type is flagged.
|
||||
python3 tools/extern_c_prototype_check.py --selftest
|
||||
|
||||
- name: Tree check
|
||||
shell: bash
|
||||
run: |
|
||||
set -eu
|
||||
# A .cpp file that defines a win32_common.h function under
|
||||
# extern "C" without including the header (uwp_main.cpp) is
|
||||
# never checked against the prototype by any compiler: the
|
||||
# linker matches on the name alone. This is the check that
|
||||
# compares the two, for every such definition in the tree.
|
||||
python3 tools/extern_c_prototype_check.py
|
||||
@@ -1,91 +0,0 @@
|
||||
name: CI FreeBSD
|
||||
|
||||
# Builds RetroArch inside a FreeBSD VM (GitHub has no FreeBSD runners)
|
||||
# with the same feature set the games/retroarch port enables by default,
|
||||
# minus Qt (very slow to install). This is what kept the port carrying
|
||||
# patches on top of each release: libudev is FreeBSD's libudev-devd, the
|
||||
# input headers come from dev/evdev/, and there is no linux_common.o to
|
||||
# link against.
|
||||
#
|
||||
# Deliberately NOT installed: evdev-proto and v4l_compat (the Linux
|
||||
# compatibility headers). A stock FreeBSD box must build udev + Wayland
|
||||
# from the dev/evdev/ headers in base, and configure must turn V4L2 off
|
||||
# on its own when libv4l is present but linux/videodev2.h is not.
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [master]
|
||||
pull_request:
|
||||
repository_dispatch:
|
||||
types: [run_build]
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
|
||||
cancel-in-progress: ${{ github.ref != 'refs/heads/master' }}
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 90
|
||||
|
||||
steps:
|
||||
- name: Check Out Repo
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Build in FreeBSD
|
||||
uses: vmactions/freebsd-vm@v1
|
||||
with:
|
||||
release: "14.3"
|
||||
usesh: true
|
||||
copyback: false
|
||||
prepare: |
|
||||
pkg install -y \
|
||||
gmake pkgconf \
|
||||
ffmpeg freetype2 openal-soft mesa-libs \
|
||||
libudev-devd libxkbcommon wayland wayland-protocols \
|
||||
libX11 libXext libXxf86vm libXinerama libXrandr libXv \
|
||||
sdl2 pipewire alsa-lib pulseaudio libdrm libv4l
|
||||
run: |
|
||||
set -e
|
||||
freebsd-version
|
||||
NCPU=$(sysctl -n hw.ncpu)
|
||||
|
||||
# --disable-pipewire must not leave HAVE_PIPEWIRE_STABLE
|
||||
# behind just because libpipewire is installed.
|
||||
./configure --disable-qt --disable-discord --disable-pipewire
|
||||
grep -q '^HAVE_PIPEWIRE_STABLE = 0' config.mk
|
||||
# libv4l2.pc is installed but linux/videodev2.h is not
|
||||
# (v4l_compat): V4L2 must be auto-disabled, not left on to
|
||||
# fail at compile time.
|
||||
grep -q '^HAVE_V4L2 = 0' config.mk
|
||||
|
||||
# Real build: force the FreeBSD-specific paths on so a missing
|
||||
# dependency fails configure instead of silently skipping them.
|
||||
./configure --disable-qt --disable-discord \
|
||||
--enable-udev --enable-x11 --enable-wayland --enable-sdl2 \
|
||||
--enable-ffmpeg --enable-pipewire --enable-alsa \
|
||||
--enable-pulse --enable-oss
|
||||
gmake -j$NCPU clean
|
||||
gmake -j$NCPU info all
|
||||
./retroarch --version
|
||||
# udev input must multiplex with kqueue here, so the binary
|
||||
# must not need libepoll-shim's epoll_* at all.
|
||||
if nm -u retroarch | grep -q 'epoll_'; then
|
||||
echo "retroarch still references epoll (libepoll-shim)"; exit 1
|
||||
fi
|
||||
|
||||
# A fresh RetroArch must pick up data sets the ports install
|
||||
# under /usr/local/share/libretro (assets, info, shaders,
|
||||
# autoconfig) without any retroarch.cfg edits.
|
||||
sh samples/frontend/packaged_dirs/run.sh
|
||||
|
||||
# With v4l_compat the V4L2 camera and video processor core
|
||||
# must still compile; only those two objects are rebuilt.
|
||||
pkg install -y v4l_compat
|
||||
./configure --disable-qt --disable-discord --enable-v4l2
|
||||
grep -q '^HAVE_V4L2 = 1' config.mk
|
||||
gmake -j$NCPU camera/drivers/video4linux2.o \
|
||||
cores/libretro-video-processor/video_processor_v4l2.o
|
||||
@@ -24,7 +24,7 @@ jobs:
|
||||
timeout-minutes: 30
|
||||
steps:
|
||||
- name: Dependencies
|
||||
timeout-minutes: 20
|
||||
timeout-minutes: 5
|
||||
run: |
|
||||
pkgs="build-essential libxkbcommon-dev libx11-xcb-dev zlib1g-dev libfreetype6-dev libegl1-mesa-dev libgles2-mesa-dev libgbm-dev nvidia-cg-toolkit nvidia-cg-dev libavcodec-dev libsdl2-dev libsdl-image1.2-dev libxml2-dev yasm"
|
||||
if dpkg -s $pkgs >/dev/null 2>&1; then
|
||||
@@ -37,23 +37,6 @@ jobs:
|
||||
echo "apt-get update failed (attempt $i); retrying in 10s"
|
||||
sleep 10
|
||||
done
|
||||
# Fetch first, under a bound: a download is safe to stop and
|
||||
# resumes, an install is not. A mirror that stalls is left for
|
||||
# the main archive on the next attempt.
|
||||
for i in 1 2 3; do
|
||||
sudo timeout 240 apt-get install -y --download-only \
|
||||
-o Acquire::Retries=3 -o Acquire::http::Timeout=30 \
|
||||
$pkgs && break
|
||||
[ $i = 3 ] && exit 1
|
||||
echo "package download failed or stalled (attempt $i); retrying from archive.ubuntu.com"
|
||||
for f in /etc/apt/sources.list /etc/apt/sources.list.d/*; do
|
||||
[ -f "$f" ] || continue
|
||||
sudo sed -i -e 's#mirror+file:/etc/apt/apt-mirrors.txt#http://archive.ubuntu.com/ubuntu/#g' \
|
||||
-e 's#http://azure\.archive\.ubuntu\.com/ubuntu#http://archive.ubuntu.com/ubuntu#g' "$f"
|
||||
done
|
||||
sudo timeout 90 apt-get update -y \
|
||||
-o Acquire::Retries=3 -o Acquire::http::Timeout=30 || true
|
||||
done
|
||||
sudo apt-get install -y \
|
||||
-o Acquire::Retries=3 -o Acquire::http::Timeout=30 \
|
||||
$pkgs
|
||||
|
||||
@@ -72,7 +72,7 @@ jobs:
|
||||
# comment for why SDL2 over xvideo. No sanitizer-specific
|
||||
# packages required; libasan / libubsan ship with the gcc
|
||||
# that ubuntu-latest has installed by default.
|
||||
timeout-minutes: 20
|
||||
timeout-minutes: 5
|
||||
run: |
|
||||
pkgs="build-essential libxkbcommon-dev libx11-xcb-dev xvfb x11-utils zlib1g-dev libfreetype6-dev libegl1-mesa-dev libgles2-mesa-dev libgbm-dev nvidia-cg-toolkit nvidia-cg-dev libavcodec-dev libsdl2-dev libsdl-image1.2-dev libxml2-dev yasm"
|
||||
if dpkg -s $pkgs >/dev/null 2>&1; then
|
||||
@@ -85,23 +85,6 @@ jobs:
|
||||
echo "apt-get update failed (attempt $i); retrying in 10s"
|
||||
sleep 10
|
||||
done
|
||||
# Fetch first, under a bound: a download is safe to stop and
|
||||
# resumes, an install is not. A mirror that stalls is left for
|
||||
# the main archive on the next attempt.
|
||||
for i in 1 2 3; do
|
||||
sudo timeout 240 apt-get install -y --download-only \
|
||||
-o Acquire::Retries=3 -o Acquire::http::Timeout=30 \
|
||||
$pkgs && break
|
||||
[ $i = 3 ] && exit 1
|
||||
echo "package download failed or stalled (attempt $i); retrying from archive.ubuntu.com"
|
||||
for f in /etc/apt/sources.list /etc/apt/sources.list.d/*; do
|
||||
[ -f "$f" ] || continue
|
||||
sudo sed -i -e 's#mirror+file:/etc/apt/apt-mirrors.txt#http://archive.ubuntu.com/ubuntu/#g' \
|
||||
-e 's#http://azure\.archive\.ubuntu\.com/ubuntu#http://archive.ubuntu.com/ubuntu#g' "$f"
|
||||
done
|
||||
sudo timeout 90 apt-get update -y \
|
||||
-o Acquire::Retries=3 -o Acquire::http::Timeout=30 || true
|
||||
done
|
||||
sudo apt-get install -y \
|
||||
-o Acquire::Retries=3 -o Acquire::http::Timeout=30 \
|
||||
$pkgs
|
||||
@@ -400,64 +383,3 @@ jobs:
|
||||
| sort -u | head -20
|
||||
fi
|
||||
echo "[done] imageviewer headless smoke (soft-fail)"
|
||||
|
||||
- name: Screenshot on exit under LeakSanitizer (strict)
|
||||
# The smoke above runs with detect_leaks=0, which is how a
|
||||
# synchronous screenshot leaking its task state and the whole
|
||||
# captured frame on every call went unnoticed: --max-frames-ss
|
||||
# and save-state thumbnails both take that path. This run
|
||||
# takes one and checks for leaks with only the system libraries
|
||||
# suppressed - Mesa, X11, SDL, D-Bus and friends keep caches
|
||||
# alive to exit that are not RetroArch's - so anything left is
|
||||
# ours and fails the job.
|
||||
env:
|
||||
ASAN_OPTIONS: abort_on_error=1:detect_leaks=1:print_stacktrace=1
|
||||
UBSAN_OPTIONS: print_stacktrace=1:halt_on_error=1
|
||||
LSAN_OPTIONS: suppressions=/tmp/lsan-system.supp:print_suppressions=0
|
||||
run: |
|
||||
set -eu
|
||||
cat > /tmp/lsan-system.supp <<EOF
|
||||
leak:libGL
|
||||
leak:libGLX
|
||||
leak:libGLdispatch
|
||||
leak:libgallium
|
||||
leak:libLLVM
|
||||
leak:_dri.so
|
||||
leak:libEGL
|
||||
leak:libgbm
|
||||
leak:libX11
|
||||
leak:libxcb
|
||||
leak:libSDL2
|
||||
leak:libdbus-1
|
||||
leak:libfontconfig
|
||||
leak:libfreetype
|
||||
leak:libasound
|
||||
leak:libpulse
|
||||
EOF
|
||||
mkdir -p /tmp/lsan-cfg /tmp/lsan-shots
|
||||
cat > /tmp/lsan-cfg/retroarch.cfg <<EOF
|
||||
video_driver = "sdl2"
|
||||
audio_driver = "null"
|
||||
video_threaded = "false"
|
||||
video_fullscreen = "false"
|
||||
video_windowed_fullscreen = "false"
|
||||
screenshot_directory = "/tmp/lsan-shots"
|
||||
EOF
|
||||
set +e
|
||||
timeout 120 xvfb-run -a -s "-screen 0 320x240x24" ./retroarch \
|
||||
-c /tmp/lsan-cfg/retroarch.cfg \
|
||||
-L cores/libretro-imageviewer/image_core.so \
|
||||
/tmp/test.png \
|
||||
--max-frames=60 \
|
||||
--max-frames-ss \
|
||||
--max-frames-ss-path=/tmp/lsan-shots/exit.png \
|
||||
> /tmp/lsan.log 2>&1
|
||||
rc=$?
|
||||
set -e
|
||||
if [ "$rc" != 0 ] || grep -qE "leak of|AddressSanitizer:|runtime error:" /tmp/lsan.log; then
|
||||
echo "exit=${rc}"
|
||||
grep -E -A 12 "leak of|AddressSanitizer:|runtime error:" /tmp/lsan.log | head -120
|
||||
exit 1
|
||||
fi
|
||||
test -s /tmp/lsan-shots/exit.png
|
||||
echo "[pass] screenshot on exit, no leaks outside the system libraries"
|
||||
@@ -21,26 +21,13 @@ concurrency:
|
||||
|
||||
jobs:
|
||||
autoconf-tests:
|
||||
name: Build and run the autoconfig regression suite (${{ matrix.lane }})
|
||||
name: Build and run the autoconfig regression suite
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: ${{ matrix.sanitizer == '' && 15 || 40 }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
# The suite fails on any sanitizer report in any run, so the
|
||||
# sanitized lanes hold the whole frontend it drives - runloop,
|
||||
# task queue, autodetect - to the same bar as the assertions.
|
||||
include:
|
||||
- lane: plain
|
||||
sanitizer: ''
|
||||
- lane: ASan + UBSan
|
||||
sanitizer: address,undefined
|
||||
- lane: TSan
|
||||
sanitizer: thread
|
||||
timeout-minutes: 15
|
||||
|
||||
steps:
|
||||
- name: Install dependencies
|
||||
timeout-minutes: 20
|
||||
timeout-minutes: 5
|
||||
run: |
|
||||
pkgs="build-essential zlib1g-dev libfreetype6-dev"
|
||||
if dpkg -s $pkgs >/dev/null 2>&1; then
|
||||
@@ -53,23 +40,6 @@ jobs:
|
||||
echo "apt-get update failed (attempt $i); retrying in 10s"
|
||||
sleep 10
|
||||
done
|
||||
# Fetch first, under a bound: a download is safe to stop and
|
||||
# resumes, an install is not. A mirror that stalls is left for
|
||||
# the main archive on the next attempt.
|
||||
for i in 1 2 3; do
|
||||
sudo timeout 240 apt-get install -y --download-only \
|
||||
-o Acquire::Retries=3 -o Acquire::http::Timeout=30 \
|
||||
$pkgs && break
|
||||
[ $i = 3 ] && exit 1
|
||||
echo "package download failed or stalled (attempt $i); retrying from archive.ubuntu.com"
|
||||
for f in /etc/apt/sources.list /etc/apt/sources.list.d/*; do
|
||||
[ -f "$f" ] || continue
|
||||
sudo sed -i -e 's#mirror+file:/etc/apt/apt-mirrors.txt#http://archive.ubuntu.com/ubuntu/#g' \
|
||||
-e 's#http://azure\.archive\.ubuntu\.com/ubuntu#http://archive.ubuntu.com/ubuntu#g' "$f"
|
||||
done
|
||||
sudo timeout 90 apt-get update -y \
|
||||
-o Acquire::Retries=3 -o Acquire::http::Timeout=30 || true
|
||||
done
|
||||
sudo apt-get install -y \
|
||||
-o Acquire::Retries=3 -o Acquire::http::Timeout=30 \
|
||||
$pkgs
|
||||
@@ -89,22 +59,11 @@ jobs:
|
||||
grep -q '^HAVE_TEST_DRIVERS = 1' config.mk
|
||||
|
||||
- name: Build
|
||||
run: |
|
||||
if [ -n "${{ matrix.sanitizer }}" ]; then
|
||||
make -j"$(nproc)" SANITIZER=${{ matrix.sanitizer }} DEBUG=1
|
||||
else
|
||||
make -j"$(nproc)"
|
||||
fi
|
||||
run: make -j"$(nproc)"
|
||||
|
||||
- name: Run autoconfig regression suite
|
||||
shell: bash
|
||||
run: |
|
||||
set -eu
|
||||
test -x ./retroarch
|
||||
if [ "${{ matrix.sanitizer }}" = thread ]; then
|
||||
# TSan cannot map its shadow under the runner's ASLR
|
||||
# entropy; setarch -R carries to every child the suite runs.
|
||||
setarch "$(uname -m)" -R ./tests-other/test_autoconf.sh ./retroarch
|
||||
else
|
||||
./tests-other/test_autoconf.sh ./retroarch
|
||||
fi
|
||||
./tests-other/test_autoconf.sh ./retroarch
|
||||
@@ -1,79 +0,0 @@
|
||||
name: CI Linux filter plugins
|
||||
|
||||
# Builds every video filter (gfx/video_filters) and audio DSP filter
|
||||
# (libretro-common/audio/dsp_filters) plugin with its own Makefile and
|
||||
# runs samples/gfx/filter_plugins over all of them: each plugin is
|
||||
# loaded with RTLD_NOW into a host that does not link libm, so a
|
||||
# symbol it cannot resolve fails the load, then fed a frame or a run
|
||||
# of audio blocks at its default settings. Every video filter that
|
||||
# takes more than one worker must give the single-worker output byte for
|
||||
# byte with 2, 3 and 4 workers running at once. The same run is repeated
|
||||
# with the plugins and the harness built under ASan, UBSan and
|
||||
# LeakSanitizer, and under ThreadSanitizer.
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
paths:
|
||||
- 'gfx/video_filters/**'
|
||||
- 'libretro-common/audio/dsp_filters/**'
|
||||
- 'libretro-common/include/libretro_dspfilter.h'
|
||||
- 'samples/gfx/filter_plugins/**'
|
||||
- '.github/workflows/Linux-filter-plugins.yml'
|
||||
pull_request:
|
||||
paths:
|
||||
- 'gfx/video_filters/**'
|
||||
- 'libretro-common/audio/dsp_filters/**'
|
||||
- 'libretro-common/include/libretro_dspfilter.h'
|
||||
- 'samples/gfx/filter_plugins/**'
|
||||
- '.github/workflows/Linux-filter-plugins.yml'
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event_name == 'pull_request' && github.ref || github.sha }}
|
||||
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
|
||||
|
||||
jobs:
|
||||
filter-plugins:
|
||||
name: Load and run every filter plugin
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 15
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v5
|
||||
|
||||
- name: Build the plugins and the harness
|
||||
run: |
|
||||
set -eu
|
||||
make -C samples/gfx/filter_plugins
|
||||
# The harness must not bring libm in on the plugins' behalf.
|
||||
if readelf -d samples/gfx/filter_plugins/filter_plugins_test \
|
||||
| grep -q 'libm\.so'; then
|
||||
echo "filter_plugins_test links libm; it would hide a plugin that does not"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Load and run every plugin
|
||||
run: make -C samples/gfx/filter_plugins check
|
||||
|
||||
- name: Load and run every plugin under ASan, UBSan and LeakSanitizer
|
||||
env:
|
||||
ASAN_OPTIONS: abort_on_error=1:detect_leaks=1:print_stacktrace=1
|
||||
UBSAN_OPTIONS: halt_on_error=1:print_stacktrace=1
|
||||
run: |
|
||||
set -eu
|
||||
make -C samples/gfx/filter_plugins clean
|
||||
make -C samples/gfx/filter_plugins check SANITIZER=address,undefined
|
||||
|
||||
- name: Load and run every plugin under ThreadSanitizer
|
||||
env:
|
||||
TSAN_OPTIONS: halt_on_error=1
|
||||
run: |
|
||||
set -eu
|
||||
make -C samples/gfx/filter_plugins clean
|
||||
make -C samples/gfx/filter_plugins check SANITIZER=thread
|
||||
@@ -1,92 +0,0 @@
|
||||
name: CI Linux frontend overhead [frame-path harness]
|
||||
|
||||
# What one runloop_iterate() costs when the core costs nothing, and
|
||||
# whether anything on the frame path allocates. The harness links the
|
||||
# shipping release objects with main() replaced, boots on the null
|
||||
# drivers, runs a core that pushes one frame, one audio batch and one
|
||||
# input read per retro_run, and counts heap calls over the frame path
|
||||
# by glibc interposition - which is why this is a release build with
|
||||
# no allocator sanitizer in the way. It also prints microseconds per
|
||||
# frame under fast-forward and trips on a loose bound, so a blocking
|
||||
# call or a sleep that lands on the frame path fails here rather than
|
||||
# in a bug report about stutter.
|
||||
#
|
||||
# Everything a default desktop build compiles in is compiled in, so a
|
||||
# product that starts costing the frame while idle is measured, not
|
||||
# excluded.
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
pull_request:
|
||||
branches:
|
||||
- master
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
|
||||
cancel-in-progress: ${{ github.ref != 'refs/heads/master' }}
|
||||
|
||||
jobs:
|
||||
linux-frontend-overhead:
|
||||
name: Release build, frame-path heap and cost
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 30
|
||||
|
||||
steps:
|
||||
- name: Install dependencies
|
||||
timeout-minutes: 20
|
||||
run: |
|
||||
pkgs="build-essential libxkbcommon-dev libx11-xcb-dev zlib1g-dev libfreetype6-dev libegl1-mesa-dev libgles2-mesa-dev libgbm-dev libavcodec-dev libsdl2-dev libxml2-dev yasm"
|
||||
if dpkg -s $pkgs >/dev/null 2>&1; then
|
||||
echo "All packages already present on runner image; skipping apt."
|
||||
exit 0
|
||||
fi
|
||||
for i in 1 2 3; do
|
||||
sudo timeout 90 apt-get update -y \
|
||||
-o Acquire::Retries=3 -o Acquire::http::Timeout=30 && break
|
||||
echo "apt-get update failed (attempt $i); retrying in 10s"
|
||||
sleep 10
|
||||
done
|
||||
# Fetch first, under a bound: a download is safe to stop and
|
||||
# resumes, an install is not. A mirror that stalls is left for
|
||||
# the main archive on the next attempt.
|
||||
for i in 1 2 3; do
|
||||
sudo timeout 240 apt-get install -y --download-only \
|
||||
-o Acquire::Retries=3 -o Acquire::http::Timeout=30 \
|
||||
$pkgs && break
|
||||
[ $i = 3 ] && exit 1
|
||||
echo "package download failed or stalled (attempt $i); retrying from archive.ubuntu.com"
|
||||
for f in /etc/apt/sources.list /etc/apt/sources.list.d/*; do
|
||||
[ -f "$f" ] || continue
|
||||
sudo sed -i -e 's#mirror+file:/etc/apt/apt-mirrors.txt#http://archive.ubuntu.com/ubuntu/#g' \
|
||||
-e 's#http://azure\.archive\.ubuntu\.com/ubuntu#http://archive.ubuntu.com/ubuntu#g' "$f"
|
||||
done
|
||||
sudo timeout 90 apt-get update -y \
|
||||
-o Acquire::Retries=3 -o Acquire::http::Timeout=30 || true
|
||||
done
|
||||
sudo apt-get install -y \
|
||||
-o Acquire::Retries=3 -o Acquire::http::Timeout=30 \
|
||||
$pkgs
|
||||
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v5
|
||||
|
||||
- name: Configure
|
||||
# Non-Qt, as the harness requires (with Qt, main() lives in
|
||||
# ui_qt.o). Everything else at its default.
|
||||
run: ./configure --disable-qt
|
||||
|
||||
- name: Build (release)
|
||||
run: make -j$(getconf _NPROCESSORS_ONLN)
|
||||
|
||||
- name: Run the frontend-overhead harness
|
||||
shell: bash
|
||||
run: |
|
||||
set -o pipefail
|
||||
SAN=none make -C samples/runloop/frontend_overhead check
|
||||
make -C samples/runloop/frontend_overhead clean
|
||||
@@ -1,125 +0,0 @@
|
||||
name: CI Linux gl1 upload
|
||||
|
||||
# Runs the gl1 video driver under Xvfb with a test core and checks the
|
||||
# screen against the frame the core drew, pixel for pixel, down each of
|
||||
# the driver's upload paths: frames taken straight from the core's
|
||||
# buffer, the no-GL_EXT_bgra swizzle, native RGB565, the GL 1.1 CPU
|
||||
# expansion, and texture storage respecified across frame size changes.
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
paths:
|
||||
- 'gfx/drivers/gl1.c'
|
||||
- 'libretro-common/gfx/scaler/pixconv.c'
|
||||
- 'libretro-common/include/gfx/scaler/pixconv.h'
|
||||
- 'samples/gfx/gl1_upload/**'
|
||||
- '.github/workflows/Linux-gl1-upload.yml'
|
||||
pull_request:
|
||||
paths:
|
||||
- 'gfx/drivers/gl1.c'
|
||||
- 'libretro-common/gfx/scaler/pixconv.c'
|
||||
- 'libretro-common/include/gfx/scaler/pixconv.h'
|
||||
- 'samples/gfx/gl1_upload/**'
|
||||
- '.github/workflows/Linux-gl1-upload.yml'
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event_name == 'pull_request' && github.ref || github.sha }}
|
||||
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
|
||||
|
||||
jobs:
|
||||
gl1-upload:
|
||||
name: Run retroarch on gl1 under Xvfb and check the frame
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 25
|
||||
|
||||
steps:
|
||||
- name: Install dependencies
|
||||
timeout-minutes: 20
|
||||
run: |
|
||||
for i in 1 2 3; do
|
||||
sudo timeout 90 apt-get update -y \
|
||||
-o Acquire::Retries=3 -o Acquire::http::Timeout=30 && break
|
||||
echo "apt-get update failed (attempt $i); retrying in 10s"
|
||||
sleep 10
|
||||
done
|
||||
# Fetch first, under a bound: a download is safe to stop and
|
||||
# resumes, an install is not. A mirror that stalls is left for
|
||||
# the main archive on the next attempt.
|
||||
for i in 1 2 3; do
|
||||
sudo timeout 240 apt-get install -y --download-only \
|
||||
-o Acquire::Retries=3 -o Acquire::http::Timeout=30 \
|
||||
build-essential zlib1g-dev ccache python3 \
|
||||
xvfb libgl1-mesa-dri \
|
||||
libx11-dev libx11-xcb-dev libxcb1-dev libxext-dev \
|
||||
libxxf86vm-dev libxrandr-dev libxinerama-dev \
|
||||
libgl-dev libegl-dev && break
|
||||
[ $i = 3 ] && exit 1
|
||||
echo "package download failed or stalled (attempt $i); retrying from archive.ubuntu.com"
|
||||
for f in /etc/apt/sources.list /etc/apt/sources.list.d/*; do
|
||||
[ -f "$f" ] || continue
|
||||
sudo sed -i -e 's#mirror+file:/etc/apt/apt-mirrors.txt#http://archive.ubuntu.com/ubuntu/#g' \
|
||||
-e 's#http://azure\.archive\.ubuntu\.com/ubuntu#http://archive.ubuntu.com/ubuntu#g' "$f"
|
||||
done
|
||||
sudo timeout 90 apt-get update -y \
|
||||
-o Acquire::Retries=3 -o Acquire::http::Timeout=30 || true
|
||||
done
|
||||
sudo apt-get install -y \
|
||||
-o Acquire::Retries=3 -o Acquire::http::Timeout=30 \
|
||||
build-essential zlib1g-dev ccache python3 \
|
||||
xvfb libgl1-mesa-dri \
|
||||
libx11-dev libx11-xcb-dev libxcb1-dev libxext-dev \
|
||||
libxxf86vm-dev libxrandr-dev libxinerama-dev \
|
||||
libgl-dev libegl-dev
|
||||
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v5
|
||||
|
||||
- name: Restore ccache
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ~/.ccache
|
||||
key: ccache-${{ github.workflow }}-${{ github.job }}-${{ github.sha }}
|
||||
restore-keys: |
|
||||
ccache-${{ github.workflow }}-${{ github.job }}-
|
||||
|
||||
- name: Enable ccache
|
||||
run: |
|
||||
set -eu
|
||||
export CCACHE_DIR="$HOME/.ccache"
|
||||
echo "CCACHE_DIR=$CCACHE_DIR" >> "$GITHUB_ENV"
|
||||
echo "/usr/lib/ccache" >> "$GITHUB_PATH"
|
||||
ccache -M 500M
|
||||
ccache -z
|
||||
|
||||
- name: Build RetroArch (X11 + OpenGL, the rest off)
|
||||
shell: bash
|
||||
run: |
|
||||
set -eu
|
||||
./configure --disable-qt --disable-wayland --disable-sdl \
|
||||
--disable-sdl2 --disable-ffmpeg --disable-vulkan \
|
||||
--disable-pulse --disable-discord \
|
||||
--disable-networking --disable-cheevos \
|
||||
--disable-glslang --disable-slang \
|
||||
--disable-spirv_cross --disable-xmb \
|
||||
--disable-materialui --disable-ozone \
|
||||
--disable-7zip --disable-flac \
|
||||
--disable-audiomixer --disable-translate \
|
||||
--disable-accessibility --disable-crtswitchres \
|
||||
--disable-langextra --disable-libretrodb \
|
||||
--disable-video_filter --disable-dsp_filter
|
||||
grep -q '^HAVE_OPENGL1 = 1' config.mk
|
||||
make -j$(nproc)
|
||||
ccache -s
|
||||
|
||||
- name: Run gl1_upload_test
|
||||
shell: bash
|
||||
run: |
|
||||
set -eu
|
||||
make -C samples/gfx/gl1_upload
|
||||
timeout 600 python3 samples/gfx/gl1_upload/gl1_upload_test.py ./retroarch
|
||||
@@ -1,139 +0,0 @@
|
||||
name: CI Linux library network backends
|
||||
|
||||
# The built-in TLS, SMB and NFS clients are the defaults and nothing is
|
||||
# bundled. A system mbedTLS or BearSSL can stand in for the TLS client
|
||||
# (--enable-mbedtls, --enable-bearssl) and a system libsmb2 for the SMB
|
||||
# client (--enable-libsmb). Each lane configures with its option, builds
|
||||
# RetroArch to the link, checks the binary carries that library's
|
||||
# backend and not the built-in client, and then operates the backend:
|
||||
# the TLS lanes run the cheevos login transport against a local HTTPS
|
||||
# server, the libsmb2 lane runs the SMB VFS tests against a local smbd.
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
pull_request:
|
||||
branches:
|
||||
- master
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
|
||||
cancel-in-progress: ${{ github.ref != 'refs/heads/master' }}
|
||||
|
||||
jobs:
|
||||
library-backends:
|
||||
name: ${{ matrix.lane }}
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 60
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- lane: mbedtls
|
||||
configure: --enable-mbedtls
|
||||
present: mbedtls_ssl_handshake
|
||||
absent: ' T ssl_socket_retro_was_resumed'
|
||||
- lane: bearssl
|
||||
configure: --enable-bearssl
|
||||
present: br_ssl_client_init_full
|
||||
absent: ' T ssl_socket_retro_was_resumed'
|
||||
- lane: libsmb2
|
||||
configure: --enable-libsmb
|
||||
present: ' U smb2_connect_share'
|
||||
absent: ' T rsmb_connect'
|
||||
|
||||
steps:
|
||||
- name: Dependencies
|
||||
run: |
|
||||
sudo apt-get update -y
|
||||
sudo apt-get install -y build-essential pkg-config cmake \
|
||||
libgl1-mesa-dev libx11-dev libxext-dev libasound2-dev libsdl2-dev \
|
||||
zlib1g-dev libfreetype6-dev python3 openssl \
|
||||
libmbedtls-dev libbearssl-dev samba
|
||||
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v5
|
||||
|
||||
- name: libsmb2 from upstream (no Ubuntu package)
|
||||
if: matrix.lane == 'libsmb2'
|
||||
run: |
|
||||
set -eu
|
||||
git clone -q --depth 1 https://github.com/sahlberg/libsmb2.git "$RUNNER_TEMP/libsmb2"
|
||||
cmake -S "$RUNNER_TEMP/libsmb2" -B "$RUNNER_TEMP/libsmb2/build" \
|
||||
-DCMAKE_INSTALL_PREFIX=/usr/local -DBUILD_SHARED_LIBS=ON >/dev/null
|
||||
make -C "$RUNNER_TEMP/libsmb2/build" -j"$(nproc)" >/dev/null
|
||||
sudo make -C "$RUNNER_TEMP/libsmb2/build" install >/dev/null
|
||||
sudo ldconfig
|
||||
echo "PKG_CONFIG_PATH=/usr/local/lib/pkgconfig" >> "$GITHUB_ENV"
|
||||
pkg-config --modversion libsmb2 || PKG_CONFIG_PATH=/usr/local/lib/pkgconfig pkg-config --modversion libsmb2
|
||||
|
||||
- name: Configure
|
||||
run: |
|
||||
set -eu
|
||||
./configure ${{ matrix.configure }} --disable-qt --disable-wayland --disable-ffmpeg
|
||||
grep -E '^HAVE_(RETROSSL|MBEDTLS|BEARSSL|RETROSMB|SMBCLIENT|SSL) ' config.mk
|
||||
case "${{ matrix.lane }}" in
|
||||
mbedtls) grep -q '^HAVE_MBEDTLS = 1' config.mk; grep -q '^HAVE_RETROSSL = 0' config.mk ;;
|
||||
bearssl) grep -q '^HAVE_BEARSSL = 1' config.mk; grep -q '^HAVE_RETROSSL = 0' config.mk ;;
|
||||
libsmb2) grep -q '^HAVE_SMBCLIENT = 1' config.mk; grep -q '^HAVE_RETROSMB = 0' config.mk ;;
|
||||
esac
|
||||
|
||||
- name: Build
|
||||
run: make -j"$(nproc)"
|
||||
|
||||
- name: Check which backend linked
|
||||
run: |
|
||||
set -eu
|
||||
nm retroarch | grep -q -- '${{ matrix.present }}'
|
||||
if nm retroarch | grep -q -- '${{ matrix.absent }}'; then
|
||||
echo "the built-in client is still linked in the ${{ matrix.lane }} build"; exit 1
|
||||
fi
|
||||
echo "[pass] ${{ matrix.lane }} backend linked, built-in client absent"
|
||||
|
||||
- name: Operate the TLS backend (cheevos login transport, ASan + UBSan)
|
||||
if: matrix.lane != 'libsmb2'
|
||||
working-directory: samples/network/cheevos_login
|
||||
run: |
|
||||
set -eu
|
||||
case "${{ matrix.lane }}" in mbedtls) be=mbed ;; bearssl) be=bear ;; esac
|
||||
make clean all SSL_BACKEND=$be SANITIZER=address,undefined
|
||||
ASAN_OPTIONS=detect_leaks=1:detect_stack_use_after_return=1 \
|
||||
UBSAN_OPTIONS=print_stacktrace=1:halt_on_error=1 timeout 120 \
|
||||
make check SSL_BACKEND=$be
|
||||
echo "[pass] ${{ matrix.lane }}: cheevos login transport over a local HTTPS server"
|
||||
|
||||
- name: Operate the libsmb2 backend (SMB VFS against a local smbd)
|
||||
if: matrix.lane == 'libsmb2'
|
||||
run: |
|
||||
set -eu
|
||||
sudo -E env PATH="$PATH" PKG_CONFIG_PATH="$PKG_CONFIG_PATH" WITH_LIBSMB2=1 \
|
||||
samples/network/smb_retro/local_samba_test.sh
|
||||
sudo make -C samples/network/smb_retro clean
|
||||
|
||||
# libsmb2 keeps process-wide state (its list of live contexts) with
|
||||
# no lock of its own; the VFS backend must serialise it. A race
|
||||
# there needs two cores to show up by timing, so the same tests run
|
||||
# again under TSan against a TSan-built libsmb2, which reports it on
|
||||
# any runner, every time.
|
||||
- name: Operate the libsmb2 backend under TSan (instrumented libsmb2)
|
||||
if: matrix.lane == 'libsmb2'
|
||||
run: |
|
||||
set -eu
|
||||
cmake -S "$RUNNER_TEMP/libsmb2" -B "$RUNNER_TEMP/libsmb2/build-tsan" \
|
||||
-DCMAKE_INSTALL_PREFIX=/opt/libsmb2-tsan -DBUILD_SHARED_LIBS=ON \
|
||||
-DCMAKE_C_FLAGS="-O1 -g -fsanitize=thread" \
|
||||
-DCMAKE_SHARED_LINKER_FLAGS="-fsanitize=thread" >/dev/null
|
||||
make -C "$RUNNER_TEMP/libsmb2/build-tsan" -j"$(nproc)" >/dev/null
|
||||
sudo make -C "$RUNNER_TEMP/libsmb2/build-tsan" install >/dev/null
|
||||
sudo -E env PATH="$PATH" \
|
||||
PKG_CONFIG_PATH=/opt/libsmb2-tsan/lib/pkgconfig \
|
||||
LD_LIBRARY_PATH=/opt/libsmb2-tsan/lib \
|
||||
TSAN_OPTIONS="halt_on_error=1 exitcode=66" \
|
||||
CFLAGS="-O1 -g -fsanitize=thread" LDLIBS="-fsanitize=thread" \
|
||||
WITH_LIBSMB2=1 samples/network/smb_retro/local_samba_test.sh
|
||||
sudo make -C samples/network/smb_retro clean
|
||||
@@ -27,14 +27,13 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Install dependencies
|
||||
timeout-minutes: 20
|
||||
timeout-minutes: 5
|
||||
run: |
|
||||
# All three packages ship on the stock ubuntu-latest image, so
|
||||
# this step is a no-op there and apt is only contacted if the
|
||||
# image ever drops one of them.
|
||||
# ffmpeg (with libx264 and libx265): formats/h264/rh264_lossless_test
|
||||
# and formats/h265/rh265_lossless_test build their corpora and
|
||||
# their reference decodes with it.
|
||||
# ffmpeg (with libx264): formats/h264/rh264_lossless_test builds
|
||||
# its lossless corpus and its reference decodes with it.
|
||||
if dpkg -s build-essential zlib1g-dev clang ffmpeg >/dev/null 2>&1; then
|
||||
echo "All packages already present on runner image; skipping apt."
|
||||
exit 0
|
||||
@@ -45,23 +44,6 @@ jobs:
|
||||
echo "apt-get update failed (attempt $i); retrying in 10s"
|
||||
sleep 10
|
||||
done
|
||||
# Fetch first, under a bound: a download is safe to stop and
|
||||
# resumes, an install is not. A mirror that stalls is left for
|
||||
# the main archive on the next attempt.
|
||||
for i in 1 2 3; do
|
||||
sudo timeout 240 apt-get install -y --download-only \
|
||||
-o Acquire::Retries=3 -o Acquire::http::Timeout=30 \
|
||||
build-essential zlib1g-dev clang ffmpeg && break
|
||||
[ $i = 3 ] && exit 1
|
||||
echo "package download failed or stalled (attempt $i); retrying from archive.ubuntu.com"
|
||||
for f in /etc/apt/sources.list /etc/apt/sources.list.d/*; do
|
||||
[ -f "$f" ] || continue
|
||||
sudo sed -i -e 's#mirror+file:/etc/apt/apt-mirrors.txt#http://archive.ubuntu.com/ubuntu/#g' \
|
||||
-e 's#http://azure\.archive\.ubuntu\.com/ubuntu#http://archive.ubuntu.com/ubuntu#g' "$f"
|
||||
done
|
||||
sudo timeout 90 apt-get update -y \
|
||||
-o Acquire::Retries=3 -o Acquire::http::Timeout=30 || true
|
||||
done
|
||||
sudo apt-get install -y \
|
||||
-o Acquire::Retries=3 -o Acquire::http::Timeout=30 \
|
||||
build-essential zlib1g-dev clang ffmpeg
|
||||
@@ -110,7 +92,6 @@ jobs:
|
||||
# self-contained test and exits 0 on success / non-zero on
|
||||
# failure. These are built AND executed.
|
||||
declare -a RUN_TARGETS=(
|
||||
float_to_s32_test
|
||||
compat_fnmatch_test
|
||||
snprintf
|
||||
unbase64_test
|
||||
@@ -121,20 +102,17 @@ jobs:
|
||||
config_file_test
|
||||
path_resolve_realpath_test
|
||||
fill_pathname_test
|
||||
nbio_test
|
||||
rpng
|
||||
rzip_chunk_size_test
|
||||
rzip_matches_buf_test
|
||||
rzip_codec_test
|
||||
rzip_parallel_test
|
||||
data_transfer_source_test
|
||||
data_transfer_prefix_test
|
||||
data_transfer_window_test
|
||||
audio_transfer_window_test
|
||||
rjpeg_test
|
||||
rh264_lossless_test
|
||||
rh264_lossless_test_nothreads
|
||||
rh265_lossless_test
|
||||
rh265_lossless_test_nothreads
|
||||
r7z_lzma_props_test
|
||||
vcdiff_test
|
||||
net_ifinfo
|
||||
@@ -157,11 +135,6 @@ jobs:
|
||||
rtga_test
|
||||
rbmp_test
|
||||
rbmp_encode_test
|
||||
# Every 16-bit input through each pixconv converter, with
|
||||
# SSE2, with MMX only, and with no SIMD at all.
|
||||
pixconv_test
|
||||
pixconv_test_nosimd
|
||||
pixconv_test_scalar
|
||||
rpng_chunk_overflow_test
|
||||
rpng_roundtrip_test
|
||||
rpng_decode_corpus_test
|
||||
@@ -173,18 +146,11 @@ jobs:
|
||||
screenshot_io_bench
|
||||
word_wrap_overflow_test
|
||||
task_queue_title_error_test
|
||||
task_queue_snapshot_test
|
||||
task_queue_init_failure_test
|
||||
# Stops the worker with a bound when a detachable
|
||||
# handler is stuck; waits ~1.5 s by design.
|
||||
task_queue_detach_test
|
||||
task_queue_main_thread_test
|
||||
task_queue_budget_test
|
||||
image_blit_bands_test
|
||||
tpool_wait_test
|
||||
# Four CPU-burning jobs on a pool of four must overlap;
|
||||
# on a one-core runner it notes that and still passes.
|
||||
tpool_parallel_test
|
||||
data_transfer_pool_scrub_test
|
||||
retro_atomic_test
|
||||
retro_atomic_extern_c_linkage_test
|
||||
@@ -200,12 +166,13 @@ jobs:
|
||||
# microsecond clock; the late bound is loose for a loaded
|
||||
# runner.
|
||||
retro_sleep_until_test
|
||||
# Runs its natural tier only here; procbarrier-tiers.yml
|
||||
# forces every tier on Linux, Windows and macOS runners.
|
||||
# Runs its natural tier only here; Makefile.test's
|
||||
# threads-samples forces each tier in turn. On the
|
||||
# runner's cores the fence check is load-bearing, which
|
||||
# this box cannot make it.
|
||||
retro_procbarrier_test
|
||||
retro_asym_eventcount_test
|
||||
sthread_ex_test
|
||||
fast_cores_test
|
||||
memshm_test
|
||||
fastjmp_test
|
||||
rdds_block_test
|
||||
@@ -587,26 +554,6 @@ jobs:
|
||||
make clean
|
||||
CC=clang make check SANITIZER=thread
|
||||
|
||||
- name: Run rzip_parallel_test under Clang + ThreadSanitizer
|
||||
shell: bash
|
||||
working-directory: libretro-common/samples/streams/rzip
|
||||
run: |
|
||||
# rzip_stream.c's chunk-writer pool hands each slot between the
|
||||
# writer and one worker on an acquire/release status word and
|
||||
# parks both sides on retro_eventcount, with no lock anywhere.
|
||||
# The value checks prove in-order emission; a weakened store
|
||||
# or a lost wakeup still passes them on x86 TSO, and only TSan
|
||||
# sees the missing barrier. The test drives the pool at every
|
||||
# width and through two hundred short lifetimes so the
|
||||
# start / park / shutdown edges are exercised often.
|
||||
set -u
|
||||
set -o pipefail
|
||||
|
||||
make clean
|
||||
CC=clang make rzip_parallel_test SANITIZER=thread
|
||||
|
||||
TSAN_OPTIONS=halt_on_error=1 ./rzip_parallel_test
|
||||
|
||||
- name: Run crc32_test under Clang + ThreadSanitizer
|
||||
shell: bash
|
||||
working-directory: libretro-common/samples/encodings/crc32
|
||||
@@ -743,7 +690,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Install dependencies
|
||||
timeout-minutes: 20
|
||||
timeout-minutes: 5
|
||||
run: |
|
||||
# clang ships on the stock ubuntu-latest image; apt is only
|
||||
# contacted if the image ever drops it.
|
||||
@@ -757,23 +704,6 @@ jobs:
|
||||
echo "apt-get update failed (attempt $i); retrying in 10s"
|
||||
sleep 10
|
||||
done
|
||||
# Fetch first, under a bound: a download is safe to stop and
|
||||
# resumes, an install is not. A mirror that stalls is left for
|
||||
# the main archive on the next attempt.
|
||||
for i in 1 2 3; do
|
||||
sudo timeout 240 apt-get install -y --download-only \
|
||||
-o Acquire::Retries=3 -o Acquire::http::Timeout=30 \
|
||||
clang && break
|
||||
[ $i = 3 ] && exit 1
|
||||
echo "package download failed or stalled (attempt $i); retrying from archive.ubuntu.com"
|
||||
for f in /etc/apt/sources.list /etc/apt/sources.list.d/*; do
|
||||
[ -f "$f" ] || continue
|
||||
sudo sed -i -e 's#mirror+file:/etc/apt/apt-mirrors.txt#http://archive.ubuntu.com/ubuntu/#g' \
|
||||
-e 's#http://azure\.archive\.ubuntu\.com/ubuntu#http://archive.ubuntu.com/ubuntu#g' "$f"
|
||||
done
|
||||
sudo timeout 90 apt-get update -y \
|
||||
-o Acquire::Retries=3 -o Acquire::http::Timeout=30 || true
|
||||
done
|
||||
sudo apt-get install -y \
|
||||
-o Acquire::Retries=3 -o Acquire::http::Timeout=30 \
|
||||
clang
|
||||
@@ -863,7 +793,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Install dependencies
|
||||
timeout-minutes: 20
|
||||
timeout-minutes: 5
|
||||
run: |
|
||||
for i in 1 2 3; do
|
||||
sudo timeout 90 apt-get update -y \
|
||||
@@ -871,23 +801,6 @@ jobs:
|
||||
echo "apt-get update failed (attempt $i); retrying in 10s"
|
||||
sleep 10
|
||||
done
|
||||
# Fetch first, under a bound: a download is safe to stop and
|
||||
# resumes, an install is not. A mirror that stalls is left for
|
||||
# the main archive on the next attempt.
|
||||
for i in 1 2 3; do
|
||||
sudo timeout 240 apt-get install -y --download-only \
|
||||
-o Acquire::Retries=3 -o Acquire::http::Timeout=30 \
|
||||
build-essential ${{ matrix.apt_pkgs }} qemu-user-static && break
|
||||
[ $i = 3 ] && exit 1
|
||||
echo "package download failed or stalled (attempt $i); retrying from archive.ubuntu.com"
|
||||
for f in /etc/apt/sources.list /etc/apt/sources.list.d/*; do
|
||||
[ -f "$f" ] || continue
|
||||
sudo sed -i -e 's#mirror+file:/etc/apt/apt-mirrors.txt#http://archive.ubuntu.com/ubuntu/#g' \
|
||||
-e 's#http://azure\.archive\.ubuntu\.com/ubuntu#http://archive.ubuntu.com/ubuntu#g' "$f"
|
||||
done
|
||||
sudo timeout 90 apt-get update -y \
|
||||
-o Acquire::Retries=3 -o Acquire::http::Timeout=30 || true
|
||||
done
|
||||
sudo apt-get install -y \
|
||||
-o Acquire::Retries=3 -o Acquire::http::Timeout=30 \
|
||||
build-essential ${{ matrix.apt_pkgs }} qemu-user-static
|
||||
@@ -1015,7 +928,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Install dependencies
|
||||
timeout-minutes: 20
|
||||
timeout-minutes: 5
|
||||
run: |
|
||||
for i in 1 2 3; do
|
||||
sudo timeout 90 apt-get update -y \
|
||||
@@ -1023,23 +936,6 @@ jobs:
|
||||
echo "apt-get update failed (attempt $i); retrying in 10s"
|
||||
sleep 10
|
||||
done
|
||||
# Fetch first, under a bound: a download is safe to stop and
|
||||
# resumes, an install is not. A mirror that stalls is left for
|
||||
# the main archive on the next attempt.
|
||||
for i in 1 2 3; do
|
||||
sudo timeout 240 apt-get install -y --download-only \
|
||||
-o Acquire::Retries=3 -o Acquire::http::Timeout=30 \
|
||||
build-essential ${{ matrix.apt_pkgs }} qemu-user-static && break
|
||||
[ $i = 3 ] && exit 1
|
||||
echo "package download failed or stalled (attempt $i); retrying from archive.ubuntu.com"
|
||||
for f in /etc/apt/sources.list /etc/apt/sources.list.d/*; do
|
||||
[ -f "$f" ] || continue
|
||||
sudo sed -i -e 's#mirror+file:/etc/apt/apt-mirrors.txt#http://archive.ubuntu.com/ubuntu/#g' \
|
||||
-e 's#http://azure\.archive\.ubuntu\.com/ubuntu#http://archive.ubuntu.com/ubuntu#g' "$f"
|
||||
done
|
||||
sudo timeout 90 apt-get update -y \
|
||||
-o Acquire::Retries=3 -o Acquire::http::Timeout=30 || true
|
||||
done
|
||||
sudo apt-get install -y \
|
||||
-o Acquire::Retries=3 -o Acquire::http::Timeout=30 \
|
||||
build-essential ${{ matrix.apt_pkgs }} qemu-user-static
|
||||
@@ -1084,27 +980,6 @@ jobs:
|
||||
set -o pipefail
|
||||
${{ matrix.qemu }} -L ${{ matrix.sysroot }} ./crc32_test
|
||||
|
||||
- name: Build and run pixconv_test under qemu-user
|
||||
shell: bash
|
||||
working-directory: libretro-common/samples/gfx/pixconv
|
||||
run: |
|
||||
set -euo pipefail
|
||||
# The ARM legs are the only place the NEON converters run;
|
||||
# PowerPC runs the scalar paths big-endian. The armhf
|
||||
# toolchain defaults to VFP without NEON, so armv7 builds a
|
||||
# second time with NEON on.
|
||||
make clean
|
||||
CC="${{ matrix.cc }}" CFLAGS="${{ matrix.cflags }}" make all
|
||||
for t in pixconv_test pixconv_test_nosimd pixconv_test_scalar; do
|
||||
${{ matrix.qemu }} -L ${{ matrix.sysroot }} ./$t
|
||||
done
|
||||
if [ "${{ matrix.name }}" = armv7 ]; then
|
||||
make clean
|
||||
CC="${{ matrix.cc }}" CFLAGS="-mfpu=neon" make all
|
||||
${{ matrix.qemu }} -L ${{ matrix.sysroot }} ./pixconv_test
|
||||
fi
|
||||
make clean
|
||||
|
||||
- name: Run vfs_mapped_ptr_test without mappings (MMAP=0)
|
||||
shell: bash
|
||||
working-directory: libretro-common/samples/file/vfs
|
||||
|
||||
@@ -33,11 +33,11 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Install dependencies
|
||||
timeout-minutes: 20
|
||||
timeout-minutes: 8
|
||||
run: |
|
||||
# ffmpeg: tools/companion_thumbs_test.sh builds its video-hover
|
||||
# fixture (a 2 KB Baseline MP4) with it.
|
||||
pkgs="build-essential check pkg-config zlib1g-dev lcov gcc-mingw-w64-x86-64 gcc-mingw-w64-i686 wine wine64 ffmpeg gcc-aarch64-linux-gnu gcc-arm-linux-gnueabihf gcc-mipsel-linux-gnu qemu-user"
|
||||
pkgs="build-essential check pkg-config zlib1g-dev lcov gcc-mingw-w64-x86-64 gcc-mingw-w64-i686 wine wine64 ffmpeg"
|
||||
if dpkg -s $pkgs >/dev/null 2>&1; then
|
||||
echo "All packages already present on runner image; skipping apt."
|
||||
exit 0
|
||||
@@ -48,23 +48,6 @@ jobs:
|
||||
echo "apt-get update failed (attempt $i); retrying in 10s"
|
||||
sleep 10
|
||||
done
|
||||
# Fetch first, under a bound: a download is safe to stop and
|
||||
# resumes, an install is not. A mirror that stalls is left for
|
||||
# the main archive on the next attempt.
|
||||
for i in 1 2 3; do
|
||||
sudo timeout 240 apt-get install -y --download-only \
|
||||
-o Acquire::Retries=3 -o Acquire::http::Timeout=30 \
|
||||
$pkgs && break
|
||||
[ $i = 3 ] && exit 1
|
||||
echo "package download failed or stalled (attempt $i); retrying from archive.ubuntu.com"
|
||||
for f in /etc/apt/sources.list /etc/apt/sources.list.d/*; do
|
||||
[ -f "$f" ] || continue
|
||||
sudo sed -i -e 's#mirror+file:/etc/apt/apt-mirrors.txt#http://archive.ubuntu.com/ubuntu/#g' \
|
||||
-e 's#http://azure\.archive\.ubuntu\.com/ubuntu#http://archive.ubuntu.com/ubuntu#g' "$f"
|
||||
done
|
||||
sudo timeout 90 apt-get update -y \
|
||||
-o Acquire::Retries=3 -o Acquire::http::Timeout=30 || true
|
||||
done
|
||||
sudo apt-get install -y \
|
||||
-o Acquire::Retries=3 -o Acquire::http::Timeout=30 \
|
||||
$pkgs
|
||||
@@ -124,15 +107,6 @@ jobs:
|
||||
LIBCHECK_CFLAGS="$(pkg-config --cflags check)" \
|
||||
CFLAGS="$(pkg-config --cflags check)"
|
||||
echo "[pass] libretro-common unit test suites"
|
||||
# The ARMv8 AES + PMULL path executes only on ARM; this
|
||||
# runs the crypto suite under qemu-aarch64 in both the
|
||||
# dispatched and the baseline-crypto shape.
|
||||
make -f Makefile.test crypto-aarch64-check
|
||||
make -f Makefile.test crypto-arm-check
|
||||
make -f Makefile.test crypto-mips-check
|
||||
# every lazily set up table or CPU check, first used by
|
||||
# eight threads at once under TSan
|
||||
make -f Makefile.test crypto-tsan-check
|
||||
|
||||
- name: Check VFS backend symbol parity
|
||||
shell: bash
|
||||
@@ -149,44 +123,6 @@ jobs:
|
||||
# free, on Linux.
|
||||
python3 tools/vfs_backend_parity.py
|
||||
|
||||
- name: Check every command describes itself
|
||||
shell: bash
|
||||
working-directory: ${{ github.workspace }}
|
||||
run: |
|
||||
set -eu
|
||||
# HELP prints command.h's tables, and interfaces that hand the
|
||||
# commands to a client are generated from them: a command
|
||||
# added without a description reaches all of those blank.
|
||||
python3 tools/command_table_check.py
|
||||
|
||||
- name: Check no video driver deletes overlay textures it was lent
|
||||
shell: bash
|
||||
run: |
|
||||
set -eu
|
||||
# load_textures lends a driver the overlay pack's handles for
|
||||
# one page; they are on the pack's other pages too and are
|
||||
# the pack's to delete. gl2 marked them as borrowed and then
|
||||
# deleted them anyway at the next page, so rotating an
|
||||
# Android device blacked the overlay out until the core was
|
||||
# restarted. No runner can run a GL driver, so this is the
|
||||
# check at source level: every driver with load_textures
|
||||
# sets, tests and clears its borrowed marker.
|
||||
python3 tools/overlay_texture_ownership.py
|
||||
|
||||
- name: Check overlay setters refuse an image the driver does not hold
|
||||
shell: bash
|
||||
run: |
|
||||
set -eu
|
||||
# set_alpha, vertex_geom and tex_geom are called whenever the
|
||||
# layout, opacity or page changes, not only after a load()
|
||||
# that worked: with no page the driver's arrays are NULL, and
|
||||
# after a smaller page or a failed load the index is off the
|
||||
# end. 7661ab06e7 bounded the GL geometry setters and missed
|
||||
# set_alpha; D3D, Vulkan and Metal had no check at all. No
|
||||
# runner can run these drivers, so this is the check at
|
||||
# source level for the ones that have been audited.
|
||||
python3 tools/overlay_setter_bounds.py
|
||||
|
||||
- name: Check companion backend feature parity
|
||||
shell: bash
|
||||
working-directory: ${{ github.workspace }}
|
||||
|
||||
@@ -27,7 +27,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Install dependencies
|
||||
timeout-minutes: 20
|
||||
timeout-minutes: 5
|
||||
run: |
|
||||
pkgs="build-essential zlib1g-dev"
|
||||
if dpkg -s $pkgs >/dev/null 2>&1; then
|
||||
@@ -40,23 +40,6 @@ jobs:
|
||||
echo "apt-get update failed (attempt $i); retrying in 10s"
|
||||
sleep 10
|
||||
done
|
||||
# Fetch first, under a bound: a download is safe to stop and
|
||||
# resumes, an install is not. A mirror that stalls is left for
|
||||
# the main archive on the next attempt.
|
||||
for i in 1 2 3; do
|
||||
sudo timeout 240 apt-get install -y --download-only \
|
||||
-o Acquire::Retries=3 -o Acquire::http::Timeout=30 \
|
||||
$pkgs && break
|
||||
[ $i = 3 ] && exit 1
|
||||
echo "package download failed or stalled (attempt $i); retrying from archive.ubuntu.com"
|
||||
for f in /etc/apt/sources.list /etc/apt/sources.list.d/*; do
|
||||
[ -f "$f" ] || continue
|
||||
sudo sed -i -e 's#mirror+file:/etc/apt/apt-mirrors.txt#http://archive.ubuntu.com/ubuntu/#g' \
|
||||
-e 's#http://azure\.archive\.ubuntu\.com/ubuntu#http://archive.ubuntu.com/ubuntu#g' "$f"
|
||||
done
|
||||
sudo timeout 90 apt-get update -y \
|
||||
-o Acquire::Retries=3 -o Acquire::http::Timeout=30 || true
|
||||
done
|
||||
sudo apt-get install -y \
|
||||
-o Acquire::Retries=3 -o Acquire::http::Timeout=30 \
|
||||
$pkgs
|
||||
|
||||
@@ -22,15 +22,6 @@ on:
|
||||
- 'playlist.h'
|
||||
- 'tasks/task_content*'
|
||||
- 'samples/menu/playlist_nav/**'
|
||||
- 'samples/menu/nav_repeat_stall/**'
|
||||
- 'runloop.c'
|
||||
- 'samples/menu/network_settings/**'
|
||||
- 'intl/msg_hash_lbl.h'
|
||||
- 'msg_hash_lbl_str.h'
|
||||
- 'samples/menu/resolution_reset/**'
|
||||
- 'samples/settings/packed_rows/**'
|
||||
- 'setting_list.h'
|
||||
- 'gfx/video_defines.h'
|
||||
- '.github/workflows/Linux-menu-harness.yml'
|
||||
pull_request:
|
||||
paths:
|
||||
@@ -39,15 +30,6 @@ on:
|
||||
- 'playlist.h'
|
||||
- 'tasks/task_content*'
|
||||
- 'samples/menu/playlist_nav/**'
|
||||
- 'samples/menu/nav_repeat_stall/**'
|
||||
- 'runloop.c'
|
||||
- 'samples/menu/network_settings/**'
|
||||
- 'intl/msg_hash_lbl.h'
|
||||
- 'msg_hash_lbl_str.h'
|
||||
- 'samples/menu/resolution_reset/**'
|
||||
- 'samples/settings/packed_rows/**'
|
||||
- 'setting_list.h'
|
||||
- 'gfx/video_defines.h'
|
||||
- '.github/workflows/Linux-menu-harness.yml'
|
||||
workflow_dispatch:
|
||||
|
||||
@@ -63,7 +45,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Install dependencies
|
||||
timeout-minutes: 20
|
||||
timeout-minutes: 5
|
||||
run: |
|
||||
pkgs="build-essential zlib1g-dev"
|
||||
if dpkg -s $pkgs >/dev/null 2>&1; then
|
||||
@@ -76,23 +58,6 @@ jobs:
|
||||
echo "apt-get update failed (attempt $i); retrying in 10s"
|
||||
sleep 10
|
||||
done
|
||||
# Fetch first, under a bound: a download is safe to stop and
|
||||
# resumes, an install is not. A mirror that stalls is left for
|
||||
# the main archive on the next attempt.
|
||||
for i in 1 2 3; do
|
||||
sudo timeout 240 apt-get install -y --download-only \
|
||||
-o Acquire::Retries=3 -o Acquire::http::Timeout=30 \
|
||||
$pkgs && break
|
||||
[ $i = 3 ] && exit 1
|
||||
echo "package download failed or stalled (attempt $i); retrying from archive.ubuntu.com"
|
||||
for f in /etc/apt/sources.list /etc/apt/sources.list.d/*; do
|
||||
[ -f "$f" ] || continue
|
||||
sudo sed -i -e 's#mirror+file:/etc/apt/apt-mirrors.txt#http://archive.ubuntu.com/ubuntu/#g' \
|
||||
-e 's#http://azure\.archive\.ubuntu\.com/ubuntu#http://archive.ubuntu.com/ubuntu#g' "$f"
|
||||
done
|
||||
sudo timeout 90 apt-get update -y \
|
||||
-o Acquire::Retries=3 -o Acquire::http::Timeout=30 || true
|
||||
done
|
||||
sudo apt-get install -y \
|
||||
-o Acquire::Retries=3 -o Acquire::http::Timeout=30 \
|
||||
$pkgs
|
||||
@@ -133,12 +98,10 @@ jobs:
|
||||
#
|
||||
# Qt in particular has to go: with it enabled main() lives
|
||||
# in ui_qt.o, and the harness replaces main().
|
||||
# Networking stays on: the network settings harness below
|
||||
# needs the SMB and NFS client screens compiled in.
|
||||
./configure --disable-qt --disable-x11 \
|
||||
--disable-opengl --disable-opengl_core \
|
||||
--disable-opengl1 --disable-vulkan \
|
||||
--disable-cheevos \
|
||||
--disable-cheevos --disable-networking \
|
||||
--disable-ffmpeg
|
||||
make -j$(nproc)
|
||||
|
||||
@@ -183,64 +146,6 @@ jobs:
|
||||
./samples/menu/playlist_nav/menu_playlist_nav_test
|
||||
make -C samples/menu/playlist_nav clean
|
||||
|
||||
- name: Build and run menu_nav_repeat_stall_test
|
||||
shell: bash
|
||||
run: |
|
||||
set -eu
|
||||
# One press on a menu entry whose frame stalls - Ozone and
|
||||
# XMB build a large playlist synchronously when the selection
|
||||
# lands on it - has to move the selection once. The menu acts
|
||||
# on input one poll old, so the hold time has to be measured
|
||||
# between polls or the stall fires an auto-repeat. Drives the
|
||||
# real runloop with a scripted input driver; also checks that
|
||||
# a hold across the stall still repeats and that a steady hold
|
||||
# waits out the initial scroll delay. Shares the non-Qt build
|
||||
# made above.
|
||||
samples/menu/nav_repeat_stall/build.sh
|
||||
./samples/menu/nav_repeat_stall/menu_nav_repeat_stall_test
|
||||
make -C samples/menu/nav_repeat_stall clean
|
||||
|
||||
- name: Build and run menu_network_settings_test
|
||||
shell: bash
|
||||
run: |
|
||||
set -eu
|
||||
# The NFS Network Settings entry opened an empty "null"
|
||||
# browser: the label its deferred list is pushed with had no
|
||||
# row in msg_hash_lbl.h, so it resolved to "null" and the
|
||||
# push found nothing. The harness resolves the SMB and NFS
|
||||
# labels, opens Settings > Network and both screens through
|
||||
# the real dispatch, and checks the Quick Menu no longer
|
||||
# carries the two entries.
|
||||
samples/menu/network_settings/build.sh
|
||||
./samples/menu/network_settings/menu_network_settings_test
|
||||
make -C samples/menu/network_settings clean
|
||||
|
||||
- name: Build and run resolution_reset_test
|
||||
shell: bash
|
||||
run: |
|
||||
set -eu
|
||||
# Start on the Resolution entry reinits and sets the video
|
||||
# mode at the output size; it must keep the window state the
|
||||
# frontend is in rather than take a windowed desktop session
|
||||
# full screen. Shares the non-Qt build made above.
|
||||
samples/menu/resolution_reset/build.sh
|
||||
./samples/menu/resolution_reset/resolution_reset_test
|
||||
make -C samples/menu/resolution_reset clean
|
||||
|
||||
- name: Build and run packed_rows_test
|
||||
shell: bash
|
||||
run: |
|
||||
set -eu
|
||||
# Settings rows that each own one half of a packed settings_t
|
||||
# word - the custom viewport's origin and size, the window
|
||||
# size pair - driven through their display strings, a right
|
||||
# press and the dropdown list. Each row has to show and set
|
||||
# its own axis and leave its partner as it stands. Shares the
|
||||
# non-Qt build made above.
|
||||
samples/settings/packed_rows/build.sh
|
||||
./samples/settings/packed_rows/packed_rows_test
|
||||
make -C samples/settings/packed_rows clean
|
||||
|
||||
- name: Hot function budget
|
||||
shell: bash
|
||||
run: |
|
||||
@@ -252,17 +157,6 @@ jobs:
|
||||
# this job already built.
|
||||
.github/workflows/scripts/hot-function-budget.sh
|
||||
|
||||
- name: Lock budget
|
||||
shell: bash
|
||||
run: |
|
||||
set -eu
|
||||
# Lock acquisitions on the paths meant to take none, or a
|
||||
# counted few, against tools/lock_acquire_budget.ref: a lock
|
||||
# put back on a per-frame path passes every lane and shows up
|
||||
# only as a slower frame. Same release objects as above; this
|
||||
# build has no Vulkan, so the Vulkan entries report SKIP.
|
||||
tools/lock_acquire_budget.sh
|
||||
|
||||
- name: Build and run threaded_video_test
|
||||
shell: bash
|
||||
run: |
|
||||
@@ -280,29 +174,3 @@ jobs:
|
||||
samples/gfx/threaded_video/build.sh
|
||||
./samples/gfx/threaded_video/threaded_video_test 3
|
||||
make -C samples/gfx/threaded_video clean
|
||||
|
||||
- name: Build and run content_load_test
|
||||
shell: bash
|
||||
run: |
|
||||
set -eu
|
||||
# The content load as a chain of main-thread tasks, against the
|
||||
# real runloop on the same non-Qt menu build: frames presented
|
||||
# between stages, one load at a time, a core's reinit deferred
|
||||
# to the load's driver stage, the dummy-core fallback, a
|
||||
# hardware-render request carried across the driver rebuild,
|
||||
# the task queue surviving the load, and the close waiting out
|
||||
# a save state from the frame loop.
|
||||
samples/tasks/content_load/build.sh
|
||||
./samples/tasks/content_load/content_load_test
|
||||
make -C samples/tasks/content_load clean
|
||||
|
||||
- name: Build and run explore_build_test
|
||||
shell: bash
|
||||
run: |
|
||||
set -eu
|
||||
# The Explore index built in steps: one item per step hashes
|
||||
# the same as the build in one go, and the explore task returns
|
||||
# unfinished across checks with Threaded Tasks off.
|
||||
samples/menu/explore_build/build.sh
|
||||
./samples/menu/explore_build/explore_build_test
|
||||
make -C samples/menu/explore_build clean
|
||||
@@ -1,222 +0,0 @@
|
||||
name: CI Linux network clients
|
||||
|
||||
# The built-in TLS, SMB2/3 and NFSv3 clients against real servers:
|
||||
# openssl s_server, Samba, unfs3, nfs-ganesha and a MIT KDC on the runner, natively, as Windows
|
||||
# builds under Wine, under ASan + UBSan + LSan, plus libFuzzer over the
|
||||
# parsers. Its own job so the libretro-common unit-test job keeps its
|
||||
# short budget.
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
pull_request:
|
||||
branches:
|
||||
- master
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
|
||||
cancel-in-progress: ${{ github.ref != 'refs/heads/master' }}
|
||||
|
||||
jobs:
|
||||
network-clients:
|
||||
name: TLS, SMB and NFS clients against local servers
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 45
|
||||
|
||||
steps:
|
||||
- name: Install dependencies
|
||||
timeout-minutes: 25
|
||||
run: |
|
||||
sudo apt-get update -y
|
||||
# Fetch first, under a bound: a download is safe to stop and
|
||||
# resumes, an install is not. A mirror that stalls is left for
|
||||
# the main archive on the next attempt.
|
||||
# tshark's dumpcap asks whether to be setuid: no, runs are root
|
||||
echo "wireshark-common wireshark-common/install-setuid boolean false" | sudo debconf-set-selections
|
||||
export DEBIAN_FRONTEND=noninteractive
|
||||
for i in 1 2 3; do
|
||||
sudo timeout 240 apt-get install -y --download-only --no-install-recommends build-essential clang \
|
||||
gcc-mingw-w64-x86-64 openssl samba libtirpc-dev flex bison autoconf automake wine wine64 \
|
||||
nfs-ganesha nfs-ganesha-vfs krb5-kdc krb5-admin-server krb5-user winbind \
|
||||
samba-dsdb-modules samba-ad-provision samba-vfs-modules acl smbclient tshark && break
|
||||
[ $i = 3 ] && exit 1
|
||||
echo "package download failed or stalled (attempt $i); retrying from archive.ubuntu.com"
|
||||
for f in /etc/apt/sources.list /etc/apt/sources.list.d/*; do
|
||||
[ -f "$f" ] || continue
|
||||
sudo sed -i -e 's#mirror+file:/etc/apt/apt-mirrors.txt#http://archive.ubuntu.com/ubuntu/#g' \
|
||||
-e 's#http://azure\.archive\.ubuntu\.com/ubuntu#http://archive.ubuntu.com/ubuntu#g' "$f"
|
||||
done
|
||||
sudo timeout 90 apt-get update -y \
|
||||
-o Acquire::Retries=3 -o Acquire::http::Timeout=30 || true
|
||||
done
|
||||
sudo apt-get install -y --no-install-recommends build-essential clang \
|
||||
gcc-mingw-w64-x86-64 openssl samba libtirpc-dev flex bison autoconf automake wine wine64 \
|
||||
nfs-ganesha nfs-ganesha-vfs krb5-kdc krb5-admin-server krb5-user winbind \
|
||||
samba-dsdb-modules samba-ad-provision samba-vfs-modules acl smbclient tshark
|
||||
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v5
|
||||
|
||||
- name: Built-in TLS client against a local openssl server
|
||||
shell: bash
|
||||
working-directory: ${{ github.workspace }}
|
||||
run: |
|
||||
set -eu
|
||||
# net_socket_ssl_retro.c end to end: every cipher suite, both
|
||||
# curves, RSA / P-256 / P-384 certificates under a CA made on
|
||||
# the spot, verify required / optional / disabled, and the
|
||||
# refusals that must happen (wrong hostname, untrusted CA). An
|
||||
# IP address matches only an iPAddress SAN entry, never the
|
||||
# same address written as a DNS name.
|
||||
samples/network/tls_retro/local_server_test.sh
|
||||
# The same tool cross-linked for Windows: the sample's link
|
||||
# line has to carry Winsock, which nothing on Linux checks.
|
||||
make -C samples/network/tls_retro clean
|
||||
|
||||
|
||||
- name: Built-in SMB2/3 client against a local smbd
|
||||
shell: bash
|
||||
working-directory: ${{ github.workspace }}
|
||||
run: |
|
||||
set -eu
|
||||
# net_smb2.c end to end against Samba on the loopback: every
|
||||
# dialect 2.0.2 to 3.1.1 with signing mandatory, AES-CCM and
|
||||
# AES-GCM sealing required, and a refused logon. Root for
|
||||
# port 445; the script itself skips without smbd.
|
||||
sudo samples/network/smb_retro/local_samba_test.sh
|
||||
# and with Kerberos against a Samba AD DC provisioned on the
|
||||
# spot, NTLM disabled on it so only a ticket can log in
|
||||
sudo samples/network/smb_retro/local_ad_dc_test.sh
|
||||
sudo make -C samples/network/smb_retro clean
|
||||
|
||||
|
||||
- name: Windows builds under Wine
|
||||
shell: bash
|
||||
working-directory: ${{ github.workspace }}
|
||||
run: |
|
||||
set -eu
|
||||
# The MinGW builds run, not just link: the crypto and keychain
|
||||
# suites, then the three server matrices with the Windows tools
|
||||
# talking to the same local openssl, Samba and unfs3.
|
||||
export WINEDEBUG=-all WINEPREFIX=$RUNNER_TEMP/wineprefix
|
||||
# the tools below are run through wine, so it must be there
|
||||
command -v wine >/dev/null
|
||||
make -C libretro-common -f Makefile.test win32-wine-check
|
||||
export CC=x86_64-w64-mingw32-gcc RUNNER=wine EXE=.exe
|
||||
make -C samples/network/tls_retro clean
|
||||
samples/network/tls_retro/local_server_test.sh
|
||||
make -C samples/network/tls_retro clean
|
||||
make -C samples/network/smb_retro clean
|
||||
# smbd needs root for port 445, and Wine refuses both a prefix
|
||||
# owned by someone else and creating one inside a directory it
|
||||
# does not own ($RUNNER_TEMP belongs to the runner user), so the
|
||||
# root run gets a prefix under /tmp with root's own HOME
|
||||
sudo -E env HOME=/root WINEPREFIX=/tmp/wineprefix-root wineboot -u
|
||||
sudo -E env HOME=/root WINEPREFIX=/tmp/wineprefix-root samples/network/smb_retro/local_samba_test.sh
|
||||
sudo -E env HOME=/root WINEPREFIX=/tmp/wineprefix-root samples/network/smb_retro/local_ad_dc_test.sh
|
||||
sudo make -C samples/network/smb_retro clean
|
||||
make -C samples/network/nfs_retro clean
|
||||
samples/network/nfs_retro/local_unfs3_test.sh
|
||||
make -C samples/network/nfs_retro clean
|
||||
sudo -E env HOME=/root WINEPREFIX=/tmp/wineprefix-root samples/network/nfs_retro/local_ganesha_test.sh
|
||||
sudo make -C samples/network/nfs_retro clean
|
||||
sudo -E env HOME=/root WINEPREFIX=/tmp/wineprefix-root samples/network/nfs_retro/local_read_plus_test.sh
|
||||
sudo make -C samples/network/nfs_retro clean
|
||||
make -C samples/network/krb5_retro clean
|
||||
samples/network/krb5_retro/local_kdc_test.sh
|
||||
make -C samples/network/krb5_retro clean
|
||||
|
||||
|
||||
- name: Fuzz the network parsers
|
||||
shell: bash
|
||||
working-directory: ${{ github.workspace }}
|
||||
run: |
|
||||
set -eu
|
||||
# libFuzzer over the X.509 parser and the TLS, SMB and NFS
|
||||
# clients' receive paths (a listener in the harness answers the
|
||||
# client with the fuzzer's input), under ASan + UBSan, from a
|
||||
# seed corpus recorded off real servers. Any crash, timeout or
|
||||
# leak artifact fails the step.
|
||||
make -C libretro-common/test/fuzz fuzz-run FUZZ_SECONDS=30
|
||||
if ls libretro-common/test/fuzz/crash-* libretro-common/test/fuzz/leak-* libretro-common/test/fuzz/timeout-* >/dev/null 2>&1; then
|
||||
echo "fuzzer artifacts found"; exit 1
|
||||
fi
|
||||
make -C libretro-common/test/fuzz clean
|
||||
|
||||
|
||||
- name: Network client matrices under ASan + UBSan + LSan
|
||||
shell: bash
|
||||
working-directory: ${{ github.workspace }}
|
||||
run: |
|
||||
set -eu
|
||||
# The three server matrices again, with the clients built under
|
||||
# the sanitizers: every code path a real server drives, checked
|
||||
# for memory errors, undefined behaviour and leaks. UBSan found
|
||||
# a NULL memcpy in the SHA-2 stream updates on the SHA-384 PRF
|
||||
# path the first time this ran.
|
||||
export CFLAGS="-O1 -g -fsanitize=address,undefined -fno-sanitize-recover=all -fno-omit-frame-pointer"
|
||||
export LDFLAGS="-fsanitize=address,undefined"
|
||||
export ASAN_OPTIONS=detect_leaks=1:strict_string_checks=1
|
||||
export UBSAN_OPTIONS=print_stacktrace=1
|
||||
make -C samples/network/tls_retro clean
|
||||
samples/network/tls_retro/local_server_test.sh
|
||||
make -C samples/network/tls_retro clean
|
||||
make -C samples/network/smb_retro clean
|
||||
sudo -E samples/network/smb_retro/local_samba_test.sh
|
||||
sudo -E samples/network/smb_retro/local_ad_dc_test.sh
|
||||
make -C samples/network/smb_retro clean
|
||||
make -C samples/network/nfs_retro clean
|
||||
samples/network/nfs_retro/local_unfs3_test.sh
|
||||
make -C samples/network/nfs_retro clean
|
||||
sudo -E samples/network/nfs_retro/local_ganesha_test.sh
|
||||
sudo make -C samples/network/nfs_retro clean
|
||||
sudo -E samples/network/nfs_retro/local_read_plus_test.sh
|
||||
sudo make -C samples/network/nfs_retro clean
|
||||
make -C samples/network/krb5_retro clean
|
||||
samples/network/krb5_retro/local_kdc_test.sh
|
||||
make -C samples/network/krb5_retro clean
|
||||
|
||||
|
||||
- name: Built-in NFSv3 client against unfs3
|
||||
shell: bash
|
||||
working-directory: ${{ github.workspace }}
|
||||
run: |
|
||||
set -eu
|
||||
# net_nfs3.c end to end against a user-space NFSv3 server built
|
||||
# from unfs3's repository (Ubuntu no longer packages it), on
|
||||
# fixed ports without a portmapper: mount, write, read, seek,
|
||||
# stat, mkdir, rename, truncate, list, unlink, refused export.
|
||||
samples/network/nfs_retro/local_unfs3_test.sh
|
||||
# NFSv4.0 (one COMPOUND per operation, no portmapper or MOUNT,
|
||||
# pseudo-filesystem export) against nfs-ganesha from apt
|
||||
sudo -E samples/network/nfs_retro/local_ganesha_test.sh
|
||||
# NFSv4.2 READ_PLUS against a fake server reporting the holes
|
||||
# the filesystem really holds, its replies checked by
|
||||
# Wireshark's own dissector
|
||||
sudo make -C samples/network/nfs_retro clean
|
||||
sudo -E samples/network/nfs_retro/local_read_plus_test.sh
|
||||
# and its Windows link line, cross-linked
|
||||
sudo make -C samples/network/nfs_retro clean
|
||||
CC=x86_64-w64-mingw32-gcc make -C samples/network/nfs_retro nfs_test
|
||||
make -C samples/network/nfs_retro clean
|
||||
CC=x86_64-w64-mingw32-gcc make -C samples/network/tls_retro tls_fetch
|
||||
make -C samples/network/tls_retro clean
|
||||
|
||||
- name: Built-in Kerberos client against a MIT KDC
|
||||
shell: bash
|
||||
working-directory: ${{ github.workspace }}
|
||||
run: |
|
||||
set -eu
|
||||
# net_krb5.c against krb5kdc on a fixed port: AS with and
|
||||
# without PA-ENC-TIMESTAMP, TGS for a cifs/ service with the
|
||||
# ticket checked against the service's keytab key, wrong
|
||||
# password and unknown service refused.
|
||||
samples/network/krb5_retro/local_kdc_test.sh
|
||||
make -C samples/network/krb5_retro clean
|
||||
CC=x86_64-w64-mingw32-gcc make -C samples/network/krb5_retro krb5_test
|
||||
make -C samples/network/krb5_retro clean
|
||||
@@ -1,107 +0,0 @@
|
||||
name: CI Linux without pkg-config
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
pull_request:
|
||||
branches:
|
||||
- master
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
env:
|
||||
ACTIONS_ALLOW_USE_UNSECURE_NODE_VERSION: true
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
|
||||
cancel-in-progress: ${{ github.ref != 'refs/heads/master' }}
|
||||
|
||||
jobs:
|
||||
no-pkg-config:
|
||||
name: Configure and build with pkg-config unavailable
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 30
|
||||
|
||||
steps:
|
||||
- name: Install dependencies
|
||||
shell: bash
|
||||
run: |
|
||||
set -eu
|
||||
sudo apt-get update -qq
|
||||
sudo apt-get install -y -qq --no-install-recommends \
|
||||
build-essential zlib1g-dev libfreetype-dev libdrm-dev \
|
||||
libasound2-dev libpulse-dev libpipewire-0.3-dev \
|
||||
libdbus-1-dev libwayland-dev libwayland-bin libxkbcommon-dev \
|
||||
libdecor-0-dev libssl-dev qt6-base-dev qt6-base-dev-tools
|
||||
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v5
|
||||
|
||||
- name: Configure
|
||||
shell: bash
|
||||
run: |
|
||||
set -eu
|
||||
# PKG_CONF_PATH=none makes configure behave as it does on a
|
||||
# system with no pkg-config binary: every check_val falls
|
||||
# back to a plain link test and takes its cflags from the
|
||||
# include directories it is given. A library that links but
|
||||
# whose headers are not on the include path must end up
|
||||
# disabled, never enabled with empty cflags.
|
||||
PKG_CONF_PATH=none ./configure --enable-dbus
|
||||
grep -q '^HAVE_PIPEWIRE = 1' config.mk
|
||||
grep -q '^HAVE_PIPEWIRE_STABLE = 1' config.mk
|
||||
grep '^PIPEWIRE_CFLAGS = ' config.mk | grep -q -- '-I/usr/include/pipewire-0.3'
|
||||
grep '^PIPEWIRE_CFLAGS = ' config.mk | grep -q -- '-I/usr/include/spa-0.2'
|
||||
# Found without pkg-config: D-Bus by its headers, one of them
|
||||
# under the library directory; Wayland by its libraries and the
|
||||
# scanner's own version; Qt through qmake -query.
|
||||
for have in DBUS WAYLAND WAYLAND_SCANNER LIBDECOR OPENSSL QT QT6; do
|
||||
grep -q "^HAVE_$have = 1" config.mk \
|
||||
|| { echo "::error::HAVE_$have not found without pkg-config"; exit 1; }
|
||||
done
|
||||
grep '^DBUS_CFLAGS = ' config.mk | grep -q -- '-I/usr/include/dbus-1.0'
|
||||
grep '^DBUS_CFLAGS = ' config.mk | grep -q -- 'dbus-1.0/include'
|
||||
|
||||
- name: Build
|
||||
shell: bash
|
||||
run: |
|
||||
set -eu
|
||||
make -j"$(nproc)"
|
||||
test -x ./retroarch
|
||||
test -f obj-unix/release/audio/drivers/pipewire.o
|
||||
test -f obj-unix/release/camera/drivers/pipewire.o
|
||||
test -f obj-unix/release/gfx/common/dbus_common.o
|
||||
test -f obj-unix/release/gfx/common/wayland_common.o
|
||||
test -f obj-unix/release/ui/drivers/ui_qt.o
|
||||
|
||||
- name: The run-time libdbus mirror matches libdbus
|
||||
shell: bash
|
||||
run: |
|
||||
set -eu
|
||||
# gfx/common/dbus_runtime.h mirrors libdbus's public structs and
|
||||
# constants so RetroArch can load it without its headers; the
|
||||
# D-Bus CFLAGS configure found above lead to the real ones.
|
||||
make -C samples/frontend/dbus_runtime check \
|
||||
DBUS_CFLAGS="$(sed -n 's/^DBUS_CFLAGS = //p' config.mk)"
|
||||
|
||||
- name: A package forced by name fails when nothing can find it
|
||||
shell: bash
|
||||
run: |
|
||||
set -u
|
||||
# rsound is found only through pkg-config. Asked for by name with
|
||||
# no pkg-config, configure must stop and say so rather than
|
||||
# quietly build without it.
|
||||
if PKG_CONF_PATH=none ./configure --enable-rsound > forced.log 2>&1; then
|
||||
cat forced.log
|
||||
echo "::error::--enable-rsound without pkg-config configured instead of failing"
|
||||
exit 1
|
||||
fi
|
||||
grep -q 'Forced to build with package rsound, but pkg-config was not found' forced.log \
|
||||
|| { cat forced.log; echo "::error::configure failed for another reason"; exit 1; }
|
||||
# A forced package with a library check behind it still configures.
|
||||
PKG_CONF_PATH=none ./configure --enable-pulse > forced.log 2>&1 \
|
||||
|| { cat forced.log; echo "::error::--enable-pulse failed without pkg-config"; exit 1; }
|
||||
grep -q '^HAVE_PULSE = 1' config.mk
|
||||
@@ -1,178 +0,0 @@
|
||||
name: CI Linux overlay on screen
|
||||
|
||||
# The one overlay test that looks at the screen. It builds a real
|
||||
# retroarch with X11 and OpenGL, runs it under Xvfb on Mesa's software
|
||||
# rasteriser with an input overlay enabled, threaded video off and then
|
||||
# on, grabs the screen and checks the overlay is where its config puts
|
||||
# it and nowhere else. It then checks the menu's mouse cursor with an
|
||||
# overlay up: a page of "nul" buttons (an LED overlay) must leave the
|
||||
# cursor to the menu, a page that takes input must not. Last, with the
|
||||
# overlay LED driver on, it checks that only LED images are hidden and
|
||||
# that a hidden one stays hidden while its button is held.
|
||||
#
|
||||
# Every stubbed overlay test passed while an enabled overlay covered the
|
||||
# whole screen (2026-09-19). Nothing that stubs the driver, or builds
|
||||
# the pack by hand, can see that; this can.
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
paths:
|
||||
- 'input/input_driver.c'
|
||||
- 'input/input_overlay*'
|
||||
- 'tasks/task_overlay.c'
|
||||
- 'led/drivers/led_overlay.c'
|
||||
- 'gfx/gfx_surface.*'
|
||||
- 'gfx/video_driver.c'
|
||||
- 'gfx/video_thread_wrapper.*'
|
||||
- 'gfx/drivers/gl2.c'
|
||||
- 'menu/menu_driver.c'
|
||||
- 'input/drivers/x11_input.c'
|
||||
- 'samples/input/overlay_on_screen/**'
|
||||
- '.github/workflows/Linux-overlay-on-screen.yml'
|
||||
pull_request:
|
||||
paths:
|
||||
- 'input/input_driver.c'
|
||||
- 'input/input_overlay*'
|
||||
- 'tasks/task_overlay.c'
|
||||
- 'led/drivers/led_overlay.c'
|
||||
- 'gfx/gfx_surface.*'
|
||||
- 'gfx/video_driver.c'
|
||||
- 'gfx/video_thread_wrapper.*'
|
||||
- 'gfx/drivers/gl2.c'
|
||||
- 'menu/menu_driver.c'
|
||||
- 'input/drivers/x11_input.c'
|
||||
- 'samples/input/overlay_on_screen/**'
|
||||
- '.github/workflows/Linux-overlay-on-screen.yml'
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
# Only pull requests cancel a superseded run. On master every push
|
||||
# gets its own group (keyed by the commit), so a burst of pushes queues
|
||||
# instead of cancelling the run for the commit before it.
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event_name == 'pull_request' && github.ref || github.sha }}
|
||||
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
|
||||
|
||||
jobs:
|
||||
overlay-on-screen:
|
||||
name: Run retroarch under Xvfb and look at the overlay
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 25
|
||||
|
||||
steps:
|
||||
- name: Install dependencies
|
||||
timeout-minutes: 20
|
||||
run: |
|
||||
for i in 1 2 3; do
|
||||
sudo timeout 90 apt-get update -y \
|
||||
-o Acquire::Retries=3 -o Acquire::http::Timeout=30 && break
|
||||
echo "apt-get update failed (attempt $i); retrying in 10s"
|
||||
sleep 10
|
||||
done
|
||||
# Fetch first, under a bound: a download is safe to stop and
|
||||
# resumes, an install is not. A mirror that stalls is left for
|
||||
# the main archive on the next attempt.
|
||||
for i in 1 2 3; do
|
||||
sudo timeout 240 apt-get install -y --download-only \
|
||||
-o Acquire::Retries=3 -o Acquire::http::Timeout=30 \
|
||||
build-essential zlib1g-dev ccache python3 \
|
||||
xvfb x11-apps libgl1-mesa-dri \
|
||||
libx11-dev libx11-xcb-dev libxcb1-dev libxext-dev libxtst6 \
|
||||
libxxf86vm-dev libxrandr-dev libxinerama-dev \
|
||||
libgl-dev libegl-dev && break
|
||||
[ $i = 3 ] && exit 1
|
||||
echo "package download failed or stalled (attempt $i); retrying from archive.ubuntu.com"
|
||||
for f in /etc/apt/sources.list /etc/apt/sources.list.d/*; do
|
||||
[ -f "$f" ] || continue
|
||||
sudo sed -i -e 's#mirror+file:/etc/apt/apt-mirrors.txt#http://archive.ubuntu.com/ubuntu/#g' \
|
||||
-e 's#http://azure\.archive\.ubuntu\.com/ubuntu#http://archive.ubuntu.com/ubuntu#g' "$f"
|
||||
done
|
||||
sudo timeout 90 apt-get update -y \
|
||||
-o Acquire::Retries=3 -o Acquire::http::Timeout=30 || true
|
||||
done
|
||||
sudo apt-get install -y \
|
||||
-o Acquire::Retries=3 -o Acquire::http::Timeout=30 \
|
||||
build-essential zlib1g-dev ccache python3 \
|
||||
xvfb x11-apps libgl1-mesa-dri \
|
||||
libx11-dev libx11-xcb-dev libxcb1-dev libxext-dev libxtst6 \
|
||||
libxxf86vm-dev libxrandr-dev libxinerama-dev \
|
||||
libgl-dev libegl-dev
|
||||
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v5
|
||||
|
||||
- name: Restore ccache
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ~/.ccache
|
||||
key: ccache-${{ github.workflow }}-${{ github.job }}-${{ github.sha }}
|
||||
restore-keys: |
|
||||
ccache-${{ github.workflow }}-${{ github.job }}-
|
||||
|
||||
- name: Enable ccache
|
||||
run: |
|
||||
set -eu
|
||||
export CCACHE_DIR="$HOME/.ccache"
|
||||
echo "CCACHE_DIR=$CCACHE_DIR" >> "$GITHUB_ENV"
|
||||
echo "/usr/lib/ccache" >> "$GITHUB_PATH"
|
||||
ccache -M 500M
|
||||
ccache -z
|
||||
|
||||
- name: Build RetroArch (X11 + OpenGL, the rest off)
|
||||
shell: bash
|
||||
run: |
|
||||
set -eu
|
||||
# What draws the overlay has to be real: X11, the gl driver,
|
||||
# the menu, threads. Everything else is build time.
|
||||
./configure --disable-qt --disable-wayland --disable-sdl \
|
||||
--disable-sdl2 --disable-ffmpeg --disable-vulkan \
|
||||
--disable-pulse --disable-discord \
|
||||
--disable-networking --disable-cheevos \
|
||||
--disable-glslang --disable-slang \
|
||||
--disable-spirv_cross --disable-xmb \
|
||||
--disable-materialui --disable-ozone \
|
||||
--disable-7zip --disable-flac \
|
||||
--disable-audiomixer --disable-translate \
|
||||
--disable-accessibility --disable-crtswitchres \
|
||||
--disable-langextra --disable-libretrodb \
|
||||
--disable-video_filter --disable-dsp_filter
|
||||
make -j$(nproc)
|
||||
ccache -s
|
||||
|
||||
- name: Run overlay_on_screen_test (threaded video off, then on)
|
||||
shell: bash
|
||||
run: |
|
||||
set -eu
|
||||
# Two solid magenta squares on a full-screen page. Each must
|
||||
# be where the config puts it, the middle of the screen must
|
||||
# not be overlay, and the overlay must cover about the area of
|
||||
# its two squares. With "does this desc have an image" asked
|
||||
# of pixels that had just been released, no desc's geometry
|
||||
# was set and the overlay covered 100% of the screen, in both
|
||||
# modes.
|
||||
timeout 180 python3 samples/input/overlay_on_screen/overlay_on_screen_test.py ./retroarch
|
||||
|
||||
- name: Run overlay_menu_mouse_test (display and input packs, threaded off and on)
|
||||
shell: bash
|
||||
run: |
|
||||
set -eu
|
||||
# The menu switched its mouse off whenever any overlay was
|
||||
# alive, so an LED overlay - nothing on it to press - left a
|
||||
# fullscreen desktop user with no menu cursor. A pack of
|
||||
# "nul" buttons must now show the cursor where the pointer is
|
||||
# moved; the same pack bound to "a" must still not.
|
||||
timeout 180 python3 samples/input/overlay_on_screen/overlay_menu_mouse_test.py ./retroarch
|
||||
|
||||
- name: Run overlay_led_hidden_test (mapped and named LED packs, threaded off and on)
|
||||
shell: bash
|
||||
run: |
|
||||
set -eu
|
||||
# With the overlay LED driver on and Up held through XTEST: an
|
||||
# image at a ledN_map slot, "nul" or a control, stays hidden,
|
||||
# as does one named with _led, and a pack that names its LEDs
|
||||
# is not mapped.
|
||||
timeout 300 python3 samples/input/overlay_on_screen/overlay_led_hidden_test.py ./retroarch
|
||||
@@ -39,7 +39,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Install dependencies
|
||||
timeout-minutes: 20
|
||||
timeout-minutes: 5
|
||||
run: |
|
||||
pkgs="build-essential libxkbcommon-dev libx11-xcb-dev xvfb x11-utils zlib1g-dev libfreetype6-dev libegl1-mesa-dev libgles2-mesa-dev libgbm-dev libavcodec-dev libsdl2-dev libsdl-image1.2-dev libxml2-dev yasm"
|
||||
if dpkg -s $pkgs >/dev/null 2>&1; then
|
||||
@@ -52,23 +52,6 @@ jobs:
|
||||
echo "apt-get update failed (attempt $i); retrying in 10s"
|
||||
sleep 10
|
||||
done
|
||||
# Fetch first, under a bound: a download is safe to stop and
|
||||
# resumes, an install is not. A mirror that stalls is left for
|
||||
# the main archive on the next attempt.
|
||||
for i in 1 2 3; do
|
||||
sudo timeout 240 apt-get install -y --download-only \
|
||||
-o Acquire::Retries=3 -o Acquire::http::Timeout=30 \
|
||||
$pkgs && break
|
||||
[ $i = 3 ] && exit 1
|
||||
echo "package download failed or stalled (attempt $i); retrying from archive.ubuntu.com"
|
||||
for f in /etc/apt/sources.list /etc/apt/sources.list.d/*; do
|
||||
[ -f "$f" ] || continue
|
||||
sudo sed -i -e 's#mirror+file:/etc/apt/apt-mirrors.txt#http://archive.ubuntu.com/ubuntu/#g' \
|
||||
-e 's#http://azure\.archive\.ubuntu\.com/ubuntu#http://archive.ubuntu.com/ubuntu#g' "$f"
|
||||
done
|
||||
sudo timeout 90 apt-get update -y \
|
||||
-o Acquire::Retries=3 -o Acquire::http::Timeout=30 || true
|
||||
done
|
||||
sudo apt-get install -y \
|
||||
-o Acquire::Retries=3 -o Acquire::http::Timeout=30 \
|
||||
$pkgs
|
||||
|
||||
@@ -35,7 +35,7 @@ jobs:
|
||||
timeout-minutes: 30
|
||||
steps:
|
||||
- name: Dependencies
|
||||
timeout-minutes: 20
|
||||
timeout-minutes: 5
|
||||
run: |
|
||||
pkgs="build-essential pkg-config zlib1g-dev qt6-base-dev libgl-dev"
|
||||
if dpkg -s $pkgs >/dev/null 2>&1; then
|
||||
@@ -48,23 +48,6 @@ jobs:
|
||||
echo "apt-get update failed (attempt $i); retrying in 10s"
|
||||
sleep 10
|
||||
done
|
||||
# Fetch first, under a bound: a download is safe to stop and
|
||||
# resumes, an install is not. A mirror that stalls is left for
|
||||
# the main archive on the next attempt.
|
||||
for i in 1 2 3; do
|
||||
sudo timeout 240 apt-get install -y --download-only \
|
||||
-o Acquire::Retries=3 -o Acquire::http::Timeout=30 \
|
||||
$pkgs && break
|
||||
[ $i = 3 ] && exit 1
|
||||
echo "package download failed or stalled (attempt $i); retrying from archive.ubuntu.com"
|
||||
for f in /etc/apt/sources.list /etc/apt/sources.list.d/*; do
|
||||
[ -f "$f" ] || continue
|
||||
sudo sed -i -e 's#mirror+file:/etc/apt/apt-mirrors.txt#http://archive.ubuntu.com/ubuntu/#g' \
|
||||
-e 's#http://azure\.archive\.ubuntu\.com/ubuntu#http://archive.ubuntu.com/ubuntu#g' "$f"
|
||||
done
|
||||
sudo timeout 90 apt-get update -y \
|
||||
-o Acquire::Retries=3 -o Acquire::http::Timeout=30 || true
|
||||
done
|
||||
sudo apt-get install -y \
|
||||
-o Acquire::Retries=3 -o Acquire::http::Timeout=30 \
|
||||
$pkgs
|
||||
|
||||
@@ -27,7 +27,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Install dependencies
|
||||
timeout-minutes: 20
|
||||
timeout-minutes: 5
|
||||
run: |
|
||||
pkgs="build-essential libasound2-dev libsdl2-dev libopenal-dev"
|
||||
if dpkg -s $pkgs >/dev/null 2>&1; then
|
||||
@@ -40,23 +40,6 @@ jobs:
|
||||
echo "apt-get update failed (attempt $i); retrying in 10s"
|
||||
sleep 10
|
||||
done
|
||||
# Fetch first, under a bound: a download is safe to stop and
|
||||
# resumes, an install is not. A mirror that stalls is left for
|
||||
# the main archive on the next attempt.
|
||||
for i in 1 2 3; do
|
||||
sudo timeout 240 apt-get install -y --download-only \
|
||||
-o Acquire::Retries=3 -o Acquire::http::Timeout=30 \
|
||||
$pkgs && break
|
||||
[ $i = 3 ] && exit 1
|
||||
echo "package download failed or stalled (attempt $i); retrying from archive.ubuntu.com"
|
||||
for f in /etc/apt/sources.list /etc/apt/sources.list.d/*; do
|
||||
[ -f "$f" ] || continue
|
||||
sudo sed -i -e 's#mirror+file:/etc/apt/apt-mirrors.txt#http://archive.ubuntu.com/ubuntu/#g' \
|
||||
-e 's#http://azure\.archive\.ubuntu\.com/ubuntu#http://archive.ubuntu.com/ubuntu#g' "$f"
|
||||
done
|
||||
sudo timeout 90 apt-get update -y \
|
||||
-o Acquire::Retries=3 -o Acquire::http::Timeout=30 || true
|
||||
done
|
||||
sudo apt-get install -y \
|
||||
-o Acquire::Retries=3 -o Acquire::http::Timeout=30 \
|
||||
$pkgs
|
||||
@@ -572,24 +555,6 @@ jobs:
|
||||
./device_list_driver_test
|
||||
echo "[pass] device_list_driver_test (ASan)"
|
||||
|
||||
- name: Build and run device_label_path_test (ASan + UBSan)
|
||||
shell: bash
|
||||
working-directory: samples/audio/device_label_path
|
||||
run: |
|
||||
set -eu
|
||||
# A device list entry with the hardware's name after the path,
|
||||
# "/dev/dsp0 (HDA Intel)", is stored by the menu as shown and
|
||||
# comes back to init as the device string; oss opened it as a
|
||||
# path and failed, so a device picked from the list could not
|
||||
# be used. Compiles oss and audioio whole with open() redirected
|
||||
# to a recorder, and checks each reaches it with the path.
|
||||
make clean && make -j$(nproc) all SANITIZER=address,undefined
|
||||
test -x device_label_path_test
|
||||
ASAN_OPTIONS=detect_leaks=1:detect_stack_use_after_return=1 \
|
||||
UBSAN_OPTIONS=print_stacktrace=1 timeout 60 \
|
||||
./device_label_path_test
|
||||
echo "[pass] device_label_path_test (ASan)"
|
||||
|
||||
- name: Build and run rate_control_bounds_test (ASan + UBSan)
|
||||
shell: bash
|
||||
working-directory: samples/audio/rate_control_bounds
|
||||
@@ -778,36 +743,6 @@ jobs:
|
||||
WINEDEBUG=-all timeout 300 make check
|
||||
echo "[pass] dsound_pump_test (wine)"
|
||||
|
||||
- name: Build and run wdmks_loop_test (MinGW, under wine)
|
||||
shell: bash
|
||||
working-directory: samples/audio/wdmks_loop
|
||||
run: |
|
||||
set -eu
|
||||
# audio/drivers/wdmks.c compiled whole, its WaveRT loop driven
|
||||
# by hand: the test owns the driver's state, points the
|
||||
# position register at a word of its own and moves the
|
||||
# hardware itself, so nothing reaches the kernel and wine
|
||||
# needs no sound card. Pins that every byte the cursor passes
|
||||
# is cleared, so the loop plays silence when the frontend
|
||||
# stops writing - a pause, a menu, a stall - instead of the
|
||||
# last loop of audio round and round; that a write after
|
||||
# that lands a margin ahead of the hardware rather than a
|
||||
# loop behind it; that a full loop is not taken for an
|
||||
# overrun; and that a start puts the model back at the top
|
||||
# of the loop with the hardware; and that the resync margin is
|
||||
# the FIFO the pin reports plus half a millisecond, two
|
||||
# milliseconds where it reports none; and that gaps in the
|
||||
# audio are counted once each, neither a start nor every
|
||||
# sample of a pause.
|
||||
if ! command -v x86_64-w64-mingw32-gcc >/dev/null 2>&1 \
|
||||
|| ! command -v wine >/dev/null 2>&1; then
|
||||
sudo apt-get install -y -qq gcc-mingw-w64-x86-64 wine64 \
|
||||
-o Acquire::Retries=3 -o Acquire::http::Timeout=30
|
||||
fi
|
||||
make clean all
|
||||
WINEDEBUG=-all timeout 120 make check
|
||||
echo "[pass] wdmks_loop_test (wine)"
|
||||
|
||||
- name: Build and run wasapi_exclusive_pacing_test (ASan + UBSan, then TSan)
|
||||
shell: bash
|
||||
working-directory: samples/audio/wasapi_exclusive_pacing
|
||||
|
||||
@@ -41,7 +41,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Install dependencies
|
||||
timeout-minutes: 20
|
||||
timeout-minutes: 5
|
||||
run: |
|
||||
pkgs="build-essential pkg-config ffmpeg libavcodec-dev libavformat-dev libavutil-dev libswscale-dev libswresample-dev"
|
||||
if dpkg -s $pkgs >/dev/null 2>&1; then
|
||||
@@ -54,23 +54,6 @@ jobs:
|
||||
echo "apt-get update failed (attempt $i); retrying in 10s"
|
||||
sleep 10
|
||||
done
|
||||
# Fetch first, under a bound: a download is safe to stop and
|
||||
# resumes, an install is not. A mirror that stalls is left for
|
||||
# the main archive on the next attempt.
|
||||
for i in 1 2 3; do
|
||||
sudo timeout 240 apt-get install -y --download-only \
|
||||
-o Acquire::Retries=3 -o Acquire::http::Timeout=30 \
|
||||
$pkgs && break
|
||||
[ $i = 3 ] && exit 1
|
||||
echo "package download failed or stalled (attempt $i); retrying from archive.ubuntu.com"
|
||||
for f in /etc/apt/sources.list /etc/apt/sources.list.d/*; do
|
||||
[ -f "$f" ] || continue
|
||||
sudo sed -i -e 's#mirror+file:/etc/apt/apt-mirrors.txt#http://archive.ubuntu.com/ubuntu/#g' \
|
||||
-e 's#http://azure\.archive\.ubuntu\.com/ubuntu#http://archive.ubuntu.com/ubuntu#g' "$f"
|
||||
done
|
||||
sudo timeout 90 apt-get update -y \
|
||||
-o Acquire::Retries=3 -o Acquire::http::Timeout=30 || true
|
||||
done
|
||||
sudo apt-get install -y \
|
||||
-o Acquire::Retries=3 -o Acquire::http::Timeout=30 \
|
||||
$pkgs
|
||||
|
||||
@@ -27,7 +27,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Install dependencies
|
||||
timeout-minutes: 20
|
||||
timeout-minutes: 5
|
||||
run: |
|
||||
pkgs="build-essential ffmpeg"
|
||||
if dpkg -s $pkgs >/dev/null 2>&1; then
|
||||
@@ -40,23 +40,6 @@ jobs:
|
||||
echo "apt-get update failed (attempt $i); retrying in 10s"
|
||||
sleep 10
|
||||
done
|
||||
# Fetch first, under a bound: a download is safe to stop and
|
||||
# resumes, an install is not. A mirror that stalls is left for
|
||||
# the main archive on the next attempt.
|
||||
for i in 1 2 3; do
|
||||
sudo timeout 240 apt-get install -y --download-only \
|
||||
-o Acquire::Retries=3 -o Acquire::http::Timeout=30 \
|
||||
$pkgs && break
|
||||
[ $i = 3 ] && exit 1
|
||||
echo "package download failed or stalled (attempt $i); retrying from archive.ubuntu.com"
|
||||
for f in /etc/apt/sources.list /etc/apt/sources.list.d/*; do
|
||||
[ -f "$f" ] || continue
|
||||
sudo sed -i -e 's#mirror+file:/etc/apt/apt-mirrors.txt#http://archive.ubuntu.com/ubuntu/#g' \
|
||||
-e 's#http://azure\.archive\.ubuntu\.com/ubuntu#http://archive.ubuntu.com/ubuntu#g' "$f"
|
||||
done
|
||||
sudo timeout 90 apt-get update -y \
|
||||
-o Acquire::Retries=3 -o Acquire::http::Timeout=30 || true
|
||||
done
|
||||
sudo apt-get install -y \
|
||||
-o Acquire::Retries=3 -o Acquire::http::Timeout=30 \
|
||||
$pkgs
|
||||
|
||||
@@ -20,31 +20,6 @@ concurrency:
|
||||
cancel-in-progress: ${{ github.ref != 'refs/heads/master' }}
|
||||
|
||||
jobs:
|
||||
video-thread-state:
|
||||
name: The video thread reads its snapshot
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 10
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v5
|
||||
|
||||
- name: Tree check
|
||||
shell: bash
|
||||
run: |
|
||||
set -eu
|
||||
# The main thread builds a video_frame_info_t and hands it
|
||||
# over; a driver that reaches back into config_get_ptr() from
|
||||
# frame() or present_last() reads settings the main thread
|
||||
# may be writing. TSan found one of those in the null driver
|
||||
# rather than anyone noticing it by reading, so this looks
|
||||
# for the rest: it walks what those two entry points call
|
||||
# and fails on live-state access it can reach. The drivers
|
||||
# for platforms with no video thread are listed in the
|
||||
# checker with that reason, and the list has to shrink when
|
||||
# one of them stops doing it.
|
||||
python3 tools/video_thread_state_check.py
|
||||
|
||||
surface-requirements:
|
||||
name: Producers ask the surface layer
|
||||
runs-on: ubuntu-latest
|
||||
@@ -87,11 +62,10 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Install dependencies
|
||||
timeout-minutes: 20
|
||||
timeout-minutes: 5
|
||||
run: |
|
||||
pkgs="build-essential libx11-dev libxrandr-dev libdrm-dev"
|
||||
pkgs="$pkgs xserver-xorg-core xserver-xorg-video-dummy x11-xserver-utils libsdl2-dev libxinerama-dev"
|
||||
pkgs="$pkgs libwayland-dev"
|
||||
pkgs="$pkgs gcc-mingw-w64-x86-64 wine64 openbox cmake ninja-build"
|
||||
if dpkg -s $pkgs >/dev/null 2>&1; then
|
||||
echo "All packages already present on runner image; skipping apt."
|
||||
@@ -103,23 +77,6 @@ jobs:
|
||||
echo "apt-get update failed (attempt $i); retrying in 10s"
|
||||
sleep 10
|
||||
done
|
||||
# Fetch first, under a bound: a download is safe to stop and
|
||||
# resumes, an install is not. A mirror that stalls is left for
|
||||
# the main archive on the next attempt.
|
||||
for i in 1 2 3; do
|
||||
sudo timeout 240 apt-get install -y --download-only \
|
||||
-o Acquire::Retries=3 -o Acquire::http::Timeout=30 \
|
||||
$pkgs && break
|
||||
[ $i = 3 ] && exit 1
|
||||
echo "package download failed or stalled (attempt $i); retrying from archive.ubuntu.com"
|
||||
for f in /etc/apt/sources.list /etc/apt/sources.list.d/*; do
|
||||
[ -f "$f" ] || continue
|
||||
sudo sed -i -e 's#mirror+file:/etc/apt/apt-mirrors.txt#http://archive.ubuntu.com/ubuntu/#g' \
|
||||
-e 's#http://azure\.archive\.ubuntu\.com/ubuntu#http://archive.ubuntu.com/ubuntu#g' "$f"
|
||||
done
|
||||
sudo timeout 90 apt-get update -y \
|
||||
-o Acquire::Retries=3 -o Acquire::http::Timeout=30 || true
|
||||
done
|
||||
sudo apt-get install -y \
|
||||
-o Acquire::Retries=3 -o Acquire::http::Timeout=30 \
|
||||
$pkgs
|
||||
@@ -223,6 +180,51 @@ jobs:
|
||||
done
|
||||
echo "[pass] atomic backend portability"
|
||||
|
||||
- name: Build and run vblank_clock_ddraw_test (MinGW, under wine)
|
||||
shell: bash
|
||||
working-directory: samples/gfx/vblank_clock_ddraw
|
||||
run: |
|
||||
set -eu
|
||||
# Scanline Sync's Windows 2000/XP tier against a real DirectDraw
|
||||
# (wine's, which reports the counter as an XDDM driver does):
|
||||
# GetScanLine readings into vblank_sampler_feed and the vblank
|
||||
# clock, and the clock's beam checked against GetScanLine.
|
||||
if ! command -v x86_64-w64-mingw32-gcc >/dev/null 2>&1 \
|
||||
|| ! command -v wine >/dev/null 2>&1 \
|
||||
|| ! command -v xvfb-run >/dev/null 2>&1; then
|
||||
sudo apt-get install -y -qq gcc-mingw-w64-x86-64 wine64 xvfb \
|
||||
-o Acquire::Retries=3 -o Acquire::http::Timeout=30
|
||||
fi
|
||||
make clean all
|
||||
WINEDEBUG=-all timeout 300 xvfb-run -a -s "-screen 0 1280x1024x24" make check
|
||||
echo "[pass] vblank_clock_ddraw_test (wine)"
|
||||
|
||||
- name: Build and run vblank_clock_test and vblank_clock_pub_test (C89, ASan + UBSan, TSan)
|
||||
shell: bash
|
||||
working-directory: samples/gfx/vblank_clock
|
||||
run: |
|
||||
set -eu
|
||||
# gfx/common/vblank_clock.h: the arithmetic Scanline Sync on
|
||||
# Windows uses in place of polling D3DKMTGetScanLine - a clock
|
||||
# fed vblank timestamps from a waiter that only ever wakes
|
||||
# late, and the beam position it implies. Against 50 simulated
|
||||
# displays up to 600 ppm off their nominal rate: no beam
|
||||
# before it settles, beam and wait landing within 6 lines,
|
||||
# missed and duplicate vblanks, a stale clock, and variable
|
||||
# refresh never reported.
|
||||
make clean all SANITIZER=address,undefined
|
||||
UBSAN_OPTIONS=print_stacktrace=1:halt_on_error=1 timeout 60 ./vblank_clock_test
|
||||
echo "[pass] vblank_clock_test"
|
||||
# The seqlock handing the clock from the kernel-event thread to
|
||||
# the frame thread: one writer publishing flat out, three
|
||||
# readers checking every snapshot is one whole publish and
|
||||
# none goes backwards - under ASan+UBSan, then TSan, which
|
||||
# also checks the pattern is free of formal data races.
|
||||
UBSAN_OPTIONS=print_stacktrace=1:halt_on_error=1 timeout 120 ./vblank_clock_pub_test
|
||||
make clean all SANITIZER=thread
|
||||
TSAN_OPTIONS=halt_on_error=1 timeout 300 ./vblank_clock_pub_test
|
||||
echo "[pass] vblank_clock_pub_test (ASan, TSan)"
|
||||
|
||||
- name: Build and run vulkan_extension_count_test (ASan)
|
||||
shell: bash
|
||||
working-directory: samples/gfx/vulkan_extension_count
|
||||
@@ -376,20 +378,20 @@ jobs:
|
||||
set -eu
|
||||
# Regression test for the partial-init leak fix in
|
||||
# gfx/common/vulkan_common.c::vulkan_emulated_mailbox_init.
|
||||
# The function brings up three things in sequence (two
|
||||
# eventcounts and the acquire thread; a condition variable,
|
||||
# a mutex and the thread when this was written) and used to
|
||||
# return `false` directly when the second or third failed,
|
||||
# leaking what the earlier ones had allocated. Both
|
||||
# production call sites in vulkan_create_swapchain ignore
|
||||
# the return value, so an init failure also left the
|
||||
# mailbox half built while VK_DATA_FLAG_EMULATING_MAILBOX
|
||||
# was still set, setting up a crash the next time
|
||||
# vulkan_acquire_next_image routed into the emulated path.
|
||||
# Fix routes every early failure through `goto error` to a
|
||||
# single deinit call, which is safe on a partly built
|
||||
# mailbox and ends with a memset, leaving the struct in the
|
||||
# same shape the deinit-on-shutdown path produces and
|
||||
# Pre-fix the function had three sequential allocations
|
||||
# (scond_new, slock_new, sthread_create) and on any of the
|
||||
# latter two failures returned `false` directly, leaking
|
||||
# the already-allocated cond and/or lock. Both production
|
||||
# call sites in vulkan_create_swapchain ignore the return
|
||||
# value, so an init failure also left vk->mailbox.lock ==
|
||||
# NULL and vk->mailbox.cond == NULL while VK_DATA_FLAG_
|
||||
# EMULATING_MAILBOX was still set, setting up a NULL-deref
|
||||
# the next time vulkan_acquire_next_image routed into
|
||||
# vulkan_emulated_mailbox_acquire_next_image (slock_lock
|
||||
# on a NULL pointer). Fix routes every early failure
|
||||
# through `goto error` to a single deinit call, which is
|
||||
# null-safe and ends with a memset, leaving the struct in
|
||||
# the same shape the deinit-on-shutdown path produces and
|
||||
# tripping the existing `mailbox.swapchain == VK_NULL_
|
||||
# HANDLE` guard at vulkan_acquire_next_image. Build under
|
||||
# AddressSanitizer with leak detection so any
|
||||
@@ -483,19 +485,42 @@ jobs:
|
||||
working-directory: samples/gfx/gfx_widgets_msg_queue_race
|
||||
run: |
|
||||
set -eu
|
||||
# The widget message ring in gfx/gfx_widgets.c is a lock-free
|
||||
# single-producer / single-consumer queue: the main thread
|
||||
# publishes through msg_queue_tail, the widgets' owner pops
|
||||
# through msg_queue_head, each cursor released by its writer
|
||||
# and acquired by the other side, and the slot pointer rides
|
||||
# that pairing. The test builds the production push and pop
|
||||
# out of gfx_widgets.c itself and drives them from one producer
|
||||
# thread and one consumer thread, checking order, loss, and the
|
||||
# full-ring refusal. ThreadSanitizer is the validator that
|
||||
# matters: a store to the slot array outside the release /
|
||||
# acquire pairing, or a cursor published before its slot is
|
||||
# written, is a race it reports and the value checks may not.
|
||||
# halt_on_error=1 fails the run on the first race.
|
||||
# Regression test for the producer-producer race fix on
|
||||
# dispgfx_widget_t::msg_queue in gfx/gfx_widgets.c.
|
||||
# Pre-fix the FIFO had no dedicated lock: producers could
|
||||
# be invoked from any thread (the threaded task system at
|
||||
# libretro-common/queues/task_queue.c runs a worker
|
||||
# thread; gfx/video_driver.c::video_driver_frame released
|
||||
# RUNLOOP_MSG_QUEUE_LOCK before calling the producer, so
|
||||
# main-thread producers ran unlocked) yet there was no
|
||||
# lock dedicated to msg_queue itself. Post-fix a
|
||||
# msg_queue_lock is acquired around every fifo_write (in
|
||||
# gfx_widgets_msg_queue_push), every fifo_read (in
|
||||
# gfx_widgets_iterate), and the avail-check that gates
|
||||
# them. The outer fast-path FIFO_*_AVAIL checks that
|
||||
# used to wrap the function bodies were dropped: reading
|
||||
# the FIFO cursors outside the lock is itself a data
|
||||
# race against the locked writes (TSan-detectable;
|
||||
# benign on x86 TSO but real on weak-memory hardware).
|
||||
#
|
||||
# ThreadSanitizer is the right validator: it instruments
|
||||
# every memory access on fifo_buffer_t::end and
|
||||
# ::first and would flag a missing-lock regression as a
|
||||
# data race on the producer side, the consumer side, or
|
||||
# the avail check. ASan/UBSan would not catch the lock
|
||||
# removal at all. halt_on_error=1 makes TSan exit
|
||||
# non-zero on the first observed race, which is what we
|
||||
# want for CI: any race here means the production
|
||||
# locking discipline is broken.
|
||||
#
|
||||
# The test mirrors the post-fix locking protocol of
|
||||
# gfx_widgets_msg_queue_push and gfx_widgets_iterate's
|
||||
# FIFO interaction; the widget allocation, font
|
||||
# measurement, and animation push that wrap the actual
|
||||
# FIFO calls in production are not part of the race and
|
||||
# are stripped from the test. If gfx_widgets.c amends
|
||||
# the locking around msg_queue, the verbatim copies in
|
||||
# gfx_widgets_msg_queue_race_test.c must follow.
|
||||
make clean all SANITIZER=thread
|
||||
test -x gfx_widgets_msg_queue_race_test
|
||||
TSAN_OPTIONS=halt_on_error=1 timeout 60 \
|
||||
@@ -576,16 +601,6 @@ jobs:
|
||||
TSAN_OPTIONS=halt_on_error=1 timeout 120 \
|
||||
./gfx_widgets_sanitize_test
|
||||
echo "[pass] gfx_widgets_sanitize_test (TSan)"
|
||||
- name: Build and run widget snapshot ownership test
|
||||
shell: bash
|
||||
working-directory: tests-other
|
||||
run: |
|
||||
set -eu
|
||||
make -f Makefile.widget_snapshot clean
|
||||
make -f Makefile.widget_snapshot
|
||||
timeout 60 ./widget_snapshot_ownership_test
|
||||
make -f Makefile.widget_snapshot clean
|
||||
|
||||
- name: Build and run gfx_widgets_task_bar_stack_test (ASan + UBSan, TSan)
|
||||
shell: bash
|
||||
working-directory: samples/gfx/gfx_widgets_task_bar_stack
|
||||
@@ -637,79 +652,6 @@ jobs:
|
||||
timeout 60 ./display_servers_test
|
||||
echo "[pass] display_servers_test"
|
||||
|
||||
- name: Build and run dispserv_gx_test (ASan + UBSan)
|
||||
shell: bash
|
||||
working-directory: samples/gfx/dispserv_gx
|
||||
run: |
|
||||
set -eu
|
||||
# The GameCube/Wii display server (dispserv_gx.c and the mode
|
||||
# rules in dispserv_gx_modes.c), linked as they ship against
|
||||
# stubbed libogc queries, on every TV standard: the list holds
|
||||
# only modes the standard shows, one of them current, and each
|
||||
# entry - through the label the menu prints and the parse the
|
||||
# menu does - selects exactly that mode, stores its config id
|
||||
# and reports the refresh rate it listed.
|
||||
#
|
||||
# The menu's parse used to run strtoul() inside
|
||||
# VIDEO_SCALE_PUT_H, whose PACK evaluates its argument twice;
|
||||
# the second call returned 0 and every pick from the list
|
||||
# went out as Wx0. Guard the shape as well as the behaviour.
|
||||
if grep -nE 'VIDEO_SCALE_(PUT_[WH]|PACK)\([^;]*strto' \
|
||||
../../../menu/cbs/menu_cbs_ok.c; then
|
||||
echo "::error::strtoul inside a VIDEO_SCALE macro runs twice"
|
||||
exit 1
|
||||
fi
|
||||
make clean all SANITIZER=address,undefined
|
||||
ASAN_OPTIONS=detect_leaks=1 UBSAN_OPTIONS=print_stacktrace=1:halt_on_error=1 \
|
||||
timeout 60 ./dispserv_gx_test
|
||||
make clean
|
||||
|
||||
- name: Build and run dispserv_ps3_test (ASan + UBSan)
|
||||
shell: bash
|
||||
working-directory: samples/gfx/dispserv_ps3
|
||||
run: |
|
||||
set -eu
|
||||
# The PS3 display server (dispserv_ps3.c and the mode rules in
|
||||
# dispserv_ps3_modes.c), linked as they ship against stubbed
|
||||
# PSL1GHT video output queries, for displays taking different
|
||||
# sets of modes with the system menu in each: the list holds
|
||||
# exactly what the display takes with the mode in use marked,
|
||||
# each entry - through the menu's label and parse - selects
|
||||
# that mode and reinitialises the driver once, and
|
||||
# ps3_display_server_resolution(), which the RSX driver
|
||||
# configures the output from, follows the choice (before, the
|
||||
# RSX driver ignored the setting and kept the system menu's
|
||||
# mode) and falls back to the system mode when the display
|
||||
# does not take it.
|
||||
make clean all SANITIZER=address,undefined
|
||||
ASAN_OPTIONS=detect_leaks=1 UBSAN_OPTIONS=print_stacktrace=1:halt_on_error=1 \
|
||||
timeout 60 ./dispserv_ps3_test
|
||||
make clean
|
||||
|
||||
- name: X11 window poll sends no requests (Xvfb, ASan + UBSan)
|
||||
shell: bash
|
||||
run: |
|
||||
set -eu
|
||||
# x11_check_window() and x11_has_focus() run every frame; a
|
||||
# reply-bearing request there waits behind whatever else is
|
||||
# queued on the connection (a software Vulkan WSI's whole
|
||||
# PutImage frame). Focus comes from FocusIn/FocusOut, so the
|
||||
# X contexts must select FocusChangeMask.
|
||||
for f in x_ctx.c x_vk_ctx.c xegl_ctx.c; do
|
||||
grep -q FocusChangeMask gfx/drivers_context/$f \
|
||||
|| { echo "::error::$f does not select FocusChangeMask"; exit 1; }
|
||||
done
|
||||
sudo apt-get install -y -qq xvfb > /dev/null
|
||||
Xvfb :97 -screen 0 1024x768x24 > /tmp/xvfb97.log 2>&1 &
|
||||
XVFB_PID=$!
|
||||
sleep 1
|
||||
(cd samples/gfx/x11_frame_poll \
|
||||
&& make clean all SANITIZER=address,undefined \
|
||||
&& DISPLAY=:97 ASAN_OPTIONS=detect_leaks=1 UBSAN_OPTIONS=print_stacktrace=1 \
|
||||
timeout 60 ./x11_frame_poll_test)
|
||||
kill $XVFB_PID
|
||||
echo "[pass] x11_frame_poll_test"
|
||||
|
||||
- name: Live mode switching on an Xorg dummy server (X11 + SDL)
|
||||
shell: bash
|
||||
run: |
|
||||
@@ -722,10 +664,7 @@ jobs:
|
||||
# cannot stand in). The X11 sample runs the modeline engine
|
||||
# end to end - generate a 15 kHz timing, put it on the wire
|
||||
# through dispserv_x11, read the crtc back and compare every
|
||||
# field, switch again, restore - then the Screen Resolution
|
||||
# callbacks: the listed modes, a smaller, a larger and a
|
||||
# rate-only switch with the framebuffer following, an
|
||||
# unlisted size refused, and back. The SDL sample switches
|
||||
# field, switch again, restore - and the SDL sample switches
|
||||
# a real SDL2 window among the listed modes through
|
||||
# dispserv_sdl2 (and dispserv_sdl3 on an SDL3 build), for the
|
||||
# modeline path and the menu picker.
|
||||
@@ -750,18 +689,6 @@ jobs:
|
||||
&& ASAN_OPTIONS=detect_leaks=1 UBSAN_OPTIONS=print_stacktrace=1 \
|
||||
timeout 120 ./display_servers_x11_live_test)
|
||||
echo "[pass] display_servers_x11_live_test"
|
||||
# The refresh rate a core polls every frame: answered from
|
||||
# memory once the X event pump drains the connection, with no
|
||||
# request to the server, and changed by the next pump after
|
||||
# another client switches the mode - under a reader on a
|
||||
# second thread, once under ASan+UBSan and once under TSan.
|
||||
(cd samples/gfx/x11_refresh_rate \
|
||||
&& make clean all SANITIZER=address,undefined \
|
||||
&& ASAN_OPTIONS=detect_leaks=1 UBSAN_OPTIONS=print_stacktrace=1:halt_on_error=1 \
|
||||
timeout 120 ./x11_refresh_rate_test \
|
||||
&& make clean all SANITIZER=thread \
|
||||
&& TSAN_OPTIONS=halt_on_error=1 timeout 120 ./x11_refresh_rate_test)
|
||||
echo "[pass] x11_refresh_rate_test"
|
||||
(cd samples/gfx/display_servers_sdl2_live \
|
||||
&& make clean all SANITIZER=address,undefined \
|
||||
&& ASAN_OPTIONS=detect_leaks=1 UBSAN_OPTIONS=print_stacktrace=1 \
|
||||
@@ -819,80 +746,6 @@ jobs:
|
||||
pkill openbox || true
|
||||
sudo kill "$XORG_PID" || true
|
||||
|
||||
- name: Screen Resolution through Mutter's DisplayConfig (ASan + UBSan)
|
||||
shell: bash
|
||||
run: |
|
||||
set -eu
|
||||
# Under GNOME's Wayland session the X11 driver runs on
|
||||
# XWayland, which lists every mode at the desktop's current
|
||||
# rate and only scales the window when "switched", and the
|
||||
# Wayland driver had no resolution list at all; Mutter's
|
||||
# org.gnome.Mutter.DisplayConfig D-Bus interface is the only
|
||||
# way to the monitor's real modes. mock_mutter.py carries
|
||||
# Mutter's signatures and its ApplyMonitorsConfig checks; the
|
||||
# test drives gfx/common/mutter_displayconfig.c directly and
|
||||
# through dispserv_x11 (real Xorg dummy server, and Xwayland
|
||||
# under a headless Weston) and dispserv_wl, with and without
|
||||
# Mutter on the bus. Without it every path must behave
|
||||
# exactly as before: no session bus, a bus without Mutter, a
|
||||
# real X server (Mutter is never asked), XWayland's own list,
|
||||
# and no Wayland resolution list. Last, a real headless Mutter
|
||||
# and its XWayland: Mutter names XWayland's outputs after its
|
||||
# connectors (DP-1, Meta-0), so only the XWAYLAND extension
|
||||
# says what the server is.
|
||||
sudo apt-get install -y -qq libdbus-1-dev dbus python3-dbus \
|
||||
python3-gi weston xwayland mutter > /dev/null
|
||||
(cd samples/gfx/mutter_displayconfig \
|
||||
&& make clean all SANITIZER=address,undefined \
|
||||
&& ASAN_OPTIONS=detect_leaks=1 \
|
||||
UBSAN_OPTIONS=print_stacktrace=1:halt_on_error=1 \
|
||||
XORG="sudo Xorg" timeout 300 ./run.sh)
|
||||
echo "[pass] mutter_displayconfig_test"
|
||||
|
||||
- name: D3D11 hardware ring under threaded video (mingw + Wine, headless)
|
||||
shell: bash
|
||||
run: |
|
||||
set -o pipefail
|
||||
# The threaded wrapper hands a D3D11 hardware core a deferred
|
||||
# context and replays its frames on the video thread. Nothing
|
||||
# on Linux exercises that, and it shipped showing one frame
|
||||
# and then black. The real proxy and the driver's own
|
||||
# FinishCommandList argument, run under Wine, where wined3d
|
||||
# implements deferred contexts over OpenGL. Requires the ring
|
||||
# and the immediate context to show every frame, and the ring
|
||||
# as it used to be to fail.
|
||||
sudo apt-get install -y -qq xvfb > /dev/null
|
||||
samples/gfx/d3d11_hw_ring/run.sh
|
||||
|
||||
- name: libretro_d3d11.h version 2, the contract run (mingw + Wine, headless)
|
||||
shell: bash
|
||||
run: |
|
||||
set -o pipefail
|
||||
# Version 2 of the D3D11 hardware render interface lets a core
|
||||
# and a frontend that presents from another thread share the
|
||||
# device's one immediate context by taking turns, so the core
|
||||
# keeps everything a deferred context cannot do - reading back
|
||||
# above all. This runs the two against each other on a real
|
||||
# D3D11 device (wined3d), and requires every frame and every
|
||||
# readback to be right, and a core that ignores what
|
||||
# lock_context tells it to be caught.
|
||||
sudo apt-get install -y -qq xvfb > /dev/null
|
||||
REQUIRE_D3D11=1 samples/gfx/d3d11_hw_interface_v2/run.sh
|
||||
|
||||
- name: libretro_d3d12.h version 2, the contract run (mingw + Wine + vkd3d, headless)
|
||||
shell: bash
|
||||
run: |
|
||||
set -o pipefail
|
||||
# Version 2 of the D3D12 hardware render interface lets a core
|
||||
# hand a frame to a frontend that presents from another thread
|
||||
# without the copy version 1 forces. This runs a core and such a
|
||||
# frontend against each other on a real D3D12 device - vkd3d
|
||||
# over Mesa's software Vulkan driver - and requires every frame
|
||||
# to arrive intact, and a core that ignores wait_sync_index to
|
||||
# be caught.
|
||||
sudo apt-get install -y -qq xvfb libvkd3d1 mesa-vulkan-drivers > /dev/null
|
||||
REQUIRE_D3D12=1 samples/gfx/d3d12_hw_interface_v2/run.sh
|
||||
|
||||
- name: Build and run display_servers_kms_test (ASan + UBSan)
|
||||
shell: bash
|
||||
working-directory: samples/gfx/display_servers_kms
|
||||
@@ -929,125 +782,6 @@ jobs:
|
||||
timeout 60 ./display_servers_kms_test
|
||||
echo "[pass] display_servers_kms_test"
|
||||
|
||||
- name: Build and run drm_hdr_test (ASan + UBSan)
|
||||
shell: bash
|
||||
working-directory: samples/gfx/drm_hdr
|
||||
run: |
|
||||
set -eu
|
||||
# gfx/common/drm_hdr.c: which EDIDs take HDR10 and what they
|
||||
# declare, malformed ones refused without an overread, and the
|
||||
# HDR_OUTPUT_METADATA blob, whose layout must be the kernel's.
|
||||
make clean all SANITIZER=address,undefined
|
||||
ASAN_OPTIONS=detect_leaks=1 UBSAN_OPTIONS=print_stacktrace=1:halt_on_error=1 \
|
||||
timeout 60 ./drm_hdr_test
|
||||
echo "[pass] drm_hdr_test"
|
||||
|
||||
- name: Build and run wayland_wlr_output_test (ASan + UBSan)
|
||||
shell: bash
|
||||
working-directory: samples/gfx/wayland_wlr_output
|
||||
run: |
|
||||
set -eu
|
||||
# gfx/common/wayland_wlr_output.c, the Wayland display server's
|
||||
# resolution list and mode switch on wlroots compositors, against
|
||||
# an in-process compositor that checks every configuration names
|
||||
# each head exactly once, as the protocol requires.
|
||||
make clean all SANITIZER=address,undefined
|
||||
ASAN_OPTIONS=detect_leaks=1 UBSAN_OPTIONS=print_stacktrace=1:halt_on_error=1 \
|
||||
timeout 60 ./wayland_wlr_output_test
|
||||
echo "[pass] wayland_wlr_output_test"
|
||||
|
||||
- name: Build and run wayland_cursor_test (ASan + UBSan)
|
||||
shell: bash
|
||||
working-directory: samples/gfx/wayland_cursor
|
||||
run: |
|
||||
set -eu
|
||||
# input/common/wayland_cursor.h: the size and scale the Wayland
|
||||
# context loads its cursor theme at when the compositor has no
|
||||
# cursor-shape-v1 - XCURSOR_SIZE honoured, fractional scales
|
||||
# rounded up to a whole buffer scale.
|
||||
make clean all SANITIZER=address,undefined
|
||||
ASAN_OPTIONS=detect_leaks=1 UBSAN_OPTIONS=print_stacktrace=1:halt_on_error=1 \
|
||||
timeout 60 ./wayland_cursor_test
|
||||
echo "[pass] wayland_cursor_test"
|
||||
|
||||
- name: Build and run wayland_resize_test (ASan + UBSan)
|
||||
shell: bash
|
||||
working-directory: samples/gfx/wayland_resize
|
||||
run: |
|
||||
set -eu
|
||||
# gfx/common/wayland_resize.h, what the Wayland GL and Vulkan
|
||||
# contexts do once their buffers are resized, against an
|
||||
# in-process compositor: configures count again whatever the
|
||||
# scaling, and the buffer scale goes only to a surface on the
|
||||
# whole-scale path that is new enough to take it.
|
||||
export XDG_RUNTIME_DIR=$(mktemp -d)
|
||||
make clean all SANITIZER=address,undefined
|
||||
ASAN_OPTIONS=detect_leaks=1 UBSAN_OPTIONS=print_stacktrace=1:halt_on_error=1 \
|
||||
timeout 60 ./wayland_resize_test
|
||||
echo "[pass] wayland_resize_test"
|
||||
|
||||
- name: Build and run video_mode_select_test (ASan + UBSan)
|
||||
shell: bash
|
||||
working-directory: samples/gfx/video_mode_select
|
||||
run: |
|
||||
set -eu
|
||||
# gfx/common/video_mode_select.c: which listed mode a size and
|
||||
# rate name, and the order a resolution list is shown in - the
|
||||
# code every display server's mode switch goes through.
|
||||
make clean all SANITIZER=address,undefined
|
||||
ASAN_OPTIONS=detect_leaks=1 UBSAN_OPTIONS=print_stacktrace=1:halt_on_error=1 \
|
||||
timeout 60 ./video_mode_select_test
|
||||
echo "[pass] video_mode_select_test"
|
||||
|
||||
- name: Build and run wayland_kwin_output_test (ASan + UBSan)
|
||||
shell: bash
|
||||
working-directory: samples/gfx/wayland_kwin_output
|
||||
run: |
|
||||
set -eu
|
||||
# gfx/common/wayland_kwin_output.c, the Wayland display server's
|
||||
# resolution list and mode switch on KWin, against an in-process
|
||||
# compositor speaking KWin's output protocols in both the
|
||||
# registry form and the older per-output globals.
|
||||
make clean all SANITIZER=address,undefined
|
||||
ASAN_OPTIONS=detect_leaks=1 UBSAN_OPTIONS=print_stacktrace=1:halt_on_error=1 \
|
||||
timeout 60 ./wayland_kwin_output_test
|
||||
echo "[pass] wayland_kwin_output_test"
|
||||
|
||||
- name: Build and run wayland_color_management_test (ASan + UBSan)
|
||||
shell: bash
|
||||
working-directory: samples/gfx/wayland_color_management
|
||||
run: |
|
||||
set -eu
|
||||
# gfx/common/wayland_color.c, which tags an HDR GL surface as
|
||||
# Windows-scRGB, against an in-process compositor that records
|
||||
# every fatal wp_color_manager_v1 rule the client could break.
|
||||
make clean all SANITIZER=address,undefined
|
||||
ASAN_OPTIONS=detect_leaks=1 UBSAN_OPTIONS=print_stacktrace=1:halt_on_error=1 \
|
||||
timeout 60 ./wayland_color_management_test
|
||||
echo "[pass] wayland_color_management_test"
|
||||
|
||||
- name: Build and run wayland_drm_lease_test (ASan + UBSan)
|
||||
shell: bash
|
||||
working-directory: samples/gfx/wayland_drm_lease
|
||||
run: |
|
||||
set -eu
|
||||
# The DRM lease path in gfx/common/wayland_drm_lease.c and
|
||||
# gfx/display_servers/dispserv_wl.c, driven by a test
|
||||
# compositor on libwayland-server: what is offered, which
|
||||
# connector an index picks, an index past the offer, a
|
||||
# refusal, a revoke arriving while the client is idle, and
|
||||
# no Wayland session at all. The leased descriptor and the
|
||||
# device's non-master fd are the ones a mistake leaks, so
|
||||
# the lane runs under ASan. Both protocol halves are
|
||||
# generated here from deps/wayland-protocols, which needs
|
||||
# wayland-scanner and the client and server libraries -
|
||||
# libwayland-dev carries all three.
|
||||
make clean all SANITIZER=address,undefined
|
||||
test -x wayland_drm_lease_test
|
||||
ASAN_OPTIONS=detect_leaks=1 UBSAN_OPTIONS=print_stacktrace=1 \
|
||||
timeout 60 ./wayland_drm_lease_test
|
||||
echo "[pass] wayland_drm_lease_test"
|
||||
|
||||
- name: Build and run modeline_edid_parse_test (ASan + UBSan)
|
||||
shell: bash
|
||||
working-directory: samples/gfx/modeline_edid_parse
|
||||
@@ -1068,73 +802,6 @@ jobs:
|
||||
ASAN_OPTIONS=detect_leaks=1 UBSAN_OPTIONS=print_stacktrace=1 \
|
||||
timeout 60 ./modeline_edid_parse_test
|
||||
|
||||
- name: Build and run integer_scale_axis_test (ASan + UBSan)
|
||||
shell: bash
|
||||
working-directory: samples/gfx/integer_scale_axis
|
||||
run: |
|
||||
set -eu
|
||||
# video_viewport_get_scaled_integer(), pulled out of
|
||||
# gfx/video_driver.c by extract_integer.awk, swept over
|
||||
# viewport sizes, frame sizes, every axis mode that touches
|
||||
# X and every scaling mode. Y+X has to be integer on both
|
||||
# axes: the X axis was once multiplied from the
|
||||
# aspect-corrected width (256x224 at 64:49 is 293), giving
|
||||
# 1172x896 at 1080p - a 4.58x horizontal scale and uneven
|
||||
# pixel columns - on every video driver.
|
||||
make clean all SANITIZER=address,undefined
|
||||
test -x integer_scale_axis_test
|
||||
ASAN_OPTIONS=detect_leaks=1 UBSAN_OPTIONS=print_stacktrace=1 \
|
||||
timeout 60 ./integer_scale_axis_test
|
||||
make clean
|
||||
|
||||
- name: Build and run font_lifecycle_test (ASan + UBSan)
|
||||
shell: bash
|
||||
working-directory: samples/gfx/font_lifecycle
|
||||
run: |
|
||||
set -eu
|
||||
# Where a font is created follows the threading hint; where
|
||||
# it is REBUILT does not - a rebuild marshals whenever the
|
||||
# wrapper is up, because a GL backend making its context
|
||||
# current on the main thread while the video thread holds it
|
||||
# is an X BadAccess. Nothing ran this sample, and it had the
|
||||
# older rule written into it.
|
||||
# The fallback tests read DejaVu in place of the assets'
|
||||
# fallback fonts: Sans for fallback-font.ttf, and Sans Mono
|
||||
# and Sans as a general and a second face for the chain test.
|
||||
sudo apt-get install -y -qq fonts-dejavu-core fonts-dejavu-mono \
|
||||
> /dev/null 2>&1 || sudo apt-get install -y -qq fonts-dejavu-core > /dev/null
|
||||
make clean all SANITIZER=address,undefined
|
||||
ASAN_OPTIONS=detect_leaks=1:allocator_may_return_null=1 \
|
||||
UBSAN_OPTIONS=print_stacktrace=1 timeout 120 make check SANITIZER=address,undefined
|
||||
make clean
|
||||
# A fallback font is read on a thread of its own while two
|
||||
# fonts on two threads ask for it, and a second face behind
|
||||
# the first.
|
||||
make fallback chain SAN=thread
|
||||
TSAN_OPTIONS=halt_on_error=1 setarch "$(uname -m)" -R \
|
||||
timeout 120 ./fallback
|
||||
TSAN_OPTIONS=halt_on_error=1 setarch "$(uname -m)" -R \
|
||||
timeout 120 ./chain
|
||||
make clean
|
||||
|
||||
- name: Build and run overlay_apng_test (ASan + UBSan, TSan)
|
||||
shell: bash
|
||||
working-directory: samples/gfx/overlay_apng
|
||||
run: |
|
||||
set -eu
|
||||
# An animated overlay image is a surface with slots whose
|
||||
# texture is updated in place, so the pages' handles stand
|
||||
# while its frames advance. The sample builds its own APNG
|
||||
# fixture and holds that contract: distinct frames, a pass
|
||||
# that ends and replays, one texture across the submits, and
|
||||
# a free with a frame still in flight.
|
||||
make clean all SANITIZER=address,undefined
|
||||
ASAN_OPTIONS=detect_leaks=1:allocator_may_return_null=1 \
|
||||
UBSAN_OPTIONS=print_stacktrace=1 timeout 120 ./overlay_apng_test fixtures/anim.png
|
||||
make clean all SANITIZER=thread
|
||||
TSAN_OPTIONS=halt_on_error=1 timeout 120 ./overlay_apng_test fixtures/anim.png
|
||||
make clean
|
||||
|
||||
- name: Build and run gfx_thumbnail_anim_test (ASan + UBSan, TSan)
|
||||
shell: bash
|
||||
working-directory: samples/gfx/gfx_thumbnail_anim
|
||||
@@ -1209,50 +876,13 @@ jobs:
|
||||
# unloaded, never handed to whatever reused the slot), an
|
||||
# off-main-thread ask only recorded until the main thread's
|
||||
# service starts it, the default badge lent rather than
|
||||
# given, a 0 reported as a short wait only while a file on
|
||||
# disk is loading, and a refused async upload treated as a
|
||||
# failure.
|
||||
# given, and a refused async upload treated as a failure.
|
||||
make clean all SANITIZER=address,undefined
|
||||
test -x badge_cache_test
|
||||
ASAN_OPTIONS=detect_leaks=1 timeout 60 ./badge_cache_test
|
||||
echo "[pass] badge_cache_test (ASan)"
|
||||
make clean
|
||||
|
||||
- name: Build and run psp1_gfx_contracts_test (ASan + UBSan)
|
||||
shell: bash
|
||||
working-directory: samples/gfx/psp1_gfx_contracts
|
||||
run: |
|
||||
set -eu
|
||||
# gfx/drivers/psp1_gfx.c is reached only by the griffin unity
|
||||
# build for PSP, so nothing else here compiles it, let alone
|
||||
# runs it. This links the real driver against a stand-in for
|
||||
# the parts of the machine its correctness turns on: main RAM
|
||||
# and VRAM each mapped twice so the cached and uncached
|
||||
# aliases genuinely alias, a no-access page after VRAM so a
|
||||
# blit off the end faults, display lists that stay the GE's
|
||||
# until they are waited on, and a vblank sub-interrupt that
|
||||
# can be fired from inside free().
|
||||
#
|
||||
# What it holds: the 24bpp BGR byte order video_driver.h
|
||||
# specifies for read_viewport, filling the whole buffer the
|
||||
# screenshot task hands over, releasing the interrupt handler
|
||||
# before the driver struct it writes through, keeping the GE
|
||||
# off a display list or framebuffer that is in use, both blits
|
||||
# staying inside the buffers they target, cache-line ownership
|
||||
# of everything the GE writes, and init failing cleanly when
|
||||
# an allocation does.
|
||||
make clean all SANITIZER=address,undefined
|
||||
rc=0
|
||||
ASAN_OPTIONS=detect_leaks=1 UBSAN_OPTIONS=print_stacktrace=1 \
|
||||
timeout 120 ./psp1_gfx_contracts_test || rc=$?
|
||||
# 77: the runner would not hand over the PSP address windows.
|
||||
if [ "$rc" = 77 ]; then
|
||||
echo "[skip] PSP address windows unavailable"
|
||||
exit 0
|
||||
fi
|
||||
[ "$rc" = 0 ]
|
||||
make clean
|
||||
|
||||
- name: Build and run gfx_thumbnail_preview_test (ASan + UBSan, TSan)
|
||||
shell: bash
|
||||
working-directory: samples/gfx/gfx_thumbnail_preview
|
||||
@@ -1322,15 +952,6 @@ jobs:
|
||||
AUDIO_BYTES_PER_TICK=65536 FRAMES=120 timeout 300 \
|
||||
./gfx_thumbnail_preview_test fixtures/*.mp4 fixtures/anim_lossless.webp
|
||||
echo "[pass] gfx_thumbnail_preview_test (ASan)"
|
||||
# The same session with HAVE_THREADS: the thumbnail decodes on
|
||||
# its worker and the pool, the app's own path, paced at a
|
||||
# display's rate with a bound on the longest stall.
|
||||
test -x gfx_thumbnail_preview_test_threaded
|
||||
ASAN_OPTIONS=detect_leaks=1:allocator_may_return_null=1 \
|
||||
UBSAN_OPTIONS=print_stacktrace=1 \
|
||||
THREADS=4 FRAMES=180 PACE_HZ=60 PACE_MAX_STALL_MS=800 NOAUDIO=1 timeout 300 \
|
||||
./gfx_thumbnail_preview_test_threaded fixtures/bframes_h264.mp4 fixtures/bframes_hevc.mp4
|
||||
echo "[pass] gfx_thumbnail_preview_test_threaded (ASan, 4 threads)"
|
||||
# The frame-indexed types (animated WEBP, APNG) through the
|
||||
# same session, windowed: animated WEBP had no progressive
|
||||
# open, so the session read the whole file into the
|
||||
@@ -1384,29 +1005,8 @@ jobs:
|
||||
UBSAN_OPTIONS=print_stacktrace=1 timeout 300 bash -c '
|
||||
./preview_blit_bands_test fixtures/long_video.mp4
|
||||
./preview_blit_bands_test fixtures/vp9_tiles.webm
|
||||
./preview_blit_bands_test fixtures/hevc_wpp.mp4
|
||||
./preview_blit_bands_test fixtures/bframes_hevc.mp4
|
||||
./preview_blit_bands_test fixtures/hevc_nowpp.mp4 fixtures/mbaff_h264.mp4
|
||||
RH265_PUBLISH_DELAY=200 ./preview_blit_bands_test fixtures/hevc_wpp.mp4 fixtures/bframes_hevc.mp4 fixtures/hevc_nowpp.mp4'
|
||||
./preview_blit_bands_test fixtures/hevc_wpp.mp4'
|
||||
echo "[pass] preview_blit_bands_test (ASan)"
|
||||
# Catching up: behind the clock the stream drops only what
|
||||
# nothing references, and every picture still shown is the
|
||||
# full decode's; the threaded build does the same with four
|
||||
# pictures in flight, the drops taken at submit.
|
||||
test -x preview_catchup_test && test -x preview_catchup_test_threaded
|
||||
ASAN_OPTIONS=detect_leaks=1:allocator_may_return_null=1 \
|
||||
UBSAN_OPTIONS=print_stacktrace=1 timeout 300 bash -c '
|
||||
./preview_catchup_test fixtures/bframes_h264.mp4 fixtures/bframes_hevc.mp4
|
||||
./preview_catchup_test_threaded fixtures/bframes_h264.mp4 fixtures/bframes_hevc.mp4'
|
||||
echo "[pass] preview_catchup_test (ASan, one picture at a time and four in flight)"
|
||||
# The audio chain against ffmpeg: the track's rate exact, the
|
||||
# resampled one within a fraction of a percent, from the first
|
||||
# half second.
|
||||
test -x preview_audio_oracle_test
|
||||
ASAN_OPTIONS=detect_leaks=1:allocator_may_return_null=1 \
|
||||
UBSAN_OPTIONS=print_stacktrace=1 timeout 300 \
|
||||
./preview_audio_oracle_test fixtures/trailing_small.mp4 fixtures/trailing_large.mp4
|
||||
echo "[pass] preview_audio_oracle_test (ASan)"
|
||||
# clean keeps fixtures/ by design; regenerate anyway so the
|
||||
# step cannot silently run against an empty glob.
|
||||
make clean all SANITIZER=thread
|
||||
@@ -1414,48 +1014,15 @@ jobs:
|
||||
TSAN_OPTIONS=halt_on_error=1:allocator_may_return_null=1 FRAMES=60 timeout 300 \
|
||||
./gfx_thumbnail_preview_test fixtures/*.mp4 fixtures/anim_lossless.webp
|
||||
echo "[pass] gfx_thumbnail_preview_test (TSan)"
|
||||
# and threaded under TSan, which is the run that can see a
|
||||
# race between the worker, the pool and the poll
|
||||
THREADS=4 FRAMES=180 PACE_HZ=60 PACE_MAX_STALL_MS=1500 NOAUDIO=1 timeout 300 \
|
||||
./gfx_thumbnail_preview_test_threaded fixtures/bframes_h264.mp4 fixtures/bframes_hevc.mp4
|
||||
echo "[pass] gfx_thumbnail_preview_test_threaded (TSan, 4 threads)"
|
||||
# The pool and the tile-column threads under TSan: the
|
||||
# shared frame buffers and contexts are written in disjoint
|
||||
# columns, the counts merged after the join.
|
||||
TSAN_OPTIONS=halt_on_error=1:allocator_may_return_null=1 timeout 300 bash -c '
|
||||
./preview_blit_bands_test fixtures/long_video.mp4
|
||||
./preview_blit_bands_test fixtures/vp9_tiles.webm
|
||||
./preview_blit_bands_test fixtures/hevc_wpp.mp4
|
||||
./preview_blit_bands_test fixtures/bframes_hevc.mp4
|
||||
./preview_blit_bands_test fixtures/hevc_nowpp.mp4 fixtures/mbaff_h264.mp4
|
||||
RH265_PUBLISH_DELAY=200 ./preview_blit_bands_test fixtures/hevc_wpp.mp4 fixtures/bframes_hevc.mp4 fixtures/hevc_nowpp.mp4'
|
||||
./preview_blit_bands_test fixtures/hevc_wpp.mp4'
|
||||
echo "[pass] preview_blit_bands_test (TSan)"
|
||||
TSAN_OPTIONS=halt_on_error=1:allocator_may_return_null=1 timeout 300 bash -c '
|
||||
./preview_catchup_test_threaded fixtures/bframes_h264.mp4 fixtures/bframes_hevc.mp4'
|
||||
echo "[pass] preview_catchup_test_threaded (TSan)"
|
||||
TSAN_OPTIONS=halt_on_error=1 timeout 600 bash -c '
|
||||
./preview_audio_decode_test fixtures/trailing_large.mp4 1024 island
|
||||
./preview_audio_decode_test fixtures/trailing_large.mp4 262144 island'
|
||||
echo "[pass] preview_audio_decode_test (TSan)"
|
||||
|
||||
- name: Build and run hw_ring_fence_test (ASan + UBSan, TSan)
|
||||
shell: bash
|
||||
working-directory: samples/gfx/hw_ring_fence
|
||||
run: |
|
||||
set -u
|
||||
set -o pipefail
|
||||
# The hardware ring's fence handoff between the video thread
|
||||
# and the core's thread (gfx/video_thread_hw_fence.h), the
|
||||
# tree's own header, with two threads at one slot: a fence is
|
||||
# signalled only while idle, waited only once signalled, and
|
||||
# never inside two calls at once - with no lock. The single
|
||||
# re-signalled fence it replaced is built too and must fail.
|
||||
make clean all SANITIZER=address,undefined
|
||||
timeout 120 make check SANITIZER=address,undefined
|
||||
echo "[pass] hw_ring_fence_test (ASan + UBSan)"
|
||||
# The two threads share nothing but atomics, and TSan is
|
||||
# what says so.
|
||||
make clean all SANITIZER=thread
|
||||
TSAN_OPTIONS=halt_on_error=1 timeout 120 make check SANITIZER=thread
|
||||
echo "[pass] hw_ring_fence_test (TSan)"
|
||||
make clean
|
||||
@@ -23,13 +23,13 @@ jobs:
|
||||
samples-input:
|
||||
name: Build and run samples/input
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 20
|
||||
timeout-minutes: 15
|
||||
|
||||
steps:
|
||||
- name: Install dependencies
|
||||
timeout-minutes: 20
|
||||
timeout-minutes: 5
|
||||
run: |
|
||||
pkgs="build-essential libusb-1.0-0-dev libx11-dev libxtst-dev libxi-dev xvfb gcc-mingw-w64-x86-64 wine64"
|
||||
pkgs="build-essential libusb-1.0-0-dev"
|
||||
if dpkg -s $pkgs >/dev/null 2>&1; then
|
||||
echo "All packages already present on runner image; skipping apt."
|
||||
exit 0
|
||||
@@ -40,23 +40,6 @@ jobs:
|
||||
echo "apt-get update failed (attempt $i); retrying in 10s"
|
||||
sleep 10
|
||||
done
|
||||
# Fetch first, under a bound: a download is safe to stop and
|
||||
# resumes, an install is not. A mirror that stalls is left for
|
||||
# the main archive on the next attempt.
|
||||
for i in 1 2 3; do
|
||||
sudo timeout 240 apt-get install -y --download-only \
|
||||
-o Acquire::Retries=3 -o Acquire::http::Timeout=30 \
|
||||
$pkgs && break
|
||||
[ $i = 3 ] && exit 1
|
||||
echo "package download failed or stalled (attempt $i); retrying from archive.ubuntu.com"
|
||||
for f in /etc/apt/sources.list /etc/apt/sources.list.d/*; do
|
||||
[ -f "$f" ] || continue
|
||||
sudo sed -i -e 's#mirror+file:/etc/apt/apt-mirrors.txt#http://archive.ubuntu.com/ubuntu/#g' \
|
||||
-e 's#http://azure\.archive\.ubuntu\.com/ubuntu#http://archive.ubuntu.com/ubuntu#g' "$f"
|
||||
done
|
||||
sudo timeout 90 apt-get update -y \
|
||||
-o Acquire::Retries=3 -o Acquire::http::Timeout=30 || true
|
||||
done
|
||||
sudo apt-get install -y \
|
||||
-o Acquire::Retries=3 -o Acquire::http::Timeout=30 \
|
||||
$pkgs
|
||||
@@ -82,35 +65,6 @@ jobs:
|
||||
ccache -M 500M
|
||||
ccache -z
|
||||
|
||||
- name: X11 input poll sends no requests (Xvfb, ASan + UBSan, TSan)
|
||||
shell: bash
|
||||
working-directory: samples/input/x11_input_poll
|
||||
run: |
|
||||
set -eu
|
||||
# The X input driver's poll runs every frame. Keys, and with
|
||||
# XInput 2 the pointer, come from events on the driver's own
|
||||
# connection, drained at the poll, so it sends no request on
|
||||
# either connection; a query on the shared one waits behind
|
||||
# whatever the video thread presents through it. XTEST drives
|
||||
# the keyboard (held before init, auto-repeat, focus changes,
|
||||
# another client's keyboard grab) and the pointer (a press
|
||||
# without motion, buttons 1, 3 and 8, the wheel, a drag out of
|
||||
# the window, a new driver over a held button, and grabbed
|
||||
# motion across a warp the server holds back behind a server
|
||||
# grab - where any wait on the server would hang the test).
|
||||
Xvfb :96 -screen 0 1024x768x24 > /tmp/xvfb96.log 2>&1 &
|
||||
XVFB_PID=$!
|
||||
sleep 1
|
||||
make clean all SANITIZER=address,undefined
|
||||
DISPLAY=:96 ASAN_OPTIONS=detect_leaks=1 \
|
||||
UBSAN_OPTIONS=print_stacktrace=1:halt_on_error=1 \
|
||||
timeout 120 ./x11_input_poll_test
|
||||
make clean all SANITIZER=thread
|
||||
DISPLAY=:96 TSAN_OPTIONS=halt_on_error=1 timeout 120 ./x11_input_poll_test
|
||||
make clean
|
||||
kill $XVFB_PID
|
||||
echo "[pass] x11_input_poll_test (ASan, TSan)"
|
||||
|
||||
- name: Build and run illuminance_sensor_test (ASan + UBSan)
|
||||
shell: bash
|
||||
working-directory: samples/input/illuminance_sensor
|
||||
@@ -180,90 +134,3 @@ jobs:
|
||||
ASAN_OPTIONS=detect_leaks=1 UBSAN_OPTIONS=print_stacktrace=1:halt_on_error=1 \
|
||||
timeout 60 ./wii_handshake_test
|
||||
echo "[pass] wii_handshake_test (ASan)"
|
||||
|
||||
- name: Build and run android_lifecycle_flush_test (ASan + UBSan, TSan)
|
||||
shell: bash
|
||||
working-directory: samples/input/android_lifecycle_flush
|
||||
run: |
|
||||
set -eu
|
||||
# The Android background flush handshake, a UI thread against
|
||||
# an app thread. Pins that onPause() and onStop() return only
|
||||
# once SRAM, core options and the config are on disk - a
|
||||
# swiped-away task is killed as soon as they have returned -
|
||||
# that the save is written once per trip to the background,
|
||||
# and that the startup pump acknowledges without saving.
|
||||
make clean all SANITIZER=address,undefined
|
||||
ASAN_OPTIONS=detect_leaks=1 UBSAN_OPTIONS=print_stacktrace=1:halt_on_error=1 \
|
||||
timeout 60 ./android_lifecycle_flush_test
|
||||
make clean all SANITIZER=thread
|
||||
TSAN_OPTIONS=halt_on_error=1 timeout 60 ./android_lifecycle_flush_test
|
||||
make clean
|
||||
echo "[pass] android_lifecycle_flush_test (ASan, TSan)"
|
||||
|
||||
- name: Build and run android_kbd_keys_test (ASan + UBSan)
|
||||
shell: bash
|
||||
working-directory: samples/input/android_kbd_keys
|
||||
run: |
|
||||
set -eu
|
||||
# Key events from the Android system keyboard, through the
|
||||
# native input queue to the text field that owns the menu
|
||||
# line. Pins that a Backspace the soft keyboard sends as a
|
||||
# key event is handed back to the field instead of being
|
||||
# consumed natively - consumed, the field kept the
|
||||
# characters and the next snapshot put them back on the line.
|
||||
make clean all SANITIZER=address,undefined
|
||||
ASAN_OPTIONS=detect_leaks=1 UBSAN_OPTIONS=print_stacktrace=1:halt_on_error=1 \
|
||||
timeout 60 ./android_kbd_keys_test
|
||||
make clean
|
||||
echo "[pass] android_kbd_keys_test (ASan)"
|
||||
|
||||
- name: Build and run overlay_textures_test (plain, ASan + UBSan, threaded)
|
||||
shell: bash
|
||||
working-directory: samples/input/overlay_textures
|
||||
run: |
|
||||
set -eu
|
||||
# input/input_overlay_textures.c as it is, against a stub
|
||||
# driver that reads every pixel it is handed. Pins every
|
||||
# route a page takes to the driver: as textures (each page
|
||||
# gets its own images' textures, a switch uploads nothing,
|
||||
# the pixels go once the driver has them and the sizes
|
||||
# stay), and as pixels through load() for a driver that
|
||||
# declines the textures, has no such path, or fails an
|
||||
# upload - live pixels on every page. 86fbf34e01 freed the
|
||||
# pixels before the page lists were matched by them, so
|
||||
# with threaded video off the fallback showed freed memory;
|
||||
# under ASan that is a use-after-free here.
|
||||
make clean all
|
||||
timeout 60 ./overlay_textures_test
|
||||
make clean all SANITIZER=address,undefined
|
||||
ASAN_OPTIONS=detect_leaks=1 UBSAN_OPTIONS=print_stacktrace=1:halt_on_error=1 \
|
||||
timeout 60 ./overlay_textures_test
|
||||
# THREADS=1: the threaded wrapper as a stub whose video
|
||||
# thread runs when the test says so, so both outcomes of
|
||||
# the race between the uploads and the poll are lanes. The
|
||||
# quick thread is 86fbf34e01's bug again under threaded
|
||||
# video; the slow one must find the pack's pixels alive.
|
||||
make clean all THREADS=1 SANITIZER=address,undefined
|
||||
ASAN_OPTIONS=detect_leaks=1 UBSAN_OPTIONS=print_stacktrace=1:halt_on_error=1 \
|
||||
timeout 60 ./overlay_textures_test
|
||||
make clean
|
||||
echo "[pass] overlay_textures_test (plain, ASan, threaded ASan)"
|
||||
|
||||
- name: Build and run winraw_abs_pos_test (mingw + Wine)
|
||||
shell: bash
|
||||
working-directory: samples/input/winraw_abs_pos
|
||||
run: |
|
||||
set -eu
|
||||
# input/drivers/winraw_input.c, cross-built with mingw-w64 and
|
||||
# run under Wine: an absolute pointer report's position is
|
||||
# kept as one word, and every read must give back a position
|
||||
# a report carried, including a report that lands during poll.
|
||||
# mingw-w64 and Wine come from "Install dependencies", whose
|
||||
# download is bounded and retried; an unbounded install here
|
||||
# stalled silently until the job timeout. Stop wineserver on
|
||||
# exit so it cannot hold the step's output open.
|
||||
trap 'wineserver -k >/dev/null 2>&1 || true' EXIT
|
||||
make clean all
|
||||
WINEDEBUG=-all timeout 300 make check
|
||||
make clean
|
||||
echo "[pass] winraw_abs_pos_test (wine)"
|
||||
@@ -27,9 +27,9 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Install dependencies
|
||||
timeout-minutes: 20
|
||||
timeout-minutes: 5
|
||||
run: |
|
||||
pkgs="build-essential libmbedtls-dev libbearssl-dev"
|
||||
pkgs="build-essential libmbedtls-dev"
|
||||
if dpkg -s $pkgs >/dev/null 2>&1; then
|
||||
echo "All packages already present on runner image; skipping apt."
|
||||
exit 0
|
||||
@@ -40,23 +40,6 @@ jobs:
|
||||
echo "apt-get update failed (attempt $i); retrying in 10s"
|
||||
sleep 10
|
||||
done
|
||||
# Fetch first, under a bound: a download is safe to stop and
|
||||
# resumes, an install is not. A mirror that stalls is left for
|
||||
# the main archive on the next attempt.
|
||||
for i in 1 2 3; do
|
||||
sudo timeout 240 apt-get install -y --download-only \
|
||||
-o Acquire::Retries=3 -o Acquire::http::Timeout=30 \
|
||||
$pkgs && break
|
||||
[ $i = 3 ] && exit 1
|
||||
echo "package download failed or stalled (attempt $i); retrying from archive.ubuntu.com"
|
||||
for f in /etc/apt/sources.list /etc/apt/sources.list.d/*; do
|
||||
[ -f "$f" ] || continue
|
||||
sudo sed -i -e 's#mirror+file:/etc/apt/apt-mirrors.txt#http://archive.ubuntu.com/ubuntu/#g' \
|
||||
-e 's#http://azure\.archive\.ubuntu\.com/ubuntu#http://archive.ubuntu.com/ubuntu#g' "$f"
|
||||
done
|
||||
sudo timeout 90 apt-get update -y \
|
||||
-o Acquire::Retries=3 -o Acquire::http::Timeout=30 || true
|
||||
done
|
||||
sudo apt-get install -y \
|
||||
-o Acquire::Retries=3 -o Acquire::http::Timeout=30 \
|
||||
$pkgs
|
||||
@@ -109,136 +92,6 @@ jobs:
|
||||
TSAN_OPTIONS=halt_on_error=1 timeout 120 ./webdav_options_test
|
||||
echo "[pass] webdav_options_test (TSan)"
|
||||
|
||||
- name: Build and run webdav_reauth_test (ASan + UBSan, TSan)
|
||||
shell: bash
|
||||
working-directory: samples/network/webdav_reauth
|
||||
run: |
|
||||
set -eu
|
||||
# Includes network/cloud_sync/webdav.c and plays the server
|
||||
# for every request type the driver sends (OPTIONS, GET, PUT,
|
||||
# DELETE, MOVE, MKCOL). A server that keeps answering 401 with
|
||||
# a Digest challenge - a wrong password - must get one retry
|
||||
# and then a reported failure; the driver used to retry every
|
||||
# challenge, so a mistyped password looped the first request
|
||||
# of a sync forever. A single stale-nonce challenge must still
|
||||
# be retried and succeed. A retried PUT must send the file:
|
||||
# it used to re-read it from the end the first attempt left
|
||||
# it at and upload uninitialised memory in place of the save.
|
||||
# A malformed challenge (a value with no closing quote, a
|
||||
# trailing unquoted parameter) must be refused, not crash the
|
||||
# parser or send it reading past the end of the line.
|
||||
make clean all SANITIZER=address,undefined
|
||||
test -x webdav_reauth_test
|
||||
ASAN_OPTIONS=detect_leaks=1:detect_stack_use_after_return=1 \
|
||||
UBSAN_OPTIONS=print_stacktrace=1 timeout 60 \
|
||||
./webdav_reauth_test
|
||||
echo "[pass] webdav_reauth_test (ASan)"
|
||||
make clean all SANITIZER=thread
|
||||
TSAN_OPTIONS=halt_on_error=1 timeout 120 ./webdav_reauth_test
|
||||
echo "[pass] webdav_reauth_test (TSan)"
|
||||
|
||||
- name: Build and run webdav_backup_test (ASan + UBSan, TSan)
|
||||
shell: bash
|
||||
working-directory: samples/network/webdav_backup
|
||||
run: |
|
||||
set -eu
|
||||
# Includes network/cloud_sync/webdav.c and plays the server,
|
||||
# request by request. With destructive sync off, an upload
|
||||
# must COPY the server's file to deleted/<path>-<time> before
|
||||
# the PUT replaces it - it used to overwrite it in place, so
|
||||
# the setting protected against deletes but not overwrites.
|
||||
# Covers the order (backup first, into an existing deleted/
|
||||
# collection, named as a delete names it), 404 as "nothing to
|
||||
# back up", a failed backup stopping the upload, destructive
|
||||
# sync and the server manifest skipping the backup, and COPY
|
||||
# and PUT each getting their own Digest retry.
|
||||
make clean all SANITIZER=address,undefined
|
||||
test -x webdav_backup_test
|
||||
ASAN_OPTIONS=detect_leaks=1:detect_stack_use_after_return=1 \
|
||||
UBSAN_OPTIONS=print_stacktrace=1 timeout 60 \
|
||||
./webdav_backup_test
|
||||
echo "[pass] webdav_backup_test (ASan)"
|
||||
make clean all SANITIZER=thread
|
||||
TSAN_OPTIONS=halt_on_error=1 timeout 120 ./webdav_backup_test
|
||||
echo "[pass] webdav_backup_test (TSan)"
|
||||
|
||||
- name: Build and run s3_backup_test (ASan + UBSan, TSan)
|
||||
shell: bash
|
||||
working-directory: samples/network/s3_backup
|
||||
run: |
|
||||
set -eu
|
||||
# Includes network/cloud_sync/s3.c and plays the server,
|
||||
# request by request. The S3 driver never read the
|
||||
# destructive sync setting: deletes removed objects for good
|
||||
# and uploads overwrote them. With it off, both must first
|
||||
# CopyObject the object to deleted/<path>-<time>. Covers the
|
||||
# copy source for virtual-hosted and path-style endpoints,
|
||||
# x-amz-copy-source being signed, 404 as "nothing to keep", a
|
||||
# failed copy (including a 200 with an <Error> body) stopping
|
||||
# the delete or upload, and destructive sync and the server
|
||||
# manifest skipping the copy.
|
||||
make clean all SANITIZER=address,undefined
|
||||
test -x s3_backup_test
|
||||
ASAN_OPTIONS=detect_leaks=1:detect_stack_use_after_return=1 \
|
||||
UBSAN_OPTIONS=print_stacktrace=1 timeout 60 \
|
||||
./s3_backup_test
|
||||
echo "[pass] s3_backup_test (ASan)"
|
||||
make clean all SANITIZER=thread
|
||||
TSAN_OPTIONS=halt_on_error=1 timeout 120 ./s3_backup_test
|
||||
echo "[pass] s3_backup_test (TSan)"
|
||||
|
||||
- name: Build and run smb_backup_test (ASan + UBSan, TSan)
|
||||
shell: bash
|
||||
working-directory: samples/network/smb_backup
|
||||
run: |
|
||||
set -eu
|
||||
# Includes network/cloud_sync/smb.c and runs it against an
|
||||
# in-memory libsmb2 share (fake_smb.c), checking what the
|
||||
# share holds after each upload. The driver used to truncate
|
||||
# the server file and then write, so a write failing part way
|
||||
# left a truncated save; and with destructive sync off it
|
||||
# overwrote the file without keeping the old copy a delete
|
||||
# keeps. Uploads now go through <path>.rauploading and the
|
||||
# replaced file is kept as <path>-<time> (non-destructive).
|
||||
# Covers a failed write, backup and final rename each leaving
|
||||
# the old file intact with no temporary file left, no backup
|
||||
# for destructive sync, a new file or the manifest, an empty
|
||||
# upload and the configured subdirectory.
|
||||
make clean all SANITIZER=address,undefined
|
||||
test -x smb_backup_test
|
||||
ASAN_OPTIONS=detect_leaks=1:detect_stack_use_after_return=1 \
|
||||
UBSAN_OPTIONS=print_stacktrace=1 timeout 60 \
|
||||
./smb_backup_test
|
||||
echo "[pass] smb_backup_test (ASan)"
|
||||
make clean all SANITIZER=thread
|
||||
TSAN_OPTIONS=halt_on_error=1 timeout 120 ./smb_backup_test
|
||||
echo "[pass] smb_backup_test (TSan)"
|
||||
|
||||
- name: Build and run gdrive_backup_test (ASan + UBSan, TSan)
|
||||
shell: bash
|
||||
working-directory: samples/network/gdrive_backup
|
||||
run: |
|
||||
set -eu
|
||||
# Includes network/cloud_sync/google_drive.c and plays the
|
||||
# Drive API, request by request. With destructive sync off, a
|
||||
# delete kept the old file (renamed <name>-<time>) but an
|
||||
# upload replaced its content outright. An upload to an
|
||||
# existing file must now files.copy it to that name in the
|
||||
# same folder before the PATCH. Covers the order and the copy
|
||||
# request, a failed copy stopping the upload, a 401 on the
|
||||
# copy and on the PATCH each refreshing the token once (the
|
||||
# PATCH retry repeating only the PATCH, not the backup), and
|
||||
# destructive sync, a new file and the manifest skipping it.
|
||||
make clean all SANITIZER=address,undefined
|
||||
test -x gdrive_backup_test
|
||||
ASAN_OPTIONS=detect_leaks=1:detect_stack_use_after_return=1 \
|
||||
UBSAN_OPTIONS=print_stacktrace=1 timeout 60 \
|
||||
./gdrive_backup_test
|
||||
echo "[pass] gdrive_backup_test (ASan)"
|
||||
make clean all SANITIZER=thread
|
||||
TSAN_OPTIONS=halt_on_error=1 timeout 120 ./gdrive_backup_test
|
||||
echo "[pass] gdrive_backup_test (TSan)"
|
||||
|
||||
- name: Build and run cheevos_login_test (bear ASan+UBSan, bear TSan, mbed ASan+UBSan)
|
||||
shell: bash
|
||||
working-directory: samples/network/cheevos_login
|
||||
@@ -253,12 +106,7 @@ jobs:
|
||||
# from a realistic messy CA bundle (BearSSL), keep-alive pool
|
||||
# reuse, one-shot replay of a request on a pooled connection
|
||||
# the server closed while idle, and no replay once response
|
||||
# bytes have been seen. Each TLS backend's non-blocking send
|
||||
# must push a 12 MiB body past a server that stops reading for
|
||||
# a second without ever blocking, and deliver every byte; and
|
||||
# net_http must send the same upload a slice per update, plain
|
||||
# and over TLS - it used to go out in one blocking send, a call
|
||||
# of a second or more (spinning at 100% CPU on a plain socket).
|
||||
# bytes have been seen.
|
||||
make clean all SANITIZER=address,undefined
|
||||
test -x cheevos_login_test
|
||||
ASAN_OPTIONS=detect_leaks=1:detect_stack_use_after_return=1 \
|
||||
@@ -273,87 +121,3 @@ jobs:
|
||||
UBSAN_OPTIONS=print_stacktrace=1:halt_on_error=1 timeout 120 \
|
||||
make check SSL_BACKEND=mbed
|
||||
echo "[pass] cheevos_login_test (mbedTLS, ASan)"
|
||||
# The built-in client, RetroArch's default backend.
|
||||
make clean all SSL_BACKEND=retro SANITIZER=address,undefined
|
||||
ASAN_OPTIONS=detect_leaks=1:detect_stack_use_after_return=1 \
|
||||
UBSAN_OPTIONS=print_stacktrace=1:halt_on_error=1 timeout 120 \
|
||||
make check SSL_BACKEND=retro
|
||||
echo "[pass] cheevos_login_test (built-in TLS, ASan)"
|
||||
# The runner's libmbedtls-dev is 2.28, which has no TLS 1.3,
|
||||
# so the leg above cannot see either TLS 1.3 failure. Mbed
|
||||
# TLS 3.6.0 is the one release that shows both: it fails
|
||||
# every handshake unless psa_crypto_init() was called, and it
|
||||
# reports session tickets from mbedtls_ssl_read(). Library
|
||||
# only, ~20 s to build. A download that still fails after
|
||||
# the retries skips the leg rather than failing the job.
|
||||
mbed_ver=3.6.0
|
||||
mbed_sha=3ecf94fcfdaacafb757786a01b7538a61750ebd85c4b024f56ff8ba1490fcd38
|
||||
mbed_tar="$RUNNER_TEMP/mbedtls-$mbed_ver.tar.bz2"
|
||||
if curl -fsSL --retry 3 --retry-delay 5 --max-time 120 -o "$mbed_tar" \
|
||||
"https://github.com/Mbed-TLS/mbedtls/releases/download/v$mbed_ver/mbedtls-$mbed_ver.tar.bz2"; then
|
||||
echo "$mbed_sha $mbed_tar" | sha256sum -c -
|
||||
tar -xjf "$mbed_tar" -C "$RUNNER_TEMP"
|
||||
make -C "$RUNNER_TEMP/mbedtls-$mbed_ver" -j"$(nproc)" lib >/dev/null
|
||||
make clean all SSL_BACKEND=mbed SANITIZER=address,undefined \
|
||||
MBEDTLS_DIR="$RUNNER_TEMP/mbedtls-$mbed_ver"
|
||||
ASAN_OPTIONS=detect_leaks=1:detect_stack_use_after_return=1 \
|
||||
UBSAN_OPTIONS=print_stacktrace=1:halt_on_error=1 timeout 120 \
|
||||
make check SSL_BACKEND=mbed
|
||||
echo "[pass] cheevos_login_test (mbedTLS $mbed_ver TLS 1.3, ASan)"
|
||||
else
|
||||
echo "::warning::Mbed TLS $mbed_ver download failed; TLS 1.3 leg skipped"
|
||||
fi
|
||||
|
||||
- name: Build and run net_http_semantics_test (ASan + UBSan, TSan)
|
||||
shell: bash
|
||||
working-directory: samples/network/net_http_semantics
|
||||
run: |
|
||||
set -eu
|
||||
# HTTP/1.1 semantics of libretro-common/net/net_http.c against
|
||||
# an in-process scripted server on 127.0.0.1, which logs which
|
||||
# connection every request arrived on. Covers GET 200 framing
|
||||
# and pool reuse, and responses without a body: HEAD, 204 and
|
||||
# 304 on keep-alive used to sit waiting for a close that never
|
||||
# came, a 1xx other than 100 was taken as the final status,
|
||||
# and a 101 or stray bytes after a bodyless response left the
|
||||
# socket in the pool. Connection and Transfer-Encoding are
|
||||
# read as lists ("Keep-Alive, Close", "gzip, chunked"),
|
||||
# HTTP/1.0 without keep-alive is not pooled, and a length
|
||||
# after "chunked" does not reframe the body. Redirects: an
|
||||
# absolute-path Location used to go out as "GET //foo", a
|
||||
# 302 loop was followed forever (now 8 hops), a streamed PUT
|
||||
# redirected by a 307 is rewound and resent whole, and a 3xx
|
||||
# body no longer lands in the caller's sink. Bounds: a header
|
||||
# block (1xx blocks included) over 64 KiB and a chunk size
|
||||
# with no hex digits fail instead of growing the buffer or
|
||||
# passing as the last chunk, a Content-Length buffer never
|
||||
# grows past the body, and a pooled connection that dies
|
||||
# after the status line is not replayed. A URL shorter than
|
||||
# its scheme is rejected without an overread. Unit matrix for
|
||||
# net_http_url_join() and net_http_urldecode(), including
|
||||
# buffer bounds and in-place decoding; redirects resolved
|
||||
# through it ("../", "?q", "//host", fragments dropped,
|
||||
# %20 left encoded). The response header block: lookup and
|
||||
# iteration, the legacy string_list accessor, and 64 header
|
||||
# lines costing no per-line allocations. A Content-Length or
|
||||
# "chunked" on a 1xx block does not frame the final response.
|
||||
# A followed 3xx's body is dropped as it arrives, not
|
||||
# buffered to its Content-Length, and bytes past a body's
|
||||
# Content-Length end the connection, not the transfer.
|
||||
# Every case has a
|
||||
# deadline, so a hang fails the case instead of the job.
|
||||
# net_http is lock-free by design (DNS cache and pool owned by
|
||||
# the driving thread, resolvers publish with an atomic store);
|
||||
# keep it that way.
|
||||
if grep -nE '\b(slock|scond)_[a-z_]+\(' ../../../libretro-common/net/net_http.c; then
|
||||
echo "::error title=net_http lock::net_http.c must not take locks"
|
||||
exit 1
|
||||
fi
|
||||
make clean all SANITIZER=address,undefined
|
||||
ASAN_OPTIONS=detect_leaks=1:detect_stack_use_after_return=1 \
|
||||
UBSAN_OPTIONS=print_stacktrace=1:halt_on_error=1 timeout 120 \
|
||||
./net_http_semantics_test
|
||||
echo "[pass] net_http_semantics_test (ASan)"
|
||||
make clean all SANITIZER=thread
|
||||
TSAN_OPTIONS=halt_on_error=1 timeout 300 ./net_http_semantics_test
|
||||
echo "[pass] net_http_semantics_test (TSan)"
|
||||
@@ -41,7 +41,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Install dependencies
|
||||
timeout-minutes: 20
|
||||
timeout-minutes: 5
|
||||
run: |
|
||||
pkgs="build-essential pkg-config libavcodec-dev libavformat-dev libavutil-dev libswscale-dev libswresample-dev"
|
||||
if dpkg -s $pkgs >/dev/null 2>&1; then
|
||||
@@ -54,23 +54,6 @@ jobs:
|
||||
echo "apt-get update failed (attempt $i); retrying in 10s"
|
||||
sleep 10
|
||||
done
|
||||
# Fetch first, under a bound: a download is safe to stop and
|
||||
# resumes, an install is not. A mirror that stalls is left for
|
||||
# the main archive on the next attempt.
|
||||
for i in 1 2 3; do
|
||||
sudo timeout 240 apt-get install -y --download-only \
|
||||
-o Acquire::Retries=3 -o Acquire::http::Timeout=30 \
|
||||
$pkgs && break
|
||||
[ $i = 3 ] && exit 1
|
||||
echo "package download failed or stalled (attempt $i); retrying from archive.ubuntu.com"
|
||||
for f in /etc/apt/sources.list /etc/apt/sources.list.d/*; do
|
||||
[ -f "$f" ] || continue
|
||||
sudo sed -i -e 's#mirror+file:/etc/apt/apt-mirrors.txt#http://archive.ubuntu.com/ubuntu/#g' \
|
||||
-e 's#http://azure\.archive\.ubuntu\.com/ubuntu#http://archive.ubuntu.com/ubuntu#g' "$f"
|
||||
done
|
||||
sudo timeout 90 apt-get update -y \
|
||||
-o Acquire::Retries=3 -o Acquire::http::Timeout=30 || true
|
||||
done
|
||||
sudo apt-get install -y \
|
||||
-o Acquire::Retries=3 -o Acquire::http::Timeout=30 \
|
||||
$pkgs
|
||||
|
||||
@@ -27,7 +27,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Install dependencies
|
||||
timeout-minutes: 20
|
||||
timeout-minutes: 5
|
||||
run: |
|
||||
pkgs="build-essential"
|
||||
if dpkg -s $pkgs >/dev/null 2>&1; then
|
||||
@@ -40,23 +40,6 @@ jobs:
|
||||
echo "apt-get update failed (attempt $i); retrying in 10s"
|
||||
sleep 10
|
||||
done
|
||||
# Fetch first, under a bound: a download is safe to stop and
|
||||
# resumes, an install is not. A mirror that stalls is left for
|
||||
# the main archive on the next attempt.
|
||||
for i in 1 2 3; do
|
||||
sudo timeout 240 apt-get install -y --download-only \
|
||||
-o Acquire::Retries=3 -o Acquire::http::Timeout=30 \
|
||||
$pkgs && break
|
||||
[ $i = 3 ] && exit 1
|
||||
echo "package download failed or stalled (attempt $i); retrying from archive.ubuntu.com"
|
||||
for f in /etc/apt/sources.list /etc/apt/sources.list.d/*; do
|
||||
[ -f "$f" ] || continue
|
||||
sudo sed -i -e 's#mirror+file:/etc/apt/apt-mirrors.txt#http://archive.ubuntu.com/ubuntu/#g' \
|
||||
-e 's#http://azure\.archive\.ubuntu\.com/ubuntu#http://archive.ubuntu.com/ubuntu#g' "$f"
|
||||
done
|
||||
sudo timeout 90 apt-get update -y \
|
||||
-o Acquire::Retries=3 -o Acquire::http::Timeout=30 || true
|
||||
done
|
||||
sudo apt-get install -y \
|
||||
-o Acquire::Retries=3 -o Acquire::http::Timeout=30 \
|
||||
$pkgs
|
||||
|
||||
@@ -27,7 +27,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Install dependencies
|
||||
timeout-minutes: 20
|
||||
timeout-minutes: 5
|
||||
run: |
|
||||
pkgs="build-essential zlib1g-dev"
|
||||
if dpkg -s $pkgs >/dev/null 2>&1; then
|
||||
@@ -40,23 +40,6 @@ jobs:
|
||||
echo "apt-get update failed (attempt $i); retrying in 10s"
|
||||
sleep 10
|
||||
done
|
||||
# Fetch first, under a bound: a download is safe to stop and
|
||||
# resumes, an install is not. A mirror that stalls is left for
|
||||
# the main archive on the next attempt.
|
||||
for i in 1 2 3; do
|
||||
sudo timeout 240 apt-get install -y --download-only \
|
||||
-o Acquire::Retries=3 -o Acquire::http::Timeout=30 \
|
||||
$pkgs && break
|
||||
[ $i = 3 ] && exit 1
|
||||
echo "package download failed or stalled (attempt $i); retrying from archive.ubuntu.com"
|
||||
for f in /etc/apt/sources.list /etc/apt/sources.list.d/*; do
|
||||
[ -f "$f" ] || continue
|
||||
sudo sed -i -e 's#mirror+file:/etc/apt/apt-mirrors.txt#http://archive.ubuntu.com/ubuntu/#g' \
|
||||
-e 's#http://azure\.archive\.ubuntu\.com/ubuntu#http://archive.ubuntu.com/ubuntu#g' "$f"
|
||||
done
|
||||
sudo timeout 90 apt-get update -y \
|
||||
-o Acquire::Retries=3 -o Acquire::http::Timeout=30 || true
|
||||
done
|
||||
sudo apt-get install -y \
|
||||
-o Acquire::Retries=3 -o Acquire::http::Timeout=30 \
|
||||
$pkgs
|
||||
@@ -134,38 +117,6 @@ jobs:
|
||||
timeout 60 ./archive_name_safety_test
|
||||
echo "[pass] archive_name_safety_test"
|
||||
|
||||
- name: Build and run cloudsync_path_safety_test
|
||||
shell: bash
|
||||
working-directory: samples/tasks/cloudsync
|
||||
run: |
|
||||
set -eu
|
||||
# Regression test for the manifest-key check in
|
||||
# tasks/task_cloudsync.c::task_cloud_sync_fetch_server_file.
|
||||
# A hostile sync server's manifest key could escape the
|
||||
# cloud-sync directory via ".." or an absolute component, or
|
||||
# crash the fetch with a key that has no '/'. The test
|
||||
# #includes the real predicate (tasks/task_cloudsync_path.c).
|
||||
make clean all
|
||||
test -x cloudsync_path_safety_test
|
||||
timeout 60 ./cloudsync_path_safety_test
|
||||
echo "[pass] cloudsync_path_safety_test"
|
||||
|
||||
- name: Build and run natt_desc_parse_test
|
||||
shell: bash
|
||||
working-directory: samples/tasks/natt_desc
|
||||
run: |
|
||||
set -eu
|
||||
# Regression test for the UPnP IGD description parser
|
||||
# (network/natt_desc.c::natt_parse_desc_node). A router's
|
||||
# description XML is untrusted; the old parser crashed on a
|
||||
# leaf or empty root node and never reached a nested
|
||||
# <service>. The test #includes the real parser and feeds it
|
||||
# hand-built hostile trees.
|
||||
make clean all
|
||||
test -x natt_desc_parse_test
|
||||
timeout 60 ./natt_desc_parse_test
|
||||
echo "[pass] natt_desc_parse_test"
|
||||
|
||||
- name: Build and run http_method_match_test (ASan)
|
||||
shell: bash
|
||||
working-directory: samples/tasks/http
|
||||
@@ -279,32 +230,6 @@ jobs:
|
||||
timeout 60 ./bsv_replay_bounds_test
|
||||
echo "[pass] bsv_replay_bounds_test"
|
||||
|
||||
- name: Build and run bsv_replay_init_test (plain, ASan + UBSan)
|
||||
shell: bash
|
||||
working-directory: samples/tasks/bsv_replay_init
|
||||
run: |
|
||||
set -eu
|
||||
# Regression oracle for replay start-up and shutdown in
|
||||
# tasks/task_movie.c and input/bsv/bsvmovie.c (#19622).
|
||||
# Links the shipping units against a stub core and the real
|
||||
# task queue / intfstream / VFS, in the iOS configuration
|
||||
# (RZSTD on, STATESTREAM off). Pre-fix a replay truncated
|
||||
# inside its first checkpoint, or halted before its first
|
||||
# frame, left BSV_FLAG_MOVIE_END set with no handle: the
|
||||
# runloop then paused after every frame, for every content,
|
||||
# until the process was restarted. Also covers halting a
|
||||
# recording before its first frame leaving the session stuck
|
||||
# "recording". If runloop.c changes its per-frame replay
|
||||
# protocol, frame() in the test must follow.
|
||||
make clean all
|
||||
test -x bsv_replay_init_test
|
||||
timeout 120 ./bsv_replay_init_test
|
||||
echo "[pass] bsv_replay_init_test"
|
||||
make clean all SANITIZER=address,undefined
|
||||
ASAN_OPTIONS=detect_leaks=1 UBSAN_OPTIONS=print_stacktrace=1 \
|
||||
timeout 120 ./bsv_replay_init_test
|
||||
echo "[pass] bsv_replay_init_test (ASan + UBSan)"
|
||||
|
||||
- name: Build and run bps_patch_bounds_test (ASan)
|
||||
shell: bash
|
||||
working-directory: samples/tasks/bps_patch
|
||||
@@ -802,14 +727,16 @@ jobs:
|
||||
working-directory: samples/tasks/overlay
|
||||
run: |
|
||||
set -eu
|
||||
# Builds the shipping input/input_overlay_alpha.c and
|
||||
# led/drivers/led_overlay.c against a stub driver. Which images
|
||||
# the overlay LED driver hides: a pack's _led images, else the
|
||||
# image at a ledN_map slot whatever its desc does, and never an
|
||||
# index off the page - led_map comes from the config with no
|
||||
# range of its own. And the per-poll alpha pass: an image is set only when
|
||||
# its alpha changes, a page load sets them all again, and an
|
||||
# LED going out and back on between polls is shown again.
|
||||
# The overlay visibility index does not come from the overlay:
|
||||
# led/drivers/led_overlay.c forwards settings->uints.led_map[],
|
||||
# which configuration.c reads from the config file with no
|
||||
# range of its own. Unbounded, "led1_map = 500" wrote past
|
||||
# overlay_visibility[MAX_VISIBILITY] during drivers_init() and
|
||||
# handed the same index to the video driver's set_alpha().
|
||||
# This builds the shipping LED driver to pin that it really
|
||||
# does forward config values unaltered, and checks the bound
|
||||
# covers the array exactly. If input_driver.c changes that
|
||||
# bound, the verbatim copy in the test must follow.
|
||||
make -f Makefile.visibility clean >/dev/null
|
||||
make -f Makefile.visibility all SANITIZER=address,undefined -j$(nproc)
|
||||
ASAN_OPTIONS=detect_leaks=1 \
|
||||
@@ -1029,39 +956,22 @@ jobs:
|
||||
# by cancellation plus a stale-completion generation guard
|
||||
# (menu_explore_cancel_init_task / menu_dbinfo_cancel_task).
|
||||
#
|
||||
# Scope is mechanical: menu/, the task translation units the
|
||||
# menu drives directly, and the content load - a chain of
|
||||
# main-thread tasks that closes content from the frame loop.
|
||||
# The remaining wait sites (NAT traversal, the CLI database
|
||||
# scan, the exit path) are tracked separately; widening this
|
||||
# guard is what should happen as each of those is converted.
|
||||
hits=$(grep -rn 'task_queue_wait' menu/ network/ command.c \
|
||||
# Scope is deliberately narrow and mechanical: menu/ and the
|
||||
# two task translation units the menu drives directly. The
|
||||
# remaining wait sites elsewhere (core deinit save/load, NAT
|
||||
# traversal, netplay MITM) are tracked separately and are
|
||||
# NOT covered here - widening this guard is what should
|
||||
# happen as each of those is converted.
|
||||
hits=$(grep -rn 'task_queue_wait' menu/ \
|
||||
tasks/task_menu_explore.c \
|
||||
tasks/task_database_info.c \
|
||||
tasks/task_netplay_nat_traversal.c \
|
||||
tasks/task_content.c || true)
|
||||
tasks/task_netplay_nat_traversal.c || true)
|
||||
if [[ -n "$hits" ]]; then
|
||||
printf '%s\n' "$hits"
|
||||
printf '::error title=Blocking wait on a menu path::task_queue_wait() must not be reachable from the menu. Use cancellation with a stale-completion guard, or a deferred continuation.\n'
|
||||
exit 1
|
||||
fi
|
||||
# The close's blocking helpers wait inside task_save.c, where
|
||||
# the grep above cannot see them: the menu and the tasks must
|
||||
# not call them. The load closes through
|
||||
# retroarch_main_deinit_begin/pending/finish instead.
|
||||
hits=$(grep -rn 'content_wait_for_\(save\|load\)_state_task' \
|
||||
menu/ tasks/ --include='*.c' \
|
||||
| grep -v '^tasks/task_save.c:' || true)
|
||||
if [[ -n "$hits" ]]; then
|
||||
printf '%s\n' "$hits"
|
||||
printf '::error title=Blocking close on a menu path::content_wait_for_*_state_task() blocks the frame loop. Close through retroarch_main_deinit_begin/pending/finish from a main-thread task.\n'
|
||||
exit 1
|
||||
fi
|
||||
grep -q 'retroarch_main_deinit_pending' tasks/task_content.c || {
|
||||
printf '::error title=Blocking close on a menu path::the content load no longer waits for state tasks from the frame loop.\n'
|
||||
exit 1
|
||||
}
|
||||
printf 'No task_queue_wait or blocking close on the guarded menu paths.\n'
|
||||
printf 'No task_queue_wait on the guarded menu paths.\n'
|
||||
|
||||
- name: Build and run playlist_parity_test (plain, ASan + UBSan)
|
||||
shell: bash
|
||||
@@ -1322,7 +1232,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Install dependencies
|
||||
timeout-minutes: 20
|
||||
timeout-minutes: 5
|
||||
run: |
|
||||
pkgs="build-essential zlib1g-dev"
|
||||
if dpkg -s $pkgs >/dev/null 2>&1; then
|
||||
@@ -1335,23 +1245,6 @@ jobs:
|
||||
echo "apt-get update failed (attempt $i); retrying in 10s"
|
||||
sleep 10
|
||||
done
|
||||
# Fetch first, under a bound: a download is safe to stop and
|
||||
# resumes, an install is not. A mirror that stalls is left for
|
||||
# the main archive on the next attempt.
|
||||
for i in 1 2 3; do
|
||||
sudo timeout 240 apt-get install -y --download-only \
|
||||
-o Acquire::Retries=3 -o Acquire::http::Timeout=30 \
|
||||
$pkgs && break
|
||||
[ $i = 3 ] && exit 1
|
||||
echo "package download failed or stalled (attempt $i); retrying from archive.ubuntu.com"
|
||||
for f in /etc/apt/sources.list /etc/apt/sources.list.d/*; do
|
||||
[ -f "$f" ] || continue
|
||||
sudo sed -i -e 's#mirror+file:/etc/apt/apt-mirrors.txt#http://archive.ubuntu.com/ubuntu/#g' \
|
||||
-e 's#http://azure\.archive\.ubuntu\.com/ubuntu#http://archive.ubuntu.com/ubuntu#g' "$f"
|
||||
done
|
||||
sudo timeout 90 apt-get update -y \
|
||||
-o Acquire::Retries=3 -o Acquire::http::Timeout=30 || true
|
||||
done
|
||||
sudo apt-get install -y \
|
||||
-o Acquire::Retries=3 -o Acquire::http::Timeout=30 \
|
||||
$pkgs
|
||||
@@ -1471,9 +1364,8 @@ jobs:
|
||||
# sits between two representations: RetroArch's raw
|
||||
# CRLF-delimited blob and the NULL-terminated key/value array
|
||||
# emscripten_fetch wants, plus the response blob that has to
|
||||
# become the same "Name: Value" header block net_http.c
|
||||
# produces - through net_http_headers_compact(), which the test
|
||||
# links rather than copies. Neither conversion existed while the backend was
|
||||
# become the same "Name: Value" string_list net_http.c
|
||||
# produces. Neither conversion existed while the backend was
|
||||
# on emscripten_async_wget2_data, which has no header support
|
||||
# at all - the `headers` argument was accepted and dropped on
|
||||
# every entry point, so cloud sync could not authenticate.
|
||||
@@ -1648,78 +1540,6 @@ jobs:
|
||||
make -f Makefile.transport_fail clean
|
||||
echo "[pass] http_transport_failure_test (TSan)"
|
||||
|
||||
- name: Build and run http_resume_test (ASan + UBSan, TSan)
|
||||
shell: bash
|
||||
working-directory: samples/tasks/http
|
||||
run: |
|
||||
set -eu
|
||||
# An interrupted download to a file (task_push_http_download_file,
|
||||
# as the core updater and thumbnails use) resumes with
|
||||
# "Range: bytes=N-" and If-Range on the first response's strong
|
||||
# ETag. A 206 from the right offset continues the file; a 200
|
||||
# (resource changed) starts it over; a 206 from the wrong offset,
|
||||
# a first response without Accept-Ranges or a validator, and a
|
||||
# connection that keeps dropping (more than three resumes) fail
|
||||
# and remove the partial file. Every interrupted download used
|
||||
# to fail outright.
|
||||
make -f Makefile.resume clean all SANITIZER=address,undefined
|
||||
ASAN_OPTIONS=detect_leaks=1:detect_stack_use_after_return=1 \
|
||||
UBSAN_OPTIONS=print_stacktrace=1:halt_on_error=1 timeout 300 \
|
||||
./http_resume_test
|
||||
echo "[pass] http_resume_test (ASan)"
|
||||
make -f Makefile.resume clean all SANITIZER=thread
|
||||
TSAN_OPTIONS=halt_on_error=1 timeout 300 ./http_resume_test
|
||||
make -f Makefile.resume clean
|
||||
echo "[pass] http_resume_test (TSan)"
|
||||
|
||||
- name: Build and run HTTP initialization failure test
|
||||
shell: bash
|
||||
working-directory: samples/tasks/http
|
||||
run: |
|
||||
set -eu
|
||||
for threads in 0 1; do
|
||||
make -f Makefile.init_failure clean
|
||||
make -f Makefile.init_failure THREADS=$threads
|
||||
timeout 60 ./http_init_failure_test
|
||||
done
|
||||
make -f Makefile.init_failure clean
|
||||
|
||||
- name: Build and run http_limits_test (ASan + UBSan, threaded and not)
|
||||
shell: bash
|
||||
working-directory: samples/tasks/http
|
||||
run: |
|
||||
set -eu
|
||||
# net_http.c included whole, with the clock and the socket
|
||||
# calls stubbed. The DNS cache is capped (it grew by one entry
|
||||
# per distinct host:port for the whole session), evicts least
|
||||
# recently used first, and never evicts a lookup still in
|
||||
# flight. The pool keeps at most 8 idle connections, closing
|
||||
# the longest-idle first, and closes rather than reuses one
|
||||
# idle for more than 30 s. On a system without IPv6, an IPv6
|
||||
# address listed first no longer fails the transfer, and one
|
||||
# mid-list no longer hides the addresses after it. Bracketed
|
||||
# IPv6 literals ([::1]:8080) parse in URLs and redirects and
|
||||
# reach the resolver without their brackets.
|
||||
for threads in 1 0; do
|
||||
make -f Makefile.limits clean
|
||||
make -f Makefile.limits THREADS=$threads SANITIZER=address,undefined
|
||||
ASAN_OPTIONS=detect_leaks=1 UBSAN_OPTIONS=halt_on_error=1 \
|
||||
timeout 60 ./http_limits_test
|
||||
done
|
||||
make -f Makefile.limits clean
|
||||
|
||||
- name: Build and run HTTP DNS connect lifetime test
|
||||
shell: bash
|
||||
working-directory: samples/tasks/http
|
||||
run: |
|
||||
set -eu
|
||||
for ssl in 0 1; do
|
||||
make -f Makefile.dns_lifetime clean
|
||||
make -f Makefile.dns_lifetime SSL=$ssl
|
||||
timeout 60 ./http_dns_lifetime_test
|
||||
done
|
||||
make -f Makefile.dns_lifetime clean
|
||||
|
||||
- name: Build and run http_dns_evict_deadlock_test (ASan, TSan)
|
||||
shell: bash
|
||||
working-directory: samples/tasks/http
|
||||
|
||||
@@ -1,117 +0,0 @@
|
||||
name: CI Linux samples [nothing else runs these]
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
pull_request:
|
||||
branches:
|
||||
- master
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
env:
|
||||
ACTIONS_ALLOW_USE_UNSECURE_NODE_VERSION: true
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
|
||||
cancel-in-progress: ${{ github.ref != 'refs/heads/master' }}
|
||||
|
||||
jobs:
|
||||
samples-unclaimed:
|
||||
name: Samples no other job runs
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 30
|
||||
|
||||
steps:
|
||||
- name: Install dependencies
|
||||
timeout-minutes: 20
|
||||
run: |
|
||||
pkgs="build-essential"
|
||||
if dpkg -s $pkgs >/dev/null 2>&1; then
|
||||
echo "All packages already present on runner image; skipping apt."
|
||||
exit 0
|
||||
fi
|
||||
for i in 1 2 3; do
|
||||
sudo timeout 90 apt-get update -y \
|
||||
-o Acquire::Retries=3 -o Acquire::http::Timeout=30 && break
|
||||
echo "apt-get update failed (attempt $i); retrying in 10s"
|
||||
sleep 10
|
||||
done
|
||||
# Fetch first, under a bound: a download is safe to stop and
|
||||
# resumes, an install is not. A mirror that stalls is left for
|
||||
# the main archive on the next attempt.
|
||||
for i in 1 2 3; do
|
||||
sudo timeout 240 apt-get install -y --download-only -o Acquire::Retries=3 \
|
||||
-o Acquire::http::Timeout=30 $pkgs && break
|
||||
[ $i = 3 ] && exit 1
|
||||
echo "package download failed or stalled (attempt $i); retrying from archive.ubuntu.com"
|
||||
for f in /etc/apt/sources.list /etc/apt/sources.list.d/*; do
|
||||
[ -f "$f" ] || continue
|
||||
sudo sed -i -e 's#mirror+file:/etc/apt/apt-mirrors.txt#http://archive.ubuntu.com/ubuntu/#g' \
|
||||
-e 's#http://azure\.archive\.ubuntu\.com/ubuntu#http://archive.ubuntu.com/ubuntu#g' "$f"
|
||||
done
|
||||
sudo timeout 90 apt-get update -y \
|
||||
-o Acquire::Retries=3 -o Acquire::http::Timeout=30 || true
|
||||
done
|
||||
sudo apt-get install -y -o Acquire::Retries=3 \
|
||||
-o Acquire::http::Timeout=30 $pkgs
|
||||
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v5
|
||||
|
||||
- name: Run them
|
||||
shell: bash
|
||||
run: |
|
||||
set -eu
|
||||
# A sample that builds and runs, and that no workflow names,
|
||||
# is a test nobody runs: font_lifecycle sat that way while
|
||||
# its expectation drifted away from the font driver it was
|
||||
# written against. This job takes whatever is left over -
|
||||
# the list is computed, so a new sample joins it by existing
|
||||
# rather than by being remembered.
|
||||
#
|
||||
# Samples that need a library the runner does not carry
|
||||
# (SDL, FFmpeg, libusb) are skipped: their own workflows
|
||||
# install what they need. A sample cross-built with mingw-w64
|
||||
# and run under Wine gets both installed here, the first time
|
||||
# one turns up, so it runs rather than fails for want of a
|
||||
# compiler.
|
||||
fails=0
|
||||
wine_ready=0
|
||||
for mk in $(find samples libretro-common/samples -name Makefile | sort); do
|
||||
dir=$(dirname "$mk")
|
||||
name=$(basename "$dir")
|
||||
(cd "$dir" && make -n check >/dev/null 2>&1) || continue
|
||||
if grep -rqlF "$name" .github/workflows/ 2>/dev/null; then
|
||||
continue
|
||||
fi
|
||||
if grep -qiE 'sdl|ffmpeg|libav|libusb' "$mk"; then
|
||||
echo "-- skip $dir (needs a library this job does not install)"
|
||||
continue
|
||||
fi
|
||||
if [ $wine_ready -eq 0 ] && grep -qE 'mingw32-|wine' "$mk"; then
|
||||
sudo apt-get update -y -qq -o Acquire::Retries=3 \
|
||||
-o Acquire::http::Timeout=30 >/dev/null || true
|
||||
sudo apt-get install -y -qq gcc-mingw-w64-x86-64 wine64 \
|
||||
-o Acquire::Retries=3 -o Acquire::http::Timeout=30
|
||||
wine_ready=1
|
||||
fi
|
||||
echo "== $dir"
|
||||
if (cd "$dir" && make clean >/dev/null 2>&1 \
|
||||
&& ASAN_OPTIONS=detect_leaks=1:allocator_may_return_null=1 \
|
||||
UBSAN_OPTIONS=print_stacktrace=1 \
|
||||
timeout 300 make SANITIZER=address,undefined check); then
|
||||
echo " ok"
|
||||
else
|
||||
echo " FAIL $dir"
|
||||
fails=$((fails + 1))
|
||||
fi
|
||||
(cd "$dir" && make clean >/dev/null 2>&1) || true
|
||||
done
|
||||
if [ "$fails" -ne 0 ]; then
|
||||
echo "$fails sample(s) failed"
|
||||
exit 1
|
||||
fi
|
||||
@@ -38,7 +38,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Install dependencies
|
||||
timeout-minutes: 20
|
||||
timeout-minutes: 5
|
||||
run: |
|
||||
pkgs="build-essential libxkbcommon-dev libx11-xcb-dev xvfb x11-utils zlib1g-dev libfreetype6-dev libegl1-mesa-dev libgles2-mesa-dev libgbm-dev libavcodec-dev libsdl2-dev libsdl-image1.2-dev libxml2-dev yasm"
|
||||
if dpkg -s $pkgs >/dev/null 2>&1; then
|
||||
@@ -51,23 +51,6 @@ jobs:
|
||||
echo "apt-get update failed (attempt $i); retrying in 10s"
|
||||
sleep 10
|
||||
done
|
||||
# Fetch first, under a bound: a download is safe to stop and
|
||||
# resumes, an install is not. A mirror that stalls is left for
|
||||
# the main archive on the next attempt.
|
||||
for i in 1 2 3; do
|
||||
sudo timeout 240 apt-get install -y --download-only \
|
||||
-o Acquire::Retries=3 -o Acquire::http::Timeout=30 \
|
||||
$pkgs && break
|
||||
[ $i = 3 ] && exit 1
|
||||
echo "package download failed or stalled (attempt $i); retrying from archive.ubuntu.com"
|
||||
for f in /etc/apt/sources.list /etc/apt/sources.list.d/*; do
|
||||
[ -f "$f" ] || continue
|
||||
sudo sed -i -e 's#mirror+file:/etc/apt/apt-mirrors.txt#http://archive.ubuntu.com/ubuntu/#g' \
|
||||
-e 's#http://azure\.archive\.ubuntu\.com/ubuntu#http://archive.ubuntu.com/ubuntu#g' "$f"
|
||||
done
|
||||
sudo timeout 90 apt-get update -y \
|
||||
-o Acquire::Retries=3 -o Acquire::http::Timeout=30 || true
|
||||
done
|
||||
sudo apt-get install -y \
|
||||
-o Acquire::Retries=3 -o Acquire::http::Timeout=30 \
|
||||
$pkgs
|
||||
|
||||
@@ -49,7 +49,7 @@ jobs:
|
||||
- name: Install dependencies
|
||||
# Same set as Linux-asan-ubsan.yml. libtsan ships with the gcc
|
||||
# on the runner image, so nothing sanitizer-specific is needed.
|
||||
timeout-minutes: 20
|
||||
timeout-minutes: 5
|
||||
run: |
|
||||
pkgs="build-essential libxkbcommon-dev libx11-xcb-dev xvfb x11-utils zlib1g-dev libfreetype6-dev libegl1-mesa-dev libgles2-mesa-dev libgbm-dev libavcodec-dev libsdl2-dev libsdl-image1.2-dev libxml2-dev yasm"
|
||||
if dpkg -s $pkgs >/dev/null 2>&1; then
|
||||
@@ -62,23 +62,6 @@ jobs:
|
||||
echo "apt-get update failed (attempt $i); retrying in 10s"
|
||||
sleep 10
|
||||
done
|
||||
# Fetch first, under a bound: a download is safe to stop and
|
||||
# resumes, an install is not. A mirror that stalls is left for
|
||||
# the main archive on the next attempt.
|
||||
for i in 1 2 3; do
|
||||
sudo timeout 240 apt-get install -y --download-only \
|
||||
-o Acquire::Retries=3 -o Acquire::http::Timeout=30 \
|
||||
$pkgs && break
|
||||
[ $i = 3 ] && exit 1
|
||||
echo "package download failed or stalled (attempt $i); retrying from archive.ubuntu.com"
|
||||
for f in /etc/apt/sources.list /etc/apt/sources.list.d/*; do
|
||||
[ -f "$f" ] || continue
|
||||
sudo sed -i -e 's#mirror+file:/etc/apt/apt-mirrors.txt#http://archive.ubuntu.com/ubuntu/#g' \
|
||||
-e 's#http://azure\.archive\.ubuntu\.com/ubuntu#http://archive.ubuntu.com/ubuntu#g' "$f"
|
||||
done
|
||||
sudo timeout 90 apt-get update -y \
|
||||
-o Acquire::Retries=3 -o Acquire::http::Timeout=30 || true
|
||||
done
|
||||
sudo apt-get install -y \
|
||||
-o Acquire::Retries=3 -o Acquire::http::Timeout=30 \
|
||||
$pkgs
|
||||
|
||||
@@ -46,16 +46,6 @@ name: CI Vulkan/GL/D3D11 [idle waits + driver harness]
|
||||
# frontend does against the driver from the wrong thread is an X
|
||||
# BadAccess here rather than a corruption elsewhere; the first run
|
||||
# caught the OSD font rebuild doing exactly that.
|
||||
#
|
||||
# 6. Teardown-order check: tools/vulkan_teardown_order_check.py holds
|
||||
# the order the driver retires a frame's objects in - command
|
||||
# pools, then descriptor pools, then the pipelines that own the set
|
||||
# layout - and the rule that destroying a descriptor pool is what
|
||||
# frees its sets. The other order is legal Vulkan and the
|
||||
# validation layer passes it, so the harness lanes above cannot see
|
||||
# it; a driver that walks its own back references as a pool is
|
||||
# destroyed faults inside vkDestroyDescriptorPool at exit, which is
|
||||
# what MoltenVK did.
|
||||
|
||||
on:
|
||||
push:
|
||||
@@ -95,27 +85,6 @@ jobs:
|
||||
set -eu
|
||||
python3 tools/vulkan_idle_wait_check.py
|
||||
|
||||
vulkan-teardown-order:
|
||||
name: Vulkan teardown order
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 5
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v5
|
||||
|
||||
- name: Checker self-test
|
||||
shell: bash
|
||||
run: |
|
||||
set -eu
|
||||
python3 tools/vulkan_teardown_order_check.py --selftest
|
||||
|
||||
- name: Tree check
|
||||
shell: bash
|
||||
run: |
|
||||
set -eu
|
||||
python3 tools/vulkan_teardown_order_check.py
|
||||
|
||||
vulkan-validation-harness:
|
||||
name: Threaded-video harness on Vulkan (lavapipe, validation layer)
|
||||
runs-on: ubuntu-latest
|
||||
@@ -123,7 +92,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Install dependencies
|
||||
timeout-minutes: 20
|
||||
timeout-minutes: 8
|
||||
run: |
|
||||
pkgs="build-essential libxkbcommon-dev libx11-xcb-dev libxcb1-dev libxinerama-dev libxrandr-dev libxxf86vm-dev libxv-dev xvfb x11-utils zlib1g-dev libfreetype6-dev libvulkan-dev mesa-vulkan-drivers vulkan-validationlayers vulkan-tools libasound2-dev"
|
||||
for i in 1 2 3; do
|
||||
@@ -132,23 +101,6 @@ jobs:
|
||||
echo "apt-get update failed (attempt $i); retrying in 10s"
|
||||
sleep 10
|
||||
done
|
||||
# Fetch first, under a bound: a download is safe to stop and
|
||||
# resumes, an install is not. A mirror that stalls is left for
|
||||
# the main archive on the next attempt.
|
||||
for i in 1 2 3; do
|
||||
sudo timeout 240 apt-get install -y --download-only \
|
||||
-o Acquire::Retries=3 -o Acquire::http::Timeout=30 \
|
||||
$pkgs && break
|
||||
[ $i = 3 ] && exit 1
|
||||
echo "package download failed or stalled (attempt $i); retrying from archive.ubuntu.com"
|
||||
for f in /etc/apt/sources.list /etc/apt/sources.list.d/*; do
|
||||
[ -f "$f" ] || continue
|
||||
sudo sed -i -e 's#mirror+file:/etc/apt/apt-mirrors.txt#http://archive.ubuntu.com/ubuntu/#g' \
|
||||
-e 's#http://azure\.archive\.ubuntu\.com/ubuntu#http://archive.ubuntu.com/ubuntu#g' "$f"
|
||||
done
|
||||
sudo timeout 90 apt-get update -y \
|
||||
-o Acquire::Retries=3 -o Acquire::http::Timeout=30 || true
|
||||
done
|
||||
sudo apt-get install -y \
|
||||
-o Acquire::Retries=3 -o Acquire::http::Timeout=30 \
|
||||
$pkgs
|
||||
@@ -169,14 +121,8 @@ jobs:
|
||||
# debug messenger that reports through RARCH_ERR. It is a
|
||||
# tree-wide define: vulkan_context_t carries a member under it,
|
||||
# so every TU that includes vulkan_common.h must see it.
|
||||
# GFX_INSTRUMENT counts what the image and surface paths do -
|
||||
# uploads, in-place updates, allocating posts, copies - so the
|
||||
# harness lanes below can hold the budgets the paths are
|
||||
# written to and print what a run cost. This is the one leg
|
||||
# that carries it: under a sanitizer the timings mean nothing,
|
||||
# and the counters change no behaviour.
|
||||
run: |
|
||||
make -j$(getconf _NPROCESSORS_ONLN) VULKAN_DEBUG=1 GFX_INSTRUMENT=1
|
||||
make -j$(getconf _NPROCESSORS_ONLN) VULKAN_DEBUG=1
|
||||
test -x retroarch
|
||||
|
||||
- name: Run the harness on Vulkan under the validation layer
|
||||
@@ -196,35 +142,8 @@ jobs:
|
||||
# lines, so it has to name the same build: VK_DEBUG=1. Its
|
||||
# surface and overlay-page lanes run the driver's real
|
||||
# in-place texture update and its borrowed-texture overlay
|
||||
# page here, under the validation layer, and with the tree
|
||||
# built GFX_INSTRUMENT=1 they print their baselines and hold
|
||||
# the budgets: one texture per streaming surface, no
|
||||
# allocation per post, no copy, a 4K submit that stays a
|
||||
# descriptor hand-off.
|
||||
SAN=none VK_DEBUG=1 GFX_INSTRUMENT=1 CYCLES=2 make -C samples/gfx/threaded_video check-vulkan
|
||||
# Once more with the harness core pushing XRGB8888: the
|
||||
# driver samples a linear 32-bit image itself where it
|
||||
# converts RGB565 with a compute pass, so a lent frame and
|
||||
# a window into it take the other upload path (the one
|
||||
# beetle-psx takes).
|
||||
HARNESS_CORE_XRGB8888=1 SAN=none VK_DEBUG=1 GFX_INSTRUMENT=1 CYCLES=1 make -C samples/gfx/threaded_video check-vulkan
|
||||
# And with the harness core as a Vulkan hardware core, which
|
||||
# runs its own lane alone: the core's image must not be drawn
|
||||
# from once the core has destroyed it - at a content close,
|
||||
# unthreaded and under the wrapper, and after a set_image(NULL)
|
||||
# - and a dupe of a hardware frame must not re-submit a fence
|
||||
# that is already signalled. Either is a validation error
|
||||
# here, and on lavapipe a crash.
|
||||
HARNESS_CORE_HW_VULKAN=1 SAN=none VK_DEBUG=1 GFX_INSTRUMENT=1 CYCLES=1 make -C samples/gfx/threaded_video check-vulkan
|
||||
make -C samples/gfx/threaded_video clean
|
||||
# And as a core that creates the device itself, through the
|
||||
# negotiation interface's create_device - Beetle PSX, for one.
|
||||
# That device has the extensions the frontend requires and
|
||||
# none of the optional ones it adds to a device of its own, so
|
||||
# anything the driver or the filter chain does that leans on
|
||||
# an optional extension is a validation error here and nowhere
|
||||
# else. The filter chain's mirror-clamp samplers were.
|
||||
HARNESS_CORE_HW_VULKAN=2 SAN=none VK_DEBUG=1 GFX_INSTRUMENT=1 CYCLES=1 make -C samples/gfx/threaded_video check-vulkan
|
||||
# page here, under the validation layer.
|
||||
SAN=none VK_DEBUG=1 CYCLES=2 make -C samples/gfx/threaded_video check-vulkan
|
||||
make -C samples/gfx/threaded_video clean
|
||||
|
||||
vulkan-validation-sample:
|
||||
@@ -234,7 +153,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Install dependencies
|
||||
timeout-minutes: 20
|
||||
timeout-minutes: 8
|
||||
run: |
|
||||
pkgs="build-essential libx11-dev libx11-xcb-dev libxcb1-dev xvfb x11-utils libvulkan-dev mesa-vulkan-drivers vulkan-validationlayers vulkan-tools"
|
||||
for i in 1 2 3; do
|
||||
@@ -243,23 +162,6 @@ jobs:
|
||||
echo "apt-get update failed (attempt $i); retrying in 10s"
|
||||
sleep 10
|
||||
done
|
||||
# Fetch first, under a bound: a download is safe to stop and
|
||||
# resumes, an install is not. A mirror that stalls is left for
|
||||
# the main archive on the next attempt.
|
||||
for i in 1 2 3; do
|
||||
sudo timeout 240 apt-get install -y --download-only \
|
||||
-o Acquire::Retries=3 -o Acquire::http::Timeout=30 \
|
||||
$pkgs && break
|
||||
[ $i = 3 ] && exit 1
|
||||
echo "package download failed or stalled (attempt $i); retrying from archive.ubuntu.com"
|
||||
for f in /etc/apt/sources.list /etc/apt/sources.list.d/*; do
|
||||
[ -f "$f" ] || continue
|
||||
sudo sed -i -e 's#mirror+file:/etc/apt/apt-mirrors.txt#http://archive.ubuntu.com/ubuntu/#g' \
|
||||
-e 's#http://azure\.archive\.ubuntu\.com/ubuntu#http://archive.ubuntu.com/ubuntu#g' "$f"
|
||||
done
|
||||
sudo timeout 90 apt-get update -y \
|
||||
-o Acquire::Retries=3 -o Acquire::http::Timeout=30 || true
|
||||
done
|
||||
sudo apt-get install -y \
|
||||
-o Acquire::Retries=3 -o Acquire::http::Timeout=30 \
|
||||
$pkgs
|
||||
@@ -298,7 +200,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Install dependencies
|
||||
timeout-minutes: 20
|
||||
timeout-minutes: 8
|
||||
run: |
|
||||
pkgs="build-essential libxkbcommon-dev libx11-xcb-dev libxcb1-dev libxinerama-dev libxrandr-dev libxxf86vm-dev libxv-dev xvfb x11-utils zlib1g-dev libfreetype6-dev libvulkan-dev mesa-vulkan-drivers vulkan-validationlayers vulkan-tools libasound2-dev"
|
||||
for i in 1 2 3; do
|
||||
@@ -307,23 +209,6 @@ jobs:
|
||||
echo "apt-get update failed (attempt $i); retrying in 10s"
|
||||
sleep 10
|
||||
done
|
||||
# Fetch first, under a bound: a download is safe to stop and
|
||||
# resumes, an install is not. A mirror that stalls is left for
|
||||
# the main archive on the next attempt.
|
||||
for i in 1 2 3; do
|
||||
sudo timeout 240 apt-get install -y --download-only \
|
||||
-o Acquire::Retries=3 -o Acquire::http::Timeout=30 \
|
||||
$pkgs && break
|
||||
[ $i = 3 ] && exit 1
|
||||
echo "package download failed or stalled (attempt $i); retrying from archive.ubuntu.com"
|
||||
for f in /etc/apt/sources.list /etc/apt/sources.list.d/*; do
|
||||
[ -f "$f" ] || continue
|
||||
sudo sed -i -e 's#mirror+file:/etc/apt/apt-mirrors.txt#http://archive.ubuntu.com/ubuntu/#g' \
|
||||
-e 's#http://azure\.archive\.ubuntu\.com/ubuntu#http://archive.ubuntu.com/ubuntu#g' "$f"
|
||||
done
|
||||
sudo timeout 90 apt-get update -y \
|
||||
-o Acquire::Retries=3 -o Acquire::http::Timeout=30 || true
|
||||
done
|
||||
sudo apt-get install -y \
|
||||
-o Acquire::Retries=3 -o Acquire::http::Timeout=30 \
|
||||
$pkgs
|
||||
@@ -355,7 +240,6 @@ jobs:
|
||||
export DISPLAY=:99
|
||||
export VK_DRIVER_FILES=/usr/share/vulkan/icd.d/lvp_icd.json
|
||||
SAN=address VK_DEBUG=1 CYCLES=1 make -C samples/gfx/threaded_video check-vulkan
|
||||
HARNESS_CORE_XRGB8888=1 SAN=address VK_DEBUG=1 CYCLES=1 make -C samples/gfx/threaded_video check-vulkan
|
||||
make -C samples/gfx/threaded_video clean
|
||||
|
||||
vulkan-tsan-harness:
|
||||
@@ -365,7 +249,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Install dependencies
|
||||
timeout-minutes: 20
|
||||
timeout-minutes: 8
|
||||
run: |
|
||||
pkgs="build-essential libxkbcommon-dev libx11-xcb-dev libxcb1-dev libxinerama-dev libxrandr-dev libxxf86vm-dev libxv-dev xvfb x11-utils zlib1g-dev libfreetype6-dev libvulkan-dev mesa-vulkan-drivers vulkan-validationlayers vulkan-tools libasound2-dev"
|
||||
for i in 1 2 3; do
|
||||
@@ -374,23 +258,6 @@ jobs:
|
||||
echo "apt-get update failed (attempt $i); retrying in 10s"
|
||||
sleep 10
|
||||
done
|
||||
# Fetch first, under a bound: a download is safe to stop and
|
||||
# resumes, an install is not. A mirror that stalls is left for
|
||||
# the main archive on the next attempt.
|
||||
for i in 1 2 3; do
|
||||
sudo timeout 240 apt-get install -y --download-only \
|
||||
-o Acquire::Retries=3 -o Acquire::http::Timeout=30 \
|
||||
$pkgs && break
|
||||
[ $i = 3 ] && exit 1
|
||||
echo "package download failed or stalled (attempt $i); retrying from archive.ubuntu.com"
|
||||
for f in /etc/apt/sources.list /etc/apt/sources.list.d/*; do
|
||||
[ -f "$f" ] || continue
|
||||
sudo sed -i -e 's#mirror+file:/etc/apt/apt-mirrors.txt#http://archive.ubuntu.com/ubuntu/#g' \
|
||||
-e 's#http://azure\.archive\.ubuntu\.com/ubuntu#http://archive.ubuntu.com/ubuntu#g' "$f"
|
||||
done
|
||||
sudo timeout 90 apt-get update -y \
|
||||
-o Acquire::Retries=3 -o Acquire::http::Timeout=30 || true
|
||||
done
|
||||
sudo apt-get install -y \
|
||||
-o Acquire::Retries=3 -o Acquire::http::Timeout=30 \
|
||||
$pkgs
|
||||
@@ -436,7 +303,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Install dependencies
|
||||
timeout-minutes: 20
|
||||
timeout-minutes: 8
|
||||
run: |
|
||||
pkgs="build-essential libxkbcommon-dev libx11-xcb-dev libxcb1-dev libxinerama-dev libxrandr-dev libxxf86vm-dev libxv-dev xvfb x11-utils zlib1g-dev libfreetype6-dev libgl-dev libglx-dev libegl-dev libgles-dev libgl1-mesa-dri libglx-mesa0 mesa-utils libasound2-dev"
|
||||
for i in 1 2 3; do
|
||||
@@ -445,23 +312,6 @@ jobs:
|
||||
echo "apt-get update failed (attempt $i); retrying in 10s"
|
||||
sleep 10
|
||||
done
|
||||
# Fetch first, under a bound: a download is safe to stop and
|
||||
# resumes, an install is not. A mirror that stalls is left for
|
||||
# the main archive on the next attempt.
|
||||
for i in 1 2 3; do
|
||||
sudo timeout 240 apt-get install -y --download-only \
|
||||
-o Acquire::Retries=3 -o Acquire::http::Timeout=30 \
|
||||
$pkgs && break
|
||||
[ $i = 3 ] && exit 1
|
||||
echo "package download failed or stalled (attempt $i); retrying from archive.ubuntu.com"
|
||||
for f in /etc/apt/sources.list /etc/apt/sources.list.d/*; do
|
||||
[ -f "$f" ] || continue
|
||||
sudo sed -i -e 's#mirror+file:/etc/apt/apt-mirrors.txt#http://archive.ubuntu.com/ubuntu/#g' \
|
||||
-e 's#http://azure\.archive\.ubuntu\.com/ubuntu#http://archive.ubuntu.com/ubuntu#g' "$f"
|
||||
done
|
||||
sudo timeout 90 apt-get update -y \
|
||||
-o Acquire::Retries=3 -o Acquire::http::Timeout=30 || true
|
||||
done
|
||||
sudo apt-get install -y \
|
||||
-o Acquire::Retries=3 -o Acquire::http::Timeout=30 \
|
||||
$pkgs
|
||||
@@ -512,15 +362,8 @@ jobs:
|
||||
# d3d12 runs the same lanes on the other explicit-API backend:
|
||||
# its in-place texture update is fence-tagged against the frame
|
||||
# that last copied from the upload buffer, which only a running
|
||||
# driver exercises. It runs with the harness core pushing
|
||||
# XRGB8888: d3d12 lends its framebuffer only for a row pitch
|
||||
# on a 256-byte boundary, which 320 pixels reach at 4 bytes and
|
||||
# not at 2, so that is the run in which a frame is lent and a
|
||||
# window into the loan is read out of it (lent-window lane).
|
||||
# driver exercises.
|
||||
driver: [d3d11, d3d12]
|
||||
include:
|
||||
- driver: d3d12
|
||||
core_xrgb8888: 1
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
@@ -552,6 +395,5 @@ jobs:
|
||||
run: |
|
||||
set -eu
|
||||
samples/gfx/threaded_video/build.sh
|
||||
HARNESS_CORE_XRGB8888=${{ matrix.core_xrgb8888 || '' }} \
|
||||
SAN=none DRIVER=${{ matrix.driver }} CYCLES=1 GDB=1 make -C samples/gfx/threaded_video check-driver
|
||||
SAN=none DRIVER=${{ matrix.driver }} CYCLES=1 GDB=1 make -C samples/gfx/threaded_video check-driver
|
||||
make -C samples/gfx/threaded_video clean
|
||||
@@ -33,12 +33,6 @@ jobs:
|
||||
run: |
|
||||
./configure --disable-qt --enable-xdelta
|
||||
|
||||
- name: Include order
|
||||
run: |
|
||||
# The tree's own headers are found before same-named ones in the
|
||||
# system include dirs configure or pkg-config put on the line.
|
||||
sh tools/include_order_check.sh
|
||||
|
||||
- name: Compile RA
|
||||
run: |
|
||||
make -j$(getconf _NPROCESSORS_ONLN) clean
|
||||
|
||||
@@ -32,58 +32,6 @@ jobs:
|
||||
# companion, the grid, the tables, the dialogs - as AppKit does them.
|
||||
tools/companion_cocoa_test_macos.sh 2>&1 | grep -v "^$"
|
||||
|
||||
- name: Metal texture behaviour the surface, overlay and lent-framebuffer paths assume
|
||||
run: |
|
||||
set -o pipefail
|
||||
# The driver's Metal paths have only ever been compiled. This
|
||||
# asserts the two behaviours they are built on, on the runner's
|
||||
# real device: a texture replaced in place while GPU work that
|
||||
# samples it is in flight - the animated preview's steady
|
||||
# state, and the one path with no fence check or DO_NOT_WAIT to
|
||||
# drop a frame with - and an overlay page holding textures the
|
||||
# pack owns, where a page switch must not take them with it.
|
||||
#
|
||||
# 77 means the runner exposed no Metal device; the sample says
|
||||
# so rather than passing quietly.
|
||||
cd samples/gfx/metal_texture_update
|
||||
make check; rc=$?
|
||||
if [ "$rc" = 77 ]; then
|
||||
echo "::warning::no Metal device on this runner; the check did not run"
|
||||
exit 0
|
||||
fi
|
||||
exit "$rc"
|
||||
|
||||
- name: iCloud Drive cloud sync driver
|
||||
run: |
|
||||
set -o pipefail
|
||||
# Includes network/cloud_sync/icloud_drive.m, with the iCloud
|
||||
# container redirected to a temporary directory. Cloud sync
|
||||
# waits for every request to report exactly once: a read whose
|
||||
# local file could not be created never reported (the sync
|
||||
# hung), and a cloud file that could not be loaded or a local
|
||||
# file that could not be read reported twice. With iCloud
|
||||
# unavailable, read and delete reported success. Also covers
|
||||
# the ordinary read, missing file, upload and delete paths.
|
||||
cd samples/network/icloud_drive
|
||||
make clean check SANITIZER=address,undefined
|
||||
|
||||
- name: iCloud (CloudKit) cloud sync driver
|
||||
run: |
|
||||
set -o pipefail
|
||||
# Includes network/cloud_sync/icloud.m, with CloudKit replaced by
|
||||
# an in-memory database (+[CKContainer defaultContainer] is
|
||||
# overridden in a category; real CKRecord/CKAsset/CKQuery). A
|
||||
# read whose local file could not be written, or whose record
|
||||
# held no readable data, reported success; an upload whose
|
||||
# lookup failed created a duplicate record; a delete reported
|
||||
# success before its deletes were sent, whatever became of
|
||||
# them; and non-destructive sync was ignored. Checks each case,
|
||||
# that deletes and uploads keep <path>-<time> with destructive
|
||||
# sync off (and not for destructive sync or the manifest), and
|
||||
# that every request reports exactly once.
|
||||
cd samples/network/icloud_cloudkit
|
||||
make clean check SANITIZER=address,undefined
|
||||
|
||||
- name: Compile RA
|
||||
run: |
|
||||
set -o pipefail
|
||||
@@ -98,30 +46,3 @@ jobs:
|
||||
name: RetroArch-${{ steps.slug.outputs.sha8 }}
|
||||
path: |
|
||||
build/Build/Products/Release
|
||||
|
||||
# The threaded-video harness on the Metal driver: the real frontend
|
||||
# under Cocoa's own main (see samples/gfx/threaded_video/harness_cocoa.m),
|
||||
# every lane the Vulkan and D3D legs run, on the runner's GPU. A
|
||||
# Makefile build, since the harness links the tree's objects.
|
||||
metal-harness:
|
||||
runs-on: macos-latest
|
||||
timeout-minutes: 60
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- name: Configure and build (Makefile, no Qt)
|
||||
run: |
|
||||
set -o pipefail
|
||||
./configure --disable-qt --disable-ffmpeg --disable-discord --disable-cheevos \
|
||||
--disable-networking --disable-sdl2 --disable-sdl --disable-translate \
|
||||
--disable-update_cores --disable-update_assets --disable-online_updater \
|
||||
--disable-cdrom --disable-7zip --disable-ssl 2>&1 | tail -5
|
||||
make -j$(sysctl -n hw.ncpu) GFX_INSTRUMENT=1 2>&1 | grep -E "error|warning: unused" || true
|
||||
test -x retroarch
|
||||
|
||||
- name: Harness on Metal (RGB565 core, then XRGB8888)
|
||||
run: |
|
||||
set -o pipefail
|
||||
SAN=none GFX_INSTRUMENT=1 DRIVER=metal CYCLES=1 make -C samples/gfx/threaded_video check-driver
|
||||
HARNESS_CORE_XRGB8888=1 SAN=none GFX_INSTRUMENT=1 DRIVER=metal CYCLES=1 make -C samples/gfx/threaded_video check-driver
|
||||
@@ -21,27 +21,23 @@ jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 30
|
||||
# The image is Alpine 3.15, whose musl cannot load the runner's
|
||||
# Node 24, so JavaScript actions run on the host and only the
|
||||
# build steps run inside the image.
|
||||
env:
|
||||
ORBIS_IMAGE: reallibretroretroarch/libretro-build-orbis:latest
|
||||
container:
|
||||
image: reallibretroretroarch/libretro-build-orbis:latest
|
||||
options: --user root
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
apk add ncurses-dev make bash python2
|
||||
apk add libintl icu-dev wget
|
||||
wget https://dot.net/v1/dotnet-install.sh && chmod 755 dotnet-install.sh && ./dotnet-install.sh -c 3.0 --install-dir ~/cli
|
||||
|
||||
- name: Compile RA
|
||||
run: |
|
||||
docker run --rm --user root -v "$PWD":/src -w /src "$ORBIS_IMAGE" bash -c '
|
||||
set -e
|
||||
apk add ncurses-dev make bash python2
|
||||
apk add libintl icu-dev wget
|
||||
wget https://dot.net/v1/dotnet-install.sh && chmod 755 dotnet-install.sh && ./dotnet-install.sh -c 3.0 --install-dir ~/cli
|
||||
export PATH=~/cli:$PATH # .net cli
|
||||
make -f Makefile.orbis -j$(getconf _NPROCESSORS_ONLN) HAVE_STATIC_DUMMY=1 info all
|
||||
clang --version
|
||||
orbis-ld --version
|
||||
'
|
||||
export PATH=~/cli:$PATH # .net cli
|
||||
make -f Makefile.orbis -j$(getconf _NPROCESSORS_ONLN) HAVE_STATIC_DUMMY=1 info all
|
||||
|
||||
- name: Get short SHA
|
||||
id: slug
|
||||
@@ -54,3 +50,8 @@ jobs:
|
||||
retroarch_orbis.elf
|
||||
retroarch_orbis.oelf
|
||||
retroarch_orbis.self
|
||||
|
||||
- name: Version of binaries
|
||||
run: |
|
||||
clang --version
|
||||
orbis-ld --version
|
||||
@@ -1,108 +0,0 @@
|
||||
name: CI removed struct members
|
||||
|
||||
# When a struct member goes away, the compiler finds every reader it
|
||||
# compiles. The readers it does not compile are the ones no Linux box
|
||||
# sees - the Objective-C drivers above all, and anything behind a
|
||||
# platform #ifdef - so each of those is found by whichever job builds
|
||||
# that platform, one round trip at a time.
|
||||
#
|
||||
# window_position_x and window_position_y went that way. Packing the
|
||||
# pair into one word updated the Win32 and SDL3 users, which is what a
|
||||
# grep of the .c and .h files turns up; ui_cocoa.m kept reading both
|
||||
# and took out the macOS job and both PowerPC cross jobs at once. Those
|
||||
# three are among the slowest jobs in the tree, and the answer they
|
||||
# eventually gave was four lines in one file.
|
||||
#
|
||||
# tools/packed_field_check.py does not cover this. It knows the six
|
||||
# names a size or origin pair is spelled with, and window_position_x is
|
||||
# not one of them; on the broken tree it printed 1184 lines, none of
|
||||
# them ui_cocoa.m. The question here is not what a member is called,
|
||||
# it is whether anything still reaches for one the change took away.
|
||||
#
|
||||
# So this lane reads the change rather than the tree's vocabulary: the
|
||||
# members a push or a pull request removes, minus the ones something
|
||||
# still declares, against every reader in every language the tree is
|
||||
# written in. It reads files rather than building them, so one job
|
||||
# covers what the platform jobs between them would only find one at a
|
||||
# time.
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
pull_request:
|
||||
branches:
|
||||
- master
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
|
||||
cancel-in-progress: ${{ github.ref != 'refs/heads/master' }}
|
||||
|
||||
jobs:
|
||||
removed-member-check:
|
||||
name: readers of a removed member
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 10
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v5
|
||||
with:
|
||||
# Both ends of the range have to be present, and a pull
|
||||
# request's base can be behind by any amount.
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Checker self-test
|
||||
shell: bash
|
||||
run: |
|
||||
set -eu
|
||||
# Fixture pass: a member removed along with its readers is
|
||||
# clean, one left behind in a language no lane here compiles
|
||||
# is caught, and a member that merely moved, one whose name
|
||||
# another struct still declares, one that was renamed, one
|
||||
# named only in a comment or a log string, one declared in a
|
||||
# declarator list, and a name too common to reason about are
|
||||
# each left alone.
|
||||
#
|
||||
# The fixtures are handed to the checker directly rather than
|
||||
# taken from the tree, so this reports the same thing whether
|
||||
# or not the tree currently passes - it measures the checker.
|
||||
python3 tools/removed_member_check.py --selftest
|
||||
|
||||
- name: Range check
|
||||
shell: bash
|
||||
run: |
|
||||
set -eu
|
||||
# A pull request is measured against its base; a push against
|
||||
# what was there before it. A force push or a first push
|
||||
# leaves 'before' as zeroes, and a merge has more than one
|
||||
# parent, so fall back to the first parent in either case.
|
||||
if [ -n "${PR_BASE:-}" ]; then
|
||||
base="$PR_BASE"; head="$PR_HEAD"
|
||||
else
|
||||
base="${EVENT_BEFORE:-}"; head="$GITHUB_SHA"
|
||||
case "$base" in
|
||||
''|*[!0]*) ;;
|
||||
*) base= ;;
|
||||
esac
|
||||
if [ -z "$base" ] || ! git cat-file -e "$base^{commit}" 2>/dev/null; then
|
||||
base="$(git rev-parse "$head^" 2>/dev/null || true)"
|
||||
fi
|
||||
fi
|
||||
if [ -z "$base" ]; then
|
||||
echo "no base commit to compare against; nothing to check"
|
||||
exit 0
|
||||
fi
|
||||
echo "range: $base..$head"
|
||||
# Every struct member the range removes, minus the ones the
|
||||
# tree still declares somewhere, against every .c .h .m .mm
|
||||
# .cpp .hpp and .inc reader of one.
|
||||
python3 tools/removed_member_check.py --range "$base..$head"
|
||||
env:
|
||||
PR_BASE: ${{ github.event.pull_request.base.sha }}
|
||||
PR_HEAD: ${{ github.event.pull_request.head.sha }}
|
||||
EVENT_BEFORE: ${{ github.event.before }}
|
||||
@@ -6,18 +6,6 @@ on:
|
||||
pull_request:
|
||||
repository_dispatch:
|
||||
types: [run_build]
|
||||
release:
|
||||
types: [published]
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
release_tag:
|
||||
description: Existing release tag to build (empty builds the selected ref)
|
||||
type: string
|
||||
default: ''
|
||||
publish_release:
|
||||
description: Upload the ARM64 package to the existing release
|
||||
type: boolean
|
||||
default: false
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
@@ -26,15 +14,13 @@ env:
|
||||
ACTIONS_ALLOW_USE_UNSECURE_NODE_VERSION: true
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event_name }}-${{ github.event.pull_request.number || inputs.release_tag || github.ref }}
|
||||
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
|
||||
cancel-in-progress: ${{ github.ref != 'refs/heads/master' }}
|
||||
|
||||
jobs:
|
||||
msvc:
|
||||
runs-on: windows-11-arm
|
||||
timeout-minutes: 90
|
||||
outputs:
|
||||
release_tag: ${{ steps.source.outputs.release_tag }}
|
||||
strategy:
|
||||
matrix:
|
||||
version: [2022]
|
||||
@@ -43,29 +29,6 @@ jobs:
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
with:
|
||||
ref: ${{ github.event.release.tag_name || inputs.release_tag || github.ref }}
|
||||
|
||||
- name: Record and verify the source revision
|
||||
id: source
|
||||
shell: pwsh
|
||||
env:
|
||||
RELEASE_TAG: ${{ github.event.release.tag_name || inputs.release_tag }}
|
||||
PUBLISH_RELEASE: ${{ github.event_name == 'release' || inputs.publish_release == true }}
|
||||
run: |
|
||||
$revision = git rev-parse HEAD
|
||||
if ($LASTEXITCODE) { throw 'Could not resolve the source revision.' }
|
||||
"revision=$revision" >> $env:GITHUB_OUTPUT
|
||||
if ($env:PUBLISH_RELEASE -eq 'true' -and !$env:RELEASE_TAG) {
|
||||
throw 'Publishing requires an existing release tag.'
|
||||
}
|
||||
if ($env:RELEASE_TAG) {
|
||||
$tagRevision = git rev-parse --verify "refs/tags/$($env:RELEASE_TAG)^{commit}"
|
||||
if ($LASTEXITCODE -or $tagRevision -ne $revision) {
|
||||
throw 'The checkout is not the requested release tag.'
|
||||
}
|
||||
}
|
||||
"release_tag=$env:RELEASE_TAG" >> $env:GITHUB_OUTPUT
|
||||
|
||||
- name: Add msbuild to PATH
|
||||
uses: microsoft/setup-msbuild@v1
|
||||
@@ -77,88 +40,11 @@ jobs:
|
||||
|
||||
- name: Get short SHA
|
||||
id: slug
|
||||
shell: pwsh
|
||||
env:
|
||||
SOURCE_REVISION: ${{ steps.source.outputs.revision }}
|
||||
run: echo "sha8=$($env:SOURCE_REVISION.Substring(0,8))" >> $env:GITHUB_OUTPUT
|
||||
shell: powershell
|
||||
run: echo "sha8=$('${{ github.sha }}'.Substring(0,8))" >> $env:GITHUB_OUTPUT
|
||||
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: retroarch-${{ matrix.version }}-${{ matrix.configuration }}-${{ matrix.platform }}-${{ steps.slug.outputs.sha8 }}
|
||||
path: |
|
||||
pkg/msvc/${{ matrix.platform }}/${{ matrix.configuration }}/RetroArch-msvc${{ matrix.version }}.exe
|
||||
|
||||
- name: Checkout packaging tools from the workflow revision
|
||||
if: matrix.configuration == 'Release'
|
||||
uses: actions/checkout@v5
|
||||
with:
|
||||
ref: ${{ github.workflow_sha }}
|
||||
path: .arm64-packaging
|
||||
sparse-checkout: pkg/msvc/packaging
|
||||
|
||||
- name: Checkout pinned Ozone assets
|
||||
if: matrix.configuration == 'Release'
|
||||
uses: actions/checkout@v5
|
||||
with:
|
||||
repository: libretro/retroarch-assets
|
||||
ref: 73106363e14e34c08a5854b4cfbc29f184e3b783
|
||||
path: .arm64-assets
|
||||
sparse-checkout: |
|
||||
ozone
|
||||
pkg
|
||||
fonts
|
||||
|
||||
- name: Checkout pinned core information
|
||||
if: matrix.configuration == 'Release'
|
||||
uses: actions/checkout@v5
|
||||
with:
|
||||
repository: libretro/libretro-super
|
||||
ref: 0532bd63ce3072f50ca4f4ada40d42aef565bd3f
|
||||
path: .arm64-info
|
||||
sparse-checkout: dist/info
|
||||
|
||||
- name: Verify and package ARM64 frontend
|
||||
if: matrix.configuration == 'Release'
|
||||
shell: pwsh
|
||||
env:
|
||||
SOURCE_REVISION: ${{ steps.source.outputs.revision }}
|
||||
run: |
|
||||
./.arm64-packaging/pkg/msvc/packaging/package-arm64.ps1 `
|
||||
-Executable ./pkg/msvc/ARM64/Release/RetroArch-msvc2022.exe `
|
||||
-SourceDirectory . -AssetsDirectory ./.arm64-assets -InfoDirectory ./.arm64-info `
|
||||
-OutputDirectory ./arm64-dist -SourceRevision $env:SOURCE_REVISION `
|
||||
-Repository $env:GITHUB_REPOSITORY `
|
||||
-AssetsRevision 73106363e14e34c08a5854b4cfbc29f184e3b783 `
|
||||
-InfoRevision 0532bd63ce3072f50ca4f4ada40d42aef565bd3f
|
||||
|
||||
- name: Upload ARM64 release package
|
||||
if: matrix.configuration == 'Release'
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: retroarch-windows-arm64-package
|
||||
path: |
|
||||
arm64-dist/RetroArch-WinARM64.zip
|
||||
arm64-dist/RetroArch-WinARM64.zip.sha256
|
||||
if-no-files-found: error
|
||||
|
||||
upload-release:
|
||||
if: github.event_name == 'release' || (github.event_name == 'workflow_dispatch' && inputs.publish_release == true)
|
||||
needs: msvc
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 10
|
||||
permissions:
|
||||
contents: write
|
||||
steps:
|
||||
- uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: retroarch-windows-arm64-package
|
||||
path: arm64-dist
|
||||
|
||||
- name: Attach ARM64 package to the existing release
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
RELEASE_TAG: ${{ needs.msvc.outputs.release_tag }}
|
||||
run: |
|
||||
gh release view "$RELEASE_TAG" --repo "$GITHUB_REPOSITORY" > /dev/null
|
||||
gh release upload "$RELEASE_TAG" --repo "$GITHUB_REPOSITORY" --clobber \
|
||||
arm64-dist/RetroArch-WinARM64.zip arm64-dist/RetroArch-WinARM64.zip.sha256
|
||||
@@ -22,7 +22,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 30
|
||||
container:
|
||||
image: reallibretroretroarch/libretro-build-mxe-win32-cross:gcc16
|
||||
image: reallibretroretroarch/libretro-build-mxe-win32-cross:gcc10
|
||||
options: --user root
|
||||
|
||||
steps:
|
||||
|
||||
@@ -22,7 +22,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 30
|
||||
container:
|
||||
image: reallibretroretroarch/libretro-build-mxe-win64-cross:gcc16
|
||||
image: reallibretroretroarch/libretro-build-mxe-win64-cross:gcc10
|
||||
options: --user root
|
||||
|
||||
steps:
|
||||
|
||||
@@ -1,141 +0,0 @@
|
||||
name: CI procbarrier tiers
|
||||
|
||||
# retro_procbarrier picks one of several ways to force a memory barrier
|
||||
# on every running thread, and which one depends on the OS and the CPU.
|
||||
# The Linux samples job only ever runs the one its runner picks
|
||||
# (membarrier). This runs every tier each platform has, on a multi-core
|
||||
# runner so the fence checks can actually catch a missing barrier, and
|
||||
# fails if a forced tier quietly fell back to another one.
|
||||
#
|
||||
# Linux x86_64 membarrier, page-flip, signal
|
||||
# Linux arm64 membarrier, signal
|
||||
# Windows x64 FlushProcessWriteBuffers, page-flip
|
||||
# Windows arm64 FlushProcessWriteBuffers
|
||||
# macOS arm64 signal
|
||||
# macOS x86_64 page-flip, signal
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [master]
|
||||
paths:
|
||||
- 'libretro-common/rthreads/**'
|
||||
- 'libretro-common/include/rthreads/**'
|
||||
- 'libretro-common/include/retro_atomic.h'
|
||||
- 'libretro-common/samples/rthreads/retro_procbarrier_test/**'
|
||||
- 'libretro-common/samples/rthreads/retro_asym_eventcount_test/**'
|
||||
- '.github/workflows/procbarrier-tiers.yml'
|
||||
pull_request:
|
||||
paths:
|
||||
- 'libretro-common/rthreads/**'
|
||||
- 'libretro-common/include/rthreads/**'
|
||||
- 'libretro-common/include/retro_atomic.h'
|
||||
- 'libretro-common/samples/rthreads/retro_procbarrier_test/**'
|
||||
- 'libretro-common/samples/rthreads/retro_asym_eventcount_test/**'
|
||||
- '.github/workflows/procbarrier-tiers.yml'
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
|
||||
cancel-in-progress: ${{ github.ref != 'refs/heads/master' }}
|
||||
|
||||
jobs:
|
||||
posix:
|
||||
name: ${{ matrix.name }}
|
||||
runs-on: ${{ matrix.os }}
|
||||
timeout-minutes: 15
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- name: linux-x86_64
|
||||
os: ubuntu-latest
|
||||
libs: -lpthread -lrt
|
||||
tiers: natural=membarrier membarrier=membarrier pageflip=page-flip signal=signal
|
||||
- name: linux-arm64
|
||||
os: ubuntu-24.04-arm
|
||||
libs: -lpthread -lrt
|
||||
tiers: natural=membarrier membarrier=membarrier signal=signal
|
||||
- name: macos-arm64
|
||||
os: macos-latest
|
||||
libs: -lpthread
|
||||
tiers: natural=signal signal=signal
|
||||
- name: macos-x86_64
|
||||
os: macos-15-intel
|
||||
libs: -lpthread
|
||||
tiers: natural=page-flip pageflip=page-flip signal=signal
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
|
||||
- name: Build
|
||||
working-directory: libretro-common
|
||||
run: |
|
||||
set -e
|
||||
cc -std=gnu99 -O1 -g -Wall -DHAVE_THREADS -Iinclude \
|
||||
samples/rthreads/retro_procbarrier_test/retro_procbarrier_test.c \
|
||||
rthreads/retro_procbarrier.c rthreads/rthreads.c \
|
||||
-o retro_procbarrier_test ${{ matrix.libs }}
|
||||
cc -std=gnu99 -O1 -g -Wall -DHAVE_THREADS -Iinclude \
|
||||
samples/rthreads/retro_asym_eventcount_test/retro_asym_eventcount_test.c \
|
||||
rthreads/retro_asym_eventcount.c \
|
||||
rthreads/retro_procbarrier.c rthreads/rthreads.c \
|
||||
-o retro_asym_eventcount_test ${{ matrix.libs }}
|
||||
|
||||
- name: Run every tier
|
||||
working-directory: libretro-common
|
||||
env:
|
||||
REQUIRE_SMP: 1
|
||||
run: |
|
||||
bash samples/rthreads/retro_procbarrier_test/run_tiers.sh \
|
||||
./retro_procbarrier_test ./retro_asym_eventcount_test \
|
||||
${{ matrix.tiers }}
|
||||
|
||||
windows:
|
||||
name: ${{ matrix.name }}
|
||||
runs-on: ${{ matrix.os }}
|
||||
timeout-minutes: 15
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- name: windows-x64
|
||||
os: windows-2022
|
||||
vcvars: vcvars64.bat
|
||||
tiers: natural=FlushProcessWriteBuffers fpwb=FlushProcessWriteBuffers pageflip=page-flip
|
||||
- name: windows-arm64
|
||||
os: windows-11-arm
|
||||
vcvars: vcvarsarm64.bat
|
||||
tiers: natural=FlushProcessWriteBuffers fpwb=FlushProcessWriteBuffers
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
|
||||
- name: Build
|
||||
shell: cmd
|
||||
working-directory: libretro-common
|
||||
run: |
|
||||
for /f "usebackq tokens=*" %%i in (`"%ProgramFiles(x86)%\Microsoft Visual Studio\Installer\vswhere.exe" -latest -products * -property installationPath`) do set VSPATH=%%i
|
||||
if not defined VSPATH exit /b 1
|
||||
call "%VSPATH%\VC\Auxiliary\Build\${{ matrix.vcvars }}"
|
||||
cl /nologo /W3 /O1 /DHAVE_THREADS /Iinclude ^
|
||||
samples\rthreads\retro_procbarrier_test\retro_procbarrier_test.c ^
|
||||
rthreads\retro_procbarrier.c rthreads\rthreads.c ^
|
||||
/Fe:retro_procbarrier_test.exe
|
||||
if errorlevel 1 exit /b 1
|
||||
cl /nologo /W3 /O1 /DHAVE_THREADS /Iinclude ^
|
||||
samples\rthreads\retro_asym_eventcount_test\retro_asym_eventcount_test.c ^
|
||||
rthreads\retro_asym_eventcount.c ^
|
||||
rthreads\retro_procbarrier.c rthreads\rthreads.c ^
|
||||
/Fe:retro_asym_eventcount_test.exe
|
||||
if errorlevel 1 exit /b 1
|
||||
|
||||
- name: Run every tier
|
||||
shell: bash
|
||||
working-directory: libretro-common
|
||||
env:
|
||||
REQUIRE_SMP: 1
|
||||
run: |
|
||||
bash samples/rthreads/retro_procbarrier_test/run_tiers.sh \
|
||||
./retro_procbarrier_test.exe ./retro_asym_eventcount_test.exe \
|
||||
${{ matrix.tiers }}
|
||||
@@ -26,7 +26,7 @@ jobs:
|
||||
timeout-minutes: 30
|
||||
steps:
|
||||
- name: Dependencies
|
||||
timeout-minutes: 20
|
||||
timeout-minutes: 5
|
||||
run: |
|
||||
# libvulkan-dev brings the Vulkan driver, the slang shader stack
|
||||
# (shader_vulkan.c, slang_process.c, glslang_util.c, slang_cache.c)
|
||||
@@ -43,23 +43,6 @@ jobs:
|
||||
echo "apt-get update failed (attempt $i); retrying in 10s"
|
||||
sleep 10
|
||||
done
|
||||
# Fetch first, under a bound: a download is safe to stop and
|
||||
# resumes, an install is not. A mirror that stalls is left for
|
||||
# the main archive on the next attempt.
|
||||
for i in 1 2 3; do
|
||||
sudo timeout 240 apt-get install -y --download-only \
|
||||
-o Acquire::Retries=3 -o Acquire::http::Timeout=30 \
|
||||
$pkgs && break
|
||||
[ $i = 3 ] && exit 1
|
||||
echo "package download failed or stalled (attempt $i); retrying from archive.ubuntu.com"
|
||||
for f in /etc/apt/sources.list /etc/apt/sources.list.d/*; do
|
||||
[ -f "$f" ] || continue
|
||||
sudo sed -i -e 's#mirror+file:/etc/apt/apt-mirrors.txt#http://archive.ubuntu.com/ubuntu/#g' \
|
||||
-e 's#http://azure\.archive\.ubuntu\.com/ubuntu#http://archive.ubuntu.com/ubuntu#g' "$f"
|
||||
done
|
||||
sudo timeout 90 apt-get update -y \
|
||||
-o Acquire::Retries=3 -o Acquire::http::Timeout=30 || true
|
||||
done
|
||||
sudo apt-get install -y \
|
||||
-o Acquire::Retries=3 -o Acquire::http::Timeout=30 \
|
||||
$pkgs
|
||||
|
||||
@@ -19,29 +19,10 @@ cd "$(dirname "$0")/../../.."
|
||||
CC=${CC:-gcc}
|
||||
INC="-I. -Ilibretro-common/include -Ideps -Ideps/rcheevos/include -Iinput/include"
|
||||
WARN="-Wall -Wno-unused-function -Werror=implicit-function-declaration"
|
||||
# gcc 14 rejects these outright and older gcc only warns; a lane on
|
||||
# an older compiler has to reject them too, or a mismatched callback
|
||||
# passes here and fails on the toolchains that build the platform.
|
||||
WARN="$WARN -Werror=incompatible-pointer-types -Werror=int-conversion"
|
||||
BASE="-DRARCH_INTERNAL -DHAVE_AUDIOMIXER -DHAVE_THREADS -DHAVE_CONFIGFILE -DHAVE_MENU"
|
||||
# A console lane that keeps the host identity takes the Linux branch
|
||||
# of every #if ladder and proves nothing, so this sits above the
|
||||
# lanes rather than among them.
|
||||
HOSTOFF="-U__linux__ -U__gnu_linux__ -Ulinux -U__unix__ -U__unix -Uunix"
|
||||
|
||||
fail=0
|
||||
|
||||
# A failing compile's output, errors first: a file with a page of
|
||||
# warnings ahead of its one error pushed the error past the cut, which
|
||||
# left a failing lane reporting nothing to act on.
|
||||
show_out() {
|
||||
if echo "$1" | grep -q ": error: "; then
|
||||
echo "$1" | grep ": error: " | sed 's/^/ /' | head -8
|
||||
else
|
||||
echo "$1" | sed 's/^/ /' | head -12
|
||||
fi
|
||||
}
|
||||
|
||||
check() {
|
||||
name="$1"; shift
|
||||
defs="$1"; shift
|
||||
@@ -50,7 +31,7 @@ check() {
|
||||
if ! out=$($CC $WARN $INC $BASE $defs -fsyntax-only "$tu" 2>&1); then
|
||||
echo "FAIL $name"
|
||||
echo " $tu"
|
||||
show_out "$out"
|
||||
echo "$out" | sed 's/^/ /' | head -12
|
||||
fail=1; bad=1
|
||||
fi
|
||||
done
|
||||
@@ -117,16 +98,14 @@ ARMWARN="$WARN -Wno-deprecated-declarations"
|
||||
# a lane whose headers are absent says so and is not a failure.
|
||||
platform_video() {
|
||||
name="$1"; defs="$2"; extra="$3"; tu="$4"; probe="$5"
|
||||
for pr in $probe; do
|
||||
if [ ! -f "$pr" ]; then
|
||||
echo "skip $name (no $pr)"
|
||||
return
|
||||
fi
|
||||
done
|
||||
if [ -n "$probe" ] && [ ! -f "$probe" ]; then
|
||||
echo "skip $name (no $probe)"
|
||||
return
|
||||
fi
|
||||
if ! out=$($CC $WARN $extra $INC $BASE $defs -fsyntax-only "$tu" 2>&1); then
|
||||
echo "FAIL $name"
|
||||
echo " $tu"
|
||||
show_out "$out"
|
||||
printf '%s\n' "$out" | sed 's/^/ /' | head -10
|
||||
fail=1
|
||||
else
|
||||
echo "ok $name"
|
||||
@@ -161,118 +140,6 @@ arm "ps3" ps3 "-D__PSL1GHT__ -DHAVE_MEMINFO"
|
||||
arm "ps2" "" "-DPS2"
|
||||
arm "emscripten" emscripten "-D__EMSCRIPTEN__"
|
||||
|
||||
# The consoles that carry the cleanroom crypto, keychain and TLS client
|
||||
# (3DS, Vita, Switch), each in its own shape with the SDK stand-ins the
|
||||
# random source and the socket layer need. Vita's and the 3DS's socket
|
||||
# layers are the SDKs' own, so the TLS client itself is compiled only
|
||||
# in the Switch shape here; those two builds are their workflows' to
|
||||
# link.
|
||||
CRYPTO_SET="libretro-common/crypto/crypto.c libretro-common/crypto/kdf.c libretro-common/crypto/pk.c libretro-common/crypto/x509.c libretro-common/file/keychain.c"
|
||||
TLS_SET="$CRYPTO_SET libretro-common/net/net_socket_ssl_retro.c network/tls_log.c"
|
||||
CONSOLE_NET="-DHAVE_CRYPTO -DHAVE_KEYCHAIN -DHAVE_SSL -DHAVE_RETROSSL -DHAVE_NETWORKING -DHAVE_THREADS -DHAVE_CONFIGFILE -DRARCH_INTERNAL"
|
||||
console_set() {
|
||||
cs_name="$1"; cs_inc="$2"; cs_defs="$3"; shift 3
|
||||
for cs_tu in "$@"; do
|
||||
check "$cs_name: $(basename $cs_tu)" "$HOSTOFF $cs_inc $CONSOLE_NET $cs_defs $CDECL" "$cs_tu"
|
||||
done
|
||||
}
|
||||
console_set "3ds crypto" "-Itools/platform_stubs/ctr" "-D_3DS -DARM11" $CRYPTO_SET
|
||||
console_set "vita crypto" "-Itools/platform_stubs/vita" "-DVITA" $CRYPTO_SET
|
||||
console_set "switch tls" "-I$STUBS/libnx" "-DHAVE_LIBNX -DSWITCH -D__SWITCH__" $TLS_SET
|
||||
# The built-in NFS client, which the 3DS, Vita, Switch and Wii U
|
||||
# build, under the defines their Makefiles set (RARCH_CONSOLE for the
|
||||
# console sleep and the 3DS transfer cap, Vita's legacy socket layer):
|
||||
# Wii U against its own wiiu/include, Vita against stubs of vitasdk's
|
||||
# socket headers.
|
||||
NFS_SET="libretro-common/net/net_nfs3.c libretro-common/vfs/vfs_implementation_nfs.c libretro-common/vfs/vfs_prefetch.c"
|
||||
NFSD="-DHAVE_RETRONFS -DHAVE_NFSCLIENT"
|
||||
console_set "3ds nfs" "-Itools/platform_stubs/ctr $NFSD" "-D_3DS -DARM11 -DRARCH_CONSOLE" $NFS_SET
|
||||
console_set "vita nfs" "-Itools/platform_stubs/vita $NFSD" "-DVITA -DRARCH_CONSOLE -DHAVE_SOCKET_LEGACY" $NFS_SET
|
||||
console_set "switch nfs" "-I$STUBS/libnx $NFSD" "-DHAVE_LIBNX -DSWITCH -D__SWITCH__ -DRARCH_CONSOLE" $NFS_SET
|
||||
console_set "wiiu nfs" "-Iwiiu/include -Itools/platform_stubs/wiiu $NFSD" "-DWIIU -D__WUT__ -DHW_WUP -D__wiiu__ -DRARCH_CONSOLE" $NFS_SET
|
||||
# The built-in SMB and Kerberos clients, which the 3DS, Vita and Switch
|
||||
# build on their crypto (the Wii U has no kernel RNG for it to draw on).
|
||||
SMB_SET="libretro-common/net/net_smb2.c libretro-common/net/net_krb5.c libretro-common/vfs/vfs_implementation_smb.c"
|
||||
SMBD="-DHAVE_RETROSMB -DHAVE_SMBCLIENT"
|
||||
console_set "3ds smb" "-Itools/platform_stubs/ctr $SMBD" "-D_3DS -DARM11 -DRARCH_CONSOLE" $SMB_SET
|
||||
console_set "vita smb" "-Itools/platform_stubs/vita $SMBD" "-DVITA -DRARCH_CONSOLE -DHAVE_SOCKET_LEGACY" $SMB_SET
|
||||
console_set "switch smb" "-I$STUBS/libnx $SMBD" "-DHAVE_LIBNX -DSWITCH -D__SWITCH__ -DRARCH_CONSOLE" $SMB_SET
|
||||
|
||||
# The network stack in the old MSVC projects (2005 - 2017), which no job
|
||||
# here builds: 32-bit Windows, C89, at the Windows floors those projects
|
||||
# target - VS2005's _WIN32_WINNT 0x0410 and XP's 0x0501. The stack keeps
|
||||
# to what those compilers and SDKs have: C89, the LL/ULL suffixes code
|
||||
# they already build uses, Windows entry points resolved at run time,
|
||||
# and a CA bundle split into parts under MSVC's 64 KiB string limit.
|
||||
MSVC_OLD_CC=${MSVC_OLD_CC:-i686-w64-mingw32-gcc}
|
||||
if command -v "$MSVC_OLD_CC" > /dev/null 2>&1; then
|
||||
MSVC_OLD_STACK="$CRYPTO_SET libretro-common/net/net_socket_ssl_retro.c network/tls_log.c libretro-common/net/net_smb2.c libretro-common/net/net_krb5.c libretro-common/net/net_nfs3.c libretro-common/vfs/vfs_implementation_smb.c libretro-common/vfs/vfs_implementation_nfs.c libretro-common/vfs/vfs_prefetch.c"
|
||||
MSVC_OLD_DEFS="-DRARCH_INTERNAL -DHAVE_NETWORKING -DHAVE_THREADS -DHAVE_CONFIGFILE -DHAVE_CRYPTO -DHAVE_KEYCHAIN -DHAVE_SSL -DHAVE_RETROSSL -DHAVE_SMBCLIENT -DHAVE_RETROSMB -DHAVE_NFSCLIENT -DHAVE_RETRONFS -DLEGACY_WIN32_NO_MIGRATION_NOTE"
|
||||
for winnt in 0x0410 0x0501; do
|
||||
mo_bad=0
|
||||
for tu in $MSVC_OLD_STACK; do
|
||||
if ! out=$($MSVC_OLD_CC -std=c89 -ansi -pedantic -Werror=pedantic -Werror=declaration-after-statement -Wno-long-long -Wno-variadic-macros -Werror=implicit-function-declaration -Wall -Wno-unused-function -Wno-overlength-strings \
|
||||
-I. -Ilibretro-common/include -D_WIN32_WINNT=$winnt $MSVC_OLD_DEFS -fsyntax-only "$tu" 2>&1) \
|
||||
|| echo "$out" | grep -q "warning:"; then
|
||||
echo "FAIL msvc-old network stack, _WIN32_WINNT=$winnt"
|
||||
echo " $tu"
|
||||
show_out "$out"
|
||||
fail=1; mo_bad=1
|
||||
fi
|
||||
done
|
||||
[ "$mo_bad" = 1 ] || echo "ok msvc-old network stack, _WIN32_WINNT=$winnt"
|
||||
done
|
||||
# and no single string literal in the CA bundle reaches MSVC's limit
|
||||
if python3 - << 'PYEOF'
|
||||
import re, sys
|
||||
t = open("libretro-common/net/cacert.h", encoding="latin-1").read()
|
||||
for body in re.findall(r"\[\] = \{(.*?)\};", t, re.S):
|
||||
lits = re.findall(r'"((?:[^"\\]|\\.)*)"', body)
|
||||
size = sum(len(l.encode("latin-1").decode("unicode_escape")) for l in lits) + len(lits)
|
||||
if size >= 65535:
|
||||
sys.exit(1)
|
||||
PYEOF
|
||||
then
|
||||
echo "ok cacert.h parts under MSVC's 64 KiB string limit"
|
||||
else
|
||||
echo "FAIL cacert.h has a part at or over MSVC's 64 KiB string limit"; fail=1
|
||||
fi
|
||||
else
|
||||
echo "skip msvc-old network stack (no $MSVC_OLD_CC)"
|
||||
fi
|
||||
|
||||
# Every platform makefile on Makefile.common, without its toolchain: the
|
||||
# graph's own DEFINES checked for consistency (TLS, SMB, NFS need the
|
||||
# networking they are built on) and the frontend compiled under exactly
|
||||
# them. Makefile.retrofw asked for the TLS client with no networking
|
||||
# and only its own workflow saw the undefined reference.
|
||||
pm_out=$(tools/platform_makefile_check.sh 2>&1); pm_rc=$?
|
||||
printf '%s\n' "$pm_out" | sed 's/^ok /ok platform makefile: /'
|
||||
[ $pm_rc -eq 0 ] || { echo "FAIL platform makefile check"; fail=1; }
|
||||
|
||||
# What each console Makefile actually builds, from its own object list:
|
||||
# no MCP server on any console (a desktop HAVE_MCP option; listed once
|
||||
# with the networking objects it broke the 3DS), and no crypto library
|
||||
# on the 24/32 MiB machines.
|
||||
co_out=$(tools/console_objects_check.sh 2>&1); co_rc=$?
|
||||
printf '%s\n' "$co_out" | sed 's/^ok /ok console objects: /'
|
||||
[ $co_rc -eq 0 ] || { echo "FAIL console object check"; fail=1; }
|
||||
|
||||
# The OpenDingux family builds with a MIPS toolchain; the crypto and the
|
||||
# TLS client compile with a MIPS cross compiler when one is installed.
|
||||
MIPSEL_CC=${MIPSEL_CC:-mipsel-linux-gnu-gcc}
|
||||
if command -v "$MIPSEL_CC" >/dev/null 2>&1; then
|
||||
for tu in $TLS_SET; do
|
||||
if out=$($MIPSEL_CC -march=mips32r2 $INC $CONSOLE_NET -DDINGUX -D_GNU_SOURCE -Wall -Werror -Wno-unused-function -fsyntax-only "$tu" 2>&1); then
|
||||
echo "ok dingux mips: $(basename $tu)"
|
||||
else
|
||||
echo "FAIL dingux mips: $(basename $tu)"; show_out "$out"; fail=1
|
||||
fi
|
||||
done
|
||||
else
|
||||
echo "skip dingux mips (no $MIPSEL_CC)"
|
||||
fi
|
||||
|
||||
platform_video "odroidgo2 video" \
|
||||
"-DHAVE_ODROIDGO2 -DHAVE_OPENGL -DHAVE_GLSL" "" \
|
||||
gfx/drivers/gl2.c ""
|
||||
@@ -281,192 +148,14 @@ platform_video "gx video" "-DGEKKO -DHW_RVL" "-I$STUBS/gx" \
|
||||
platform_video "switch video" \
|
||||
"-DHAVE_LIBNX -DSWITCH -D__SWITCH__" "-I$STUBS/libnx" \
|
||||
gfx/drivers/switch_nx_gfx.c ""
|
||||
# The PSP driver folds its VRAM and cache-alias addresses into pointers,
|
||||
# which a 64-bit host narrows; the arithmetic is 32-bit on the target.
|
||||
platform_video "psp1 video" "-DPSP" \
|
||||
"-Itools/platform_stubs/psp -Wno-pointer-to-int-cast -Wno-int-to-pointer-cast \
|
||||
-Wdeclaration-after-statement -Werror=declaration-after-statement" \
|
||||
gfx/drivers/psp1_gfx.c ""
|
||||
# The Vita driver, against psp2 stubs carrying the SceGxm declarations it
|
||||
# names in the shapes the real headers give them. Same narrowing as the
|
||||
# PSP driver above: its pool addresses are 32-bit on the target.
|
||||
platform_video "gxm video" "-DVITA -DRARCH_CONSOLE $HOSTOFF" \
|
||||
"-Itools/platform_stubs/vita -Wno-pointer-to-int-cast -Wno-int-to-pointer-cast" \
|
||||
gfx/drivers/gxm_gfx.c ""
|
||||
# gl1 on Vita, against a vitaGL stub over the host GL headers. The
|
||||
# driver's Vita branches are compiled nowhere else, and one of them
|
||||
# carried a declaration after a statement that only the Vita job saw.
|
||||
platform_video "vita gl1 video" \
|
||||
"-DVITA -DRARCH_CONSOLE -DHAVE_OPENGL1 -DHAVE_OVERLAY -DHAVE_GFX_WIDGETS $HOSTOFF" \
|
||||
"-Itools/platform_stubs/vita -Wdeclaration-after-statement \
|
||||
-Werror=declaration-after-statement" \
|
||||
gfx/drivers/gl1.c /usr/include/GL/gl.h
|
||||
# The PS2 driver, against gsKit and PS2SDK stubs carrying what it
|
||||
# names in the shapes the real headers give them. Makefile.ps2 turns on
|
||||
# the window offset, which the driver reads. This lane is also the only
|
||||
# one that compiles retro_atomic.h's EE interrupt-mask backend, which
|
||||
# no other platform selects.
|
||||
platform_video "ps2 video" \
|
||||
"-DPS2 -DRARCH_CONSOLE -DHAVE_WINDOW_OFFSET -DHAVE_RGUI $HOSTOFF" \
|
||||
"-Itools/platform_stubs/ps2" gfx/drivers/ps2_gfx.c ""
|
||||
# The DOS VGA driver, against DJGPP stubs for the DPMI and port I/O it
|
||||
# uses. Only the DJGPP job compiled it before; DJGPP defines __unix__.
|
||||
platform_video "dos vga video" \
|
||||
"-D__DJGPP__ -DDJGPP -DRARCH_CONSOLE $HOSTOFF -D__unix__" \
|
||||
"-Itools/platform_stubs/dos -Wdeclaration-after-statement \
|
||||
-Werror=declaration-after-statement" \
|
||||
gfx/drivers/vga_gfx.c ""
|
||||
# The two SDL video drivers. Nothing else here compiled them, which is
|
||||
# how a field they read through video_info_t went on being read after it
|
||||
# had been packed away. Each skips where its headers are absent, as the
|
||||
# dingux lanes below do.
|
||||
platform_video "sdl2 video" "-DHAVE_SDL2" "-I/usr/include/SDL2" \
|
||||
gfx/drivers/sdl2_gfx.c /usr/include/SDL2/SDL.h
|
||||
platform_video "sdl3 video" "-DHAVE_SDL3" "-I/usr/include/SDL3" \
|
||||
gfx/drivers/sdl3_gfx.c /usr/include/SDL3/SDL.h
|
||||
platform_video "dingux video" "-DDINGUX" "-I/usr/include/SDL" \
|
||||
gfx/drivers/sdl_dingux_gfx.c /usr/include/SDL/SDL.h
|
||||
platform_video "rs90 video" "-DDINGUX -DRS90" "-I/usr/include/SDL" \
|
||||
gfx/drivers/sdl_rs90_gfx.c /usr/include/SDL/SDL.h
|
||||
# libdrm's exynos module and its G2D headers, which distros ship only
|
||||
# where the hardware exists. A runner building libdrm with -Dexynos=true
|
||||
# turns this on.
|
||||
platform_video "wiiu gx2 video" "-DWIIU $HOSTOFF" \
|
||||
"-Iwiiu/include -Iwiiu" gfx/drivers/gx2_gfx.c ""
|
||||
platform_video "hub75 video" "" "" gfx/drivers/hub75_gfx.c ""
|
||||
platform_video "openvg video" "-DHAVE_VG -DHAVE_EGL" \
|
||||
"-Itools/platform_stubs/openvg" gfx/drivers/vg.c ""
|
||||
platform_video "exynos video" "-DHAVE_EXYNOS" "-I/usr/include/libdrm" \
|
||||
gfx/drivers/exynos_gfx.c /usr/include/exynos/exynos_fimg2d.h
|
||||
|
||||
# The Direct3D drivers, against the DirectX headers the tree vendors in
|
||||
# gfx/include/dxsdk. Six drivers no other lane compiles, and they read
|
||||
# the same frame and draw descriptors every other video driver does.
|
||||
#
|
||||
# A mingw-w64 cross compiler is all these need beyond the tree; where
|
||||
# there is none the lanes say so rather than fail.
|
||||
MINGW_CC=${MINGW_CC:-x86_64-w64-mingw32-gcc}
|
||||
d3d_video() {
|
||||
name="$1"; defs="$2"; tu="$3"
|
||||
if ! command -v "$MINGW_CC" > /dev/null 2>&1; then
|
||||
echo "skip $name (no $MINGW_CC)"
|
||||
return
|
||||
fi
|
||||
if ! out=$($MINGW_CC $WARN -isystemgfx/include/dxsdk $INC $BASE $defs \
|
||||
-fsyntax-only "$tu" 2>&1); then
|
||||
echo "FAIL $name"
|
||||
echo " $tu"
|
||||
show_out "$out"
|
||||
fail=1
|
||||
else
|
||||
echo "ok $name"
|
||||
fi
|
||||
}
|
||||
|
||||
D3DDEFS="-DHAVE_D3D -DHAVE_RGUI -DHAVE_OVERLAY"
|
||||
d3d_video "d3d8 video" "$D3DDEFS -DHAVE_D3D8" gfx/drivers/d3d8.c
|
||||
d3d_video "d3d9 video, Cg" "$D3DDEFS -DHAVE_D3D9" gfx/drivers/d3d9cg.c
|
||||
d3d_video "d3d9 video, HLSL" "$D3DDEFS -DHAVE_D3D9" gfx/drivers/d3d9hlsl.c
|
||||
d3d_video "d3d10 video" "$D3DDEFS -DHAVE_D3D10" gfx/drivers/d3d10.c
|
||||
d3d_video "d3d11 video" "$D3DDEFS -DHAVE_D3D11" gfx/drivers/d3d11.c
|
||||
d3d_video "d3d12 video" "$D3DDEFS -DHAVE_D3D12" gfx/drivers/d3d12.c
|
||||
d3d_video "gdi video" "-DHAVE_RGUI -DHAVE_OVERLAY -DHAVE_GDI" \
|
||||
gfx/drivers/gdi_gfx.c
|
||||
|
||||
# The context drivers, which no job here compiles either. Each answers
|
||||
# the frontend's window and size questions, so a change to what those
|
||||
# hand back reaches all of them at once. Every lane names the headers it
|
||||
# wants and says so when the runner has none.
|
||||
platform_video "ctx: null" "" "" gfx/drivers_context/gfx_null_ctx.c ""
|
||||
platform_video "ctx: x11 gl" "-DHAVE_X11 -DHAVE_OPENGL -DHAVE_EGL" "" \
|
||||
gfx/drivers_context/x_ctx.c /usr/include/X11/Xlib.h
|
||||
platform_video "ctx: x11 egl" "-DHAVE_X11 -DHAVE_EGL -DHAVE_OPENGLES" "" \
|
||||
gfx/drivers_context/xegl_ctx.c /usr/include/X11/Xlib.h
|
||||
platform_video "ctx: x11 vulkan" "-DHAVE_VULKAN -DHAVE_X11 -DHAVE_XCB" "" \
|
||||
gfx/drivers_context/x_vk_ctx.c \
|
||||
"/usr/include/vulkan/vulkan.h /usr/include/X11/Xlib-xcb.h"
|
||||
platform_video "ctx: khr display" "-DHAVE_VULKAN" "" \
|
||||
gfx/drivers_context/khr_display_ctx.c /usr/include/vulkan/vulkan.h
|
||||
platform_video "ctx: kms/gbm" "-DHAVE_EGL -DHAVE_OPENGL -DHAVE_KMS -DHAVE_GBM" \
|
||||
"-I/usr/include/libdrm" gfx/drivers_context/drm_ctx.c /usr/include/gbm.h
|
||||
# vivante_fbdev is left out: it calls Vivante's own fbCreateWindow and
|
||||
# fbGetDisplayByIndex, which only that vendor's EGL headers declare, and
|
||||
# a lane that let those go implicit would pass what the SDK rejects.
|
||||
platform_video "ctx: mali fbdev" "-DHAVE_EGL -DHAVE_OPENGLES" "" \
|
||||
gfx/drivers_context/mali_fbdev_ctx.c /usr/include/EGL/egl.h
|
||||
platform_video "ctx: opendingux fbdev" "-DHAVE_EGL -DHAVE_OPENGLES -DDINGUX" "" \
|
||||
gfx/drivers_context/opendingux_fbdev_ctx.c /usr/include/EGL/egl.h
|
||||
platform_video "ctx: sdl1 gl" "-DHAVE_SDL -DHAVE_OPENGL" "-I/usr/include/SDL" \
|
||||
gfx/drivers_context/sdl1_gl_ctx.c /usr/include/SDL/SDL.h
|
||||
platform_video "ctx: sdl2 gl" "-DHAVE_SDL2 -DHAVE_OPENGL" "-I/usr/include/SDL2" \
|
||||
gfx/drivers_context/sdl2_gl_ctx.c /usr/include/SDL2/SDL.h
|
||||
# osmesa is the software context a headless build selects, and
|
||||
# qb/config.libs.sh can enable it from a pkg-config probe alone.
|
||||
platform_video "ctx: osmesa" "-DHAVE_OSMESA -DHAVE_OPENGL" "" \
|
||||
gfx/drivers_context/osmesa_ctx.c /usr/include/GL/osmesa.h
|
||||
# The Wayland protocol headers are generated into gfx/common/wayland by
|
||||
# configure, so these lanes run only in a tree that has been configured
|
||||
# with Wayland.
|
||||
WL_H="/usr/include/wayland-client.h gfx/common/wayland/content-type-v1.h"
|
||||
platform_video "ctx: wayland gl" "-DHAVE_WAYLAND -DHAVE_EGL -DHAVE_OPENGL" "" \
|
||||
gfx/drivers_context/wayland_ctx.c "$WL_H"
|
||||
platform_video "ctx: wayland vulkan" "-DHAVE_WAYLAND -DHAVE_VULKAN" "" \
|
||||
gfx/drivers_context/wayland_vk_ctx.c "$WL_H /usr/include/vulkan/vulkan.h"
|
||||
# The shared Wayland code the two contexts above call into. It was the
|
||||
# only Wayland unit no lane compiled, which is how four declarations
|
||||
# after a statement sat in it. Its libdecor half is a second lane: the
|
||||
# function pointers that half calls through are declared under
|
||||
# HAVE_LIBDECOR_H *and* HAVE_DYLIB, so a lane naming only the first
|
||||
# proves nothing, and the header libdecor.h itself comes from a
|
||||
# libdecor-0 include directory the probe checks for.
|
||||
WLC89="-Wdeclaration-after-statement -Werror=declaration-after-statement"
|
||||
platform_video "wayland common" "-DHAVE_WAYLAND -DHAVE_EGL -DHAVE_OPENGL" \
|
||||
"$WLC89" gfx/common/wayland_common.c "$WL_H"
|
||||
platform_video "wayland common: libdecor" \
|
||||
"-DHAVE_WAYLAND -DHAVE_EGL -DHAVE_OPENGL -DHAVE_LIBDECOR_H -DHAVE_DYLIB" \
|
||||
"-I/usr/include/libdecor-0 $WLC89" \
|
||||
gfx/common/wayland_common.c "$WL_H /usr/include/libdecor-0/libdecor.h"
|
||||
platform_video "wayland common: backport" \
|
||||
"-DHAVE_WAYLAND -DHAVE_EGL -DHAVE_OPENGL -DHAVE_WAYLAND_BACKPORT=1" \
|
||||
"$WLC89" gfx/common/wayland_common.c "$WL_H"
|
||||
# webOS swaps its own shim in for half of the common Wayland functions,
|
||||
# and the Wayland context reaches it through prototypes of its own, so
|
||||
# the shim and the context each get a lane. The shim's webOS protocol
|
||||
# headers are wayland-scanner output of LG's webos-wayland-extensions
|
||||
# (Apache-2.0), which the SDK generates at build time; stubs/webos keeps
|
||||
# a copy laid out so the shim's ../../gfx/common/wayland includes find it.
|
||||
WEBOS="-DWEBOS -DWEBOS_APP_ID=\"com.retroarch\" -DHAVE_WAYLAND -DHAVE_EGL"
|
||||
WEBOS="$WEBOS -DHAVE_OPENGLES"
|
||||
platform_video "ctx: wayland gl (webos)" "$WEBOS" "" \
|
||||
gfx/drivers_context/wayland_ctx.c "$WL_H"
|
||||
platform_video "webos wayland shim" "$WEBOS" \
|
||||
"-I$STUBS/webos/gfx/common" \
|
||||
input/common/wayland_common_webos.c "$WL_H"
|
||||
|
||||
arm "win32" win32 "-D_WIN32 -D_WIN32_WINNT=0x0600"
|
||||
arm "win32-old" win32 "-D_WIN32 -D_WIN32_WINNT=0x0400"
|
||||
arm "macos" apple "-D__APPLE__"
|
||||
arm "ios" apple "-D__APPLE__ -DTARGET_OS_IPHONE=1"
|
||||
|
||||
# The GL core driver without slang: its HDR encode is slang's, and state
|
||||
# for it once sat outside the slang guards, which only the C89 lane -
|
||||
# built without slang - ever compiled.
|
||||
platform_video "glcore without slang" "-DHAVE_OPENGL -DHAVE_OPENGL_CORE" \
|
||||
"" gfx/drivers/gl3.c "/usr/include/GL/gl.h"
|
||||
platform_video "glcore with slang" \
|
||||
"-DHAVE_OPENGL -DHAVE_OPENGL_CORE -DHAVE_SLANG -DHAVE_SHADERPIPELINE" \
|
||||
"" gfx/drivers/gl3.c "/usr/include/GL/gl.h"
|
||||
|
||||
# The CoreText rasterizer and the font driver's Apple branch, against
|
||||
# stand-ins for the SDK headers it includes: no runner builds it but the
|
||||
# Apple jobs, so a change to the rasterizer interface reached them first.
|
||||
CORETEXT="$HOSTOFF -D__APPLE__ -D__MACH__ -DHAVE_CORETEXT"
|
||||
platform_video "macos coretext rasterizer" "$CORETEXT" "-I$STUBS/apple" \
|
||||
gfx/drivers_font_renderer/coretext.c ""
|
||||
platform_video "ios coretext rasterizer" "$CORETEXT -DTARGET_OS_IPHONE=1" \
|
||||
"-I$STUBS/apple" gfx/drivers_font_renderer/coretext.c ""
|
||||
platform_video "macos font driver" "$CORETEXT" "-I$STUBS/apple" \
|
||||
gfx/font_driver.c ""
|
||||
arm "dos/djgpp" "" "-D__DJGPP__ -D__unix__"
|
||||
arm "linux" "" ""
|
||||
|
||||
@@ -501,51 +190,6 @@ check "gl3: GLES3" \
|
||||
C89="-std=c89 -ansi -pedantic -Werror=pedantic -Werror=declaration-after-statement -Wno-long-long -Wno-variadic-macros -D_GNU_SOURCE -DC89_BUILD"
|
||||
check "gl3: desktop, C89" \
|
||||
"$GL3DEFS $GLINC $C89" gfx/drivers/gl3.c
|
||||
|
||||
# Without threads, in C89: audio_driver.h and rthreads.h both reach the
|
||||
# tasks and the audio driver, and a typedef each declared on its own is
|
||||
# one C89 refuses twice. make C89_BUILD=1 --disable-threads stopped at
|
||||
# task_save.c on it; no job builds that shape. check() always adds
|
||||
# HAVE_THREADS, so this lane spells its defines out.
|
||||
NOTHREADS_BASE=$(echo "$BASE" | sed 's/-DHAVE_THREADS//')
|
||||
nt_bad=0
|
||||
for tu in tasks/task_save.c tasks/task_audio_mixer.c \
|
||||
audio/audio_driver.c audio/audio_thread_wrapper.c; do
|
||||
if ! out=$($CC $WARN $INC $NOTHREADS_BASE $C89 -fsyntax-only "$tu" 2>&1); then
|
||||
echo "FAIL audio + tasks: no threads, C89"
|
||||
echo " $tu"
|
||||
show_out "$out"
|
||||
fail=1; nt_bad=1
|
||||
fi
|
||||
done
|
||||
[ "$nt_bad" = 1 ] || echo "ok audio + tasks: no threads, C89"
|
||||
|
||||
# WASAPI is in the Windows C89_BUILD lane, which no job here builds, and
|
||||
# its error-string helper serves only the microphone path, so both halves
|
||||
# are compiled: C89 flags, with and without HAVE_MICROPHONE, unused
|
||||
# functions as errors. A real compile rather than -fsyntax-only, which
|
||||
# stops before GCC looks for unused static functions.
|
||||
win32_audio() {
|
||||
name="$1"; defs="$2"; tu="$3"
|
||||
if ! command -v "$MINGW_CC" > /dev/null 2>&1; then
|
||||
echo "skip $name (no $MINGW_CC)"
|
||||
return
|
||||
fi
|
||||
if ! out=$($MINGW_CC $WARN -Wunused-function -Werror=unused-function \
|
||||
$INC $BASE $C89 \
|
||||
$defs -c -o /dev/null "$tu" 2>&1); then
|
||||
echo "FAIL $name"
|
||||
echo " $tu"
|
||||
show_out "$out"
|
||||
fail=1
|
||||
else
|
||||
echo "ok $name"
|
||||
fi
|
||||
}
|
||||
win32_audio "wasapi: C89, microphone" "-DHAVE_WASAPI -DHAVE_MICROPHONE" \
|
||||
audio/drivers/wasapi.c
|
||||
win32_audio "wasapi: C89, no microphone" "-DHAVE_WASAPI" \
|
||||
audio/drivers/wasapi.c
|
||||
check "hw ring: OpenGL only" \
|
||||
"-DHAVE_OPENGL $GLINC" gfx/video_thread_hw.c
|
||||
check "hw ring: GLES only" \
|
||||
@@ -572,7 +216,7 @@ check_nothreads() {
|
||||
if ! out=$($CC $WARN $INC $NOTHREADS $defs -fsyntax-only "$tu" 2>&1); then
|
||||
echo "FAIL $name"
|
||||
echo " $tu"
|
||||
show_out "$out"
|
||||
echo "$out" | sed 's/^/ /' | head -12
|
||||
fail=1; bad=1
|
||||
fi
|
||||
done
|
||||
@@ -591,10 +235,10 @@ check_nothreads() {
|
||||
# eventcount park path (threads on).
|
||||
check "audio: openal" "-Itools/platform_stubs/openal -DHAVE_AL -DHAVE_THREADS -Wdeclaration-after-statement -Werror=declaration-after-statement" audio/drivers/openal.c
|
||||
|
||||
# psp/psp2 audio: index-pair SPSC + eventcount park, one driver per
|
||||
# platform; compile-only sce stubs.
|
||||
# psp_audio: index-pair SPSC + eventcount park; compile-only sce
|
||||
# stubs, PSP and Vita arms.
|
||||
check "psp: psp_audio" "-Itools/platform_stubs/psp -DPSP -DHAVE_THREADS -Wdeclaration-after-statement -Werror=declaration-after-statement" audio/drivers/psp_audio.c
|
||||
check "vita: psp2_audio" "-Itools/platform_stubs/vita -DVITA -DHAVE_THREADS -Wdeclaration-after-statement -Werror=declaration-after-statement" audio/drivers/psp2_audio.c
|
||||
check "vita: psp_audio" "-Itools/platform_stubs/vita -DVITA -DHAVE_THREADS -Wdeclaration-after-statement -Werror=declaration-after-statement" audio/drivers/psp_audio.c
|
||||
|
||||
# features_cpu.c on every statically linked platform. It owns
|
||||
# cpu_features_get_time_usec() and retro_sleep_until_us(); the wait's
|
||||
@@ -607,6 +251,7 @@ check "vita: psp2_audio" "-Itools/platform_stubs/vita -DVITA -DHAVE_THREADS -Wde
|
||||
# them. PSP gets _POSIX_C_SOURCE for the host libc's nanosleep, which
|
||||
# pspsdk's newlib declares unconditionally.
|
||||
FCPU_TU=libretro-common/features/features_cpu.c
|
||||
HOSTOFF="-U__linux__ -U__gnu_linux__ -Ulinux -U__unix__ -U__unix -Uunix"
|
||||
check "features_cpu: 3ds" "$HOSTOFF -Itools/platform_stubs/ctr -D_3DS -D__3DS__ -DARM11 -DRARCH_CONSOLE" $FCPU_TU
|
||||
check "features_cpu: gekko" "$HOSTOFF -Itools/platform_stubs/gekko -DGEKKO -DHW_RVL -DRARCH_CONSOLE" $FCPU_TU
|
||||
check "features_cpu: wiiu" "$HOSTOFF -Itools/platform_stubs/wiiu -DWIIU -DRARCH_CONSOLE" $FCPU_TU
|
||||
@@ -616,118 +261,6 @@ check "features_cpu: ps3" "$HOSTOFF -Itools/platform_stubs/ps3 -D__PS3__
|
||||
check "features_cpu: psl1ght" "$HOSTOFF -Itools/platform_stubs/psl1ght -D__PS3__ -D__PSL1GHT__ -DRARCH_CONSOLE" $FCPU_TU
|
||||
check "features_cpu: emscripten" "$HOSTOFF -Itools/platform_stubs/emscripten -D__EMSCRIPTEN__ -DEMSCRIPTEN" $FCPU_TU
|
||||
|
||||
# Shared menu and video code under the console platform macros. These
|
||||
# files are compiled by every desktop job, but the blocks inside them
|
||||
# that a console selects are not, and a change to a struct those blocks
|
||||
# touch is green everywhere until a console job runs. The frame and
|
||||
# draw descriptors they read need no SDK header, so these lanes take
|
||||
# the stubs already here and shed the host identity like the ones
|
||||
# above.
|
||||
MENUGFX="gfx/video_driver.c gfx/gfx_display.c gfx/gfx_widgets.c \
|
||||
gfx/gfx_thumbnail.c gfx/gfx_surface.c gfx/video_thread_wrapper.c \
|
||||
menu/drivers/xmb.c menu/drivers/ozone.c menu/drivers/materialui.c"
|
||||
MG_BASE="-DHAVE_XMB -DHAVE_OZONE -DHAVE_MATERIALUI -DHAVE_RGUI \
|
||||
-DHAVE_GFX_WIDGETS -DHAVE_OVERLAY -DHAVE_CONFIGFILE -DRARCH_CONSOLE \
|
||||
-DRARCH_INTERNAL -Iinput/include"
|
||||
check "console menu+gfx: vita" "$HOSTOFF $MG_BASE -Itools/platform_stubs/vita -DVITA" $MENUGFX
|
||||
check "console menu+gfx: wiiu" "$HOSTOFF $MG_BASE -Itools/platform_stubs/wiiu -DWIIU" $MENUGFX
|
||||
check "console menu+gfx: psl1ght" "$HOSTOFF $MG_BASE -Itools/platform_stubs/psl1ght -D__PS3__ -D__PSL1GHT__" $MENUGFX
|
||||
# video_thread_wrapper.c reaches for libctru's own allocator on 3DS,
|
||||
# and a guessed linearMemAlign() would let this lane pass what the real
|
||||
# SDK rejects, so the 3DS lane takes the rest.
|
||||
check "console menu+gfx: 3ds" "$HOSTOFF $MG_BASE -Itools/platform_stubs/ctr -D_3DS -D__3DS__" \
|
||||
$(echo "$MENUGFX" | tr ' ' '\n' | grep -v video_thread_wrapper)
|
||||
check "console menu+gfx: gekko" "$HOSTOFF $MG_BASE -Itools/platform_stubs/gekko -DGEKKO -DHW_RVL" $MENUGFX
|
||||
|
||||
# Platform drivers no other job compiles. Each of these reaches a
|
||||
# compiler only inside its own console toolchain - griffin includes it
|
||||
# behind that platform's #ifdef and the desktop makefiles never build
|
||||
# it - so a C89 slip or a missing declaration in one sits until the
|
||||
# console job runs, which is how a mixed declaration lived in
|
||||
# gx_joypad.c. These are the ones the stubs already in the tree can
|
||||
# reach, with one stub added for the pair of PSP input drivers - the
|
||||
# 3ds audio drivers want fifteen more symbols whose libctru signatures
|
||||
# cannot be checked from here, and a stub that guessed one would let a
|
||||
# lane pass what the real SDK rejects, so they are left out.
|
||||
# The rest need headers no stub here supplies. psp1_gfx and
|
||||
# dispserv_android already have lanes of their own - the latter gains
|
||||
# the C89 declaration check below rather than a second lane.
|
||||
#
|
||||
# Same warnings as the driver lanes above, C89 declarations included,
|
||||
# because that is the rule these files are furthest from anyone
|
||||
# checking. GEKKO takes the vendored libogc headers before its stub:
|
||||
# the stub carries only what libogc does not.
|
||||
CDECL="-Wdeclaration-after-statement -Werror=declaration-after-statement"
|
||||
GEKKO_INC="-Iwii/libogc/include -Itools/platform_stubs/gekko"
|
||||
check "gekko: gx_input" "$HOSTOFF $GEKKO_INC -DGEKKO -DHW_RVL -DRARCH_CONSOLE $CDECL" input/drivers/gx_input.c
|
||||
check "gekko: gx_joypad" "$HOSTOFF $GEKKO_INC -DGEKKO -DHW_RVL -DRARCH_CONSOLE $CDECL" input/drivers_joypad/gx_joypad.c
|
||||
check "gekko: mem2_manager" "$HOSTOFF $GEKKO_INC -DGEKKO -DHW_RVL -DRARCH_CONSOLE $CDECL" libretro-common/memory/mem2_manager.c
|
||||
check "gekko: dispserv_gx" "$HOSTOFF $GEKKO_INC -DGEKKO -DHW_RVL -DRARCH_CONSOLE $CDECL" gfx/display_servers/dispserv_gx.c
|
||||
check "gamecube: dispserv_gx" "$HOSTOFF $GEKKO_INC -DGEKKO -DRARCH_CONSOLE $CDECL" gfx/display_servers/dispserv_gx.c
|
||||
check "gekko: dispserv_gx_modes" "$HOSTOFF $GEKKO_INC -DGEKKO -DHW_RVL -DRARCH_CONSOLE $CDECL" gfx/display_servers/dispserv_gx_modes.c
|
||||
check "psl1ght: dispserv_ps3" "$HOSTOFF -Itools/platform_stubs/ps3 -D__PS3__ -D__PSL1GHT__ -DRARCH_CONSOLE $CDECL" gfx/display_servers/dispserv_ps3.c gfx/display_servers/dispserv_ps3_modes.c
|
||||
check "3ds: ctr_input" "$HOSTOFF -Itools/platform_stubs/ctr -D_3DS -D__3DS__ -DARM11 -DRARCH_CONSOLE $CDECL" input/drivers/ctr_input.c
|
||||
PSP_DEFS="$HOSTOFF -Itools/platform_stubs/psp -DPSP -D_POSIX_C_SOURCE=199309L -DRARCH_CONSOLE"
|
||||
check "psp: psp_input" "$PSP_DEFS $CDECL" input/drivers/psp_input.c
|
||||
check "psp: psp_joypad" "$PSP_DEFS $CDECL" input/drivers_joypad/psp_joypad.c
|
||||
check "orbis: ps4_audio" "$HOSTOFF -Itools/platform_stubs/orbis -DORBIS $CDECL" audio/drivers/ps4_audio.c
|
||||
check "qnx: alsa_qsa" "$HOSTOFF -Itools/platform_stubs/qnx -D__QNX__ $CDECL" audio/drivers/alsa_qsa.c
|
||||
check "android: vfs saf" "$HOSTOFF -Itools/platform_stubs/android -DANDROID $CDECL" libretro-common/vfs/vfs_implementation_saf.c
|
||||
check "android: play delivery" "$HOSTOFF -Itools/platform_stubs/android -DANDROID $CDECL" play_feature_delivery/play_feature_delivery.c
|
||||
# the Android build carries the built-in NFS client and its backend
|
||||
# (HAVE_RETRONFS in pkg/android/phoenix-common/jni/Android.mk) and the
|
||||
# prefetcher both network backends read through
|
||||
check "android: nfs client" "$HOSTOFF -Itools/platform_stubs/android -DANDROID -DHAVE_NFSCLIENT -DHAVE_RETRONFS -DHAVE_THREADS -DHAVE_NETWORKING $CDECL" libretro-common/net/net_nfs3.c
|
||||
check "android: nfs backend" "$HOSTOFF -Itools/platform_stubs/android -DANDROID -DHAVE_NFSCLIENT -DHAVE_RETRONFS -DHAVE_THREADS -DHAVE_NETWORKING -DRARCH_INTERNAL $CDECL" libretro-common/vfs/vfs_implementation_nfs.c
|
||||
check "android: vfs prefetch" "$HOSTOFF -Itools/platform_stubs/android -DANDROID -DHAVE_THREADS $CDECL" libretro-common/vfs/vfs_prefetch.c
|
||||
|
||||
# The Android build carries the cleanroom network stack (Android.mk:
|
||||
# HAVE_CRYPTO, keychain, RETROSSL, RETROSMB, RETRONFS) and is compiled
|
||||
# by the NDK's clang for aarch64. The same compiler family and target
|
||||
# here, with the aarch64 cross headers standing in for the NDK's, over
|
||||
# every translation unit of the stack: the AES/PMULL path takes the
|
||||
# +crypto target attribute this way, which gcc lanes never exercise.
|
||||
ANDROID_CLANG=${ANDROID_CLANG:-clang}
|
||||
ANDROID_SYSINC=${ANDROID_SYSINC:-/usr/aarch64-linux-gnu/include}
|
||||
android_clang() {
|
||||
name="$1"; defs="$2"; tu="$3"
|
||||
if ! command -v "$ANDROID_CLANG" > /dev/null 2>&1 || [ ! -d "$ANDROID_SYSINC" ]; then
|
||||
echo "skip $name (no $ANDROID_CLANG or $ANDROID_SYSINC)"
|
||||
return
|
||||
fi
|
||||
if ! out=$($ANDROID_CLANG --target=aarch64-linux-gnu -isystem "$ANDROID_SYSINC" \
|
||||
-Wall -Werror -Wno-unused-function -D__ANDROID__ -DANDROID $INC $defs \
|
||||
-fsyntax-only "$tu" 2>&1); then
|
||||
echo "FAIL $name"
|
||||
echo " $tu"
|
||||
show_out "$out"
|
||||
fail=1
|
||||
else
|
||||
echo "ok $name"
|
||||
fi
|
||||
}
|
||||
ANDROID_NET="-DHAVE_CRYPTO -DHAVE_KEYCHAIN -DHAVE_SSL -DHAVE_RETROSSL -DHAVE_SMBCLIENT -DHAVE_RETROSMB -DHAVE_NFSCLIENT -DHAVE_RETRONFS -DHAVE_NETWORKING -DHAVE_THREADS -DHAVE_CONFIGFILE -DRARCH_INTERNAL -D_GNU_SOURCE"
|
||||
for tu in libretro-common/crypto/crypto.c libretro-common/crypto/kdf.c libretro-common/crypto/pk.c \
|
||||
libretro-common/crypto/x509.c libretro-common/file/keychain.c libretro-common/net/net_socket_ssl_retro.c \
|
||||
libretro-common/net/net_smb2.c libretro-common/net/net_krb5.c libretro-common/net/net_nfs3.c \
|
||||
libretro-common/vfs/vfs_implementation_smb.c libretro-common/vfs/vfs_implementation_nfs.c \
|
||||
libretro-common/vfs/vfs_prefetch.c network/tls_log.c; do
|
||||
android_clang "android clang/aarch64: $(basename $tu)" "$ANDROID_NET" "$tu"
|
||||
done
|
||||
# rwebaudio is its own translation unit in the emscripten build, not
|
||||
# part of griffin's, and nothing else compiles it at all.
|
||||
check "emscripten: rwebaudio" "$HOSTOFF -Itools/platform_stubs/emscripten -D__EMSCRIPTEN__ -DEMSCRIPTEN -DHAVE_RWEBAUDIO $CDECL" audio/drivers/rwebaudio.c
|
||||
check "emscripten: rwebcam" "$HOSTOFF -Itools/platform_stubs/emscripten -D__EMSCRIPTEN__ -DEMSCRIPTEN $CDECL" camera/drivers/rwebcam.c
|
||||
|
||||
# Two more that no job here compiles: the S3 cloud-sync backend, which
|
||||
# only griffin includes and which nothing defines HAVE_S3 for, and the
|
||||
# Lakka wifi driver, which needs HAVE_LAKKA. Both had a mixed
|
||||
# declaration. The C89 build cannot see either, because neither is in
|
||||
# its object list.
|
||||
NETDEFS="-DHAVE_NETWORKING -DHAVE_CONFIGFILE -DHAVE_OVERLAY -DHAVE_CHEATS"
|
||||
check "cloudsync: s3" "$NETDEFS -DHAVE_CLOUDSYNC -DHAVE_S3 $CDECL" network/cloud_sync/s3.c
|
||||
check "lakka: connmanctl" "$NETDEFS -DHAVE_LAKKA -DHAVE_WIFI $CDECL" network/drivers_wifi/connmanctl.c
|
||||
|
||||
# The salamander launchers link a hand-picked subset of libretro-common
|
||||
# with -DIS_SALAMANDER: rtime.c for rtime_localtime, no features_cpu.c.
|
||||
# A syntax pass cannot see the link error that a symbol from an
|
||||
@@ -739,7 +272,7 @@ salamander_link() {
|
||||
if ! out=$($CC $WARN $INC $BASE $defs -o /tmp/salamander_link_check \
|
||||
tools/platform_stubs/salamander_main.c "$@" 2>&1); then
|
||||
echo "FAIL $name"
|
||||
show_out "$out"
|
||||
echo "$out" | sed 's/^/ /' | head -12
|
||||
fail=1
|
||||
else
|
||||
echo "ok $name"
|
||||
@@ -755,7 +288,7 @@ salamander_link "salamander link: rtime.c" \
|
||||
# (jni, looper, native_activity, sensor, configuration, window) with
|
||||
# the host identity shed.
|
||||
check "android: runloop" "$HOSTOFF -DANDROID -Itools/platform_stubs/android" runloop.c
|
||||
check "android: dispserv" "$HOSTOFF -DANDROID -Itools/platform_stubs/android $CDECL" gfx/display_servers/dispserv_android.c
|
||||
check "android: dispserv" "$HOSTOFF -DANDROID -Itools/platform_stubs/android" gfx/display_servers/dispserv_android.c
|
||||
|
||||
# The Android arms of rthreads: thread affinity goes to the kernel
|
||||
# directly there (bionic keeps cpu_set_t behind _GNU_SOURCE), and the
|
||||
@@ -763,17 +296,6 @@ check "android: dispserv" "$HOSTOFF -DANDROID -Itools/platform_stubs/android $CD
|
||||
check "android: rthreads (API 21)" "-DHAVE_THREADS -D__ANDROID__ -D__ANDROID_API__=21 -include tools/platform_stubs/android/bionic_pthread_stub.h -Itools/platform_stubs/android" libretro-common/rthreads/rthreads.c
|
||||
check "android: rthreads (API 19)" "-DHAVE_THREADS -D__ANDROID__ -D__ANDROID_API__=19 -Itools/platform_stubs/android" libretro-common/rthreads/rthreads.c
|
||||
|
||||
# The D-Bus, Mutter and RealtimeKit units, and the elevation chain,
|
||||
# are built on Linux and BSD only, but griffin and other build systems
|
||||
# may still see the files elsewhere: as a target with no POSIX headers
|
||||
# at all (MSVC, consoles), each must reduce to its gate and include
|
||||
# none of them.
|
||||
check "no-posix: D-Bus/Mutter/RealtimeKit units" "$HOSTOFF -DHAVE_DYLIB -Itools/platform_stubs/no_posix" \
|
||||
gfx/common/dbus_runtime.c gfx/common/dbus_common.c \
|
||||
gfx/common/mutter_displayconfig.c \
|
||||
frontend/thread_elevation.c frontend/thread_elevation/rtkit.c \
|
||||
frontend/thread_elevation/eevdf.c
|
||||
|
||||
check "android: opensl" "-DANDROID -DHAVE_OPENSL -Itools/platform_stubs/android -Wdeclaration-after-statement -Werror=declaration-after-statement" audio/drivers/opensl.c
|
||||
|
||||
check "gekko: rgui" "-DGEKKO -DHAVE_MENU -DHAVE_RGUI -Itools/platform_stubs/gekko" menu/drivers/rgui.c
|
||||
@@ -827,7 +349,7 @@ check_gates() {
|
||||
if ! out=$($CC $WARN $INC $NOMENU $defs -fsyntax-only "$tu" 2>&1); then
|
||||
echo "FAIL $name"
|
||||
echo " $tu"
|
||||
show_out "$out"
|
||||
echo "$out" | sed 's/^/ /' | head -12
|
||||
fail=1; bad=1
|
||||
fi
|
||||
done
|
||||
@@ -837,11 +359,6 @@ UITU="retroarch.c runloop.c gfx/video_driver.c gfx/gfx_display.c"
|
||||
UIDEFS="$GLINC -DHAVE_COMMAND -DHAVE_STDIN_CMD"
|
||||
check_gates "gates: menu + widgets" "$UIDEFS -DHAVE_MENU -DHAVE_GFX_WIDGETS" $UITU
|
||||
check_gates "gates: menu only" "$UIDEFS -DHAVE_MENU" $UITU
|
||||
# The keychain without networking: its menu entry and task must be
|
||||
# declared outside the networking-only parts of tasks_internal.h.
|
||||
check_gates "gates: keychain menu, no networking" \
|
||||
"$UIDEFS -DHAVE_MENU -DHAVE_KEYCHAIN -DHAVE_CRYPTO -DHAVE_CONFIGFILE" \
|
||||
menu/menu_setting.c tasks/task_keychain.c
|
||||
check_gates "gates: widgets only" "$UIDEFS -DHAVE_GFX_WIDGETS" $UITU
|
||||
check_gates "gates: neither" "$UIDEFS" $UITU
|
||||
|
||||
@@ -859,37 +376,6 @@ check_gates "gates: no networking" "$UIDEFS $(without -DHAVE_NETWORKING)"
|
||||
check_gates "gates: no run-ahead" "$UIDEFS $(without -DHAVE_RUNAHEAD)" $FETU
|
||||
check_gates "gates: no subsystems" "$UIDEFS" $FETU
|
||||
|
||||
# Each video API owns a GPU index setting and its default, under that
|
||||
# API's own gate, and a build has any mix of APIs: a default defined
|
||||
# under another API's gate builds only where both are on. Each API that
|
||||
# a Linux build can have alone is checked alone.
|
||||
check_gates "gates: GPU index, EGL only" "$UIDEFS -DHAVE_EGL -DHAVE_OPENGL" configuration.c
|
||||
check_gates "gates: GPU index, Vulkan only" "$UIDEFS -DHAVE_VULKAN" configuration.c
|
||||
|
||||
# griffin is one translation unit, so a file-local name that is fine on
|
||||
# its own collides there: p256's fmul against gfx's did, and only the
|
||||
# MSVC job builds griffin. Both crypto shapes, on Linux, for free.
|
||||
check_gates "griffin: HAVE_CRYPTO + keychain" "$UIDEFS -DRARCH_INTERNAL -DHAVE_GRIFFIN=1 -DHAVE_MENU -DHAVE_RGUI -DHAVE_CONFIGFILE -DHAVE_CRYPTO -DHAVE_KEYCHAIN -D_GNU_SOURCE" griffin/griffin.c
|
||||
check_gates "griffin: HAVE_RETROSSL" "$UIDEFS -DRARCH_INTERNAL -DHAVE_GRIFFIN=1 -DHAVE_MENU -DHAVE_RGUI -DHAVE_CONFIGFILE -DHAVE_CRYPTO -DHAVE_KEYCHAIN -DHAVE_NETWORKING -DHAVE_SSL -DHAVE_RETROSSL -D_GNU_SOURCE" griffin/griffin.c
|
||||
check_gates "griffin: HAVE_RETROSMB + cloud sync" "$UIDEFS -DRARCH_INTERNAL -DHAVE_GRIFFIN=1 -DHAVE_MENU -DHAVE_RGUI -DHAVE_CONFIGFILE -DHAVE_CRYPTO -DHAVE_NETWORKING -DHAVE_SMBCLIENT -DHAVE_RETROSMB -DHAVE_CLOUDSYNC -D_GNU_SOURCE" griffin/griffin.c
|
||||
# The unity build the Android, Apple, webOS and MSVC targets ship: the
|
||||
# whole network stack beside every codec, image reader and subsystem
|
||||
# griffin can carry. A static helper in one translation unit that
|
||||
# another one also defines (get32 in net_smb2.c and rvorbis.c) only
|
||||
# collides here, so this lane carries as much of griffin as a host can.
|
||||
GRIFFIN_FULL="-DRARCH_INTERNAL -DHAVE_GRIFFIN=1 -DHAVE_MENU -DHAVE_RGUI -DHAVE_XMB -DHAVE_OZONE -DHAVE_MATERIALUI -DHAVE_GFX_WIDGETS -DHAVE_OVERLAY -DHAVE_CONFIGFILE -DHAVE_CHEEVOS -DRC_DISABLE_LUA -DHAVE_RUNAHEAD -DHAVE_REWIND -DHAVE_PATCH -DHAVE_XDELTA -DHAVE_CHEATS -DHAVE_SCREENSHOTS -DHAVE_TRANSLATE -DHAVE_LIBRETRODB -DHAVE_DYNAMIC -DHAVE_DYLIB -DHAVE_THREADS -DHAVE_AUDIOMIXER -DHAVE_DSP_FILTER -DHAVE_VIDEO_FILTER -DHAVE_RWAV -DHAVE_RFLAC -DHAVE_RVORBIS -DHAVE_RMP3 -DHAVE_RAC3 -DHAVE_RPNG -DHAVE_RJPEG -DHAVE_RBMP -DHAVE_RTGA -DHAVE_RWEBP -DHAVE_RDDS -DHAVE_RZSTD -DHAVE_7ZIP -DHAVE_CHD -DHAVE_RCHD -DHAVE_IMAGEVIEWER -DHAVE_STB_FONT -DHAVE_NETWORKING -DHAVE_IFINFO -DHAVE_NETWORK_CMD -DHAVE_NETPLAYDISCOVERY -DHAVE_ONLINE_UPDATER -DHAVE_UPDATE_ASSETS -DHAVE_UPDATE_CORES -DHAVE_UPDATE_CORE_INFO -DHAVE_CORE_INFO_CACHE -DHAVE_CLOUDSYNC -DHAVE_S3 -DHAVE_CRYPTO -DHAVE_KEYCHAIN -DHAVE_SSL -DHAVE_RETROSSL -DHAVE_SMBCLIENT -DHAVE_RETROSMB -DHAVE_NFSCLIENT -DHAVE_RETRONFS -DHAVE_LANGEXTRA -DHAVE_SHADERPIPELINE -DHAVE_GLSL -DHAVE_OPENGL -D_GNU_SOURCE"
|
||||
check_gates "griffin: full unity (every codec + network stack)" "$UIDEFS $GRIFFIN_FULL" griffin/griffin.c
|
||||
# The SMB show-settings row read a HAVE_MENU-only default under
|
||||
# HAVE_SMBCLIENT alone; with the built-in client on by default that
|
||||
# broke every --disable-menu build. configuration.c with SMB and no
|
||||
# menu, and the same with no menu at all.
|
||||
check_gates "gates: SMB client, no menu" "$UIDEFS -DHAVE_CONFIGFILE -DHAVE_NETWORKING -DHAVE_CRYPTO -DHAVE_SMBCLIENT -DHAVE_RETROSMB -D_GNU_SOURCE" configuration.c
|
||||
check_gates "gates: NFS client, no menu" "$UIDEFS -DHAVE_CONFIGFILE -DHAVE_NETWORKING -DHAVE_NFSCLIENT -DHAVE_RETRONFS -D_GNU_SOURCE" configuration.c
|
||||
check_gates "gates: NFS client + menu" "$UIDEFS -DHAVE_CONFIGFILE -DHAVE_NETWORKING -DHAVE_NFSCLIENT -DHAVE_RETRONFS -DHAVE_MENU -DHAVE_RGUI -D_GNU_SOURCE" configuration.c menu/menu_setting.c menu/menu_displaylist.c menu/cbs/menu_cbs_ok.c menu/cbs/menu_cbs_deferred_push.c menu/cbs/menu_cbs_sublabel.c menu/cbs/menu_cbs_title.c
|
||||
check_gates "gates: no menu, no SMB" "$UIDEFS -DHAVE_CONFIGFILE -DHAVE_NETWORKING -D_GNU_SOURCE" configuration.c
|
||||
check_gates "griffin: HAVE_RETRONFS" "$UIDEFS -DRARCH_INTERNAL -DHAVE_GRIFFIN=1 -DHAVE_MENU -DHAVE_RGUI -DHAVE_CONFIGFILE -DHAVE_NETWORKING -DHAVE_NFSCLIENT -DHAVE_RETRONFS -D_GNU_SOURCE" griffin/griffin.c
|
||||
check_gates "griffin: no crypto (console shape)" "$UIDEFS -DRARCH_INTERNAL -DHAVE_GRIFFIN=1 -DHAVE_MENU -DHAVE_RGUI -DHAVE_CONFIGFILE -DHAVE_KEYCHAIN -D_GNU_SOURCE" griffin/griffin.c
|
||||
|
||||
# A subsystem's own unit is built only when its gate is on, so each is
|
||||
# checked with that gate on and the user interface off: the achievement
|
||||
# and netplay widgets, and the menu entries either drives, are the edges
|
||||
@@ -913,47 +399,6 @@ check "dsp filters: C89" "$C89 -DHAVE_FILTERS_BUILTIN" $DSP
|
||||
check "dsp filters: C89, no C99 math declarations" \
|
||||
"$(echo "$C89" | sed 's/ -D_GNU_SOURCE//') -DHAVE_FILTERS_BUILTIN" $DSP
|
||||
|
||||
# The UI companion's shared core, which only a Qt or Cocoa build puts
|
||||
# in OBJ. A tree configured --disable-qt on a Linux host - which is what
|
||||
# a desktop job builds - compiles none of it, so a change to a struct it
|
||||
# reads is green everywhere until one of those jobs runs.
|
||||
echo "== ui companion: the shared core =="
|
||||
COMPANION="ui/companion/companion_core.c ui/companion/companion_thumbs.c \
|
||||
ui/companion/companion_dock.c"
|
||||
check "ui companion" "-DHAVE_RGUI -DHAVE_CONFIGFILE" $COMPANION
|
||||
check "ui companion: no threads" \
|
||||
"$(echo "$BASE" | sed 's/ -DHAVE_THREADS//') -DHAVE_RGUI -DHAVE_CONFIGFILE" \
|
||||
$COMPANION
|
||||
# Its own tests, which link the same objects and read the same structs.
|
||||
# They live outside ui/companion and a change that stops at the three
|
||||
# files above leaves them behind.
|
||||
check "ui companion: tests" "-DHAVE_RGUI -DHAVE_CONFIGFILE -Iui/companion" \
|
||||
ui/companion/test/companion_core_test.c \
|
||||
ui/companion/test/companion_thumbs_test.c \
|
||||
ui/companion/test/companion_core_stubs.c
|
||||
|
||||
# Video and shader back ends a Linux desktop job does not select. Each
|
||||
# reads the frame descriptor and the shader parameter block every other
|
||||
# driver does, and nothing else here compiles them: this configuration
|
||||
# has no Vulkan, no Cg and none of the framebuffer drivers, so a change
|
||||
# to those structs is green until a job that does have them runs.
|
||||
echo "== video back ends this configuration does not build =="
|
||||
GFXDEFS="-DHAVE_RGUI -DHAVE_OVERLAY -DHAVE_GFX_WIDGETS"
|
||||
VK_H=/usr/include/vulkan/vulkan.h
|
||||
platform_video "vulkan back end" "$GFXDEFS -DHAVE_VULKAN" "" \
|
||||
gfx/drivers/vulkan.c "$VK_H"
|
||||
platform_video "vulkan shader back end" "$GFXDEFS -DHAVE_VULKAN" "" \
|
||||
gfx/drivers_shader/shader_vulkan.c "$VK_H"
|
||||
check "cg shader back end" "$GFXDEFS -DHAVE_CG -DHAVE_OPENGL" \
|
||||
gfx/drivers_shader/shader_gl_cg.c
|
||||
check "framebuffer back ends" "$GFXDEFS" \
|
||||
gfx/drivers/fpga_gfx.c gfx/drivers/hub75_gfx.c gfx/drivers/sunxi_gfx.c
|
||||
X11_H=/usr/include/X11/Xlib.h
|
||||
platform_video "x11 shm back end" "$GFXDEFS -DHAVE_X11" "" \
|
||||
gfx/drivers/xshm_gfx.c "$X11_H"
|
||||
platform_video "xvideo back end" "$GFXDEFS -DHAVE_X11 -DHAVE_XVIDEO" "" \
|
||||
gfx/drivers/xvideo.c "$X11_H"
|
||||
|
||||
echo "== run-ahead: the dynamic-library gates =="
|
||||
# The secondary instance exists only with HAVE_DYNAMIC; a build that
|
||||
# can load libraries but links its core statically (HAVE_DYLIB alone)
|
||||
@@ -964,116 +409,4 @@ check "runahead: HAVE_DYLIB only" "$RADEFS -DHAVE_DYLIB" runahead
|
||||
check "runahead: HAVE_DYNAMIC" "$RADEFS -DHAVE_DYNAMIC" runahead.c
|
||||
check "runahead: both" "$RADEFS -DHAVE_DYNAMIC -DHAVE_DYLIB" runahead.c
|
||||
|
||||
# The Metal driver, as Objective-C against stand-ins for the parts of the
|
||||
# macOS SDK it uses (tools/platform_stubs/metal). No runner here has the
|
||||
# SDK, so the Apple jobs used to be the first to compile it. A selector
|
||||
# the stand-ins do not declare is an error: a new Metal call needs its
|
||||
# declaration added there.
|
||||
if command -v clang > /dev/null 2>&1; then
|
||||
if ! out=$(clang -x objective-c -fobjc-runtime=macosx-10.13 -fblocks \
|
||||
-fsyntax-only -Wall -Wno-unused-function -Werror=objc-method-access \
|
||||
-Werror=unused-but-set-variable $HOSTOFF -D__APPLE__ -D__MACH__ \
|
||||
-DHAVE_METAL -Itools/platform_stubs/metal $INC $BASE \
|
||||
gfx/drivers/metal.m 2>&1); then
|
||||
echo "FAIL metal video (stubs)"
|
||||
echo " gfx/drivers/metal.m"
|
||||
show_out "$out"
|
||||
fail=1
|
||||
else
|
||||
echo "ok metal video (stubs)"
|
||||
fi
|
||||
else
|
||||
echo "skip metal video (stubs) (no clang)"
|
||||
fi
|
||||
|
||||
# gfx/font_layout.h expands inside each driver's render function, and a
|
||||
# hook that reads a value before declaring its own locals is C99 the C89
|
||||
# lane rejects, so these lanes reject it too (the PS2 and Wii U drivers,
|
||||
# built only by C99 toolchains, opt out). And a
|
||||
# layout counter a driver has no use for is set but never read. GCC 15
|
||||
# and clang warn about that; the runner's GCC does not, so these lanes
|
||||
# ask clang, with the warning made an error.
|
||||
font_layout() {
|
||||
name="$1"; target="$2"; defs="$3"; tu="$4"
|
||||
if ! command -v clang > /dev/null 2>&1; then
|
||||
echo "skip $name (no clang)"
|
||||
return
|
||||
fi
|
||||
if ! out=$(clang $target -Werror=unused-but-set-variable \
|
||||
-Werror=declaration-after-statement \
|
||||
$INC $BASE $defs -fsyntax-only "$tu" 2>&1); then
|
||||
echo "FAIL $name"
|
||||
echo " $tu"
|
||||
show_out "$out"
|
||||
fail=1
|
||||
else
|
||||
echo "ok $name"
|
||||
fi
|
||||
}
|
||||
# Vulkan from the Khronos headers the tree carries (gfx/include), as the
|
||||
# real builds use, so the lane runs on a runner without the SDK headers
|
||||
font_layout "font layout: vulkan" "" "-DHAVE_VULKAN -Igfx/include" gfx/drivers/vulkan.c
|
||||
font_layout "font layout: gl" "" "-DHAVE_OPENGL" gfx/drivers/gl2.c
|
||||
font_layout "font layout: gl1" "" "-DHAVE_OPENGL -DHAVE_OPENGL1" \
|
||||
gfx/drivers/gl1.c
|
||||
if command -v sdl2-config > /dev/null 2>&1; then
|
||||
font_layout "font layout: sdl2" "" "-DHAVE_SDL2 $(sdl2-config --cflags)" \
|
||||
gfx/drivers/sdl2_gfx.c
|
||||
else
|
||||
echo "skip font layout: sdl2 (no sdl2-config)"
|
||||
fi
|
||||
if [ -f /usr/include/SDL3/SDL.h ]; then
|
||||
font_layout "font layout: sdl3" "" "-DHAVE_SDL3 -I/usr/include/SDL3" \
|
||||
gfx/drivers/sdl3_gfx.c
|
||||
else
|
||||
echo "skip font layout: sdl3 (no SDL3 headers)"
|
||||
fi
|
||||
# The software OSD blitters, where their headers are installed
|
||||
if [ -f /usr/include/X11/extensions/Xvlib.h ]; then
|
||||
font_layout "font layout: xvideo" "" "-DHAVE_X11 -DHAVE_XVIDEO" \
|
||||
gfx/drivers/xvideo.c
|
||||
fi
|
||||
if [ -f /usr/include/SDL/SDL.h ]; then
|
||||
font_layout "font layout: sdl" "" "-DHAVE_SDL -I/usr/include/SDL" \
|
||||
gfx/drivers/sdl_gfx.c
|
||||
fi
|
||||
if [ -f /usr/include/linux/omapfb.h ]; then
|
||||
font_layout "font layout: omap" "" "-DHAVE_OMAP" gfx/drivers/omap_gfx.c
|
||||
fi
|
||||
font_layout "font layout: switch" "" \
|
||||
"-DHAVE_LIBNX -DSWITCH -D__SWITCH__ -I$STUBS/libnx" \
|
||||
gfx/drivers/switch_nx_gfx.c
|
||||
font_layout "font layout: gxm" "" \
|
||||
"-DVITA -DRARCH_CONSOLE $HOSTOFF -Itools/platform_stubs/vita" \
|
||||
gfx/drivers/gxm_gfx.c
|
||||
font_layout "font layout: ps2" "" \
|
||||
"-DPS2 -DRARCH_CONSOLE -DHAVE_WINDOW_OFFSET -DHAVE_RGUI $HOSTOFF -Itools/platform_stubs/ps2 \
|
||||
-Wno-declaration-after-statement" \
|
||||
gfx/drivers/ps2_gfx.c
|
||||
font_layout "font layout: gx2" "" \
|
||||
"-DWIIU $HOSTOFF -Iwiiu/include -Iwiiu -Wno-declaration-after-statement" \
|
||||
gfx/drivers/gx2_gfx.c
|
||||
font_layout "font layout: glcore" "" \
|
||||
"-DHAVE_OPENGL -DHAVE_OPENGL_CORE -DHAVE_SLANG" gfx/drivers/gl3.c
|
||||
if [ -d /usr/x86_64-w64-mingw32/include ]; then
|
||||
MINGW_CLANG="--target=x86_64-w64-mingw32 -isystemgfx/include/dxsdk -isystem /usr/x86_64-w64-mingw32/include"
|
||||
font_layout "font layout: gdi" "$MINGW_CLANG" \
|
||||
"-DHAVE_RGUI -DHAVE_OVERLAY -DHAVE_GDI -D_WIN32_WINNT=0x0601" \
|
||||
gfx/drivers/gdi_gfx.c
|
||||
font_layout "font layout: d3d8" "$MINGW_CLANG" \
|
||||
"-DHAVE_D3D -DHAVE_RGUI -DHAVE_OVERLAY -DHAVE_D3D8" gfx/drivers/d3d8.c
|
||||
font_layout "font layout: d3d9 hlsl" "$MINGW_CLANG" \
|
||||
"-DHAVE_D3D -DHAVE_RGUI -DHAVE_OVERLAY -DHAVE_D3D9" gfx/drivers/d3d9hlsl.c
|
||||
font_layout "font layout: d3d9 cg" "$MINGW_CLANG" \
|
||||
"-DHAVE_D3D -DHAVE_RGUI -DHAVE_OVERLAY -DHAVE_D3D9" gfx/drivers/d3d9cg.c
|
||||
font_layout "font layout: d3d10" "$MINGW_CLANG" \
|
||||
"-DHAVE_D3D -DHAVE_RGUI -DHAVE_OVERLAY -DHAVE_D3D10" gfx/drivers/d3d10.c
|
||||
font_layout "font layout: d3d11" "$MINGW_CLANG" \
|
||||
"-DHAVE_D3D -DHAVE_RGUI -DHAVE_OVERLAY -DHAVE_D3D11" gfx/drivers/d3d11.c
|
||||
font_layout "font layout: d3d12" "$MINGW_CLANG" \
|
||||
"-DHAVE_D3D -DHAVE_RGUI -DHAVE_OVERLAY -DHAVE_D3D12" gfx/drivers/d3d12.c
|
||||
else
|
||||
echo "skip font layout: gdi, d3d8/9/10/11/12 (no MinGW headers)"
|
||||
fi
|
||||
|
||||
exit $fail
|
||||
@@ -1,43 +0,0 @@
|
||||
/* Stand-in for the macOS SDK umbrella header: only the CoreGraphics
|
||||
* and CoreText declarations gfx/drivers_font_renderer/coretext.c uses. */
|
||||
#ifndef STUB_AS_H
|
||||
#define STUB_AS_H
|
||||
#include <CoreFoundation/CFString.h>
|
||||
typedef double CGFloat;
|
||||
typedef unsigned short CGGlyph;
|
||||
typedef struct { CGFloat x, y; } CGPoint;
|
||||
typedef struct { CGFloat width, height; } CGSize;
|
||||
typedef struct { CGPoint origin; CGSize size; } CGRect;
|
||||
typedef struct { CGFloat a,b,c,d,tx,ty; } CGAffineTransform;
|
||||
extern const CGAffineTransform CGAffineTransformIdentity;
|
||||
typedef struct CGContext *CGContextRef;
|
||||
typedef struct CGColorSpace *CGColorSpaceRef;
|
||||
typedef struct CGDataProvider *CGDataProviderRef;
|
||||
typedef struct CGFont *CGFontRef;
|
||||
typedef const struct __CTFont *CTFontRef;
|
||||
typedef const struct __CTLine *CTLineRef;
|
||||
typedef uint32_t CTFontOrientation;
|
||||
typedef uint32_t CGBitmapInfo;
|
||||
enum { kCGImageAlphaNone = 0, kCGImageAlphaOnly = 7, kCGBitmapByteOrder16Host = 0x1000 };
|
||||
extern const CFStringRef kCTFontAttributeName;
|
||||
extern const CFStringRef kCTForegroundColorFromContextAttributeName;
|
||||
typedef void (*CGDataProviderReleaseDataCallback)(void *info, const void *data, size_t size);
|
||||
CGDataProviderRef CGDataProviderCreateWithData(void *, const void *, size_t, CGDataProviderReleaseDataCallback);
|
||||
CGFontRef CGFontCreateWithDataProvider(CGDataProviderRef);
|
||||
CTFontRef CTFontCreateWithGraphicsFont(CGFontRef, CGFloat, const CGAffineTransform *, void *);
|
||||
bool CTFontGetGlyphsForCharacters(CTFontRef, const UniChar *, CGGlyph *, CFIndex);
|
||||
CGRect CTFontGetBoundingRectsForGlyphs(CTFontRef, CTFontOrientation, const CGGlyph *, CGRect *, CFIndex);
|
||||
double CTFontGetAdvancesForGlyphs(CTFontRef, CTFontOrientation, const CGGlyph *, CGSize *, CFIndex);
|
||||
CGFloat CTFontGetAscent(CTFontRef);
|
||||
CGFloat CTFontGetDescent(CTFontRef);
|
||||
CGFloat CTFontGetLeading(CTFontRef);
|
||||
CGColorSpaceRef CGColorSpaceCreateDeviceGray(void);
|
||||
void CGColorSpaceRelease(CGColorSpaceRef);
|
||||
CGContextRef CGBitmapContextCreate(void *, size_t, size_t, size_t, size_t, CGColorSpaceRef, uint32_t);
|
||||
void CGContextRelease(CGContextRef);
|
||||
void CGContextSetGrayFillColor(CGContextRef, CGFloat, CGFloat);
|
||||
void CGContextSetTextMatrix(CGContextRef, CGAffineTransform);
|
||||
void CGContextSetTextPosition(CGContextRef, CGFloat, CGFloat);
|
||||
CTLineRef CTLineCreateWithAttributedString(CFAttributedStringRef);
|
||||
void CTLineDraw(CTLineRef, CGContextRef);
|
||||
#endif
|
||||
@@ -1,27 +0,0 @@
|
||||
/* Stand-in for the macOS/iOS SDK header: only the CoreFoundation
|
||||
* declarations gfx/drivers_font_renderer/coretext.c uses, so the
|
||||
* compile matrix can check that file in an Apple shape. */
|
||||
#ifndef STUB_CF_H
|
||||
#define STUB_CF_H
|
||||
#include <stddef.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
typedef long CFIndex;
|
||||
typedef unsigned short UniChar;
|
||||
typedef const void *CFTypeRef;
|
||||
typedef const struct __CFString *CFStringRef;
|
||||
typedef const struct __CFDictionary *CFDictionaryRef;
|
||||
typedef const struct __CFAllocator *CFAllocatorRef;
|
||||
typedef const struct __CFBoolean *CFBooleanRef;
|
||||
typedef const struct __CFAttributedString *CFAttributedStringRef;
|
||||
typedef struct { int v; } CFDictionaryKeyCallBacks;
|
||||
typedef struct { int v; } CFDictionaryValueCallBacks;
|
||||
extern const CFBooleanRef kCFBooleanTrue;
|
||||
extern const CFDictionaryKeyCallBacks kCFTypeDictionaryKeyCallBacks;
|
||||
extern const CFDictionaryValueCallBacks kCFTypeDictionaryValueCallBacks;
|
||||
void CFRelease(CFTypeRef);
|
||||
CFTypeRef CFRetain(CFTypeRef);
|
||||
CFStringRef CFStringCreateWithCharacters(CFAllocatorRef, const UniChar *, CFIndex);
|
||||
CFDictionaryRef CFDictionaryCreate(CFAllocatorRef, const void **, const void **, CFIndex, const CFDictionaryKeyCallBacks *, const CFDictionaryValueCallBacks *);
|
||||
CFAttributedStringRef CFAttributedStringCreate(CFAllocatorRef, CFStringRef, CFDictionaryRef);
|
||||
#endif
|
||||
@@ -1,2 +0,0 @@
|
||||
/* iOS spelling of the same stand-in */
|
||||
#include <ApplicationServices/ApplicationServices.h>
|
||||
@@ -1,2 +0,0 @@
|
||||
/* iOS spelling of the same stand-in */
|
||||
#include <ApplicationServices/ApplicationServices.h>
|
||||
@@ -51,8 +51,4 @@ void *framebufferBegin(Framebuffer *fb, u32 *out_stride);
|
||||
void framebufferEnd(Framebuffer *fb);
|
||||
void framebufferClose(Framebuffer *fb);
|
||||
|
||||
|
||||
/* the kernel random source the crypto's random path draws on */
|
||||
void randomGet(void *buf, size_t len);
|
||||
|
||||
#endif
|
||||
@@ -1,636 +0,0 @@
|
||||
/* Generated by wayland-scanner 1.22.0 */
|
||||
|
||||
#ifndef WEBOS_FOREIGN_CLIENT_PROTOCOL_H
|
||||
#define WEBOS_FOREIGN_CLIENT_PROTOCOL_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
#include "wayland-client.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @page page_webos_foreign The webos_foreign protocol
|
||||
* @section page_ifaces_webos_foreign Interfaces
|
||||
* - @subpage page_iface_wl_webos_foreign - interface for handling foreign surfaces
|
||||
* - @subpage page_iface_wl_webos_exported - interface for handling exported window
|
||||
* - @subpage page_iface_wl_webos_imported - interface for imported content or punchthrough region
|
||||
* @section page_copyright_webos_foreign Copyright
|
||||
* <pre>
|
||||
*
|
||||
* Copyright (c) 2018-2019 LG Electronics, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
* </pre>
|
||||
*/
|
||||
struct wl_region;
|
||||
struct wl_surface;
|
||||
struct wl_webos_exported;
|
||||
struct wl_webos_foreign;
|
||||
struct wl_webos_imported;
|
||||
|
||||
#ifndef WL_WEBOS_FOREIGN_INTERFACE
|
||||
#define WL_WEBOS_FOREIGN_INTERFACE
|
||||
/**
|
||||
* @page page_iface_wl_webos_foreign wl_webos_foreign
|
||||
* @section page_iface_wl_webos_foreign_desc Description
|
||||
*
|
||||
* This interface defines a way to associate one or more foreign surfaces from
|
||||
* different clients or a punch-through region to a surface that acts the main
|
||||
* window.
|
||||
*
|
||||
* You can set a region on the main window where a foreign surface or a
|
||||
* punch-through region can be placed. It is called "exported" region or window
|
||||
* in this protocol. Then you can request the compositor to punch through that
|
||||
* region or attach another surface as an "imported" content.
|
||||
* @section page_iface_wl_webos_foreign_api API
|
||||
* See @ref iface_wl_webos_foreign.
|
||||
*/
|
||||
/**
|
||||
* @defgroup iface_wl_webos_foreign The wl_webos_foreign interface
|
||||
*
|
||||
* This interface defines a way to associate one or more foreign surfaces from
|
||||
* different clients or a punch-through region to a surface that acts the main
|
||||
* window.
|
||||
*
|
||||
* You can set a region on the main window where a foreign surface or a
|
||||
* punch-through region can be placed. It is called "exported" region or window
|
||||
* in this protocol. Then you can request the compositor to punch through that
|
||||
* region or attach another surface as an "imported" content.
|
||||
*/
|
||||
extern const struct wl_interface wl_webos_foreign_interface;
|
||||
#endif
|
||||
#ifndef WL_WEBOS_EXPORTED_INTERFACE
|
||||
#define WL_WEBOS_EXPORTED_INTERFACE
|
||||
/**
|
||||
* @page page_iface_wl_webos_exported wl_webos_exported
|
||||
* @section page_iface_wl_webos_exported_desc Description
|
||||
*
|
||||
* This interface defines operations and notifications on an exported window.
|
||||
* @section page_iface_wl_webos_exported_api API
|
||||
* See @ref iface_wl_webos_exported.
|
||||
*/
|
||||
/**
|
||||
* @defgroup iface_wl_webos_exported The wl_webos_exported interface
|
||||
*
|
||||
* This interface defines operations and notifications on an exported window.
|
||||
*/
|
||||
extern const struct wl_interface wl_webos_exported_interface;
|
||||
#endif
|
||||
#ifndef WL_WEBOS_IMPORTED_INTERFACE
|
||||
#define WL_WEBOS_IMPORTED_INTERFACE
|
||||
/**
|
||||
* @page page_iface_wl_webos_imported wl_webos_imported
|
||||
* @section page_iface_wl_webos_imported_desc Description
|
||||
*
|
||||
* This interface defines operations and notifications on an imported object.
|
||||
* @section page_iface_wl_webos_imported_api API
|
||||
* See @ref iface_wl_webos_imported.
|
||||
*/
|
||||
/**
|
||||
* @defgroup iface_wl_webos_imported The wl_webos_imported interface
|
||||
*
|
||||
* This interface defines operations and notifications on an imported object.
|
||||
*/
|
||||
extern const struct wl_interface wl_webos_imported_interface;
|
||||
#endif
|
||||
|
||||
#ifndef WL_WEBOS_FOREIGN_WEBOS_EXPORTED_TYPE_ENUM
|
||||
#define WL_WEBOS_FOREIGN_WEBOS_EXPORTED_TYPE_ENUM
|
||||
/**
|
||||
* @ingroup iface_wl_webos_foreign
|
||||
* type of webos_exported
|
||||
*
|
||||
* This indicates the type of the content that the exported window can show.
|
||||
*/
|
||||
enum wl_webos_foreign_webos_exported_type {
|
||||
/**
|
||||
* exported window for video object
|
||||
*/
|
||||
WL_WEBOS_FOREIGN_WEBOS_EXPORTED_TYPE_VIDEO_OBJECT = 0,
|
||||
/**
|
||||
* exported window for subtitle object
|
||||
*/
|
||||
WL_WEBOS_FOREIGN_WEBOS_EXPORTED_TYPE_SUBTITLE_OBJECT = 1,
|
||||
/**
|
||||
* exported window for transparent object that does not need to punch through other windows
|
||||
*/
|
||||
WL_WEBOS_FOREIGN_WEBOS_EXPORTED_TYPE_TRANSPARENT_OBJECT = 2,
|
||||
/**
|
||||
* exported window for opaque object that always needs to punch through other windows
|
||||
*/
|
||||
WL_WEBOS_FOREIGN_WEBOS_EXPORTED_TYPE_OPAQUE_OBJECT = 3,
|
||||
};
|
||||
#endif /* WL_WEBOS_FOREIGN_WEBOS_EXPORTED_TYPE_ENUM */
|
||||
|
||||
#define WL_WEBOS_FOREIGN_DESTROY 0
|
||||
#define WL_WEBOS_FOREIGN_EXPORT_ELEMENT 1
|
||||
#define WL_WEBOS_FOREIGN_IMPORT_ELEMENT 2
|
||||
|
||||
|
||||
/**
|
||||
* @ingroup iface_wl_webos_foreign
|
||||
*/
|
||||
#define WL_WEBOS_FOREIGN_DESTROY_SINCE_VERSION 1
|
||||
/**
|
||||
* @ingroup iface_wl_webos_foreign
|
||||
*/
|
||||
#define WL_WEBOS_FOREIGN_EXPORT_ELEMENT_SINCE_VERSION 1
|
||||
/**
|
||||
* @ingroup iface_wl_webos_foreign
|
||||
*/
|
||||
#define WL_WEBOS_FOREIGN_IMPORT_ELEMENT_SINCE_VERSION 1
|
||||
|
||||
/** @ingroup iface_wl_webos_foreign */
|
||||
static inline void
|
||||
wl_webos_foreign_set_user_data(struct wl_webos_foreign *wl_webos_foreign, void *user_data)
|
||||
{
|
||||
wl_proxy_set_user_data((struct wl_proxy *) wl_webos_foreign, user_data);
|
||||
}
|
||||
|
||||
/** @ingroup iface_wl_webos_foreign */
|
||||
static inline void *
|
||||
wl_webos_foreign_get_user_data(struct wl_webos_foreign *wl_webos_foreign)
|
||||
{
|
||||
return wl_proxy_get_user_data((struct wl_proxy *) wl_webos_foreign);
|
||||
}
|
||||
|
||||
static inline uint32_t
|
||||
wl_webos_foreign_get_version(struct wl_webos_foreign *wl_webos_foreign)
|
||||
{
|
||||
return wl_proxy_get_version((struct wl_proxy *) wl_webos_foreign);
|
||||
}
|
||||
|
||||
/**
|
||||
* @ingroup iface_wl_webos_foreign
|
||||
*
|
||||
* Destroys the foreign object.
|
||||
*/
|
||||
static inline void
|
||||
wl_webos_foreign_destroy(struct wl_webos_foreign *wl_webos_foreign)
|
||||
{
|
||||
wl_proxy_marshal_flags((struct wl_proxy *) wl_webos_foreign,
|
||||
WL_WEBOS_FOREIGN_DESTROY, NULL, wl_proxy_get_version((struct wl_proxy *) wl_webos_foreign), WL_MARSHAL_FLAG_DESTROY);
|
||||
}
|
||||
|
||||
/**
|
||||
* @ingroup iface_wl_webos_foreign
|
||||
*
|
||||
* Requests to create an exported window on a given surface.
|
||||
*/
|
||||
static inline struct wl_webos_exported *
|
||||
wl_webos_foreign_export_element(struct wl_webos_foreign *wl_webos_foreign, struct wl_surface *surface, uint32_t exported_type)
|
||||
{
|
||||
struct wl_proxy *id;
|
||||
|
||||
id = wl_proxy_marshal_flags((struct wl_proxy *) wl_webos_foreign,
|
||||
WL_WEBOS_FOREIGN_EXPORT_ELEMENT, &wl_webos_exported_interface, wl_proxy_get_version((struct wl_proxy *) wl_webos_foreign), 0, NULL, surface, exported_type);
|
||||
|
||||
return (struct wl_webos_exported *) id;
|
||||
}
|
||||
|
||||
/**
|
||||
* @ingroup iface_wl_webos_foreign
|
||||
*
|
||||
* Requests an import object that can be associated with a given exported window.
|
||||
*/
|
||||
static inline struct wl_webos_imported *
|
||||
wl_webos_foreign_import_element(struct wl_webos_foreign *wl_webos_foreign, const char *window_id, uint32_t exported_type)
|
||||
{
|
||||
struct wl_proxy *id;
|
||||
|
||||
id = wl_proxy_marshal_flags((struct wl_proxy *) wl_webos_foreign,
|
||||
WL_WEBOS_FOREIGN_IMPORT_ELEMENT, &wl_webos_imported_interface, wl_proxy_get_version((struct wl_proxy *) wl_webos_foreign), 0, NULL, window_id, exported_type);
|
||||
|
||||
return (struct wl_webos_imported *) id;
|
||||
}
|
||||
|
||||
/**
|
||||
* @ingroup iface_wl_webos_exported
|
||||
* @struct wl_webos_exported_listener
|
||||
*/
|
||||
struct wl_webos_exported_listener {
|
||||
/**
|
||||
* window is associated with this wl_webos_exported
|
||||
*
|
||||
* Notifies window_id of the exported window.
|
||||
* @param window_id window_id of associated exported window
|
||||
*/
|
||||
void (*window_id_assigned)(void *data,
|
||||
struct wl_webos_exported *wl_webos_exported,
|
||||
const char *window_id,
|
||||
uint32_t exported_type);
|
||||
};
|
||||
|
||||
/**
|
||||
* @ingroup iface_wl_webos_exported
|
||||
*/
|
||||
static inline int
|
||||
wl_webos_exported_add_listener(struct wl_webos_exported *wl_webos_exported,
|
||||
const struct wl_webos_exported_listener *listener, void *data)
|
||||
{
|
||||
return wl_proxy_add_listener((struct wl_proxy *) wl_webos_exported,
|
||||
(void (**)(void)) listener, data);
|
||||
}
|
||||
|
||||
#define WL_WEBOS_EXPORTED_DESTROY 0
|
||||
#define WL_WEBOS_EXPORTED_SET_EXPORTED_WINDOW 1
|
||||
#define WL_WEBOS_EXPORTED_SET_CROP_REGION 2
|
||||
#define WL_WEBOS_EXPORTED_SET_PROPERTY 3
|
||||
|
||||
/**
|
||||
* @ingroup iface_wl_webos_exported
|
||||
*/
|
||||
#define WL_WEBOS_EXPORTED_WINDOW_ID_ASSIGNED_SINCE_VERSION 1
|
||||
|
||||
/**
|
||||
* @ingroup iface_wl_webos_exported
|
||||
*/
|
||||
#define WL_WEBOS_EXPORTED_DESTROY_SINCE_VERSION 1
|
||||
/**
|
||||
* @ingroup iface_wl_webos_exported
|
||||
*/
|
||||
#define WL_WEBOS_EXPORTED_SET_EXPORTED_WINDOW_SINCE_VERSION 1
|
||||
/**
|
||||
* @ingroup iface_wl_webos_exported
|
||||
*/
|
||||
#define WL_WEBOS_EXPORTED_SET_CROP_REGION_SINCE_VERSION 2
|
||||
/**
|
||||
* @ingroup iface_wl_webos_exported
|
||||
*/
|
||||
#define WL_WEBOS_EXPORTED_SET_PROPERTY_SINCE_VERSION 2
|
||||
|
||||
/** @ingroup iface_wl_webos_exported */
|
||||
static inline void
|
||||
wl_webos_exported_set_user_data(struct wl_webos_exported *wl_webos_exported, void *user_data)
|
||||
{
|
||||
wl_proxy_set_user_data((struct wl_proxy *) wl_webos_exported, user_data);
|
||||
}
|
||||
|
||||
/** @ingroup iface_wl_webos_exported */
|
||||
static inline void *
|
||||
wl_webos_exported_get_user_data(struct wl_webos_exported *wl_webos_exported)
|
||||
{
|
||||
return wl_proxy_get_user_data((struct wl_proxy *) wl_webos_exported);
|
||||
}
|
||||
|
||||
static inline uint32_t
|
||||
wl_webos_exported_get_version(struct wl_webos_exported *wl_webos_exported)
|
||||
{
|
||||
return wl_proxy_get_version((struct wl_proxy *) wl_webos_exported);
|
||||
}
|
||||
|
||||
/**
|
||||
* @ingroup iface_wl_webos_exported
|
||||
*
|
||||
* Destroys the exported object.
|
||||
*/
|
||||
static inline void
|
||||
wl_webos_exported_destroy(struct wl_webos_exported *wl_webos_exported)
|
||||
{
|
||||
wl_proxy_marshal_flags((struct wl_proxy *) wl_webos_exported,
|
||||
WL_WEBOS_EXPORTED_DESTROY, NULL, wl_proxy_get_version((struct wl_proxy *) wl_webos_exported), WL_MARSHAL_FLAG_DESTROY);
|
||||
}
|
||||
|
||||
/**
|
||||
* @ingroup iface_wl_webos_exported
|
||||
*
|
||||
* Sets the exported window with source and destination regions.
|
||||
*/
|
||||
static inline void
|
||||
wl_webos_exported_set_exported_window(struct wl_webos_exported *wl_webos_exported, struct wl_region *source_region, struct wl_region *destination_region)
|
||||
{
|
||||
wl_proxy_marshal_flags((struct wl_proxy *) wl_webos_exported,
|
||||
WL_WEBOS_EXPORTED_SET_EXPORTED_WINDOW, NULL, wl_proxy_get_version((struct wl_proxy *) wl_webos_exported), 0, source_region, destination_region);
|
||||
}
|
||||
|
||||
/**
|
||||
* @ingroup iface_wl_webos_exported
|
||||
*
|
||||
* Sets a region to be cropped.
|
||||
*/
|
||||
static inline void
|
||||
wl_webos_exported_set_crop_region(struct wl_webos_exported *wl_webos_exported, struct wl_region *original_input, struct wl_region *source_region, struct wl_region *destination_region)
|
||||
{
|
||||
wl_proxy_marshal_flags((struct wl_proxy *) wl_webos_exported,
|
||||
WL_WEBOS_EXPORTED_SET_CROP_REGION, NULL, wl_proxy_get_version((struct wl_proxy *) wl_webos_exported), 0, original_input, source_region, destination_region);
|
||||
}
|
||||
|
||||
/**
|
||||
* @ingroup iface_wl_webos_exported
|
||||
*
|
||||
* Sets a property for this webos_exported. The property is given by a string pair (name, value).
|
||||
*/
|
||||
static inline void
|
||||
wl_webos_exported_set_property(struct wl_webos_exported *wl_webos_exported, const char *name, const char *value)
|
||||
{
|
||||
wl_proxy_marshal_flags((struct wl_proxy *) wl_webos_exported,
|
||||
WL_WEBOS_EXPORTED_SET_PROPERTY, NULL, wl_proxy_get_version((struct wl_proxy *) wl_webos_exported), 0, name, value);
|
||||
}
|
||||
|
||||
#ifndef WL_WEBOS_IMPORTED_SURFACE_ALIGNMENT_ENUM
|
||||
#define WL_WEBOS_IMPORTED_SURFACE_ALIGNMENT_ENUM
|
||||
/**
|
||||
* @ingroup iface_wl_webos_imported
|
||||
* surface alignment
|
||||
*
|
||||
* The alignment of an imported surface on the associated exported window.
|
||||
*/
|
||||
enum wl_webos_imported_surface_alignment {
|
||||
WL_WEBOS_IMPORTED_SURFACE_ALIGNMENT_STRETCH = 0,
|
||||
WL_WEBOS_IMPORTED_SURFACE_ALIGNMENT_NORTH = 1,
|
||||
WL_WEBOS_IMPORTED_SURFACE_ALIGNMENT_WEST = 2,
|
||||
WL_WEBOS_IMPORTED_SURFACE_ALIGNMENT_SOUTH = 4,
|
||||
WL_WEBOS_IMPORTED_SURFACE_ALIGNMENT_EAST = 8,
|
||||
WL_WEBOS_IMPORTED_SURFACE_ALIGNMENT_CENTER = 16,
|
||||
WL_WEBOS_IMPORTED_SURFACE_ALIGNMENT_CROP = 32,
|
||||
WL_WEBOS_IMPORTED_SURFACE_ALIGNMENT_FIT = 64,
|
||||
};
|
||||
#endif /* WL_WEBOS_IMPORTED_SURFACE_ALIGNMENT_ENUM */
|
||||
|
||||
/**
|
||||
* @ingroup iface_wl_webos_imported
|
||||
* @struct wl_webos_imported_listener
|
||||
*/
|
||||
struct wl_webos_imported_listener {
|
||||
/**
|
||||
* destination region changed
|
||||
*
|
||||
* Notifies the change of the destination region of associated
|
||||
* exported window.
|
||||
* @param width destination width of exported window
|
||||
* @param height destination height of exported window
|
||||
*/
|
||||
void (*destination_region_changed)(void *data,
|
||||
struct wl_webos_imported *wl_webos_imported,
|
||||
uint32_t width,
|
||||
uint32_t height);
|
||||
/**
|
||||
* punchthrough attached
|
||||
*
|
||||
* Notifies that a punchthrough is attached to webos_imported.
|
||||
* @since 2
|
||||
*/
|
||||
void (*punchthrough_attached)(void *data,
|
||||
struct wl_webos_imported *wl_webos_imported,
|
||||
const char *context_id);
|
||||
/**
|
||||
* punchthrough detached
|
||||
*
|
||||
* Notifies that a punchthrough is detached from webos_imported.
|
||||
* @since 2
|
||||
*/
|
||||
void (*punchthrough_detached)(void *data,
|
||||
struct wl_webos_imported *wl_webos_imported,
|
||||
const char *context_id);
|
||||
/**
|
||||
* surface attached
|
||||
*
|
||||
* Notifies that a surface is attached to webos_imported.
|
||||
* @since 2
|
||||
*/
|
||||
void (*surface_attached)(void *data,
|
||||
struct wl_webos_imported *wl_webos_imported,
|
||||
struct wl_surface *surface);
|
||||
/**
|
||||
* surface detached
|
||||
*
|
||||
* Notifies that a surface is detached from webos_imported.
|
||||
* @since 2
|
||||
*/
|
||||
void (*surface_detached)(void *data,
|
||||
struct wl_webos_imported *wl_webos_imported,
|
||||
struct wl_surface *surface);
|
||||
};
|
||||
|
||||
/**
|
||||
* @ingroup iface_wl_webos_imported
|
||||
*/
|
||||
static inline int
|
||||
wl_webos_imported_add_listener(struct wl_webos_imported *wl_webos_imported,
|
||||
const struct wl_webos_imported_listener *listener, void *data)
|
||||
{
|
||||
return wl_proxy_add_listener((struct wl_proxy *) wl_webos_imported,
|
||||
(void (**)(void)) listener, data);
|
||||
}
|
||||
|
||||
#define WL_WEBOS_IMPORTED_SET_SURFACE_ALIGNMENT 0
|
||||
#define WL_WEBOS_IMPORTED_DESTROY 1
|
||||
#define WL_WEBOS_IMPORTED_ATTACH_PUNCHTHROUGH 2
|
||||
#define WL_WEBOS_IMPORTED_SET_PUNCHTHROUGH 3
|
||||
#define WL_WEBOS_IMPORTED_DETACH_PUNCHTHROUGH 4
|
||||
#define WL_WEBOS_IMPORTED_ATTACH_SURFACE 5
|
||||
#define WL_WEBOS_IMPORTED_DETACH_SURFACE 6
|
||||
#define WL_WEBOS_IMPORTED_ATTACH_PUNCHTHROUGH_WITH_CONTEXT 7
|
||||
#define WL_WEBOS_IMPORTED_SET_Z_INDEX 8
|
||||
|
||||
/**
|
||||
* @ingroup iface_wl_webos_imported
|
||||
*/
|
||||
#define WL_WEBOS_IMPORTED_DESTINATION_REGION_CHANGED_SINCE_VERSION 1
|
||||
/**
|
||||
* @ingroup iface_wl_webos_imported
|
||||
*/
|
||||
#define WL_WEBOS_IMPORTED_PUNCHTHROUGH_ATTACHED_SINCE_VERSION 2
|
||||
/**
|
||||
* @ingroup iface_wl_webos_imported
|
||||
*/
|
||||
#define WL_WEBOS_IMPORTED_PUNCHTHROUGH_DETACHED_SINCE_VERSION 2
|
||||
/**
|
||||
* @ingroup iface_wl_webos_imported
|
||||
*/
|
||||
#define WL_WEBOS_IMPORTED_SURFACE_ATTACHED_SINCE_VERSION 2
|
||||
/**
|
||||
* @ingroup iface_wl_webos_imported
|
||||
*/
|
||||
#define WL_WEBOS_IMPORTED_SURFACE_DETACHED_SINCE_VERSION 2
|
||||
|
||||
/**
|
||||
* @ingroup iface_wl_webos_imported
|
||||
*/
|
||||
#define WL_WEBOS_IMPORTED_SET_SURFACE_ALIGNMENT_SINCE_VERSION 1
|
||||
/**
|
||||
* @ingroup iface_wl_webos_imported
|
||||
*/
|
||||
#define WL_WEBOS_IMPORTED_DESTROY_SINCE_VERSION 1
|
||||
/**
|
||||
* @ingroup iface_wl_webos_imported
|
||||
*/
|
||||
#define WL_WEBOS_IMPORTED_ATTACH_PUNCHTHROUGH_SINCE_VERSION 1
|
||||
/**
|
||||
* @ingroup iface_wl_webos_imported
|
||||
*/
|
||||
#define WL_WEBOS_IMPORTED_SET_PUNCHTHROUGH_SINCE_VERSION 1
|
||||
/**
|
||||
* @ingroup iface_wl_webos_imported
|
||||
*/
|
||||
#define WL_WEBOS_IMPORTED_DETACH_PUNCHTHROUGH_SINCE_VERSION 1
|
||||
/**
|
||||
* @ingroup iface_wl_webos_imported
|
||||
*/
|
||||
#define WL_WEBOS_IMPORTED_ATTACH_SURFACE_SINCE_VERSION 1
|
||||
/**
|
||||
* @ingroup iface_wl_webos_imported
|
||||
*/
|
||||
#define WL_WEBOS_IMPORTED_DETACH_SURFACE_SINCE_VERSION 1
|
||||
/**
|
||||
* @ingroup iface_wl_webos_imported
|
||||
*/
|
||||
#define WL_WEBOS_IMPORTED_ATTACH_PUNCHTHROUGH_WITH_CONTEXT_SINCE_VERSION 2
|
||||
/**
|
||||
* @ingroup iface_wl_webos_imported
|
||||
*/
|
||||
#define WL_WEBOS_IMPORTED_SET_Z_INDEX_SINCE_VERSION 2
|
||||
|
||||
/** @ingroup iface_wl_webos_imported */
|
||||
static inline void
|
||||
wl_webos_imported_set_user_data(struct wl_webos_imported *wl_webos_imported, void *user_data)
|
||||
{
|
||||
wl_proxy_set_user_data((struct wl_proxy *) wl_webos_imported, user_data);
|
||||
}
|
||||
|
||||
/** @ingroup iface_wl_webos_imported */
|
||||
static inline void *
|
||||
wl_webos_imported_get_user_data(struct wl_webos_imported *wl_webos_imported)
|
||||
{
|
||||
return wl_proxy_get_user_data((struct wl_proxy *) wl_webos_imported);
|
||||
}
|
||||
|
||||
static inline uint32_t
|
||||
wl_webos_imported_get_version(struct wl_webos_imported *wl_webos_imported)
|
||||
{
|
||||
return wl_proxy_get_version((struct wl_proxy *) wl_webos_imported);
|
||||
}
|
||||
|
||||
/**
|
||||
* @ingroup iface_wl_webos_imported
|
||||
*
|
||||
* Sets the alignment of the imported surface.
|
||||
*/
|
||||
static inline void
|
||||
wl_webos_imported_set_surface_alignment(struct wl_webos_imported *wl_webos_imported, uint32_t format)
|
||||
{
|
||||
wl_proxy_marshal_flags((struct wl_proxy *) wl_webos_imported,
|
||||
WL_WEBOS_IMPORTED_SET_SURFACE_ALIGNMENT, NULL, wl_proxy_get_version((struct wl_proxy *) wl_webos_imported), 0, format);
|
||||
}
|
||||
|
||||
/**
|
||||
* @ingroup iface_wl_webos_imported
|
||||
*
|
||||
* Destroys the imported object.
|
||||
*/
|
||||
static inline void
|
||||
wl_webos_imported_destroy(struct wl_webos_imported *wl_webos_imported)
|
||||
{
|
||||
wl_proxy_marshal_flags((struct wl_proxy *) wl_webos_imported,
|
||||
WL_WEBOS_IMPORTED_DESTROY, NULL, wl_proxy_get_version((struct wl_proxy *) wl_webos_imported), WL_MARSHAL_FLAG_DESTROY);
|
||||
}
|
||||
|
||||
/**
|
||||
* @ingroup iface_wl_webos_imported
|
||||
*
|
||||
* Requests a punch-through on the exporetd window associated with
|
||||
* this import object.
|
||||
*/
|
||||
static inline void
|
||||
wl_webos_imported_attach_punchthrough(struct wl_webos_imported *wl_webos_imported)
|
||||
{
|
||||
wl_proxy_marshal_flags((struct wl_proxy *) wl_webos_imported,
|
||||
WL_WEBOS_IMPORTED_ATTACH_PUNCHTHROUGH, NULL, wl_proxy_get_version((struct wl_proxy *) wl_webos_imported), 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* @ingroup iface_wl_webos_imported
|
||||
*/
|
||||
static inline void
|
||||
wl_webos_imported_set_punchthrough(struct wl_webos_imported *wl_webos_imported, const char *context_id)
|
||||
{
|
||||
wl_proxy_marshal_flags((struct wl_proxy *) wl_webos_imported,
|
||||
WL_WEBOS_IMPORTED_SET_PUNCHTHROUGH, NULL, wl_proxy_get_version((struct wl_proxy *) wl_webos_imported), 0, context_id);
|
||||
}
|
||||
|
||||
/**
|
||||
* @ingroup iface_wl_webos_imported
|
||||
*
|
||||
* Requests to detatch the punch-through from the exported window
|
||||
* associated with this import object.
|
||||
*/
|
||||
static inline void
|
||||
wl_webos_imported_detach_punchthrough(struct wl_webos_imported *wl_webos_imported)
|
||||
{
|
||||
wl_proxy_marshal_flags((struct wl_proxy *) wl_webos_imported,
|
||||
WL_WEBOS_IMPORTED_DETACH_PUNCHTHROUGH, NULL, wl_proxy_get_version((struct wl_proxy *) wl_webos_imported), 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* @ingroup iface_wl_webos_imported
|
||||
*
|
||||
* Requests to attach the surface as imported to the associated
|
||||
* exported window.
|
||||
*/
|
||||
static inline void
|
||||
wl_webos_imported_attach_surface(struct wl_webos_imported *wl_webos_imported, struct wl_surface *surface)
|
||||
{
|
||||
wl_proxy_marshal_flags((struct wl_proxy *) wl_webos_imported,
|
||||
WL_WEBOS_IMPORTED_ATTACH_SURFACE, NULL, wl_proxy_get_version((struct wl_proxy *) wl_webos_imported), 0, surface);
|
||||
}
|
||||
|
||||
/**
|
||||
* @ingroup iface_wl_webos_imported
|
||||
*
|
||||
* Requests to detach the surface as imported from the associated
|
||||
* exported window.
|
||||
*/
|
||||
static inline void
|
||||
wl_webos_imported_detach_surface(struct wl_webos_imported *wl_webos_imported, struct wl_surface *surface)
|
||||
{
|
||||
wl_proxy_marshal_flags((struct wl_proxy *) wl_webos_imported,
|
||||
WL_WEBOS_IMPORTED_DETACH_SURFACE, NULL, wl_proxy_get_version((struct wl_proxy *) wl_webos_imported), 0, surface);
|
||||
}
|
||||
|
||||
/**
|
||||
* @ingroup iface_wl_webos_imported
|
||||
*
|
||||
* Requests a punch-through on the exporetd window associated with
|
||||
* this import object for a given context id.
|
||||
*/
|
||||
static inline void
|
||||
wl_webos_imported_attach_punchthrough_with_context(struct wl_webos_imported *wl_webos_imported, const char *context_id)
|
||||
{
|
||||
wl_proxy_marshal_flags((struct wl_proxy *) wl_webos_imported,
|
||||
WL_WEBOS_IMPORTED_ATTACH_PUNCHTHROUGH_WITH_CONTEXT, NULL, wl_proxy_get_version((struct wl_proxy *) wl_webos_imported), 0, context_id);
|
||||
}
|
||||
|
||||
/**
|
||||
* @ingroup iface_wl_webos_imported
|
||||
*
|
||||
* Set the z index for webos_imported. The compositor will move the surface,
|
||||
* that is attached to webos_imported, to this layer. If no surface is attached to
|
||||
* webos_imported, nothing will happen.
|
||||
*/
|
||||
static inline void
|
||||
wl_webos_imported_set_z_index(struct wl_webos_imported *wl_webos_imported, int32_t z_index)
|
||||
{
|
||||
wl_proxy_marshal_flags((struct wl_proxy *) wl_webos_imported,
|
||||
WL_WEBOS_IMPORTED_SET_Z_INDEX, NULL, wl_proxy_get_version((struct wl_proxy *) wl_webos_imported), 0, z_index);
|
||||
}
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
@@ -1,530 +0,0 @@
|
||||
/* Generated by wayland-scanner 1.22.0 */
|
||||
|
||||
#ifndef WEBOS_INPUT_MANAGER_CLIENT_PROTOCOL_H
|
||||
#define WEBOS_INPUT_MANAGER_CLIENT_PROTOCOL_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
#include "wayland-client.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @page page_webos_input_manager The webos_input_manager protocol
|
||||
* @section page_ifaces_webos_input_manager Interfaces
|
||||
* - @subpage page_iface_wl_webos_input_manager - get additional input interface
|
||||
* - @subpage page_iface_wl_webos_seat - interface for additional input data
|
||||
* - @subpage page_iface_wl_webos_gyroscope - interface for gyroscope device
|
||||
* - @subpage page_iface_wl_webos_accelerometer - interface for accelerometer device
|
||||
* @section page_copyright_webos_input_manager Copyright
|
||||
* <pre>
|
||||
*
|
||||
* Copyright (c) 2014-2020 LG Electronics, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
* </pre>
|
||||
*/
|
||||
struct wl_seat;
|
||||
struct wl_webos_accelerometer;
|
||||
struct wl_webos_gyroscope;
|
||||
struct wl_webos_input_manager;
|
||||
struct wl_webos_seat;
|
||||
|
||||
#ifndef WL_WEBOS_INPUT_MANAGER_INTERFACE
|
||||
#define WL_WEBOS_INPUT_MANAGER_INTERFACE
|
||||
/**
|
||||
* @page page_iface_wl_webos_input_manager wl_webos_input_manager
|
||||
* @section page_iface_wl_webos_input_manager_desc Description
|
||||
*
|
||||
* wl_webos_seat has more information than wl_seat. User can get it using
|
||||
* get_webos_seat(seat).
|
||||
* @section page_iface_wl_webos_input_manager_api API
|
||||
* See @ref iface_wl_webos_input_manager.
|
||||
*/
|
||||
/**
|
||||
* @defgroup iface_wl_webos_input_manager The wl_webos_input_manager interface
|
||||
*
|
||||
* wl_webos_seat has more information than wl_seat. User can get it using
|
||||
* get_webos_seat(seat).
|
||||
*/
|
||||
extern const struct wl_interface wl_webos_input_manager_interface;
|
||||
#endif
|
||||
#ifndef WL_WEBOS_SEAT_INTERFACE
|
||||
#define WL_WEBOS_SEAT_INTERFACE
|
||||
/**
|
||||
* @page page_iface_wl_webos_seat wl_webos_seat
|
||||
* @section page_iface_wl_webos_seat_desc Description
|
||||
*
|
||||
* This is a webOS extension for additional input data such as gyroscope or
|
||||
* accelerometer. If the seat has additional input data, user can request it
|
||||
* using get_*.
|
||||
* @section page_iface_wl_webos_seat_api API
|
||||
* See @ref iface_wl_webos_seat.
|
||||
*/
|
||||
/**
|
||||
* @defgroup iface_wl_webos_seat The wl_webos_seat interface
|
||||
*
|
||||
* This is a webOS extension for additional input data such as gyroscope or
|
||||
* accelerometer. If the seat has additional input data, user can request it
|
||||
* using get_*.
|
||||
*/
|
||||
extern const struct wl_interface wl_webos_seat_interface;
|
||||
#endif
|
||||
#ifndef WL_WEBOS_GYROSCOPE_INTERFACE
|
||||
#define WL_WEBOS_GYROSCOPE_INTERFACE
|
||||
/**
|
||||
* @page page_iface_wl_webos_gyroscope wl_webos_gyroscope
|
||||
* @section page_iface_wl_webos_gyroscope_desc Description
|
||||
*
|
||||
* This is an interface for gyroscope device.
|
||||
* @section page_iface_wl_webos_gyroscope_api API
|
||||
* See @ref iface_wl_webos_gyroscope.
|
||||
*/
|
||||
/**
|
||||
* @defgroup iface_wl_webos_gyroscope The wl_webos_gyroscope interface
|
||||
*
|
||||
* This is an interface for gyroscope device.
|
||||
*/
|
||||
extern const struct wl_interface wl_webos_gyroscope_interface;
|
||||
#endif
|
||||
#ifndef WL_WEBOS_ACCELEROMETER_INTERFACE
|
||||
#define WL_WEBOS_ACCELEROMETER_INTERFACE
|
||||
/**
|
||||
* @page page_iface_wl_webos_accelerometer wl_webos_accelerometer
|
||||
* @section page_iface_wl_webos_accelerometer_desc Description
|
||||
*
|
||||
* This is an interface for accelerometer device.
|
||||
* @section page_iface_wl_webos_accelerometer_api API
|
||||
* See @ref iface_wl_webos_accelerometer.
|
||||
*/
|
||||
/**
|
||||
* @defgroup iface_wl_webos_accelerometer The wl_webos_accelerometer interface
|
||||
*
|
||||
* This is an interface for accelerometer device.
|
||||
*/
|
||||
extern const struct wl_interface wl_webos_accelerometer_interface;
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @ingroup iface_wl_webos_input_manager
|
||||
* @struct wl_webos_input_manager_listener
|
||||
*/
|
||||
struct wl_webos_input_manager_listener {
|
||||
/**
|
||||
* current cursor visibility
|
||||
*
|
||||
* If the current cursor visibility is chagned, notify it.
|
||||
* @param visibility boolean vaule for visibility of current cursor
|
||||
* @param webos_seat webos_seat cursor visibility changed, if this value is Null All cursors are hidden
|
||||
*/
|
||||
void (*cursor_visibility)(void *data,
|
||||
struct wl_webos_input_manager *wl_webos_input_manager,
|
||||
uint32_t visibility,
|
||||
struct wl_webos_seat *webos_seat);
|
||||
};
|
||||
|
||||
/**
|
||||
* @ingroup iface_wl_webos_input_manager
|
||||
*/
|
||||
static inline int
|
||||
wl_webos_input_manager_add_listener(struct wl_webos_input_manager *wl_webos_input_manager,
|
||||
const struct wl_webos_input_manager_listener *listener, void *data)
|
||||
{
|
||||
return wl_proxy_add_listener((struct wl_proxy *) wl_webos_input_manager,
|
||||
(void (**)(void)) listener, data);
|
||||
}
|
||||
|
||||
#define WL_WEBOS_INPUT_MANAGER_GET_WEBOS_SEAT 0
|
||||
#define WL_WEBOS_INPUT_MANAGER_SET_CURSOR_VISIBILITY 1
|
||||
|
||||
/**
|
||||
* @ingroup iface_wl_webos_input_manager
|
||||
*/
|
||||
#define WL_WEBOS_INPUT_MANAGER_CURSOR_VISIBILITY_SINCE_VERSION 1
|
||||
|
||||
/**
|
||||
* @ingroup iface_wl_webos_input_manager
|
||||
*/
|
||||
#define WL_WEBOS_INPUT_MANAGER_GET_WEBOS_SEAT_SINCE_VERSION 1
|
||||
/**
|
||||
* @ingroup iface_wl_webos_input_manager
|
||||
*/
|
||||
#define WL_WEBOS_INPUT_MANAGER_SET_CURSOR_VISIBILITY_SINCE_VERSION 1
|
||||
|
||||
/** @ingroup iface_wl_webos_input_manager */
|
||||
static inline void
|
||||
wl_webos_input_manager_set_user_data(struct wl_webos_input_manager *wl_webos_input_manager, void *user_data)
|
||||
{
|
||||
wl_proxy_set_user_data((struct wl_proxy *) wl_webos_input_manager, user_data);
|
||||
}
|
||||
|
||||
/** @ingroup iface_wl_webos_input_manager */
|
||||
static inline void *
|
||||
wl_webos_input_manager_get_user_data(struct wl_webos_input_manager *wl_webos_input_manager)
|
||||
{
|
||||
return wl_proxy_get_user_data((struct wl_proxy *) wl_webos_input_manager);
|
||||
}
|
||||
|
||||
static inline uint32_t
|
||||
wl_webos_input_manager_get_version(struct wl_webos_input_manager *wl_webos_input_manager)
|
||||
{
|
||||
return wl_proxy_get_version((struct wl_proxy *) wl_webos_input_manager);
|
||||
}
|
||||
|
||||
/** @ingroup iface_wl_webos_input_manager */
|
||||
static inline void
|
||||
wl_webos_input_manager_destroy(struct wl_webos_input_manager *wl_webos_input_manager)
|
||||
{
|
||||
wl_proxy_destroy((struct wl_proxy *) wl_webos_input_manager);
|
||||
}
|
||||
|
||||
/**
|
||||
* @ingroup iface_wl_webos_input_manager
|
||||
*
|
||||
* Returns a webos_seat associated with the seat.
|
||||
*/
|
||||
static inline struct wl_webos_seat *
|
||||
wl_webos_input_manager_get_webos_seat(struct wl_webos_input_manager *wl_webos_input_manager, struct wl_seat *seat)
|
||||
{
|
||||
struct wl_proxy *id;
|
||||
|
||||
id = wl_proxy_marshal_flags((struct wl_proxy *) wl_webos_input_manager,
|
||||
WL_WEBOS_INPUT_MANAGER_GET_WEBOS_SEAT, &wl_webos_seat_interface, wl_proxy_get_version((struct wl_proxy *) wl_webos_input_manager), 0, NULL, seat);
|
||||
|
||||
return (struct wl_webos_seat *) id;
|
||||
}
|
||||
|
||||
/**
|
||||
* @ingroup iface_wl_webos_input_manager
|
||||
*
|
||||
* Sets the cursor visibility.
|
||||
*/
|
||||
static inline void
|
||||
wl_webos_input_manager_set_cursor_visibility(struct wl_webos_input_manager *wl_webos_input_manager, uint32_t visibility)
|
||||
{
|
||||
wl_proxy_marshal_flags((struct wl_proxy *) wl_webos_input_manager,
|
||||
WL_WEBOS_INPUT_MANAGER_SET_CURSOR_VISIBILITY, NULL, wl_proxy_get_version((struct wl_proxy *) wl_webos_input_manager), 0, visibility);
|
||||
}
|
||||
|
||||
#ifndef WL_WEBOS_SEAT_CAPABILITY_ENUM
|
||||
#define WL_WEBOS_SEAT_CAPABILITY_ENUM
|
||||
/**
|
||||
* @ingroup iface_wl_webos_seat
|
||||
* webos seat capability bitmask
|
||||
*
|
||||
* This is a bitmask of capabilities this webOS seat has; if a member is
|
||||
* set, then it is present on the seat.
|
||||
*/
|
||||
enum wl_webos_seat_capability {
|
||||
/**
|
||||
* this seat does not provide additional input
|
||||
*/
|
||||
WL_WEBOS_SEAT_CAPABILITY_DEFAULT = 0,
|
||||
/**
|
||||
* this seat can provide gyroscope data, wl_webos_gyroscope
|
||||
*/
|
||||
WL_WEBOS_SEAT_CAPABILITY_GYROSCOPE = 1,
|
||||
/**
|
||||
* this seat can provide accelerometer data, wl_webos_accelerometer
|
||||
*/
|
||||
WL_WEBOS_SEAT_CAPABILITY_ACCELEROMETER = 2,
|
||||
};
|
||||
#endif /* WL_WEBOS_SEAT_CAPABILITY_ENUM */
|
||||
|
||||
/**
|
||||
* @ingroup iface_wl_webos_seat
|
||||
* @struct wl_webos_seat_listener
|
||||
*/
|
||||
struct wl_webos_seat_listener {
|
||||
/**
|
||||
* info about the seat
|
||||
*
|
||||
* This event will be sent immediately after the client as
|
||||
* requested the webOS seat from wl_webos_input_manager.
|
||||
*
|
||||
* The designator [0, 1, ... N] indicates if this is the
|
||||
* primary(=0), secondary, etc seat.
|
||||
* @param id an unique identifier for the seat
|
||||
* @param name a human readable name for the seat
|
||||
* @param designator a value for the designation of this seat
|
||||
*/
|
||||
void (*info)(void *data,
|
||||
struct wl_webos_seat *wl_webos_seat,
|
||||
uint32_t id,
|
||||
const char *name,
|
||||
uint32_t designator,
|
||||
uint32_t capabilities);
|
||||
};
|
||||
|
||||
/**
|
||||
* @ingroup iface_wl_webos_seat
|
||||
*/
|
||||
static inline int
|
||||
wl_webos_seat_add_listener(struct wl_webos_seat *wl_webos_seat,
|
||||
const struct wl_webos_seat_listener *listener, void *data)
|
||||
{
|
||||
return wl_proxy_add_listener((struct wl_proxy *) wl_webos_seat,
|
||||
(void (**)(void)) listener, data);
|
||||
}
|
||||
|
||||
#define WL_WEBOS_SEAT_GET_GYROSCOPE 0
|
||||
#define WL_WEBOS_SEAT_GET_ACCELEROMETER 1
|
||||
|
||||
/**
|
||||
* @ingroup iface_wl_webos_seat
|
||||
*/
|
||||
#define WL_WEBOS_SEAT_INFO_SINCE_VERSION 1
|
||||
|
||||
/**
|
||||
* @ingroup iface_wl_webos_seat
|
||||
*/
|
||||
#define WL_WEBOS_SEAT_GET_GYROSCOPE_SINCE_VERSION 1
|
||||
/**
|
||||
* @ingroup iface_wl_webos_seat
|
||||
*/
|
||||
#define WL_WEBOS_SEAT_GET_ACCELEROMETER_SINCE_VERSION 1
|
||||
|
||||
/** @ingroup iface_wl_webos_seat */
|
||||
static inline void
|
||||
wl_webos_seat_set_user_data(struct wl_webos_seat *wl_webos_seat, void *user_data)
|
||||
{
|
||||
wl_proxy_set_user_data((struct wl_proxy *) wl_webos_seat, user_data);
|
||||
}
|
||||
|
||||
/** @ingroup iface_wl_webos_seat */
|
||||
static inline void *
|
||||
wl_webos_seat_get_user_data(struct wl_webos_seat *wl_webos_seat)
|
||||
{
|
||||
return wl_proxy_get_user_data((struct wl_proxy *) wl_webos_seat);
|
||||
}
|
||||
|
||||
static inline uint32_t
|
||||
wl_webos_seat_get_version(struct wl_webos_seat *wl_webos_seat)
|
||||
{
|
||||
return wl_proxy_get_version((struct wl_proxy *) wl_webos_seat);
|
||||
}
|
||||
|
||||
/** @ingroup iface_wl_webos_seat */
|
||||
static inline void
|
||||
wl_webos_seat_destroy(struct wl_webos_seat *wl_webos_seat)
|
||||
{
|
||||
wl_proxy_destroy((struct wl_proxy *) wl_webos_seat);
|
||||
}
|
||||
|
||||
/**
|
||||
* @ingroup iface_wl_webos_seat
|
||||
*
|
||||
* If the seat has gyroscope sensor, user can get this interface.
|
||||
*/
|
||||
static inline struct wl_webos_gyroscope *
|
||||
wl_webos_seat_get_gyroscope(struct wl_webos_seat *wl_webos_seat)
|
||||
{
|
||||
struct wl_proxy *id;
|
||||
|
||||
id = wl_proxy_marshal_flags((struct wl_proxy *) wl_webos_seat,
|
||||
WL_WEBOS_SEAT_GET_GYROSCOPE, &wl_webos_gyroscope_interface, wl_proxy_get_version((struct wl_proxy *) wl_webos_seat), 0, NULL);
|
||||
|
||||
return (struct wl_webos_gyroscope *) id;
|
||||
}
|
||||
|
||||
/**
|
||||
* @ingroup iface_wl_webos_seat
|
||||
*
|
||||
* If the seat has accelerometer sensor, user can get this interface.
|
||||
*/
|
||||
static inline struct wl_webos_accelerometer *
|
||||
wl_webos_seat_get_accelerometer(struct wl_webos_seat *wl_webos_seat)
|
||||
{
|
||||
struct wl_proxy *id;
|
||||
|
||||
id = wl_proxy_marshal_flags((struct wl_proxy *) wl_webos_seat,
|
||||
WL_WEBOS_SEAT_GET_ACCELEROMETER, &wl_webos_accelerometer_interface, wl_proxy_get_version((struct wl_proxy *) wl_webos_seat), 0, NULL);
|
||||
|
||||
return (struct wl_webos_accelerometer *) id;
|
||||
}
|
||||
|
||||
/**
|
||||
* @ingroup iface_wl_webos_gyroscope
|
||||
* @struct wl_webos_gyroscope_listener
|
||||
*/
|
||||
struct wl_webos_gyroscope_listener {
|
||||
/**
|
||||
* sensor data from the seat
|
||||
*
|
||||
* The gyroscope sensor data will be delivered only if the client
|
||||
* explicitly requested it via "requeest_data".
|
||||
* @param x data for x-axis
|
||||
* @param y data for y-axis
|
||||
* @param z data for z-axis
|
||||
*/
|
||||
void (*sensor_data)(void *data,
|
||||
struct wl_webos_gyroscope *wl_webos_gyroscope,
|
||||
wl_fixed_t x,
|
||||
wl_fixed_t y,
|
||||
wl_fixed_t z);
|
||||
};
|
||||
|
||||
/**
|
||||
* @ingroup iface_wl_webos_gyroscope
|
||||
*/
|
||||
static inline int
|
||||
wl_webos_gyroscope_add_listener(struct wl_webos_gyroscope *wl_webos_gyroscope,
|
||||
const struct wl_webos_gyroscope_listener *listener, void *data)
|
||||
{
|
||||
return wl_proxy_add_listener((struct wl_proxy *) wl_webos_gyroscope,
|
||||
(void (**)(void)) listener, data);
|
||||
}
|
||||
|
||||
#define WL_WEBOS_GYROSCOPE_REQUEST_DATA 0
|
||||
|
||||
/**
|
||||
* @ingroup iface_wl_webos_gyroscope
|
||||
*/
|
||||
#define WL_WEBOS_GYROSCOPE_SENSOR_DATA_SINCE_VERSION 1
|
||||
|
||||
/**
|
||||
* @ingroup iface_wl_webos_gyroscope
|
||||
*/
|
||||
#define WL_WEBOS_GYROSCOPE_REQUEST_DATA_SINCE_VERSION 1
|
||||
|
||||
/** @ingroup iface_wl_webos_gyroscope */
|
||||
static inline void
|
||||
wl_webos_gyroscope_set_user_data(struct wl_webos_gyroscope *wl_webos_gyroscope, void *user_data)
|
||||
{
|
||||
wl_proxy_set_user_data((struct wl_proxy *) wl_webos_gyroscope, user_data);
|
||||
}
|
||||
|
||||
/** @ingroup iface_wl_webos_gyroscope */
|
||||
static inline void *
|
||||
wl_webos_gyroscope_get_user_data(struct wl_webos_gyroscope *wl_webos_gyroscope)
|
||||
{
|
||||
return wl_proxy_get_user_data((struct wl_proxy *) wl_webos_gyroscope);
|
||||
}
|
||||
|
||||
static inline uint32_t
|
||||
wl_webos_gyroscope_get_version(struct wl_webos_gyroscope *wl_webos_gyroscope)
|
||||
{
|
||||
return wl_proxy_get_version((struct wl_proxy *) wl_webos_gyroscope);
|
||||
}
|
||||
|
||||
/** @ingroup iface_wl_webos_gyroscope */
|
||||
static inline void
|
||||
wl_webos_gyroscope_destroy(struct wl_webos_gyroscope *wl_webos_gyroscope)
|
||||
{
|
||||
wl_proxy_destroy((struct wl_proxy *) wl_webos_gyroscope);
|
||||
}
|
||||
|
||||
/**
|
||||
* @ingroup iface_wl_webos_gyroscope
|
||||
*
|
||||
* Requests to enable/disable sending data for gyroscope device.
|
||||
*/
|
||||
static inline void
|
||||
wl_webos_gyroscope_request_data(struct wl_webos_gyroscope *wl_webos_gyroscope, uint32_t enabled)
|
||||
{
|
||||
wl_proxy_marshal_flags((struct wl_proxy *) wl_webos_gyroscope,
|
||||
WL_WEBOS_GYROSCOPE_REQUEST_DATA, NULL, wl_proxy_get_version((struct wl_proxy *) wl_webos_gyroscope), 0, enabled);
|
||||
}
|
||||
|
||||
/**
|
||||
* @ingroup iface_wl_webos_accelerometer
|
||||
* @struct wl_webos_accelerometer_listener
|
||||
*/
|
||||
struct wl_webos_accelerometer_listener {
|
||||
/**
|
||||
* sensor data from the seat
|
||||
*
|
||||
* The accelerometer sensor data will be delivered only if the
|
||||
* client explicitly requested it via "request_data".
|
||||
* @param x data for x-axis
|
||||
* @param y data for y-axis
|
||||
* @param z data for z-axis
|
||||
*/
|
||||
void (*sensor_data)(void *data,
|
||||
struct wl_webos_accelerometer *wl_webos_accelerometer,
|
||||
wl_fixed_t x,
|
||||
wl_fixed_t y,
|
||||
wl_fixed_t z);
|
||||
};
|
||||
|
||||
/**
|
||||
* @ingroup iface_wl_webos_accelerometer
|
||||
*/
|
||||
static inline int
|
||||
wl_webos_accelerometer_add_listener(struct wl_webos_accelerometer *wl_webos_accelerometer,
|
||||
const struct wl_webos_accelerometer_listener *listener, void *data)
|
||||
{
|
||||
return wl_proxy_add_listener((struct wl_proxy *) wl_webos_accelerometer,
|
||||
(void (**)(void)) listener, data);
|
||||
}
|
||||
|
||||
#define WL_WEBOS_ACCELEROMETER_REQUEST_DATA 0
|
||||
|
||||
/**
|
||||
* @ingroup iface_wl_webos_accelerometer
|
||||
*/
|
||||
#define WL_WEBOS_ACCELEROMETER_SENSOR_DATA_SINCE_VERSION 1
|
||||
|
||||
/**
|
||||
* @ingroup iface_wl_webos_accelerometer
|
||||
*/
|
||||
#define WL_WEBOS_ACCELEROMETER_REQUEST_DATA_SINCE_VERSION 1
|
||||
|
||||
/** @ingroup iface_wl_webos_accelerometer */
|
||||
static inline void
|
||||
wl_webos_accelerometer_set_user_data(struct wl_webos_accelerometer *wl_webos_accelerometer, void *user_data)
|
||||
{
|
||||
wl_proxy_set_user_data((struct wl_proxy *) wl_webos_accelerometer, user_data);
|
||||
}
|
||||
|
||||
/** @ingroup iface_wl_webos_accelerometer */
|
||||
static inline void *
|
||||
wl_webos_accelerometer_get_user_data(struct wl_webos_accelerometer *wl_webos_accelerometer)
|
||||
{
|
||||
return wl_proxy_get_user_data((struct wl_proxy *) wl_webos_accelerometer);
|
||||
}
|
||||
|
||||
static inline uint32_t
|
||||
wl_webos_accelerometer_get_version(struct wl_webos_accelerometer *wl_webos_accelerometer)
|
||||
{
|
||||
return wl_proxy_get_version((struct wl_proxy *) wl_webos_accelerometer);
|
||||
}
|
||||
|
||||
/** @ingroup iface_wl_webos_accelerometer */
|
||||
static inline void
|
||||
wl_webos_accelerometer_destroy(struct wl_webos_accelerometer *wl_webos_accelerometer)
|
||||
{
|
||||
wl_proxy_destroy((struct wl_proxy *) wl_webos_accelerometer);
|
||||
}
|
||||
|
||||
/**
|
||||
* @ingroup iface_wl_webos_accelerometer
|
||||
*
|
||||
* Requests to enable/disable sending data for accelerometer device.
|
||||
*/
|
||||
static inline void
|
||||
wl_webos_accelerometer_request_data(struct wl_webos_accelerometer *wl_webos_accelerometer, uint32_t enabled)
|
||||
{
|
||||
wl_proxy_marshal_flags((struct wl_proxy *) wl_webos_accelerometer,
|
||||
WL_WEBOS_ACCELEROMETER_REQUEST_DATA, NULL, wl_proxy_get_version((struct wl_proxy *) wl_webos_accelerometer), 0, enabled);
|
||||
}
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
@@ -1,506 +0,0 @@
|
||||
/* Generated by wayland-scanner 1.22.0 */
|
||||
|
||||
#ifndef WEBOS_SHELL_CLIENT_PROTOCOL_H
|
||||
#define WEBOS_SHELL_CLIENT_PROTOCOL_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
#include "wayland-client.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @page page_webos_shell The webos_shell protocol
|
||||
* @section page_ifaces_webos_shell Interfaces
|
||||
* - @subpage page_iface_wl_webos_shell - interface for webOS shell
|
||||
* - @subpage page_iface_wl_webos_shell_surface - interface to handle webos shell surfaces
|
||||
* @section page_copyright_webos_shell Copyright
|
||||
* <pre>
|
||||
*
|
||||
* Copyright (c) 2013-2020 LG Electronics, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
* </pre>
|
||||
*/
|
||||
struct wl_surface;
|
||||
struct wl_webos_shell;
|
||||
struct wl_webos_shell_surface;
|
||||
|
||||
#ifndef WL_WEBOS_SHELL_INTERFACE
|
||||
#define WL_WEBOS_SHELL_INTERFACE
|
||||
/**
|
||||
* @page page_iface_wl_webos_shell wl_webos_shell
|
||||
* @section page_iface_wl_webos_shell_desc Description
|
||||
*
|
||||
* This interface provides handlings on webOS shell surfaces.
|
||||
* @section page_iface_wl_webos_shell_api API
|
||||
* See @ref iface_wl_webos_shell.
|
||||
*/
|
||||
/**
|
||||
* @defgroup iface_wl_webos_shell The wl_webos_shell interface
|
||||
*
|
||||
* This interface provides handlings on webOS shell surfaces.
|
||||
*/
|
||||
extern const struct wl_interface wl_webos_shell_interface;
|
||||
#endif
|
||||
#ifndef WL_WEBOS_SHELL_SURFACE_INTERFACE
|
||||
#define WL_WEBOS_SHELL_SURFACE_INTERFACE
|
||||
/**
|
||||
* @page page_iface_wl_webos_shell_surface wl_webos_shell_surface
|
||||
* @section page_iface_wl_webos_shell_surface_desc Description
|
||||
*
|
||||
* This allows the client to associate an normal surface with a
|
||||
* shell surface. The shell surface provides webOS specific functionality
|
||||
* to surfaces.
|
||||
* @section page_iface_wl_webos_shell_surface_api API
|
||||
* See @ref iface_wl_webos_shell_surface.
|
||||
*/
|
||||
/**
|
||||
* @defgroup iface_wl_webos_shell_surface The wl_webos_shell_surface interface
|
||||
*
|
||||
* This allows the client to associate an normal surface with a
|
||||
* shell surface. The shell surface provides webOS specific functionality
|
||||
* to surfaces.
|
||||
*/
|
||||
extern const struct wl_interface wl_webos_shell_surface_interface;
|
||||
#endif
|
||||
|
||||
#define WL_WEBOS_SHELL_GET_SYSTEM_PIP 0
|
||||
#define WL_WEBOS_SHELL_GET_SHELL_SURFACE 1
|
||||
|
||||
|
||||
/**
|
||||
* @ingroup iface_wl_webos_shell
|
||||
*/
|
||||
#define WL_WEBOS_SHELL_GET_SYSTEM_PIP_SINCE_VERSION 1
|
||||
/**
|
||||
* @ingroup iface_wl_webos_shell
|
||||
*/
|
||||
#define WL_WEBOS_SHELL_GET_SHELL_SURFACE_SINCE_VERSION 1
|
||||
|
||||
/** @ingroup iface_wl_webos_shell */
|
||||
static inline void
|
||||
wl_webos_shell_set_user_data(struct wl_webos_shell *wl_webos_shell, void *user_data)
|
||||
{
|
||||
wl_proxy_set_user_data((struct wl_proxy *) wl_webos_shell, user_data);
|
||||
}
|
||||
|
||||
/** @ingroup iface_wl_webos_shell */
|
||||
static inline void *
|
||||
wl_webos_shell_get_user_data(struct wl_webos_shell *wl_webos_shell)
|
||||
{
|
||||
return wl_proxy_get_user_data((struct wl_proxy *) wl_webos_shell);
|
||||
}
|
||||
|
||||
static inline uint32_t
|
||||
wl_webos_shell_get_version(struct wl_webos_shell *wl_webos_shell)
|
||||
{
|
||||
return wl_proxy_get_version((struct wl_proxy *) wl_webos_shell);
|
||||
}
|
||||
|
||||
/** @ingroup iface_wl_webos_shell */
|
||||
static inline void
|
||||
wl_webos_shell_destroy(struct wl_webos_shell *wl_webos_shell)
|
||||
{
|
||||
wl_proxy_destroy((struct wl_proxy *) wl_webos_shell);
|
||||
}
|
||||
|
||||
/**
|
||||
* @ingroup iface_wl_webos_shell
|
||||
*
|
||||
* This request is deprecated. If you want to create a new request,
|
||||
* you can reuse this one. FYI, in that case, you also need to modify
|
||||
* get_system_pip method in luna-surfacemanager as well.
|
||||
*/
|
||||
static inline void
|
||||
wl_webos_shell_get_system_pip(struct wl_webos_shell *wl_webos_shell)
|
||||
{
|
||||
wl_proxy_marshal_flags((struct wl_proxy *) wl_webos_shell,
|
||||
WL_WEBOS_SHELL_GET_SYSTEM_PIP, NULL, wl_proxy_get_version((struct wl_proxy *) wl_webos_shell), 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* @ingroup iface_wl_webos_shell
|
||||
*
|
||||
* Creates an webOS shell surface for an existing surface.
|
||||
*/
|
||||
static inline struct wl_webos_shell_surface *
|
||||
wl_webos_shell_get_shell_surface(struct wl_webos_shell *wl_webos_shell, struct wl_surface *surface)
|
||||
{
|
||||
struct wl_proxy *id;
|
||||
|
||||
id = wl_proxy_marshal_flags((struct wl_proxy *) wl_webos_shell,
|
||||
WL_WEBOS_SHELL_GET_SHELL_SURFACE, &wl_webos_shell_surface_interface, wl_proxy_get_version((struct wl_proxy *) wl_webos_shell), 0, NULL, surface);
|
||||
|
||||
return (struct wl_webos_shell_surface *) id;
|
||||
}
|
||||
|
||||
#ifndef WL_WEBOS_SHELL_SURFACE_LOCATION_HINT_ENUM
|
||||
#define WL_WEBOS_SHELL_SURFACE_LOCATION_HINT_ENUM
|
||||
/**
|
||||
* @ingroup iface_wl_webos_shell_surface
|
||||
* location mask for the surface
|
||||
*
|
||||
* Using this mask the surface can be positioned onto
|
||||
* different locations on screen. By not using absolute
|
||||
* coordinates we can create greater flexibility in the
|
||||
* compositor and the clients.
|
||||
*/
|
||||
enum wl_webos_shell_surface_location_hint {
|
||||
WL_WEBOS_SHELL_SURFACE_LOCATION_HINT_NORTH = 1,
|
||||
WL_WEBOS_SHELL_SURFACE_LOCATION_HINT_WEST = 2,
|
||||
WL_WEBOS_SHELL_SURFACE_LOCATION_HINT_SOUTH = 4,
|
||||
WL_WEBOS_SHELL_SURFACE_LOCATION_HINT_EAST = 8,
|
||||
WL_WEBOS_SHELL_SURFACE_LOCATION_HINT_CENTER = 16,
|
||||
};
|
||||
#endif /* WL_WEBOS_SHELL_SURFACE_LOCATION_HINT_ENUM */
|
||||
|
||||
#ifndef WL_WEBOS_SHELL_SURFACE_STATE_ENUM
|
||||
#define WL_WEBOS_SHELL_SURFACE_STATE_ENUM
|
||||
/**
|
||||
* @ingroup iface_wl_webos_shell_surface
|
||||
* the state of the surface
|
||||
*
|
||||
* The state provides info to the client on how the compositor has placed
|
||||
* the surface.
|
||||
*
|
||||
* The default state will indicate to the client that it is windowed. The
|
||||
* "position_changed" event will tell the position in screen coordinates
|
||||
* when the surface is in this state.
|
||||
*/
|
||||
enum wl_webos_shell_surface_state {
|
||||
WL_WEBOS_SHELL_SURFACE_STATE_DEFAULT = 0,
|
||||
WL_WEBOS_SHELL_SURFACE_STATE_MINIMIZED = 1,
|
||||
WL_WEBOS_SHELL_SURFACE_STATE_MAXIMIZED = 2,
|
||||
WL_WEBOS_SHELL_SURFACE_STATE_FULLSCREEN = 3,
|
||||
};
|
||||
#endif /* WL_WEBOS_SHELL_SURFACE_STATE_ENUM */
|
||||
|
||||
#ifndef WL_WEBOS_SHELL_SURFACE_WEBOS_KEY_ENUM
|
||||
#define WL_WEBOS_SHELL_SURFACE_WEBOS_KEY_ENUM
|
||||
/**
|
||||
* @ingroup iface_wl_webos_shell_surface
|
||||
* the key that the surface handles
|
||||
*
|
||||
* The client uses wl_webos_shell_surface.set_key_mask to notify server
|
||||
* that it will handle specific keys only.
|
||||
*/
|
||||
enum wl_webos_shell_surface_webos_key {
|
||||
WL_WEBOS_SHELL_SURFACE_WEBOS_KEY_HOME = 1,
|
||||
WL_WEBOS_SHELL_SURFACE_WEBOS_KEY_BACK = 2,
|
||||
WL_WEBOS_SHELL_SURFACE_WEBOS_KEY_EXIT = 4,
|
||||
WL_WEBOS_SHELL_SURFACE_WEBOS_KEY_NAVIGATION_LEFT = 8,
|
||||
WL_WEBOS_SHELL_SURFACE_WEBOS_KEY_NAVIGATION_RIGHT = 16,
|
||||
WL_WEBOS_SHELL_SURFACE_WEBOS_KEY_NAVIGATION_UP = 32,
|
||||
WL_WEBOS_SHELL_SURFACE_WEBOS_KEY_NAVIGATION_DOWN = 64,
|
||||
WL_WEBOS_SHELL_SURFACE_WEBOS_KEY_NAVIGATION_OK = 128,
|
||||
WL_WEBOS_SHELL_SURFACE_WEBOS_KEY_NUMERIC_KEYS = 256,
|
||||
WL_WEBOS_SHELL_SURFACE_WEBOS_KEY_REMOTE_COLOR_RED = 512,
|
||||
WL_WEBOS_SHELL_SURFACE_WEBOS_KEY_REMOTE_COLOR_GREEN = 1024,
|
||||
WL_WEBOS_SHELL_SURFACE_WEBOS_KEY_REMOTE_COLOR_YELLOW = 2048,
|
||||
WL_WEBOS_SHELL_SURFACE_WEBOS_KEY_REMOTE_COLOR_BLUE = 4096,
|
||||
WL_WEBOS_SHELL_SURFACE_WEBOS_KEY_REMOTE_PROGRAMME_GROUP = 8192,
|
||||
WL_WEBOS_SHELL_SURFACE_WEBOS_KEY_REMOTE_PLAYBACK_GROUP = 16384,
|
||||
WL_WEBOS_SHELL_SURFACE_WEBOS_KEY_REMOTE_TELETEXT_GROUP = 32768,
|
||||
WL_WEBOS_SHELL_SURFACE_WEBOS_KEY_LOCAL_LEFT = 65536,
|
||||
WL_WEBOS_SHELL_SURFACE_WEBOS_KEY_LOCAL_RIGHT = 131072,
|
||||
WL_WEBOS_SHELL_SURFACE_WEBOS_KEY_LOCAL_UP = 262144,
|
||||
WL_WEBOS_SHELL_SURFACE_WEBOS_KEY_LOCAL_DOWN = 524288,
|
||||
WL_WEBOS_SHELL_SURFACE_WEBOS_KEY_LOCAL_OK = 1048576,
|
||||
WL_WEBOS_SHELL_SURFACE_WEBOS_KEY_REMOTE_MAGNIFIER_GROUP = 2097152,
|
||||
WL_WEBOS_SHELL_SURFACE_WEBOS_KEY_MINIMAL_PLAYBACK_GROUP = 4194304,
|
||||
WL_WEBOS_SHELL_SURFACE_WEBOS_KEY_GUIDE = 8388608,
|
||||
WL_WEBOS_SHELL_SURFACE_WEBOS_KEY_TELETEXT_ACTIVE_GROUP = 16777216,
|
||||
WL_WEBOS_SHELL_SURFACE_WEBOS_KEY_DATA = 33554432,
|
||||
WL_WEBOS_SHELL_SURFACE_WEBOS_KEY_INFO = 67108864,
|
||||
WL_WEBOS_SHELL_SURFACE_WEBOS_KEY_DEFAULT = 0xFFFFFFF8,
|
||||
};
|
||||
#endif /* WL_WEBOS_SHELL_SURFACE_WEBOS_KEY_ENUM */
|
||||
|
||||
#ifndef WL_WEBOS_SHELL_SURFACE_ADDON_STATUS_ENUM
|
||||
#define WL_WEBOS_SHELL_SURFACE_ADDON_STATUS_ENUM
|
||||
/**
|
||||
* @ingroup iface_wl_webos_shell_surface
|
||||
* the status of the add-on
|
||||
*
|
||||
* Status of the add-on for the given shell surface.
|
||||
* null: neither server-side add-on set nor loaded
|
||||
* loaded: the server-side add-on is loaded and visible
|
||||
* denied: the server-side add-on is not allowed to load
|
||||
* error: the server-side add-on is not loaded due to an error
|
||||
*/
|
||||
enum wl_webos_shell_surface_addon_status {
|
||||
WL_WEBOS_SHELL_SURFACE_ADDON_STATUS_NULL = 0,
|
||||
WL_WEBOS_SHELL_SURFACE_ADDON_STATUS_LOADED = 1,
|
||||
WL_WEBOS_SHELL_SURFACE_ADDON_STATUS_DENIED = 2,
|
||||
WL_WEBOS_SHELL_SURFACE_ADDON_STATUS_ERROR = 3,
|
||||
};
|
||||
#endif /* WL_WEBOS_SHELL_SURFACE_ADDON_STATUS_ENUM */
|
||||
|
||||
/**
|
||||
* @ingroup iface_wl_webos_shell_surface
|
||||
* @struct wl_webos_shell_surface_listener
|
||||
*/
|
||||
struct wl_webos_shell_surface_listener {
|
||||
/**
|
||||
* the surface state was changed
|
||||
*
|
||||
* The compositor or the user has taken action that has resulted
|
||||
* in this surface to change state.
|
||||
*/
|
||||
void (*state_changed)(void *data,
|
||||
struct wl_webos_shell_surface *wl_webos_shell_surface,
|
||||
uint32_t state);
|
||||
/**
|
||||
* the surface position on screen changed
|
||||
*
|
||||
* This event will only be sent the surfaces that are in their
|
||||
* default state. It will be not sent for fullscreen surfaces nor
|
||||
* for surfaces that are minimized.
|
||||
*/
|
||||
void (*position_changed)(void *data,
|
||||
struct wl_webos_shell_surface *wl_webos_shell_surface,
|
||||
int32_t x,
|
||||
int32_t y);
|
||||
/**
|
||||
* request closing of window
|
||||
*
|
||||
* The close event is sent when the compositor requests closing
|
||||
* of a window, e.g. through some gesture or other interaction,
|
||||
* without explicitly quitting or killing the client.
|
||||
*/
|
||||
void (*close)(void *data,
|
||||
struct wl_webos_shell_surface *wl_webos_shell_surface);
|
||||
/**
|
||||
* indicates what areas are exposed
|
||||
*
|
||||
* The array contains non-overlapping rectangles that define
|
||||
* areas that are visible in this surface. The array elements are a
|
||||
* series of ints arranged as follows x,y,w,h,x,y,w,h,-1. The
|
||||
* values will not contain negative numbers and the -1 is treated
|
||||
* as a terminating marker.
|
||||
*/
|
||||
void (*exposed)(void *data,
|
||||
struct wl_webos_shell_surface *wl_webos_shell_surface,
|
||||
struct wl_array *rectangles);
|
||||
/**
|
||||
* the surface state is about to change
|
||||
*
|
||||
* Be sure that the actual state should not be changed with this.
|
||||
* Client can only prepare that state will be changed.
|
||||
*/
|
||||
void (*state_about_to_change)(void *data,
|
||||
struct wl_webos_shell_surface *wl_webos_shell_surface,
|
||||
uint32_t state);
|
||||
/**
|
||||
* notify the add-on status when changed
|
||||
*
|
||||
* Notifies when the add-on status changes.
|
||||
* @since 2
|
||||
*/
|
||||
void (*addon_status_changed)(void *data,
|
||||
struct wl_webos_shell_surface *wl_webos_shell_surface,
|
||||
uint32_t addon_status);
|
||||
};
|
||||
|
||||
/**
|
||||
* @ingroup iface_wl_webos_shell_surface
|
||||
*/
|
||||
static inline int
|
||||
wl_webos_shell_surface_add_listener(struct wl_webos_shell_surface *wl_webos_shell_surface,
|
||||
const struct wl_webos_shell_surface_listener *listener, void *data)
|
||||
{
|
||||
return wl_proxy_add_listener((struct wl_proxy *) wl_webos_shell_surface,
|
||||
(void (**)(void)) listener, data);
|
||||
}
|
||||
|
||||
#define WL_WEBOS_SHELL_SURFACE_SET_LOCATION_HINT 0
|
||||
#define WL_WEBOS_SHELL_SURFACE_SET_STATE 1
|
||||
#define WL_WEBOS_SHELL_SURFACE_SET_PROPERTY 2
|
||||
#define WL_WEBOS_SHELL_SURFACE_SET_KEY_MASK 3
|
||||
#define WL_WEBOS_SHELL_SURFACE_SET_ADDON 4
|
||||
#define WL_WEBOS_SHELL_SURFACE_RESET_ADDON 5
|
||||
|
||||
/**
|
||||
* @ingroup iface_wl_webos_shell_surface
|
||||
*/
|
||||
#define WL_WEBOS_SHELL_SURFACE_STATE_CHANGED_SINCE_VERSION 1
|
||||
/**
|
||||
* @ingroup iface_wl_webos_shell_surface
|
||||
*/
|
||||
#define WL_WEBOS_SHELL_SURFACE_POSITION_CHANGED_SINCE_VERSION 1
|
||||
/**
|
||||
* @ingroup iface_wl_webos_shell_surface
|
||||
*/
|
||||
#define WL_WEBOS_SHELL_SURFACE_CLOSE_SINCE_VERSION 1
|
||||
/**
|
||||
* @ingroup iface_wl_webos_shell_surface
|
||||
*/
|
||||
#define WL_WEBOS_SHELL_SURFACE_EXPOSED_SINCE_VERSION 1
|
||||
/**
|
||||
* @ingroup iface_wl_webos_shell_surface
|
||||
*/
|
||||
#define WL_WEBOS_SHELL_SURFACE_STATE_ABOUT_TO_CHANGE_SINCE_VERSION 1
|
||||
/**
|
||||
* @ingroup iface_wl_webos_shell_surface
|
||||
*/
|
||||
#define WL_WEBOS_SHELL_SURFACE_ADDON_STATUS_CHANGED_SINCE_VERSION 2
|
||||
|
||||
/**
|
||||
* @ingroup iface_wl_webos_shell_surface
|
||||
*/
|
||||
#define WL_WEBOS_SHELL_SURFACE_SET_LOCATION_HINT_SINCE_VERSION 1
|
||||
/**
|
||||
* @ingroup iface_wl_webos_shell_surface
|
||||
*/
|
||||
#define WL_WEBOS_SHELL_SURFACE_SET_STATE_SINCE_VERSION 1
|
||||
/**
|
||||
* @ingroup iface_wl_webos_shell_surface
|
||||
*/
|
||||
#define WL_WEBOS_SHELL_SURFACE_SET_PROPERTY_SINCE_VERSION 1
|
||||
/**
|
||||
* @ingroup iface_wl_webos_shell_surface
|
||||
*/
|
||||
#define WL_WEBOS_SHELL_SURFACE_SET_KEY_MASK_SINCE_VERSION 1
|
||||
/**
|
||||
* @ingroup iface_wl_webos_shell_surface
|
||||
*/
|
||||
#define WL_WEBOS_SHELL_SURFACE_SET_ADDON_SINCE_VERSION 2
|
||||
/**
|
||||
* @ingroup iface_wl_webos_shell_surface
|
||||
*/
|
||||
#define WL_WEBOS_SHELL_SURFACE_RESET_ADDON_SINCE_VERSION 2
|
||||
|
||||
/** @ingroup iface_wl_webos_shell_surface */
|
||||
static inline void
|
||||
wl_webos_shell_surface_set_user_data(struct wl_webos_shell_surface *wl_webos_shell_surface, void *user_data)
|
||||
{
|
||||
wl_proxy_set_user_data((struct wl_proxy *) wl_webos_shell_surface, user_data);
|
||||
}
|
||||
|
||||
/** @ingroup iface_wl_webos_shell_surface */
|
||||
static inline void *
|
||||
wl_webos_shell_surface_get_user_data(struct wl_webos_shell_surface *wl_webos_shell_surface)
|
||||
{
|
||||
return wl_proxy_get_user_data((struct wl_proxy *) wl_webos_shell_surface);
|
||||
}
|
||||
|
||||
static inline uint32_t
|
||||
wl_webos_shell_surface_get_version(struct wl_webos_shell_surface *wl_webos_shell_surface)
|
||||
{
|
||||
return wl_proxy_get_version((struct wl_proxy *) wl_webos_shell_surface);
|
||||
}
|
||||
|
||||
/** @ingroup iface_wl_webos_shell_surface */
|
||||
static inline void
|
||||
wl_webos_shell_surface_destroy(struct wl_webos_shell_surface *wl_webos_shell_surface)
|
||||
{
|
||||
wl_proxy_destroy((struct wl_proxy *) wl_webos_shell_surface);
|
||||
}
|
||||
|
||||
/**
|
||||
* @ingroup iface_wl_webos_shell_surface
|
||||
*
|
||||
* The location hint is a guideline to the compositor where
|
||||
* to place the surface. The compositor will do its best to
|
||||
* place it there, however this is not guaranteed. Currently
|
||||
* The location hint is only applied to _WEBOS_WINDOW_TYPE_POPUP
|
||||
* and _WEBOS_WINDOW_TYPE_SYSTEM_UI surfaces.
|
||||
*/
|
||||
static inline void
|
||||
wl_webos_shell_surface_set_location_hint(struct wl_webos_shell_surface *wl_webos_shell_surface, uint32_t hint)
|
||||
{
|
||||
wl_proxy_marshal_flags((struct wl_proxy *) wl_webos_shell_surface,
|
||||
WL_WEBOS_SHELL_SURFACE_SET_LOCATION_HINT, NULL, wl_proxy_get_version((struct wl_proxy *) wl_webos_shell_surface), 0, hint);
|
||||
}
|
||||
|
||||
/**
|
||||
* @ingroup iface_wl_webos_shell_surface
|
||||
*
|
||||
* The client requests its surface state to the compositor.
|
||||
*/
|
||||
static inline void
|
||||
wl_webos_shell_surface_set_state(struct wl_webos_shell_surface *wl_webos_shell_surface, uint32_t state)
|
||||
{
|
||||
wl_proxy_marshal_flags((struct wl_proxy *) wl_webos_shell_surface,
|
||||
WL_WEBOS_SHELL_SURFACE_SET_STATE, NULL, wl_proxy_get_version((struct wl_proxy *) wl_webos_shell_surface), 0, state);
|
||||
}
|
||||
|
||||
/**
|
||||
* @ingroup iface_wl_webos_shell_surface
|
||||
*
|
||||
* The client sets the surface property to the compositor. The property
|
||||
* is given by a string pair (name, value).
|
||||
*/
|
||||
static inline void
|
||||
wl_webos_shell_surface_set_property(struct wl_webos_shell_surface *wl_webos_shell_surface, const char *name, const char *value)
|
||||
{
|
||||
wl_proxy_marshal_flags((struct wl_proxy *) wl_webos_shell_surface,
|
||||
WL_WEBOS_SHELL_SURFACE_SET_PROPERTY, NULL, wl_proxy_get_version((struct wl_proxy *) wl_webos_shell_surface), 0, name, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* @ingroup iface_wl_webos_shell_surface
|
||||
*
|
||||
* A value of '1' in the mask indicates that the respective key event (both press and release)
|
||||
* will be delivered to the client. Bitwise XOR operation against the default mask value and
|
||||
* a supported individual values will result in that key not being delivered to the client.
|
||||
* Notice that the default value omits certain keys.
|
||||
*
|
||||
* The compositor is free to ignore certain keys even if set to '1' depending on the overall
|
||||
* system UI behaviour, most commonly this will mean the HOME key.
|
||||
*
|
||||
* NOTE: This protocol will eventually replace the string property based key filtering.
|
||||
*/
|
||||
static inline void
|
||||
wl_webos_shell_surface_set_key_mask(struct wl_webos_shell_surface *wl_webos_shell_surface, uint32_t webos_key)
|
||||
{
|
||||
wl_proxy_marshal_flags((struct wl_proxy *) wl_webos_shell_surface,
|
||||
WL_WEBOS_SHELL_SURFACE_SET_KEY_MASK, NULL, wl_proxy_get_version((struct wl_proxy *) wl_webos_shell_surface), 0, webos_key);
|
||||
}
|
||||
|
||||
/**
|
||||
* @ingroup iface_wl_webos_shell_surface
|
||||
*
|
||||
* Sets the path to the server-side add-on. The compositor loads the add-on
|
||||
* on receiving this request if the add-on is identified as a valid one.
|
||||
*/
|
||||
static inline void
|
||||
wl_webos_shell_surface_set_addon(struct wl_webos_shell_surface *wl_webos_shell_surface, const char *path)
|
||||
{
|
||||
wl_proxy_marshal_flags((struct wl_proxy *) wl_webos_shell_surface,
|
||||
WL_WEBOS_SHELL_SURFACE_SET_ADDON, NULL, wl_proxy_get_version((struct wl_proxy *) wl_webos_shell_surface), 0, path);
|
||||
}
|
||||
|
||||
/**
|
||||
* @ingroup iface_wl_webos_shell_surface
|
||||
*
|
||||
* Requests to unload the add-on if it is loaded.
|
||||
* If there is no add-on loaded this request is silently ignored.
|
||||
*/
|
||||
static inline void
|
||||
wl_webos_shell_surface_reset_addon(struct wl_webos_shell_surface *wl_webos_shell_surface)
|
||||
{
|
||||
wl_proxy_marshal_flags((struct wl_proxy *) wl_webos_shell_surface,
|
||||
WL_WEBOS_SHELL_SURFACE_RESET_ADDON, NULL, wl_proxy_get_version((struct wl_proxy *) wl_webos_shell_surface), 0);
|
||||
}
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
@@ -1,597 +0,0 @@
|
||||
/* Generated by wayland-scanner 1.22.0 */
|
||||
|
||||
#ifndef WEBOS_SURFACE_GROUP_CLIENT_PROTOCOL_H
|
||||
#define WEBOS_SURFACE_GROUP_CLIENT_PROTOCOL_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
#include "wayland-client.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @page page_webos_surface_group The webos_surface_group protocol
|
||||
* @section page_ifaces_webos_surface_group Interfaces
|
||||
* - @subpage page_iface_wl_webos_surface_group_compositor - a way to group multi client surfaces together
|
||||
* - @subpage page_iface_wl_webos_surface_group -
|
||||
* - @subpage page_iface_wl_webos_surface_group_layer - the handle for the group owner to manipulate the layer
|
||||
* @section page_copyright_webos_surface_group Copyright
|
||||
* <pre>
|
||||
*
|
||||
* Copyright (c) 2014-2020 LG Electronics, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
* </pre>
|
||||
*/
|
||||
struct wl_surface;
|
||||
struct wl_webos_surface_group;
|
||||
struct wl_webos_surface_group_compositor;
|
||||
struct wl_webos_surface_group_layer;
|
||||
|
||||
#ifndef WL_WEBOS_SURFACE_GROUP_COMPOSITOR_INTERFACE
|
||||
#define WL_WEBOS_SURFACE_GROUP_COMPOSITOR_INTERFACE
|
||||
/**
|
||||
* @page page_iface_wl_webos_surface_group_compositor wl_webos_surface_group_compositor
|
||||
* @section page_iface_wl_webos_surface_group_compositor_desc Description
|
||||
*
|
||||
* A surface group is a mechanism to group surfaces together that are
|
||||
* not part of the same client, hence transiency, nor subsurfaces will
|
||||
* fit the bill. However the compositor will treat surfaces belonging
|
||||
* to a group very similarly as to surfaces that are transient.
|
||||
*
|
||||
* The group will be controlled by the process that creates it. The group
|
||||
* will have a root surface and the grouped surfaces will be added as
|
||||
* layers on top or below it. The creator of the group will designate a z order
|
||||
* and a name for each layer. Other processes wanting to add their
|
||||
* surface into the group need to know the both the name of the group and
|
||||
* the name of the layer.
|
||||
*
|
||||
* A group can allow anonymous surfaces to be attached to them as well.
|
||||
*
|
||||
* Practical suggestion, the name of the group should match the appId of
|
||||
* parent surface.
|
||||
* @section page_iface_wl_webos_surface_group_compositor_api API
|
||||
* See @ref iface_wl_webos_surface_group_compositor.
|
||||
*/
|
||||
/**
|
||||
* @defgroup iface_wl_webos_surface_group_compositor The wl_webos_surface_group_compositor interface
|
||||
*
|
||||
* A surface group is a mechanism to group surfaces together that are
|
||||
* not part of the same client, hence transiency, nor subsurfaces will
|
||||
* fit the bill. However the compositor will treat surfaces belonging
|
||||
* to a group very similarly as to surfaces that are transient.
|
||||
*
|
||||
* The group will be controlled by the process that creates it. The group
|
||||
* will have a root surface and the grouped surfaces will be added as
|
||||
* layers on top or below it. The creator of the group will designate a z order
|
||||
* and a name for each layer. Other processes wanting to add their
|
||||
* surface into the group need to know the both the name of the group and
|
||||
* the name of the layer.
|
||||
*
|
||||
* A group can allow anonymous surfaces to be attached to them as well.
|
||||
*
|
||||
* Practical suggestion, the name of the group should match the appId of
|
||||
* parent surface.
|
||||
*/
|
||||
extern const struct wl_interface wl_webos_surface_group_compositor_interface;
|
||||
#endif
|
||||
#ifndef WL_WEBOS_SURFACE_GROUP_INTERFACE
|
||||
#define WL_WEBOS_SURFACE_GROUP_INTERFACE
|
||||
/**
|
||||
* @page page_iface_wl_webos_surface_group wl_webos_surface_group
|
||||
* @section page_iface_wl_webos_surface_group_api API
|
||||
* See @ref iface_wl_webos_surface_group.
|
||||
*/
|
||||
/**
|
||||
* @defgroup iface_wl_webos_surface_group The wl_webos_surface_group interface
|
||||
*/
|
||||
extern const struct wl_interface wl_webos_surface_group_interface;
|
||||
#endif
|
||||
#ifndef WL_WEBOS_SURFACE_GROUP_LAYER_INTERFACE
|
||||
#define WL_WEBOS_SURFACE_GROUP_LAYER_INTERFACE
|
||||
/**
|
||||
* @page page_iface_wl_webos_surface_group_layer wl_webos_surface_group_layer
|
||||
* @section page_iface_wl_webos_surface_group_layer_desc Description
|
||||
*
|
||||
* The handle can be used to change the z order of a particular layer
|
||||
* @section page_iface_wl_webos_surface_group_layer_api API
|
||||
* See @ref iface_wl_webos_surface_group_layer.
|
||||
*/
|
||||
/**
|
||||
* @defgroup iface_wl_webos_surface_group_layer The wl_webos_surface_group_layer interface
|
||||
*
|
||||
* The handle can be used to change the z order of a particular layer
|
||||
*/
|
||||
extern const struct wl_interface wl_webos_surface_group_layer_interface;
|
||||
#endif
|
||||
|
||||
#define WL_WEBOS_SURFACE_GROUP_COMPOSITOR_CREATE_SURFACE_GROUP 0
|
||||
#define WL_WEBOS_SURFACE_GROUP_COMPOSITOR_GET_SURFACE_GROUP 1
|
||||
|
||||
|
||||
/**
|
||||
* @ingroup iface_wl_webos_surface_group_compositor
|
||||
*/
|
||||
#define WL_WEBOS_SURFACE_GROUP_COMPOSITOR_CREATE_SURFACE_GROUP_SINCE_VERSION 1
|
||||
/**
|
||||
* @ingroup iface_wl_webos_surface_group_compositor
|
||||
*/
|
||||
#define WL_WEBOS_SURFACE_GROUP_COMPOSITOR_GET_SURFACE_GROUP_SINCE_VERSION 1
|
||||
|
||||
/** @ingroup iface_wl_webos_surface_group_compositor */
|
||||
static inline void
|
||||
wl_webos_surface_group_compositor_set_user_data(struct wl_webos_surface_group_compositor *wl_webos_surface_group_compositor, void *user_data)
|
||||
{
|
||||
wl_proxy_set_user_data((struct wl_proxy *) wl_webos_surface_group_compositor, user_data);
|
||||
}
|
||||
|
||||
/** @ingroup iface_wl_webos_surface_group_compositor */
|
||||
static inline void *
|
||||
wl_webos_surface_group_compositor_get_user_data(struct wl_webos_surface_group_compositor *wl_webos_surface_group_compositor)
|
||||
{
|
||||
return wl_proxy_get_user_data((struct wl_proxy *) wl_webos_surface_group_compositor);
|
||||
}
|
||||
|
||||
static inline uint32_t
|
||||
wl_webos_surface_group_compositor_get_version(struct wl_webos_surface_group_compositor *wl_webos_surface_group_compositor)
|
||||
{
|
||||
return wl_proxy_get_version((struct wl_proxy *) wl_webos_surface_group_compositor);
|
||||
}
|
||||
|
||||
/** @ingroup iface_wl_webos_surface_group_compositor */
|
||||
static inline void
|
||||
wl_webos_surface_group_compositor_destroy(struct wl_webos_surface_group_compositor *wl_webos_surface_group_compositor)
|
||||
{
|
||||
wl_proxy_destroy((struct wl_proxy *) wl_webos_surface_group_compositor);
|
||||
}
|
||||
|
||||
/**
|
||||
* @ingroup iface_wl_webos_surface_group_compositor
|
||||
*
|
||||
* The group will be identified by the 'name' parameter. If a group already
|
||||
* exists with the given name an error will be reported to the client.
|
||||
*/
|
||||
static inline struct wl_webos_surface_group *
|
||||
wl_webos_surface_group_compositor_create_surface_group(struct wl_webos_surface_group_compositor *wl_webos_surface_group_compositor, struct wl_surface *parent, const char *name)
|
||||
{
|
||||
struct wl_proxy *id;
|
||||
|
||||
id = wl_proxy_marshal_flags((struct wl_proxy *) wl_webos_surface_group_compositor,
|
||||
WL_WEBOS_SURFACE_GROUP_COMPOSITOR_CREATE_SURFACE_GROUP, &wl_webos_surface_group_interface, wl_proxy_get_version((struct wl_proxy *) wl_webos_surface_group_compositor), 0, NULL, parent, name);
|
||||
|
||||
return (struct wl_webos_surface_group *) id;
|
||||
}
|
||||
|
||||
/**
|
||||
* @ingroup iface_wl_webos_surface_group_compositor
|
||||
*
|
||||
* A handle to an existing group.
|
||||
*/
|
||||
static inline struct wl_webos_surface_group *
|
||||
wl_webos_surface_group_compositor_get_surface_group(struct wl_webos_surface_group_compositor *wl_webos_surface_group_compositor, const char *name)
|
||||
{
|
||||
struct wl_proxy *id;
|
||||
|
||||
id = wl_proxy_marshal_flags((struct wl_proxy *) wl_webos_surface_group_compositor,
|
||||
WL_WEBOS_SURFACE_GROUP_COMPOSITOR_GET_SURFACE_GROUP, &wl_webos_surface_group_interface, wl_proxy_get_version((struct wl_proxy *) wl_webos_surface_group_compositor), 0, NULL, name);
|
||||
|
||||
return (struct wl_webos_surface_group *) id;
|
||||
}
|
||||
|
||||
#ifndef WL_WEBOS_SURFACE_GROUP_Z_HINT_ENUM
|
||||
#define WL_WEBOS_SURFACE_GROUP_Z_HINT_ENUM
|
||||
/**
|
||||
* @ingroup iface_wl_webos_surface_group
|
||||
* hint for the z position of the anonymous surfaces
|
||||
*
|
||||
* The position of anonymous layer is always relative to the root element
|
||||
* and the named layers in this group.
|
||||
*
|
||||
* If there are multiple entries with the same index designator they will
|
||||
* get positioned in the order of which they get attached.
|
||||
*/
|
||||
enum wl_webos_surface_group_z_hint {
|
||||
/**
|
||||
* below the root surface and the lowest named layer
|
||||
*/
|
||||
WL_WEBOS_SURFACE_GROUP_Z_HINT_BELOW = 0,
|
||||
/**
|
||||
* above the root surface and above the highest named layer
|
||||
*/
|
||||
WL_WEBOS_SURFACE_GROUP_Z_HINT_ABOVE = 1,
|
||||
/**
|
||||
* above all of the anonymous surfaces
|
||||
*/
|
||||
WL_WEBOS_SURFACE_GROUP_Z_HINT_TOP = 2,
|
||||
};
|
||||
#endif /* WL_WEBOS_SURFACE_GROUP_Z_HINT_ENUM */
|
||||
|
||||
/**
|
||||
* @ingroup iface_wl_webos_surface_group
|
||||
* @struct wl_webos_surface_group_listener
|
||||
*/
|
||||
struct wl_webos_surface_group_listener {
|
||||
/**
|
||||
* sent to clients attached to this group
|
||||
*
|
||||
* If the owner crashes or normally destroys this group the
|
||||
* attached client will receive this notification.
|
||||
*
|
||||
* Since compositor does not know what to do with client surface
|
||||
* still attached to this group it will not show them. A well
|
||||
* behaving client will 'detach' from this group and release its
|
||||
* assosicated resource
|
||||
*/
|
||||
void (*owner_destroyed)(void *data,
|
||||
struct wl_webos_surface_group *wl_webos_surface_group);
|
||||
};
|
||||
|
||||
/**
|
||||
* @ingroup iface_wl_webos_surface_group
|
||||
*/
|
||||
static inline int
|
||||
wl_webos_surface_group_add_listener(struct wl_webos_surface_group *wl_webos_surface_group,
|
||||
const struct wl_webos_surface_group_listener *listener, void *data)
|
||||
{
|
||||
return wl_proxy_add_listener((struct wl_proxy *) wl_webos_surface_group,
|
||||
(void (**)(void)) listener, data);
|
||||
}
|
||||
|
||||
#define WL_WEBOS_SURFACE_GROUP_CREATE_LAYER 0
|
||||
#define WL_WEBOS_SURFACE_GROUP_ATTACH 1
|
||||
#define WL_WEBOS_SURFACE_GROUP_ATTACH_ANONYMOUS 2
|
||||
#define WL_WEBOS_SURFACE_GROUP_ALLOW_ANONYMOUS_LAYERS 3
|
||||
#define WL_WEBOS_SURFACE_GROUP_DETACH 4
|
||||
#define WL_WEBOS_SURFACE_GROUP_DESTROY 5
|
||||
#define WL_WEBOS_SURFACE_GROUP_FOCUS_OWNER 6
|
||||
#define WL_WEBOS_SURFACE_GROUP_FOCUS_LAYER 7
|
||||
#define WL_WEBOS_SURFACE_GROUP_COMMIT_KEY_INDEX 8
|
||||
|
||||
/**
|
||||
* @ingroup iface_wl_webos_surface_group
|
||||
*/
|
||||
#define WL_WEBOS_SURFACE_GROUP_OWNER_DESTROYED_SINCE_VERSION 1
|
||||
|
||||
/**
|
||||
* @ingroup iface_wl_webos_surface_group
|
||||
*/
|
||||
#define WL_WEBOS_SURFACE_GROUP_CREATE_LAYER_SINCE_VERSION 1
|
||||
/**
|
||||
* @ingroup iface_wl_webos_surface_group
|
||||
*/
|
||||
#define WL_WEBOS_SURFACE_GROUP_ATTACH_SINCE_VERSION 1
|
||||
/**
|
||||
* @ingroup iface_wl_webos_surface_group
|
||||
*/
|
||||
#define WL_WEBOS_SURFACE_GROUP_ATTACH_ANONYMOUS_SINCE_VERSION 1
|
||||
/**
|
||||
* @ingroup iface_wl_webos_surface_group
|
||||
*/
|
||||
#define WL_WEBOS_SURFACE_GROUP_ALLOW_ANONYMOUS_LAYERS_SINCE_VERSION 1
|
||||
/**
|
||||
* @ingroup iface_wl_webos_surface_group
|
||||
*/
|
||||
#define WL_WEBOS_SURFACE_GROUP_DETACH_SINCE_VERSION 1
|
||||
/**
|
||||
* @ingroup iface_wl_webos_surface_group
|
||||
*/
|
||||
#define WL_WEBOS_SURFACE_GROUP_DESTROY_SINCE_VERSION 1
|
||||
/**
|
||||
* @ingroup iface_wl_webos_surface_group
|
||||
*/
|
||||
#define WL_WEBOS_SURFACE_GROUP_FOCUS_OWNER_SINCE_VERSION 1
|
||||
/**
|
||||
* @ingroup iface_wl_webos_surface_group
|
||||
*/
|
||||
#define WL_WEBOS_SURFACE_GROUP_FOCUS_LAYER_SINCE_VERSION 1
|
||||
/**
|
||||
* @ingroup iface_wl_webos_surface_group
|
||||
*/
|
||||
#define WL_WEBOS_SURFACE_GROUP_COMMIT_KEY_INDEX_SINCE_VERSION 1
|
||||
|
||||
/** @ingroup iface_wl_webos_surface_group */
|
||||
static inline void
|
||||
wl_webos_surface_group_set_user_data(struct wl_webos_surface_group *wl_webos_surface_group, void *user_data)
|
||||
{
|
||||
wl_proxy_set_user_data((struct wl_proxy *) wl_webos_surface_group, user_data);
|
||||
}
|
||||
|
||||
/** @ingroup iface_wl_webos_surface_group */
|
||||
static inline void *
|
||||
wl_webos_surface_group_get_user_data(struct wl_webos_surface_group *wl_webos_surface_group)
|
||||
{
|
||||
return wl_proxy_get_user_data((struct wl_proxy *) wl_webos_surface_group);
|
||||
}
|
||||
|
||||
static inline uint32_t
|
||||
wl_webos_surface_group_get_version(struct wl_webos_surface_group *wl_webos_surface_group)
|
||||
{
|
||||
return wl_proxy_get_version((struct wl_proxy *) wl_webos_surface_group);
|
||||
}
|
||||
|
||||
/**
|
||||
* @ingroup iface_wl_webos_surface_group
|
||||
*
|
||||
* The layer will be identified by the 'name' parameter. The compositor
|
||||
* will place the layer in the given z-index relative to the root layer.
|
||||
* If negative z-indecies are provided they will be placed underneath the
|
||||
* root surface accordingly
|
||||
*
|
||||
* The creator will get a handle 'wl_webos_surface_group_layer' to the
|
||||
* newly created group. The handle can be used to manipulate the layer.
|
||||
*
|
||||
* If a layer group already exists with the given parameters an error
|
||||
* will be reported to the client.
|
||||
*/
|
||||
static inline struct wl_webos_surface_group_layer *
|
||||
wl_webos_surface_group_create_layer(struct wl_webos_surface_group *wl_webos_surface_group, const char *name, int32_t z_index)
|
||||
{
|
||||
struct wl_proxy *id;
|
||||
|
||||
id = wl_proxy_marshal_flags((struct wl_proxy *) wl_webos_surface_group,
|
||||
WL_WEBOS_SURFACE_GROUP_CREATE_LAYER, &wl_webos_surface_group_layer_interface, wl_proxy_get_version((struct wl_proxy *) wl_webos_surface_group), 0, NULL, name, z_index);
|
||||
|
||||
return (struct wl_webos_surface_group_layer *) id;
|
||||
}
|
||||
|
||||
/**
|
||||
* @ingroup iface_wl_webos_surface_group
|
||||
*
|
||||
* Processes that want to attach a surface to an existing group and layer
|
||||
* will call this function.
|
||||
*
|
||||
* An error will be reported if either the group, the layer or the layer
|
||||
* already has a surface attached to it.
|
||||
*/
|
||||
static inline void
|
||||
wl_webos_surface_group_attach(struct wl_webos_surface_group *wl_webos_surface_group, struct wl_surface *surface, const char *layer_name)
|
||||
{
|
||||
wl_proxy_marshal_flags((struct wl_proxy *) wl_webos_surface_group,
|
||||
WL_WEBOS_SURFACE_GROUP_ATTACH, NULL, wl_proxy_get_version((struct wl_proxy *) wl_webos_surface_group), 0, surface, layer_name);
|
||||
}
|
||||
|
||||
/**
|
||||
* @ingroup iface_wl_webos_surface_group
|
||||
*
|
||||
* Clients that have a "multi-purpose" surface that might apply to many use
|
||||
* cases can try to attach their surface to a named group. The group controls
|
||||
* if that is allowed or not.
|
||||
*
|
||||
* An error will be reported if the group is not available or it does not
|
||||
* allow anonymous surfaces
|
||||
*/
|
||||
static inline void
|
||||
wl_webos_surface_group_attach_anonymous(struct wl_webos_surface_group *wl_webos_surface_group, struct wl_surface *surface, uint32_t z_hint)
|
||||
{
|
||||
wl_proxy_marshal_flags((struct wl_proxy *) wl_webos_surface_group,
|
||||
WL_WEBOS_SURFACE_GROUP_ATTACH_ANONYMOUS, NULL, wl_proxy_get_version((struct wl_proxy *) wl_webos_surface_group), 0, surface, z_hint);
|
||||
}
|
||||
|
||||
/**
|
||||
* @ingroup iface_wl_webos_surface_group
|
||||
*
|
||||
* Anonymous layers are layers that do not belong to any named layer.
|
||||
* They do not enjoy the same benefits as named layers. Their actucal
|
||||
* z-index might change if there are named layers or a z index of a
|
||||
* named layer is updated.
|
||||
*/
|
||||
static inline void
|
||||
wl_webos_surface_group_allow_anonymous_layers(struct wl_webos_surface_group *wl_webos_surface_group, uint32_t allow)
|
||||
{
|
||||
wl_proxy_marshal_flags((struct wl_proxy *) wl_webos_surface_group,
|
||||
WL_WEBOS_SURFACE_GROUP_ALLOW_ANONYMOUS_LAYERS, NULL, wl_proxy_get_version((struct wl_proxy *) wl_webos_surface_group), 0, allow);
|
||||
}
|
||||
|
||||
/**
|
||||
* @ingroup iface_wl_webos_surface_group
|
||||
*
|
||||
* When a client detaches from a group the compositor will treat is as any other surface.
|
||||
* In practice this means that if the client does not want the surface to appear "dangling"
|
||||
* or visible in recents the client should hide/destroy the surface, then detach it from
|
||||
* the group to avoid any unwanted behavior.
|
||||
*/
|
||||
static inline void
|
||||
wl_webos_surface_group_detach(struct wl_webos_surface_group *wl_webos_surface_group, struct wl_surface *surface)
|
||||
{
|
||||
wl_proxy_marshal_flags((struct wl_proxy *) wl_webos_surface_group,
|
||||
WL_WEBOS_SURFACE_GROUP_DETACH, NULL, wl_proxy_get_version((struct wl_proxy *) wl_webos_surface_group), 0, surface);
|
||||
}
|
||||
|
||||
/**
|
||||
* @ingroup iface_wl_webos_surface_group
|
||||
*
|
||||
* Destroys the surface group object.
|
||||
*/
|
||||
static inline void
|
||||
wl_webos_surface_group_destroy(struct wl_webos_surface_group *wl_webos_surface_group)
|
||||
{
|
||||
wl_proxy_marshal_flags((struct wl_proxy *) wl_webos_surface_group,
|
||||
WL_WEBOS_SURFACE_GROUP_DESTROY, NULL, wl_proxy_get_version((struct wl_proxy *) wl_webos_surface_group), WL_MARSHAL_FLAG_DESTROY);
|
||||
}
|
||||
|
||||
/**
|
||||
* @ingroup iface_wl_webos_surface_group
|
||||
*
|
||||
* Using "focus_owner" a surface-group-client or a surface-group-owner can transfer
|
||||
* keybord-focus to surface-group-owner.
|
||||
*/
|
||||
static inline void
|
||||
wl_webos_surface_group_focus_owner(struct wl_webos_surface_group *wl_webos_surface_group)
|
||||
{
|
||||
wl_proxy_marshal_flags((struct wl_proxy *) wl_webos_surface_group,
|
||||
WL_WEBOS_SURFACE_GROUP_FOCUS_OWNER, NULL, wl_proxy_get_version((struct wl_proxy *) wl_webos_surface_group), 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* @ingroup iface_wl_webos_surface_group
|
||||
*
|
||||
* Using "focus_layer" a surface-group-owner or a surface-group-client can transfer
|
||||
* keybord-focus to specific surface-group-client attached to surface-group-layer "layer".
|
||||
*/
|
||||
static inline void
|
||||
wl_webos_surface_group_focus_layer(struct wl_webos_surface_group *wl_webos_surface_group, const char *layer)
|
||||
{
|
||||
wl_proxy_marshal_flags((struct wl_proxy *) wl_webos_surface_group,
|
||||
WL_WEBOS_SURFACE_GROUP_FOCUS_LAYER, NULL, wl_proxy_get_version((struct wl_proxy *) wl_webos_surface_group), 0, layer);
|
||||
}
|
||||
|
||||
/**
|
||||
* @ingroup iface_wl_webos_surface_group
|
||||
*
|
||||
* Using "commit_key_index", the compositor will send key events in key index order
|
||||
* regardless of z-order in the surface group.
|
||||
* Key index set to each layer becomes effective after "commit_key_index" request.
|
||||
* Parameter 'commit' should be 1 when applying the key index order.
|
||||
* Set parameter 'commit' to 0 not to use the key index order.
|
||||
* If anonymous layer is allowed, the key index order doesn't work.
|
||||
*/
|
||||
static inline void
|
||||
wl_webos_surface_group_commit_key_index(struct wl_webos_surface_group *wl_webos_surface_group, uint32_t commit)
|
||||
{
|
||||
wl_proxy_marshal_flags((struct wl_proxy *) wl_webos_surface_group,
|
||||
WL_WEBOS_SURFACE_GROUP_COMMIT_KEY_INDEX, NULL, wl_proxy_get_version((struct wl_proxy *) wl_webos_surface_group), 0, commit);
|
||||
}
|
||||
|
||||
/**
|
||||
* @ingroup iface_wl_webos_surface_group_layer
|
||||
* @struct wl_webos_surface_group_layer_listener
|
||||
*/
|
||||
struct wl_webos_surface_group_layer_listener {
|
||||
/**
|
||||
* notify surface-group-owner that surface is attached to this layer
|
||||
*
|
||||
* When any surafce is attached to this layer, the compositor
|
||||
* needs to notify surface-group-owner.
|
||||
*/
|
||||
void (*surface_attached)(void *data,
|
||||
struct wl_webos_surface_group_layer *wl_webos_surface_group_layer);
|
||||
/**
|
||||
* notify surface-group-owner that surface is detached from this layer
|
||||
*
|
||||
* When any surafce is detached from this layer, the compositor
|
||||
* needs to notify surface-group-owner. This will be usefull if
|
||||
* surface attached to this layer is destroyed by associated
|
||||
* surface-group-client.
|
||||
*/
|
||||
void (*surface_detached)(void *data,
|
||||
struct wl_webos_surface_group_layer *wl_webos_surface_group_layer);
|
||||
};
|
||||
|
||||
/**
|
||||
* @ingroup iface_wl_webos_surface_group_layer
|
||||
*/
|
||||
static inline int
|
||||
wl_webos_surface_group_layer_add_listener(struct wl_webos_surface_group_layer *wl_webos_surface_group_layer,
|
||||
const struct wl_webos_surface_group_layer_listener *listener, void *data)
|
||||
{
|
||||
return wl_proxy_add_listener((struct wl_proxy *) wl_webos_surface_group_layer,
|
||||
(void (**)(void)) listener, data);
|
||||
}
|
||||
|
||||
#define WL_WEBOS_SURFACE_GROUP_LAYER_SET_Z_INDEX 0
|
||||
#define WL_WEBOS_SURFACE_GROUP_LAYER_SET_KEY_INDEX 1
|
||||
#define WL_WEBOS_SURFACE_GROUP_LAYER_DESTROY 2
|
||||
|
||||
/**
|
||||
* @ingroup iface_wl_webos_surface_group_layer
|
||||
*/
|
||||
#define WL_WEBOS_SURFACE_GROUP_LAYER_SURFACE_ATTACHED_SINCE_VERSION 1
|
||||
/**
|
||||
* @ingroup iface_wl_webos_surface_group_layer
|
||||
*/
|
||||
#define WL_WEBOS_SURFACE_GROUP_LAYER_SURFACE_DETACHED_SINCE_VERSION 1
|
||||
|
||||
/**
|
||||
* @ingroup iface_wl_webos_surface_group_layer
|
||||
*/
|
||||
#define WL_WEBOS_SURFACE_GROUP_LAYER_SET_Z_INDEX_SINCE_VERSION 1
|
||||
/**
|
||||
* @ingroup iface_wl_webos_surface_group_layer
|
||||
*/
|
||||
#define WL_WEBOS_SURFACE_GROUP_LAYER_SET_KEY_INDEX_SINCE_VERSION 1
|
||||
/**
|
||||
* @ingroup iface_wl_webos_surface_group_layer
|
||||
*/
|
||||
#define WL_WEBOS_SURFACE_GROUP_LAYER_DESTROY_SINCE_VERSION 1
|
||||
|
||||
/** @ingroup iface_wl_webos_surface_group_layer */
|
||||
static inline void
|
||||
wl_webos_surface_group_layer_set_user_data(struct wl_webos_surface_group_layer *wl_webos_surface_group_layer, void *user_data)
|
||||
{
|
||||
wl_proxy_set_user_data((struct wl_proxy *) wl_webos_surface_group_layer, user_data);
|
||||
}
|
||||
|
||||
/** @ingroup iface_wl_webos_surface_group_layer */
|
||||
static inline void *
|
||||
wl_webos_surface_group_layer_get_user_data(struct wl_webos_surface_group_layer *wl_webos_surface_group_layer)
|
||||
{
|
||||
return wl_proxy_get_user_data((struct wl_proxy *) wl_webos_surface_group_layer);
|
||||
}
|
||||
|
||||
static inline uint32_t
|
||||
wl_webos_surface_group_layer_get_version(struct wl_webos_surface_group_layer *wl_webos_surface_group_layer)
|
||||
{
|
||||
return wl_proxy_get_version((struct wl_proxy *) wl_webos_surface_group_layer);
|
||||
}
|
||||
|
||||
/**
|
||||
* @ingroup iface_wl_webos_surface_group_layer
|
||||
*
|
||||
* Updates the z index for this layer. The compositor will move the surface
|
||||
* attached to this layer accordingly. If no surface is attached to this
|
||||
* layer nothing will happen
|
||||
*/
|
||||
static inline void
|
||||
wl_webos_surface_group_layer_set_z_index(struct wl_webos_surface_group_layer *wl_webos_surface_group_layer, int32_t z_index)
|
||||
{
|
||||
wl_proxy_marshal_flags((struct wl_proxy *) wl_webos_surface_group_layer,
|
||||
WL_WEBOS_SURFACE_GROUP_LAYER_SET_Z_INDEX, NULL, wl_proxy_get_version((struct wl_proxy *) wl_webos_surface_group_layer), 0, z_index);
|
||||
}
|
||||
|
||||
/**
|
||||
* @ingroup iface_wl_webos_surface_group_layer
|
||||
*
|
||||
* Sets the key index for this layer. The compositor will send key events
|
||||
* to this layer accordingly after "commit_key_index" request.
|
||||
* If no surface is attached to this layer nothing will happen.
|
||||
* Key index should be neither redundant nor 0 (0 is onwner's key index).
|
||||
* The larger the key index, the faster the layer receives the key event.
|
||||
*/
|
||||
static inline void
|
||||
wl_webos_surface_group_layer_set_key_index(struct wl_webos_surface_group_layer *wl_webos_surface_group_layer, int32_t key_index)
|
||||
{
|
||||
wl_proxy_marshal_flags((struct wl_proxy *) wl_webos_surface_group_layer,
|
||||
WL_WEBOS_SURFACE_GROUP_LAYER_SET_KEY_INDEX, NULL, wl_proxy_get_version((struct wl_proxy *) wl_webos_surface_group_layer), 0, key_index);
|
||||
}
|
||||
|
||||
/**
|
||||
* @ingroup iface_wl_webos_surface_group_layer
|
||||
*
|
||||
* Destroys the surface group layer object.
|
||||
*/
|
||||
static inline void
|
||||
wl_webos_surface_group_layer_destroy(struct wl_webos_surface_group_layer *wl_webos_surface_group_layer)
|
||||
{
|
||||
wl_proxy_marshal_flags((struct wl_proxy *) wl_webos_surface_group_layer,
|
||||
WL_WEBOS_SURFACE_GROUP_LAYER_DESTROY, NULL, wl_proxy_get_version((struct wl_proxy *) wl_webos_surface_group_layer), WL_MARSHAL_FLAG_DESTROY);
|
||||
}
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
+1
-28
@@ -86,7 +86,7 @@ apple/RetroArch_iOS.xcodeproj/project.xcworkspace/*
|
||||
.cproject
|
||||
.settings
|
||||
libretro-super
|
||||
/run.sh
|
||||
run.sh
|
||||
convert_rumble.awk
|
||||
*~
|
||||
assets
|
||||
@@ -266,41 +266,21 @@ gfx/common/wayland/webos-surface-group.c
|
||||
gfx/common/wayland/webos-surface-group.h
|
||||
gfx/common/wayland/tearing-control-v1.h
|
||||
gfx/common/wayland/tearing-control-v1.c
|
||||
gfx/common/wayland/drm-lease-v1.h
|
||||
gfx/common/wayland/drm-lease-v1.c
|
||||
|
||||
# samples/: built harness executables and objects. Each directory's
|
||||
# test is <dir>_test; nothing built there is source.
|
||||
samples/**/*_test
|
||||
samples/**/*_test_*
|
||||
samples/audio/stretch/stretch_bench
|
||||
samples/**/*.o
|
||||
libretro-common/samples/**/*_test
|
||||
libretro-common/samples/**/*_test_*
|
||||
!libretro-common/samples/**/*_test/
|
||||
!samples/**/*_test_*.c
|
||||
!libretro-common/samples/**/*_test_*.c
|
||||
!samples/**/*_test_*.h
|
||||
!libretro-common/samples/**/*_test_*.h
|
||||
# Harness executables that are not named <dir>_test. This one had been
|
||||
# committed as a built binary, which made any make clean in that
|
||||
# directory show up as a deletion.
|
||||
samples/audio/embedded_alsa/abi_check
|
||||
libretro-common/samples/rthreads/retro_eventcount_bench/retro_eventcount_bench
|
||||
libretro-common/samples/rthreads/retro_eventcount_bench/retro_eventcount_bench_*
|
||||
# The driver's text with its Windows includes swapped, produced by each
|
||||
# suite's Makefile from audio/drivers/wasapi.c. Committed copies went
|
||||
# stale against the driver they are generated from.
|
||||
samples/audio/wasapi_exclusive_pacing/wasapi_sim.c
|
||||
samples/audio/discrete_multichannel/wasapi_sim.c
|
||||
samples/gfx/font_lifecycle/lifecycle
|
||||
samples/gfx/font_lifecycle/lifecycle-threaded
|
||||
samples/gfx/font_lifecycle/create
|
||||
# Generated by samples/gfx/wayland_drm_lease from the bundled protocol
|
||||
# XML; the client half of the same pair lands in gfx/common/wayland.
|
||||
samples/gfx/wayland_drm_lease/drm-lease-v1-client-protocol.h
|
||||
samples/gfx/wayland_drm_lease/drm-lease-v1-server-protocol.h
|
||||
samples/gfx/wayland_drm_lease/drm-lease-v1-protocol.c
|
||||
|
||||
# libretro-common samples
|
||||
libretro-common/samples/streams/rzip/rzip
|
||||
@@ -339,10 +319,3 @@ gfx/drivers/rsx_shaders/modern_opaque.vpo
|
||||
pkg/psl1ght/pkg/USRDIR/shaders_cg/
|
||||
!pkg/psl1ght/pkg/USRDIR/system/
|
||||
samples/audio/embedded_alsa/ealsa_abi.h
|
||||
|
||||
# Python bytecode caches, written by tools/*.py whenever they run.
|
||||
__pycache__/
|
||||
*.pyc
|
||||
samples/audio/asio_clock/asio_clock_driver.h
|
||||
samples/gfx/integer_scale_axis/integer_scale_driver.h
|
||||
samples/gfx/integer_scale_axis/integer_scale_axis_test
|
||||
+2
-2
@@ -7,7 +7,7 @@ stages:
|
||||
- trigger-static-cores
|
||||
|
||||
build-retroarch-windows-x64:
|
||||
image: $CI_SERVER_HOST:5050/libretro-infrastructure/libretro-build-mxe-win64-cross:gcc16
|
||||
image: $CI_SERVER_HOST:5050/libretro-infrastructure/libretro-build-mxe-win64-cross:gcc10
|
||||
stage: build
|
||||
variables:
|
||||
MEDIA_PATH: .media
|
||||
@@ -51,7 +51,7 @@ build-retroarch-windows-x64:
|
||||
- "rm -f ${MEDIA_PATH}/${CI_PROJECT_NAME}/redist/opengl32.dll"
|
||||
|
||||
build-retroarch-windows-i686:
|
||||
image: $CI_SERVER_HOST:5050/libretro-infrastructure/libretro-build-mxe-win32-cross:gcc16
|
||||
image: $CI_SERVER_HOST:5050/libretro-infrastructure/libretro-build-mxe-win32-cross:gcc10
|
||||
stage: build
|
||||
variables:
|
||||
MEDIA_PATH: .media
|
||||
|
||||
-17
@@ -90,23 +90,6 @@
|
||||
- MENU/XMB: Tab change possible by gestures
|
||||
- MENU/XMB: Horizontal menu is now optional
|
||||
- NETWORK: New network commands SAVE_STATE_SLOT N and GET_CONFIG_PARAM
|
||||
- NETWORK: HELP network command lists every command with what it does
|
||||
- NETWORK: LIST_CORES, LIST_PLAYLISTS and GET_PLAYLIST network commands, to find content and the core to load it with
|
||||
- NETWORK: MCP server: AI assistants can drive RetroArch through the Model Context Protocol, every network command a tool (off by default, see docs/mcp-server.md)
|
||||
- NETWORK: Built-in TLS 1.2/1.3 client; mbedTLS and BearSSL are no longer bundled (--enable-mbedtls / --enable-bearssl use a system one)
|
||||
- NETWORK: Built-in SMB2/3 client with signing, AES-CCM/GCM encryption and Kerberos; libsmb2 is no longer bundled (--enable-libsmb uses a system one)
|
||||
- NETWORK: Built-in NFSv3 and NFSv4 (4.0, 4.1, 4.2) client for loading content from nfs:// exports
|
||||
- NETWORK: NFS files stay usable across a server restart, a dropped connection or a long pause: the client reconnects and reopens what it needs
|
||||
- 3DS/VITA/SWITCH/WIIU: nfs:// content through the built-in NFS client
|
||||
- 3DS/VITA/SWITCH: smb:// content through the built-in SMB client
|
||||
- WINDOWS/MSVC: the Visual Studio 2005 - 2017 projects build the TLS client, the keychain, SMB and NFS
|
||||
- NETWORK: SMB files stay usable across a server restart: the connection is mended and the file opened again where it was
|
||||
- NETWORK: NFSv4.2 reads use READ_PLUS: holes in sparse files no longer cross the network
|
||||
- 3DS/SWITCH: nfs:// content through the built-in NFS client
|
||||
- NETWORK: Optional SMB/NFS read-ahead with background prefetch, for large disc images over a slow link (off by default)
|
||||
- NETWORK: Saved passwords are encrypted in retroarch-keychain.cfg; an optional passphrase lets the keychain move to another machine (see docs/network-stack.md)
|
||||
- NETWORK: SMB 3.1.1 signs with AES-GMAC where the server offers it, several times faster than AES-CMAC on signed reads
|
||||
- NETWORK: HTTPS downloads whose length is set by the server closing the connection now finish instead of hanging
|
||||
- OVERLAY: Allow overlays to position the viewport
|
||||
- OVERLAY: Hold button function
|
||||
- OVERLAY: Allow interaction during next overlay swap
|
||||
|
||||
@@ -268,16 +268,6 @@ Debug builds report handlers that overrun a frame - see
|
||||
task_queue_set_slow_handler_cb() - which is the cheapest way to find
|
||||
out whether a handler you are adding respects any of this.
|
||||
|
||||
The waits that remain are the accepted ones, none of them reachable
|
||||
while the menu is up or a core runs, each commented where it is:
|
||||
* the command-line scan without a menu, which exits once done
|
||||
(retroarch.c);
|
||||
* the save/load state waits on exit, at startup and when an init
|
||||
fails (content_wait_for_save_state_task() and
|
||||
content_wait_for_load_state_task());
|
||||
* the Wii U main loop's vsync wait (platform_wiiu.c).
|
||||
Win32 file dialogs are modal by nature and stay so.
|
||||
|
||||
Commit message density
|
||||
----------------------
|
||||
Keep commit message short and appropriate for the size of the change. One or two
|
||||
|
||||
@@ -10,12 +10,6 @@ endif
|
||||
|
||||
include config.mk
|
||||
|
||||
# The absolute include paths configure found on the system
|
||||
# (/usr/local/include on the BSDs, /opt/vc/include, ...) are searched after
|
||||
# the tree's own; the relative ones (gfx/include/ANGLE) are the tree's.
|
||||
SYSTEM_INCLUDE_DIRS := $(filter -I/%,$(INCLUDE_DIRS))
|
||||
INCLUDE_DIRS := $(filter-out -I/%,$(INCLUDE_DIRS))
|
||||
|
||||
# Put your favorite compile flags in this file, if you want different defaults than upstream.
|
||||
# Do not attempt to create that file upstream.
|
||||
# (It'd be better to put this comment in that file, but .gitignore doesn't work on files that exist in the repo.)
|
||||
@@ -131,11 +125,7 @@ ifneq ($(CXX_BUILD), 1)
|
||||
CFLAGS += -D_GNU_SOURCE
|
||||
endif
|
||||
|
||||
# The tree's own headers come before any system or pkg-config path, so a
|
||||
# same-named header installed on the system cannot stand in for one of
|
||||
# ours.
|
||||
DEF_FLAGS := -I. $(INCLUDE_DIRS) -Ideps $(filter-out -I.,$(DEF_FLAGS)) \
|
||||
$(SYSTEM_INCLUDE_DIRS)
|
||||
DEF_FLAGS += $(INCLUDE_DIRS) -Ideps
|
||||
|
||||
CFLAGS += $(DEF_FLAGS)
|
||||
CXXFLAGS += $(DEF_FLAGS) -D__STDC_CONSTANT_MACROS
|
||||
@@ -245,10 +235,7 @@ config.mk:
|
||||
else
|
||||
-include $(RARCH_OBJ:.o=.d)
|
||||
ifeq ($(HAVE_CONFIG_MK),)
|
||||
# The Wayland protocol sources are generated by configure, so a tree
|
||||
# configured before a protocol was added has no way to make the new
|
||||
# object: ask for the reconfigure rather than fail on a missing file.
|
||||
config.mk: configure qb/* gfx/common/wayland/generate_wayland_protos.sh
|
||||
config.mk: configure qb/*
|
||||
@echo "config.mk is outdated or non-existing. Run ./configure again."
|
||||
@exit 1
|
||||
endif
|
||||
|
||||
+301
-148
@@ -325,12 +325,9 @@ OBJ += \
|
||||
audio/audio_pipeline_stretch.o \
|
||||
audio/audio_binaural.o \
|
||||
input/input_driver.o \
|
||||
input/input_overlay_textures.o \
|
||||
input/input_overlay_alpha.o \
|
||||
input/common/input_hid_common.o \
|
||||
led/led_driver.o \
|
||||
gfx/video_driver.o \
|
||||
gfx/common/video_mode_select.o \
|
||||
gfx/gfx_display.o \
|
||||
gfx/gfx_animation.o \
|
||||
gfx/gfx_instrument.o \
|
||||
@@ -403,7 +400,6 @@ DEFINES += -DHAVE_WINDOW_OFFSET
|
||||
endif
|
||||
|
||||
OBJ += $(LIBRETRO_COMM_DIR)/audio/resampler/audio_resampler.o
|
||||
OBJ += $(LIBRETRO_COMM_DIR)/audio/resampler/audio_resampler_int16.o
|
||||
|
||||
ifeq ($(HAVE_DSP_FILTER), 1)
|
||||
DEFINES += -DHAVE_DSP_FILTER
|
||||
@@ -432,27 +428,8 @@ OBJ += \
|
||||
tasks/task_database.o \
|
||||
disk_control_interface.o
|
||||
|
||||
# The cleanroom crypto library. Off unless a build asks for it: the
|
||||
# round tables, curve constants and 4 KiB RSA working set are nothing
|
||||
# for a desktop and too much for GameCube, Wii or PS2, whose Makefiles
|
||||
# do not set it.
|
||||
ifeq ($(HAVE_CRYPTO), 1)
|
||||
DEFINES += -DHAVE_CRYPTO
|
||||
OBJ += $(LIBRETRO_COMM_DIR)/crypto/crypto.o \
|
||||
$(LIBRETRO_COMM_DIR)/crypto/kdf.o \
|
||||
$(LIBRETRO_COMM_DIR)/crypto/pk.o \
|
||||
$(LIBRETRO_COMM_DIR)/crypto/x509.o
|
||||
endif
|
||||
|
||||
ifeq ($(HAVE_CONFIGFILE), 1)
|
||||
DEFINES += -DHAVE_CONFIGFILE
|
||||
ifeq ($(HAVE_CRYPTO), 1)
|
||||
ifeq ($(HAVE_KEYCHAIN), 1)
|
||||
DEFINES += -DHAVE_KEYCHAIN
|
||||
OBJ += $(LIBRETRO_COMM_DIR)/file/keychain.o \
|
||||
tasks/task_keychain.o
|
||||
endif
|
||||
endif
|
||||
endif
|
||||
|
||||
ifeq ($(HAVE_BLISSBOX), 1)
|
||||
@@ -495,6 +472,9 @@ ifeq ($(HAVE_CC_RESAMPLER), 1)
|
||||
OBJ += audio/drivers_resampler/cc_resampler.o
|
||||
OBJ += audio/drivers_resampler/cc_resampler_int16.o
|
||||
|
||||
ifeq ($(HAVE_NEON),1)
|
||||
OBJ += audio/drivers_resampler/cc_resampler_neon.o
|
||||
endif
|
||||
endif
|
||||
|
||||
ifeq ($(HAVE_LANGEXTRA), 1)
|
||||
@@ -598,48 +578,199 @@ ifeq ($(HAVE_LIBRETRODB), 1)
|
||||
endif
|
||||
endif
|
||||
|
||||
# TLS needs the sockets: a makefile that asks for a client without
|
||||
# networking (a handheld with the crypto and keychain for the store
|
||||
# but no network) gets neither the define nor the object.
|
||||
ifneq ($(HAVE_NETWORKING), 1)
|
||||
HAVE_SSL = 0
|
||||
HAVE_RETROSSL = 0
|
||||
HAVE_BEARSSL = 0
|
||||
HAVE_MBEDTLS = 0
|
||||
endif
|
||||
|
||||
# TLS: the built-in client by default; a system mbedTLS or BearSSL in
|
||||
# its place when configure was asked for one. Nothing is bundled.
|
||||
ifeq ($(HAVE_RETROSSL), 1)
|
||||
HAVE_SSL = 1
|
||||
DEFINES += -DHAVE_SSL -DHAVE_RETROSSL
|
||||
else ifeq ($(HAVE_BEARSSL), 1)
|
||||
ifeq ($(HAVE_BUILTINBEARSSL), 1)
|
||||
HAVE_SSL = 1
|
||||
DEFINES += -DHAVE_SSL -DHAVE_BEARSSL
|
||||
LIBS += $(if $(BEARSSL_LIBS),$(BEARSSL_LIBS),-lbearssl)
|
||||
DEF_FLAGS += $(BEARSSL_CFLAGS)
|
||||
else ifeq ($(HAVE_MBEDTLS), 1)
|
||||
|
||||
# these -Is are only needed for BearSSL itself
|
||||
INCLUDE_DIRS += -Ideps/bearssl-0.6/src -Ideps/bearssl-0.6/inc
|
||||
OBJS_BEAR = deps/bearssl-0.6/src/aead/ccm.o \
|
||||
deps/bearssl-0.6/src/codec/ccopy.o \
|
||||
deps/bearssl-0.6/src/codec/dec32be.o \
|
||||
deps/bearssl-0.6/src/codec/dec32le.o \
|
||||
deps/bearssl-0.6/src/codec/dec64be.o \
|
||||
deps/bearssl-0.6/src/codec/enc32be.o \
|
||||
deps/bearssl-0.6/src/codec/enc32le.o \
|
||||
deps/bearssl-0.6/src/codec/enc64be.o \
|
||||
deps/bearssl-0.6/src/ec/ec_all_m31.o \
|
||||
deps/bearssl-0.6/src/ec/ec_c25519_m31.o \
|
||||
deps/bearssl-0.6/src/ec/ecdsa_atr.o \
|
||||
deps/bearssl-0.6/src/ec/ecdsa_i31_bits.o \
|
||||
deps/bearssl-0.6/src/ec/ecdsa_i31_vrfy_asn1.o \
|
||||
deps/bearssl-0.6/src/ec/ecdsa_i31_vrfy_raw.o \
|
||||
deps/bearssl-0.6/src/ec/ec_p256_m31.o \
|
||||
deps/bearssl-0.6/src/ec/ec_prime_i31.o \
|
||||
deps/bearssl-0.6/src/ec/ec_secp256r1.o \
|
||||
deps/bearssl-0.6/src/ec/ec_secp384r1.o \
|
||||
deps/bearssl-0.6/src/ec/ec_secp521r1.o \
|
||||
deps/bearssl-0.6/src/hash/ghash_ctmul64.o \
|
||||
deps/bearssl-0.6/src/hash/ghash_pclmul.o \
|
||||
deps/bearssl-0.6/src/hash/md5.o \
|
||||
deps/bearssl-0.6/src/hash/multihash.o \
|
||||
deps/bearssl-0.6/src/hash/sha1.o \
|
||||
deps/bearssl-0.6/src/hash/sha2big.o \
|
||||
deps/bearssl-0.6/src/hash/sha2small.o \
|
||||
deps/bearssl-0.6/src/int/i31_add.o \
|
||||
deps/bearssl-0.6/src/int/i31_bitlen.o \
|
||||
deps/bearssl-0.6/src/int/i31_decmod.o \
|
||||
deps/bearssl-0.6/src/int/i31_decode.o \
|
||||
deps/bearssl-0.6/src/int/i31_decred.o \
|
||||
deps/bearssl-0.6/src/int/i31_encode.o \
|
||||
deps/bearssl-0.6/src/int/i31_fmont.o \
|
||||
deps/bearssl-0.6/src/int/i31_iszero.o \
|
||||
deps/bearssl-0.6/src/int/i31_modpow2.o \
|
||||
deps/bearssl-0.6/src/int/i31_modpow.o \
|
||||
deps/bearssl-0.6/src/int/i31_montmul.o \
|
||||
deps/bearssl-0.6/src/int/i31_muladd.o \
|
||||
deps/bearssl-0.6/src/int/i31_ninv31.o \
|
||||
deps/bearssl-0.6/src/int/i31_rshift.o \
|
||||
deps/bearssl-0.6/src/int/i31_sub.o \
|
||||
deps/bearssl-0.6/src/int/i31_tmont.o \
|
||||
deps/bearssl-0.6/src/int/i32_div32.o \
|
||||
deps/bearssl-0.6/src/int/i62_modpow2.o \
|
||||
deps/bearssl-0.6/src/mac/hmac_ct.o \
|
||||
deps/bearssl-0.6/src/mac/hmac.o \
|
||||
deps/bearssl-0.6/src/rand/hmac_drbg.o \
|
||||
deps/bearssl-0.6/src/rand/sysrng.o \
|
||||
deps/bearssl-0.6/src/rsa/rsa_default_pkcs1_vrfy.o \
|
||||
deps/bearssl-0.6/src/rsa/rsa_default_pub.o \
|
||||
deps/bearssl-0.6/src/rsa/rsa_i31_pkcs1_vrfy.o \
|
||||
deps/bearssl-0.6/src/rsa/rsa_i31_pub.o \
|
||||
deps/bearssl-0.6/src/rsa/rsa_i62_pkcs1_vrfy.o \
|
||||
deps/bearssl-0.6/src/rsa/rsa_i62_pub.o \
|
||||
deps/bearssl-0.6/src/rsa/rsa_pkcs1_sig_unpad.o \
|
||||
deps/bearssl-0.6/src/ssl/prf_md5sha1.o \
|
||||
deps/bearssl-0.6/src/ssl/prf.o \
|
||||
deps/bearssl-0.6/src/ssl/prf_sha256.o \
|
||||
deps/bearssl-0.6/src/ssl/prf_sha384.o \
|
||||
deps/bearssl-0.6/src/ssl/ssl_client_default_rsapub.o \
|
||||
deps/bearssl-0.6/src/ssl/ssl_client_full.o \
|
||||
deps/bearssl-0.6/src/ssl/ssl_client.o \
|
||||
deps/bearssl-0.6/src/ssl/ssl_engine_default_aescbc.o \
|
||||
deps/bearssl-0.6/src/ssl/ssl_engine_default_aesccm.o \
|
||||
deps/bearssl-0.6/src/ssl/ssl_engine_default_aesgcm.o \
|
||||
deps/bearssl-0.6/src/ssl/ssl_engine_default_chapol.o \
|
||||
deps/bearssl-0.6/src/ssl/ssl_engine_default_descbc.o \
|
||||
deps/bearssl-0.6/src/ssl/ssl_engine_default_ecdsa.o \
|
||||
deps/bearssl-0.6/src/ssl/ssl_engine_default_rsavrfy.o \
|
||||
deps/bearssl-0.6/src/ssl/ssl_engine.o \
|
||||
deps/bearssl-0.6/src/ssl/ssl_hs_client.o \
|
||||
deps/bearssl-0.6/src/ssl/ssl_rec_cbc.o \
|
||||
deps/bearssl-0.6/src/ssl/ssl_rec_ccm.o \
|
||||
deps/bearssl-0.6/src/ssl/ssl_rec_chapol.o \
|
||||
deps/bearssl-0.6/src/ssl/ssl_rec_gcm.o \
|
||||
deps/bearssl-0.6/src/symcipher/aes_ct64_cbcdec.o \
|
||||
deps/bearssl-0.6/src/symcipher/aes_ct64_cbcenc.o \
|
||||
deps/bearssl-0.6/src/symcipher/aes_ct64_ctrcbc.o \
|
||||
deps/bearssl-0.6/src/symcipher/aes_ct64_ctr.o \
|
||||
deps/bearssl-0.6/src/symcipher/aes_ct64_dec.o \
|
||||
deps/bearssl-0.6/src/symcipher/aes_ct64_enc.o \
|
||||
deps/bearssl-0.6/src/symcipher/aes_ct64.o \
|
||||
deps/bearssl-0.6/src/symcipher/aes_x86ni_cbcdec.o \
|
||||
deps/bearssl-0.6/src/symcipher/aes_x86ni_cbcenc.o \
|
||||
deps/bearssl-0.6/src/symcipher/aes_x86ni_ctrcbc.o \
|
||||
deps/bearssl-0.6/src/symcipher/aes_x86ni_ctr.o \
|
||||
deps/bearssl-0.6/src/symcipher/aes_x86ni.o \
|
||||
deps/bearssl-0.6/src/symcipher/chacha20_ct.o \
|
||||
deps/bearssl-0.6/src/symcipher/chacha20_sse2.o \
|
||||
deps/bearssl-0.6/src/symcipher/des_ct_cbcdec.o \
|
||||
deps/bearssl-0.6/src/symcipher/des_ct_cbcenc.o \
|
||||
deps/bearssl-0.6/src/symcipher/des_ct.o \
|
||||
deps/bearssl-0.6/src/symcipher/des_support.o \
|
||||
deps/bearssl-0.6/src/symcipher/poly1305_ctmul.o \
|
||||
deps/bearssl-0.6/src/symcipher/poly1305_ctmulq.o \
|
||||
deps/bearssl-0.6/src/x509/x509_decoder.o \
|
||||
deps/bearssl-0.6/src/x509/x509_minimal_full.o \
|
||||
deps/bearssl-0.6/src/x509/x509_minimal.o \
|
||||
|
||||
OBJ += $(OBJS_BEAR)
|
||||
else ifeq ($(HAVE_BUILTINMBEDTLS), 1)
|
||||
HAVE_SSL = 1
|
||||
DEFINES += -DHAVE_SSL
|
||||
|
||||
ifeq ($(DEBUG), 1)
|
||||
DEFINES += -DMBEDTLS_SSL_DEBUG_ALL
|
||||
endif
|
||||
LIBS += $(MBEDTLS_LIBS) $(MBEDX509_LIBS) $(MBEDCRYPTO_LIBS)
|
||||
DEF_FLAGS += $(MBEDTLS_CFLAGS) $(MBEDX509_CFLAGS) $(MBEDCRYPTO_CFLAGS)
|
||||
|
||||
# MinGW requires this for some reason,
|
||||
# even though the include paths are relative to the source
|
||||
INCLUDE_DIRS += -Ideps/mbedtls
|
||||
|
||||
OBJS_TLS_CRYPTO = deps/mbedtls/aes.o \
|
||||
deps/mbedtls/aesni.o \
|
||||
deps/mbedtls/arc4.o \
|
||||
deps/mbedtls/asn1parse.o \
|
||||
deps/mbedtls/asn1write.o \
|
||||
deps/mbedtls/base64.o \
|
||||
deps/mbedtls/bignum.o \
|
||||
deps/mbedtls/blowfish.o \
|
||||
deps/mbedtls/camellia.o \
|
||||
deps/mbedtls/ccm.o \
|
||||
deps/mbedtls/cipher.o \
|
||||
deps/mbedtls/cipher_wrap.o \
|
||||
deps/mbedtls/ctr_drbg.o \
|
||||
deps/mbedtls/des.o \
|
||||
deps/mbedtls/dhm.o \
|
||||
deps/mbedtls/ecdh.o \
|
||||
deps/mbedtls/ecdsa.o \
|
||||
deps/mbedtls/ecp.o \
|
||||
deps/mbedtls/ecp_curves.o \
|
||||
deps/mbedtls/entropy.o \
|
||||
deps/mbedtls/entropy_poll.o \
|
||||
deps/mbedtls/gcm.o \
|
||||
deps/mbedtls/hmac_drbg.o \
|
||||
deps/mbedtls/md.o \
|
||||
deps/mbedtls/md5.o \
|
||||
deps/mbedtls/md_wrap.o \
|
||||
deps/mbedtls/oid.o \
|
||||
deps/mbedtls/padlock.o \
|
||||
deps/mbedtls/pem.o \
|
||||
deps/mbedtls/pk.o \
|
||||
deps/mbedtls/pk_wrap.o \
|
||||
deps/mbedtls/pkcs12.o \
|
||||
deps/mbedtls/pkcs5.o \
|
||||
deps/mbedtls/pkparse.o \
|
||||
deps/mbedtls/pkwrite.o \
|
||||
deps/mbedtls/ripemd160.o \
|
||||
deps/mbedtls/rsa.o \
|
||||
deps/mbedtls/sha1.o \
|
||||
deps/mbedtls/sha_alt.o \
|
||||
deps/mbedtls/sha256.o \
|
||||
deps/mbedtls/sha512.o \
|
||||
deps/mbedtls/threading.o \
|
||||
deps/mbedtls/timing.o \
|
||||
deps/mbedtls/xtea.o
|
||||
|
||||
OBJS_TLS_X509 = deps/mbedtls/certs.o \
|
||||
deps/mbedtls/x509.o \
|
||||
deps/mbedtls/x509_create.o \
|
||||
deps/mbedtls/x509_crl.o \
|
||||
deps/mbedtls/x509_crt.o \
|
||||
deps/mbedtls/x509_csr.o \
|
||||
deps/mbedtls/x509write_crt.o \
|
||||
deps/mbedtls/x509write_csr.o
|
||||
|
||||
OBJS_TLS = deps/mbedtls/debug.o \
|
||||
deps/mbedtls/net_sockets.o \
|
||||
deps/mbedtls/ssl_cache.o \
|
||||
deps/mbedtls/ssl_ciphersuites.o \
|
||||
deps/mbedtls/ssl_cli.o \
|
||||
deps/mbedtls/ssl_cookie.o \
|
||||
deps/mbedtls/ssl_srv.o \
|
||||
deps/mbedtls/ssl_ticket.o \
|
||||
deps/mbedtls/ssl_tls.o
|
||||
|
||||
OBJ += $(OBJS_TLS_CRYPTO) $(OBJS_TLS_X509) $(OBJS_TLS)
|
||||
else ifeq ($(HAVE_SSL), 1)
|
||||
DEFINES += -DHAVE_SSL
|
||||
LIBS += $(SYSTEMMBEDTLS_LIBS) $(SYSTEMMBEDX509_LIBS) $(SYSTEMMBEDCRYPTO_LIBS)
|
||||
DEF_FLAGS += $(SYSTEMMBEDTLS_CFLAGS) $(SYSTEMMBEDX509_CFLAGS) $(SYSTEMMBEDCRYPTO_CFLAGS)
|
||||
endif
|
||||
|
||||
# Miscellaneous
|
||||
|
||||
ifeq ($(HAVE_COMMAND), 1)
|
||||
DEFINES += -DHAVE_COMMAND
|
||||
# The MCP server: desktop builds, through ./configure; no console
|
||||
# Makefile sets HAVE_MCP.
|
||||
ifeq ($(HAVE_NETWORKING), 1)
|
||||
ifeq ($(HAVE_MCP), 1)
|
||||
DEFINES += -DHAVE_MCP
|
||||
OBJ += network/mcp_server.o
|
||||
endif
|
||||
endif
|
||||
endif
|
||||
|
||||
ifeq ($(HAVE_STDIN_CMD), 1)
|
||||
@@ -866,7 +997,6 @@ endif
|
||||
|
||||
OBJ += $(LIBRETRO_COMM_DIR)/audio/conversion/s16_to_float.o \
|
||||
$(LIBRETRO_COMM_DIR)/audio/conversion/float_to_s16.o \
|
||||
$(LIBRETRO_COMM_DIR)/audio/conversion/float_to_s32.o \
|
||||
$(LIBRETRO_COMM_DIR)/audio/conversion/mono_to_stereo_float.o \
|
||||
$(LIBRETRO_COMM_DIR)/audio/conversion/stereo_to_mono_float.o \
|
||||
|
||||
@@ -1093,19 +1223,7 @@ ifeq ($(HAVE_THREADS), 1)
|
||||
$(LIBRETRO_COMM_DIR)/queues/retro_waitable_spsc.o \
|
||||
gfx/video_thread_wrapper.o \
|
||||
gfx/video_thread_hw.o \
|
||||
audio/audio_thread_wrapper.o \
|
||||
frontend/thread_elevation.o \
|
||||
frontend/thread_elevation/eevdf.o
|
||||
|
||||
# RealtimeKit and screensaver inhibit over libdbus loaded at run
|
||||
# time: no D-Bus headers or link dependency, just the library where
|
||||
# the desktop has it.
|
||||
ifneq ($(findstring Linux,$(OS))$(findstring BSD,$(OS)),)
|
||||
OBJ += gfx/common/dbus_runtime.o \
|
||||
gfx/common/dbus_common.o \
|
||||
gfx/common/mutter_displayconfig.o \
|
||||
frontend/thread_elevation/rtkit.o
|
||||
endif
|
||||
audio/audio_thread_wrapper.o
|
||||
DEFINES += -DHAVE_THREADS
|
||||
ifeq ($(findstring Haiku,$(OS)),)
|
||||
LIBS += $(THREADS_LIBS)
|
||||
@@ -1146,13 +1264,13 @@ endif
|
||||
ifeq ($(TARGET), retroarch_orbis)
|
||||
OBJ += input/drivers/ps4_input.o \
|
||||
input/drivers_joypad/ps4_joypad.o \
|
||||
audio/drivers/ps4_audio.o
|
||||
audio/drivers/psp_audio.o
|
||||
endif
|
||||
|
||||
ifeq ($(TARGET), retroarch_vita)
|
||||
OBJ += input/drivers/psp_input.o \
|
||||
input/drivers_joypad/psp_joypad.o \
|
||||
audio/drivers/psp2_audio.o \
|
||||
audio/drivers/psp_audio.o \
|
||||
frontend/drivers/platform_psp.o
|
||||
endif
|
||||
|
||||
@@ -1217,16 +1335,7 @@ ifeq ($(HAVE_WAYLAND), 1)
|
||||
gfx/common/wayland/tablet-unstable-v2.o \
|
||||
gfx/common/wayland/content-type-v1.o \
|
||||
gfx/common/wayland/single-pixel-buffer-v1.o \
|
||||
gfx/common/wayland/tearing-control-v1.o \
|
||||
gfx/common/wayland/color-management-v1.o \
|
||||
gfx/common/wayland_color.o \
|
||||
gfx/common/wayland/kde-output-device-v2.o \
|
||||
gfx/common/wayland/kde-output-management-v2.o \
|
||||
gfx/common/wayland_kwin_output.o \
|
||||
gfx/common/wayland/wlr-output-management-unstable-v1.o \
|
||||
gfx/common/wayland_wlr_output.o \
|
||||
gfx/common/wayland/drm-lease-v1.o \
|
||||
gfx/common/wayland_drm_lease.o
|
||||
gfx/common/wayland/tearing-control-v1.o
|
||||
|
||||
ifeq ($(HAVE_VULKAN), 1)
|
||||
OBJ += gfx/drivers_context/wayland_vk_ctx.o
|
||||
@@ -1298,7 +1407,7 @@ ifeq ($(HAVE_X11), 1)
|
||||
DEFINES += -DHAVE_X11
|
||||
DEF_FLAGS += $(X11_CFLAGS) $(XEXT_CFLAGS) $(XF86VM_CFLAGS) $(XINERAMA_CFLAGS)
|
||||
ifeq ($(HAVE_XCB),1)
|
||||
LIBS += $(X11_XCB_LIBS)
|
||||
LIBS += -lX11-xcb
|
||||
endif
|
||||
ifeq ($(HAVE_XI2),1)
|
||||
LIBS += -lXi
|
||||
@@ -1311,10 +1420,7 @@ ifeq ($(HAVE_X11), 1)
|
||||
OBJ += gfx/drivers_context/x_ctx.o
|
||||
endif
|
||||
ifeq ($(HAVE_VULKAN), 1)
|
||||
# The X11 Vulkan context creates an XCB surface.
|
||||
ifeq ($(HAVE_XCB), 1)
|
||||
OBJ += gfx/drivers_context/x_vk_ctx.o
|
||||
endif
|
||||
OBJ += gfx/drivers_context/x_vk_ctx.o
|
||||
endif
|
||||
endif
|
||||
|
||||
@@ -1332,6 +1438,7 @@ endif
|
||||
ifeq ($(HAVE_DBUS), 1)
|
||||
LIBS += $(DBUS_LIBS)
|
||||
DEF_FLAGS += $(DBUS_CFLAGS)
|
||||
OBJ += gfx/common/dbus_common.o
|
||||
endif
|
||||
|
||||
ifeq ($(HAVE_UDEV), 1)
|
||||
@@ -1430,8 +1537,7 @@ OBJ += gfx/drivers_context/gfx_null_ctx.o
|
||||
ifeq ($(HAVE_KMS), 1)
|
||||
HAVE_AND_WILL_USE_DRM = 1
|
||||
OBJ += gfx/drivers_context/drm_ctx.o \
|
||||
gfx/display_servers/dispserv_kms.o \
|
||||
gfx/common/drm_hdr.o
|
||||
gfx/display_servers/dispserv_kms.o
|
||||
|
||||
ifeq ($(HAVE_ODROIDGO2), 1)
|
||||
OBJ += gfx/drivers_context/drm_go2_ctx.o
|
||||
@@ -1648,16 +1754,14 @@ else ifeq ($(HAVE_SDL2), 1)
|
||||
HAVE_SDL_COMMON = 1
|
||||
OBJ += gfx/drivers/sdl2_gfx.o \
|
||||
gfx/common/sdl2_common.o \
|
||||
gfx/display_servers/dispserv_sdl2.o \
|
||||
audio/drivers/sdl2_audio.o
|
||||
gfx/display_servers/dispserv_sdl2.o
|
||||
DEF_FLAGS += $(SDL2_CFLAGS)
|
||||
# See the SDL3 note above: drop SDL's WinMain shim lib, we set
|
||||
# SDL_MAIN_HANDLED and provide our own entry point.
|
||||
LIBS += $(filter-out -lSDL2main -lSDLmain -lSDL_main,$(SDL2_LIBS))
|
||||
else ifeq ($(HAVE_SDL), 1)
|
||||
HAVE_SDL_COMMON = 1
|
||||
OBJ += gfx/drivers/sdl_gfx.o \
|
||||
audio/drivers/sdl1_audio.o
|
||||
OBJ += gfx/drivers/sdl_gfx.o
|
||||
DEF_FLAGS += $(SDL_CFLAGS)
|
||||
# See the SDL3 note above: drop SDL's WinMain shim lib.
|
||||
LIBS += $(filter-out -lSDLmain -lSDL_main,$(SDL_LIBS))
|
||||
@@ -1674,27 +1778,18 @@ else ifeq ($(HAVE_SDL_DINGUX), 1)
|
||||
endif
|
||||
|
||||
OBJ += input/drivers/sdl_dingux_input.o \
|
||||
input/drivers_joypad/sdl_dingux_joypad.o \
|
||||
audio/drivers/sdl1_audio.o
|
||||
input/drivers_joypad/sdl_dingux_joypad.o
|
||||
DEF_FLAGS += $(SDL_DINGUX_CFLAGS)
|
||||
LIBS += $(SDL_DINGUX_LIBS)
|
||||
endif
|
||||
|
||||
ifeq ($(HAVE_SDL_COMMON), 1)
|
||||
ifeq ($(HAVE_SDL2), 1)
|
||||
OBJ += input/drivers/sdl2_input.o \
|
||||
input/drivers_joypad/sdl2_joypad.o
|
||||
else
|
||||
OBJ += input/drivers/sdl1_input.o \
|
||||
input/drivers_joypad/sdl1_joypad.o
|
||||
endif
|
||||
OBJ += input/drivers/sdl_input.o \
|
||||
input/drivers_joypad/sdl_joypad.o \
|
||||
audio/drivers/sdl_audio.o
|
||||
|
||||
ifeq ($(HAVE_GL_CONTEXT), 1)
|
||||
ifeq ($(HAVE_SDL2), 1)
|
||||
OBJ += gfx/drivers_context/sdl2_gl_ctx.o
|
||||
else
|
||||
OBJ += gfx/drivers_context/sdl1_gl_ctx.o
|
||||
endif
|
||||
OBJ += gfx/drivers_context/sdl_gl_ctx.o
|
||||
endif
|
||||
|
||||
INCLUDE_DIRS += $(BSD_LOCAL_INC)
|
||||
@@ -1777,10 +1872,6 @@ ifeq ($(HAVE_DISPMANX), 1)
|
||||
HAVE_VIDEOCORE = 1
|
||||
endif
|
||||
|
||||
ifeq ($(HAVE_VIDEOCORE), 1)
|
||||
OBJ += gfx/display_servers/dispserv_videocore.o
|
||||
endif
|
||||
|
||||
ifeq ($(HAVE_SUNXI), 1)
|
||||
OBJ += gfx/drivers/sunxi_gfx.o \
|
||||
gfx/include/pixman/pixman-arm-neon-asm.o
|
||||
@@ -2436,7 +2527,6 @@ ifeq ($(HAVE_NETWORKING), 1)
|
||||
$(LIBRETRO_COMM_DIR)/net/net_socket.o \
|
||||
core_updater_list.o \
|
||||
network/natt.o \
|
||||
network/natt_desc.o \
|
||||
tasks/task_netplay_lan_scan.o \
|
||||
tasks/task_netplay_nat_traversal.o \
|
||||
tasks/task_pl_thumbnail_download.o \
|
||||
@@ -2463,25 +2553,20 @@ ifeq ($(HAVE_NETWORKING), 1)
|
||||
ifeq ($(HAVE_CLOUDSYNC), 1)
|
||||
DEFINES += -DHAVE_CLOUDSYNC
|
||||
OBJ += tasks/task_cloudsync.o \
|
||||
tasks/task_cloudsync_path.o \
|
||||
network/cloud_sync/webdav.o \
|
||||
network/cloud_sync_driver.o
|
||||
ifeq ($(HAVE_SSL), 1)
|
||||
OBJ += network/cloud_sync/google_drive.o
|
||||
endif
|
||||
ifneq (,$(filter 1,$(HAVE_SMBCLIENT) $(HAVE_RETROSMB)))
|
||||
ifneq (,$(filter 1,$(HAVE_SMBCLIENT) $(HAVE_BUILTINSMBCLIENT)))
|
||||
OBJ += network/cloud_sync/smb.o
|
||||
endif
|
||||
endif
|
||||
|
||||
ifeq ($(HAVE_RETROSSL), 1)
|
||||
OBJ += $(LIBRETRO_COMM_DIR)/net/net_socket_ssl_retro.o \
|
||||
network/tls_log.o
|
||||
else ifeq ($(HAVE_BEARSSL), 1)
|
||||
ifeq ($(HAVE_BUILTINBEARSSL), 1)
|
||||
OBJ += $(LIBRETRO_COMM_DIR)/net/net_socket_ssl_bear.o
|
||||
else ifeq ($(HAVE_MBEDTLS), 1)
|
||||
OBJ += $(LIBRETRO_COMM_DIR)/net/net_socket_ssl_mbed.o \
|
||||
network/tls_log.o
|
||||
else ifeq ($(HAVE_SSL), 1)
|
||||
OBJ += $(LIBRETRO_COMM_DIR)/net/net_socket_ssl_mbed.o
|
||||
endif
|
||||
|
||||
ifeq ($(HAVE_IFINFO), 1)
|
||||
@@ -2845,41 +2930,109 @@ ifeq ($(HAVE_TEST_DRIVERS), 1)
|
||||
OBJ += input/drivers/test_input.o
|
||||
endif
|
||||
|
||||
# The built-in NFSv3 client and its nfs:// VFS backend. Needs only
|
||||
# networking; the console Makefiles do not set it.
|
||||
ifeq ($(HAVE_RETRONFS), 1)
|
||||
DEFINES += -DHAVE_NFSCLIENT -DHAVE_RETRONFS
|
||||
OBJ += $(LIBRETRO_COMM_DIR)/net/net_nfs3.o \
|
||||
$(LIBRETRO_COMM_DIR)/vfs/vfs_implementation_nfs.o
|
||||
endif
|
||||
|
||||
# The background prefetcher the network backends read through; once,
|
||||
# whichever of them are built.
|
||||
ifneq (,$(filter 1,$(HAVE_RETRONFS) $(HAVE_RETROSMB) $(HAVE_SMBCLIENT)))
|
||||
OBJ += $(LIBRETRO_COMM_DIR)/vfs/vfs_prefetch.o
|
||||
endif
|
||||
|
||||
# The built-in SMB2/3 client (libretro-common/net/net_smb2.c) stands in
|
||||
# for libsmb2: the same HAVE_SMBCLIENT surface for the menu, VFS and
|
||||
# cloud sync, no bundled or system library, needs HAVE_CRYPTO.
|
||||
ifeq ($(HAVE_RETROSMB), 1)
|
||||
DEFINES += -DHAVE_SMBCLIENT -DHAVE_RETROSMB
|
||||
OBJ += $(LIBRETRO_COMM_DIR)/net/net_smb2.o \
|
||||
$(LIBRETRO_COMM_DIR)/net/net_krb5.o \
|
||||
$(LIBRETRO_COMM_DIR)/vfs/vfs_implementation_smb.o
|
||||
else ifeq ($(HAVE_SMBCLIENT), 1)
|
||||
# a system libsmb2 behind the same HAVE_SMBCLIENT surface
|
||||
ifneq (,$(filter 1,$(HAVE_SMBCLIENT) $(HAVE_BUILTINSMBCLIENT)))
|
||||
DEFINES += -DHAVE_SMBCLIENT
|
||||
# The libsmb2 public headers gate their <stdint.h> include on
|
||||
# HAVE_STDINT_H (autoconf-style); every TU that includes them needs
|
||||
# the define.
|
||||
# HAVE_STDINT_H (autoconf-style), and every TU that includes
|
||||
# them - the bundled library sources and frontend consumers
|
||||
# like network/cloud_sync/smb.c alike - needs the define.
|
||||
# It used to arrive only as a side effect of an unrelated block,
|
||||
# which broke builds that did not enable that block on hundreds
|
||||
# of unknown uint*_t types. Own the dependency here.
|
||||
DEFINES += -DHAVE_STDINT_H
|
||||
ifeq ($(shell pkg-config --exists libsmb2 && echo yes),yes)
|
||||
CFLAGS += $(shell pkg-config --cflags libsmb2)
|
||||
LIBS += $(shell pkg-config --libs libsmb2)
|
||||
else
|
||||
LIBS += -lsmb2
|
||||
|
||||
ifeq ($(HAVE_SMBCLIENT),1)
|
||||
# system libsmb2
|
||||
ifeq ($(shell pkg-config --exists libsmb2 && echo yes),yes)
|
||||
CFLAGS += $(shell pkg-config --cflags libsmb2)
|
||||
LIBS += $(shell pkg-config --libs libsmb2)
|
||||
else
|
||||
LIBS += -lsmb2
|
||||
endif
|
||||
else ifeq ($(HAVE_BUILTINSMBCLIENT),1)
|
||||
# builtin libsmb2
|
||||
ifneq ($(findstring Win32,$(OS)),)
|
||||
# WIN32_LEAN_AND_MEAN is defined with an empty replacement list on
|
||||
# purpose. It has to be on the command line because the bundled
|
||||
# libsmb2 needs it, but it lands on every TU in the build, and ~40
|
||||
# RetroArch headers do a bare "#define WIN32_LEAN_AND_MEAN" of their
|
||||
# own before including <windows.h>. A plain -DWIN32_LEAN_AND_MEAN
|
||||
# expands to 1, which does not match those, so each one of them
|
||||
# warns about a macro redefinition. Nothing tests the value, only
|
||||
# #ifndef, so an empty definition is equivalent and silent.
|
||||
CFLAGS += -D_WINDOWS -DWIN32_LEAN_AND_MEAN=
|
||||
CFLAGS += -DNEED_RANDOM -DNEED_SRANDOM -DNEED_GETLOGIN_R
|
||||
endif
|
||||
INCLUDE_DIRS += -I$(DEPS_DIR)/libsmb2/include
|
||||
INCLUDE_DIRS += -I$(DEPS_DIR)/libsmb2/include/smb2
|
||||
CFLAGS += "-D_U_=__attribute__((unused))"
|
||||
CFLAGS += -DHAVE_TIME_H -DHAVE_FCNTL_H -DHAVE_UNISTD_H
|
||||
CFLAGS += -DHAVE_STDLIB_H -DSTDC_HEADERS
|
||||
CFLAGS += -DHAVE_STRING_H
|
||||
CFLAGS += -DHAVE_LINGER
|
||||
ifeq ($(findstring Win32,$(OS)),)
|
||||
CFLAGS += -DHAVE_SYS_UIO_H
|
||||
CFLAGS += -DHAVE_POLL_H -DHAVE_NETDB_H
|
||||
CFLAGS += -DHAVE_NETINET_TCP_H -DHAVE_NETINET_IN_H
|
||||
CFLAGS += -DHAVE_SYS_SOCKET_H -DHAVE_ARPA_INET_H
|
||||
endif
|
||||
OBJ += \
|
||||
deps/libsmb2/lib/aes.o \
|
||||
deps/libsmb2/lib/aes_apple.o \
|
||||
deps/libsmb2/lib/aes128ccm.o \
|
||||
deps/libsmb2/lib/asn1-ber.o \
|
||||
deps/libsmb2/lib/aes_reference.o \
|
||||
deps/libsmb2/lib/alloc.o \
|
||||
deps/libsmb2/lib/compat.o \
|
||||
deps/libsmb2/lib/dcerpc.o \
|
||||
deps/libsmb2/lib/dcerpc-lsa.o \
|
||||
deps/libsmb2/lib/dcerpc-srvsvc.o \
|
||||
deps/libsmb2/lib/errors.o \
|
||||
deps/libsmb2/lib/hmac.o \
|
||||
deps/libsmb2/lib/hmac-md5.o \
|
||||
deps/libsmb2/lib/init.o \
|
||||
deps/libsmb2/lib/krb5-wrapper.o \
|
||||
deps/libsmb2/lib/libsmb2.o \
|
||||
deps/libsmb2/lib/md4c.o \
|
||||
deps/libsmb2/lib/ntlmssp.o \
|
||||
deps/libsmb2/lib/pdu.o \
|
||||
deps/libsmb2/lib/sha1.o \
|
||||
deps/libsmb2/lib/sha224-256.o \
|
||||
deps/libsmb2/lib/sha384-512.o \
|
||||
deps/libsmb2/lib/smb2-cmd-close.o \
|
||||
deps/libsmb2/lib/smb2-cmd-create.o \
|
||||
deps/libsmb2/lib/smb2-cmd-echo.o \
|
||||
deps/libsmb2/lib/smb2-cmd-error.o \
|
||||
deps/libsmb2/lib/smb2-cmd-flush.o \
|
||||
deps/libsmb2/lib/smb2-cmd-ioctl.o \
|
||||
deps/libsmb2/lib/smb2-cmd-lock.o \
|
||||
deps/libsmb2/lib/smb2-cmd-logoff.o \
|
||||
deps/libsmb2/lib/smb2-cmd-negotiate.o \
|
||||
deps/libsmb2/lib/smb2-cmd-oplock-break.o \
|
||||
deps/libsmb2/lib/smb2-cmd-notify-change.o \
|
||||
deps/libsmb2/lib/smb2-cmd-query-directory.o \
|
||||
deps/libsmb2/lib/smb2-cmd-query-info.o \
|
||||
deps/libsmb2/lib/smb2-cmd-read.o \
|
||||
deps/libsmb2/lib/smb2-cmd-session-setup.o \
|
||||
deps/libsmb2/lib/smb2-cmd-set-info.o \
|
||||
deps/libsmb2/lib/smb2-cmd-tree-connect.o \
|
||||
deps/libsmb2/lib/smb2-cmd-tree-disconnect.o \
|
||||
deps/libsmb2/lib/smb2-cmd-write.o \
|
||||
deps/libsmb2/lib/smb2-data-file-info.o \
|
||||
deps/libsmb2/lib/smb2-data-filesystem-info.o \
|
||||
deps/libsmb2/lib/smb2-data-security-descriptor.o \
|
||||
deps/libsmb2/lib/smb2-data-reparse-point.o \
|
||||
deps/libsmb2/lib/smb2-share-enum.o \
|
||||
deps/libsmb2/lib/smb2-signing.o \
|
||||
deps/libsmb2/lib/smb3-seal.o \
|
||||
deps/libsmb2/lib/socket.o \
|
||||
deps/libsmb2/lib/spnego-wrapper.o \
|
||||
deps/libsmb2/lib/sync.o \
|
||||
deps/libsmb2/lib/timestamps.o \
|
||||
deps/libsmb2/lib/usha.o \
|
||||
deps/libsmb2/lib/unicode.o
|
||||
endif
|
||||
|
||||
OBJ += $(LIBRETRO_COMM_DIR)/vfs/vfs_implementation_smb.o
|
||||
endif
|
||||
|
||||
|
||||
+4
-8
@@ -65,8 +65,11 @@ ifeq ($(GRIFFIN_BUILD), 1)
|
||||
DEFINES += -DHAVE_OVERLAY
|
||||
DEFINES += -DHAVE_CORE_INFO_CACHE
|
||||
#DEFINES += -DHAVE_SOCKET_LEGACY
|
||||
#-DHAVE_SSL -DHAVE_BUILTINMBEDTLS -DMBEDTLS_SSL_DEBUG_ALL
|
||||
#ssl is currently incompatible with griffin due to use of the "static" flag on repeating functions that will conflict when included in one file
|
||||
CFLAGS += -I. \
|
||||
-Ideps \
|
||||
-Ideps/mbedtls \
|
||||
-Ideps/rcheevos/include \
|
||||
-Ilibretro-common/include
|
||||
else
|
||||
@@ -100,14 +103,7 @@ else
|
||||
HAVE_IFINFO = 1
|
||||
HAVE_CHEEVOS = 1
|
||||
HAVE_THREADS = 1
|
||||
# the cleanroom crypto, keychain and TLS client
|
||||
HAVE_CRYPTO = 1
|
||||
HAVE_KEYCHAIN = 1
|
||||
HAVE_RETROSSL = 1
|
||||
# the built-in NFS client (nfs://); it needs no crypto
|
||||
HAVE_RETRONFS = 1
|
||||
# the built-in SMB client (smb://), on the crypto above
|
||||
HAVE_RETROSMB = 1
|
||||
HAVE_BUILTINMBEDTLS = 1
|
||||
HAVE_CORE_INFO_CACHE = 1
|
||||
HAVE_CLOUDSYNC = 1
|
||||
|
||||
|
||||
+2
-5
@@ -43,10 +43,7 @@ HAVE_AL = 1
|
||||
HAVE_ALSA = 0
|
||||
HAVE_DSP_FILTER = 1
|
||||
HAVE_VIDEO_FILTER = 1
|
||||
# the cleanroom crypto, keychain and TLS client
|
||||
HAVE_CRYPTO = 1
|
||||
HAVE_KEYCHAIN = 1
|
||||
HAVE_RETROSSL = 1
|
||||
HAVE_BUILTINMBEDTLS = 1
|
||||
HAVE_C99 = 1
|
||||
HAVE_CC = 1
|
||||
HAVE_CC_RESAMPLER = 1
|
||||
@@ -80,7 +77,7 @@ HAVE_OPENGL1 = 0
|
||||
HAVE_OPENGLES = 1
|
||||
HAVE_OPENGLES3 = 0
|
||||
HAVE_OPENGL_CORE = 0
|
||||
HAVE_OPENSSL = 0
|
||||
HAVE_OPENSSL = 1
|
||||
HAVE_OVERLAY = 1
|
||||
HAVE_RBMP = 1
|
||||
HAVE_RJPEG = 1
|
||||
|
||||
+4
-2
@@ -3,6 +3,7 @@ DEPS_DIR := $(ROOT_DIR)/deps
|
||||
DEBUG = 0
|
||||
HAVE_LOGGER = 0
|
||||
HAVE_FILE_LOGGER = 0
|
||||
HAVE_CC_RESAMPLER = 1
|
||||
WHOLE_ARCHIVE_LINK = 0
|
||||
BIG_STACK = 1
|
||||
PC_DEVELOPMENT_IP_ADDRESS = 255.255.255.255
|
||||
@@ -327,7 +328,8 @@ else ifeq ($(platform), vita)
|
||||
libretro-common/audio/conversion/s16_to_float_neon.o \
|
||||
libretro-common/audio/conversion/float_to_s16_neon.o \
|
||||
memory/neon/memcpy-neon.o \
|
||||
libretro-common/audio/resampler/drivers/sinc_resampler_neon.o
|
||||
libretro-common/audio/resampler/drivers/sinc_resampler_neon.o \
|
||||
audio/drivers_resampler/cc_resampler_neon.o
|
||||
|
||||
LIBDIRS += -L.
|
||||
LDFLAGS += -Wl,-q
|
||||
@@ -1007,7 +1009,7 @@ else ifneq (,$(findstring unix,$(platform)))
|
||||
EXT_TARGET := $(TARGET_NAME)
|
||||
EXT_INTER_TARGET := $(TARGET_NAME)
|
||||
INCLUDE += -Ilibretro-common/include -Igfx/include -Ideps -Ideps/rcheevos/include -Ideps/SPIRV-Cross -Ideps/glslang -I.
|
||||
LIBS += -ldl -lm -lpthread -lGL -ludev -lpulse -lX11 -lX11-xcb -lxcb -lXxf86vm
|
||||
LIBS += -ldl -lm -lpthread -lGL -ludev -lpulse -lX11 -lX11-xcb -lXxf86vm
|
||||
endif
|
||||
|
||||
ifneq (,$(findstring msvc,$(platform)))
|
||||
|
||||
@@ -52,6 +52,7 @@ HAVE_VIDEO_FILTER = 1
|
||||
HAVE_STATIC_VIDEO_FILTERS = 1
|
||||
HAVE_STATIC_AUDIO_FILTERS = 1
|
||||
HAVE_FILTERS_BUILTIN = 1
|
||||
HAVE_BUILTINMBEDTLS = 0
|
||||
HAVE_C99 = 1
|
||||
HAVE_CC = 1
|
||||
HAVE_CC_RESAMPLER = 1
|
||||
|
||||
+2
-9
@@ -58,14 +58,7 @@ HAVE_RVORBIS = 1
|
||||
HAVE_RMODTRACKER = 1
|
||||
HAVE_CORE_INFO_CACHE = 1
|
||||
HAVE_CLOUDSYNC = 1
|
||||
# The cleanroom crypto, keychain and TLS 1.2/1.3 client; nothing to link.
|
||||
HAVE_CRYPTO = 1
|
||||
HAVE_KEYCHAIN = 1
|
||||
HAVE_RETROSSL = 1
|
||||
# The built-in NFS client (nfs://); it needs no crypto.
|
||||
HAVE_RETRONFS = 1
|
||||
# The built-in SMB client (smb://), on the crypto above.
|
||||
HAVE_RETROSMB = 1
|
||||
HAVE_SSL = 1
|
||||
|
||||
# RetroArch libnx useful flags
|
||||
HAVE_THREADS = 1
|
||||
@@ -157,7 +150,7 @@ LDFLAGS = -specs=$(DEVKITPRO)/libnx/switch.specs $(ARCH) -Wl,--allow-multiple-de
|
||||
# add things from Makefile.common
|
||||
CFLAGS += $(DEF_FLAGS)
|
||||
|
||||
LIBS := -lswresample -lavformat -lavcodec -lavutil -lswscale -lstdc++ -lbz2 -lpng -lz -lnx -lvpx -lopus -llzma -lnx -lm
|
||||
LIBS := -lswresample -lavformat -lavcodec -lavutil -lswscale -lstdc++ -lbz2 -lpng -lz -lnx -lvpx -lopus -llzma -lmbedtls -lmbedx509 -lmbedcrypto -lnx -lm
|
||||
|
||||
ifeq ($(HAVE_OPENGL), 1)
|
||||
LIBS := -lEGL -lglapi -ldrm_nouveau $(LIBS)
|
||||
|
||||
@@ -54,6 +54,7 @@ HAVE_VIDEO_FILTER = 1
|
||||
HAVE_STATIC_VIDEO_FILTERS = 1
|
||||
HAVE_STATIC_AUDIO_FILTERS = 1
|
||||
HAVE_FILTERS_BUILTIN = 1
|
||||
HAVE_BUILTINMBEDTLS = 0
|
||||
HAVE_C99 = 1
|
||||
HAVE_CC = 1
|
||||
HAVE_CC_RESAMPLER = 1
|
||||
|
||||
+6
-19
@@ -122,29 +122,20 @@ LIBS += -logc
|
||||
|
||||
CFLAGS += -DGEKKO -U__INT32_TYPE__ -U __UINT32_TYPE__ -D__INT32_TYPE__=int
|
||||
|
||||
# 24 MB of MEM1 makes resident code weight count, so features that cannot
|
||||
# do their job on this hardware are compiled out: runahead (the second
|
||||
# savestate never fits beside a running core), the webp/dds thumbnail
|
||||
# decoders, the mod/s3m/xm tracker, 7z (LZMA dictionaries alone outsize
|
||||
# the console's free RAM, so 7z archives could never extract anyway) and
|
||||
# libretrodb (defaulted on by Makefile.common; database scanning is
|
||||
# impractical here, and the explore menu goes with it). Together these
|
||||
# return about 640 KB of MEM1 to content.
|
||||
HAVE_LIBRETRODB := 0
|
||||
HAVE_RUNAHEAD := 0
|
||||
HAVE_RUNAHEAD := 1
|
||||
HAVE_DSP_FILTER := 1
|
||||
HAVE_VIDEO_FILTER := 1
|
||||
HAVE_FILTERS_BUILTIN := 1
|
||||
HAVE_THREADS := 1
|
||||
HAVE_RPNG := 1
|
||||
HAVE_RWEBP := 0
|
||||
HAVE_RDDS := 0
|
||||
HAVE_RWEBP := 1
|
||||
HAVE_RDDS := 1
|
||||
HAVE_RJPEG := 1
|
||||
HAVE_RBMP := 1
|
||||
HAVE_RTGA := 1
|
||||
HAVE_RMODTRACKER := 0
|
||||
HAVE_RMODTRACKER := 1
|
||||
HAVE_OVERLAY := 1
|
||||
HAVE_7ZIP := 0
|
||||
HAVE_7ZIP := 1
|
||||
HAVE_RZSTD := 1
|
||||
HAVE_CONFIGFILE := 1
|
||||
HAVE_PATCH := 1
|
||||
@@ -170,13 +161,9 @@ INCLUDE += -I./libretro-common/include \
|
||||
-Ideps
|
||||
CFLAGS += -Wall -std=gnu99 $(MACHDEP) $(PLATCFLAGS) $(INCLUDE)
|
||||
|
||||
OBJ = $(PLATOBJS) audio/drivers/gx_audio.o gfx/drivers/gx_gfx.o gfx/display_servers/dispserv_gx.o gfx/display_servers/dispserv_gx_modes.o input/drivers/gx_input.o frontend/drivers/platform_gx.o input/drivers_joypad/gx_joypad.o
|
||||
OBJ = $(PLATOBJS) audio/drivers/gx_audio.o gfx/drivers/gx_gfx.o input/drivers/gx_input.o frontend/drivers/platform_gx.o input/drivers_joypad/gx_joypad.o
|
||||
|
||||
ifeq ($(EXTERNAL_LIBOGC), 0)
|
||||
# Bridge the prebuilt in-tree libogc's __syscalls table onto the
|
||||
# per-symbol __syscall_* hooks modern devkitPPC libsysbase dispatches
|
||||
# through (see wii/libogc_syscalls_compat.c).
|
||||
OBJ += wii/libogc_syscalls_compat.o
|
||||
OBJ += wii/libogc/libfat/libfat.o wii/libogc/libfat/fatfile.o wii/libogc/libfat/file_allocation_table.o wii/libogc/libfat/cache.o wii/libogc/libfat/fatdir.o wii/libogc/libfat/partition.o wii/libogc/libfat/directory.o wii/libogc/libfat/filetime.o wii/libogc/libfat/disc.o
|
||||
endif
|
||||
|
||||
|
||||
+1
-1
@@ -19,7 +19,7 @@ AUTH_INFO = 000000000000000000000000001C004000FF00000000008000000000000000000000
|
||||
OBJ += deps/xxHash/xxhash.o \
|
||||
# input/drivers/ps4_input.o \
|
||||
# input/drivers_joypad/ps4_joypad.o \
|
||||
# audio/drivers/ps4_audio.o \
|
||||
# audio/drivers/psp_audio.o \
|
||||
# gfx/drivers_context/orbis_ctx.o \
|
||||
# frontend/drivers/platform_orbis.o
|
||||
|
||||
|
||||
+2
-6
@@ -54,11 +54,7 @@ HAVE_VIDEO_FILTER = 1
|
||||
HAVE_STATIC_VIDEO_FILTERS = 1
|
||||
HAVE_STATIC_AUDIO_FILTERS = 1
|
||||
HAVE_FILTERS_BUILTIN = 1
|
||||
# the cleanroom crypto, keychain and TLS client (HAVE_OPENSSL linked a
|
||||
# library no backend used)
|
||||
HAVE_CRYPTO = 1
|
||||
HAVE_KEYCHAIN = 1
|
||||
HAVE_RETROSSL = 1
|
||||
HAVE_BUILTINMBEDTLS = 0
|
||||
HAVE_C99 = 1
|
||||
HAVE_CC = 1
|
||||
HAVE_CC_RESAMPLER = 1
|
||||
@@ -89,7 +85,7 @@ HAVE_OPENGL1 = 0
|
||||
HAVE_OPENGLES = 0
|
||||
HAVE_OPENGLES3 = 0
|
||||
HAVE_OPENGL_CORE = 0
|
||||
HAVE_OPENSSL = 0
|
||||
HAVE_OPENSSL = 1
|
||||
HAVE_OVERLAY = 0
|
||||
HAVE_RBMP = 1
|
||||
HAVE_RJPEG = 1
|
||||
|
||||
+2
-6
@@ -51,11 +51,7 @@ HAVE_VIDEO_FILTER = 1
|
||||
HAVE_STATIC_VIDEO_FILTERS = 1
|
||||
HAVE_STATIC_AUDIO_FILTERS = 1
|
||||
HAVE_FILTERS_BUILTIN = 1
|
||||
# the cleanroom crypto, keychain and TLS client (HAVE_OPENSSL linked a
|
||||
# library no backend used)
|
||||
HAVE_CRYPTO = 1
|
||||
HAVE_KEYCHAIN = 1
|
||||
HAVE_RETROSSL = 1
|
||||
HAVE_BUILTINMBEDTLS = 0
|
||||
HAVE_C99 = 1
|
||||
HAVE_CC = 1
|
||||
HAVE_CC_RESAMPLER = 1
|
||||
@@ -87,7 +83,7 @@ HAVE_OPENGL1 = 0
|
||||
HAVE_OPENGLES = 0
|
||||
HAVE_OPENGLES3 = 0
|
||||
HAVE_OPENGL_CORE = 0
|
||||
HAVE_OPENSSL = 0
|
||||
HAVE_OPENSSL = 1
|
||||
HAVE_OVERLAY = 0
|
||||
HAVE_RBMP = 1
|
||||
HAVE_RJPEG = 1
|
||||
|
||||
@@ -51,6 +51,7 @@ HAVE_VIDEO_FILTER = 1
|
||||
HAVE_STATIC_VIDEO_FILTERS = 1
|
||||
HAVE_STATIC_AUDIO_FILTERS = 1
|
||||
HAVE_FILTERS_BUILTIN = 1
|
||||
HAVE_BUILTINMBEDTLS = 0
|
||||
HAVE_C99 = 1
|
||||
HAVE_CC = 1
|
||||
HAVE_CC_RESAMPLER = 1
|
||||
|
||||
@@ -53,6 +53,7 @@ HAVE_VIDEO_FILTER = 1
|
||||
HAVE_STATIC_VIDEO_FILTERS = 1
|
||||
HAVE_STATIC_AUDIO_FILTERS = 1
|
||||
HAVE_FILTERS_BUILTIN = 1
|
||||
HAVE_BUILTINMBEDTLS = 0
|
||||
HAVE_C99 = 1
|
||||
HAVE_CC = 1
|
||||
HAVE_CC_RESAMPLER = 1
|
||||
|
||||
+1
-8
@@ -75,17 +75,10 @@ else
|
||||
HAVE_VITAGL := 0
|
||||
endif
|
||||
HAVE_NETWORKING := 1
|
||||
# the cleanroom crypto, keychain and TLS client
|
||||
HAVE_CRYPTO := 1
|
||||
HAVE_KEYCHAIN := 1
|
||||
HAVE_RETROSSL := 1
|
||||
HAVE_BUILTINMBEDTLS := 1
|
||||
HAVE_IFINFO := 1
|
||||
HAVE_NETPLAYDISCOVERY := 1
|
||||
HAVE_SOCKET_LEGACY := 1
|
||||
# the built-in NFS client (nfs://); it needs no crypto
|
||||
HAVE_RETRONFS := 1
|
||||
# the built-in SMB client (smb://), on the crypto above
|
||||
HAVE_RETROSMB := 1
|
||||
HAVE_MENU := 1
|
||||
HAVE_MENU_COMMON := 1
|
||||
HAVE_GFX_WIDGETS := 1
|
||||
|
||||
+3
-8
@@ -49,20 +49,15 @@ HAVE_AUDIOIO = 0
|
||||
HAVE_AUDIOMIXER = 1
|
||||
HAVE_BLISSBOX = 0
|
||||
HAVE_BSV_MOVIE = 1
|
||||
HAVE_BUILTINBEARSSL = 0
|
||||
HAVE_RFLAC = 1
|
||||
HAVE_DSP_FILTER = 1
|
||||
HAVE_VIDEO_FILTER = 1
|
||||
HAVE_STATIC_VIDEO_FILTERS = 1
|
||||
HAVE_STATIC_AUDIO_FILTERS = 1
|
||||
HAVE_FILTERS_BUILTIN = 1
|
||||
# The cleanroom network stack, as on the desktop builds: the crypto,
|
||||
# the encrypted keychain, the TLS 1.2/1.3 client and the SMB2/3 client
|
||||
# with Kerberos; plus the NFSv3/v4 client. Needs nothing but sockets.
|
||||
HAVE_CRYPTO = 1
|
||||
HAVE_KEYCHAIN = 1
|
||||
HAVE_RETROSSL = 1
|
||||
HAVE_RETROSMB = 1
|
||||
HAVE_RETRONFS = 1
|
||||
HAVE_BUILTINMBEDTLS = 1
|
||||
HAVE_BUILTINSMBCLIENT := 1
|
||||
HAVE_C99 = 1
|
||||
HAVE_CC = 1
|
||||
HAVE_CC_RESAMPLER = 1
|
||||
|
||||
+1
-1
@@ -179,7 +179,7 @@ INCLUDE += -I./libretro-common/include \
|
||||
-Ideps/rcheevos/include
|
||||
CFLAGS += -Wall -std=gnu99 $(MACHDEP) $(PLATCFLAGS) $(INCLUDE)
|
||||
|
||||
OBJ = $(PLATOBJS) audio/drivers/gx_audio.o gfx/drivers/gx_gfx.o gfx/display_servers/dispserv_gx.o gfx/display_servers/dispserv_gx_modes.o input/drivers/gx_input.o frontend/drivers/platform_gx.o input/drivers_joypad/gx_joypad.o libretro-common/memory/mem2_manager.o gfx/drivers/gx_gfx_vi_encoder.o frontend/drivers/platform_wii.o input/drivers_hid/wiiusb_hid.o
|
||||
OBJ = $(PLATOBJS) audio/drivers/gx_audio.o gfx/drivers/gx_gfx.o input/drivers/gx_input.o frontend/drivers/platform_gx.o input/drivers_joypad/gx_joypad.o libretro-common/memory/mem2_manager.o gfx/drivers/gx_gfx_vi_encoder.o frontend/drivers/platform_wii.o input/drivers_hid/wiiusb_hid.o
|
||||
|
||||
ifeq ($(EXTERNAL_LIBOGC), 0)
|
||||
OBJ += wii/libogc/libfat/libfat.o wii/libogc/libfat/fatfile.o wii/libogc/libfat/file_allocation_table.o wii/libogc/libfat/cache.o wii/libogc/libfat/fatdir.o wii/libogc/libfat/partition.o wii/libogc/libfat/directory.o wii/libogc/libfat/filetime.o wii/libogc/libfat/disc.o
|
||||
|
||||
@@ -161,8 +161,6 @@ endif
|
||||
HAVE_LANGEXTRA = 1
|
||||
HAVE_LIBRETRODB = 1
|
||||
HAVE_NETWORKING = 1
|
||||
# the built-in NFS client (nfs://); it needs no crypto
|
||||
HAVE_RETRONFS = 1
|
||||
HAVE_IFINFO = 1
|
||||
HAVE_NETPLAYDISCOVERY = 1
|
||||
HAVE_CHEEVOS = 1
|
||||
|
||||
@@ -1,44 +0,0 @@
|
||||
/* RetroArch - A frontend for libretro.
|
||||
* Copyright (C) 2026 - The RetroArch team
|
||||
*
|
||||
* RetroArch is free software: you can redistribute it and/or modify it under the terms
|
||||
* of the GNU General Public License as published by the Free Software Found-
|
||||
* ation, either version 3 of the License, or (at your option) any later version.
|
||||
*
|
||||
* RetroArch is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
|
||||
* PURPOSE. See the GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with RetroArch.
|
||||
* If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef __AUDIO_DEVICE_LABEL_H
|
||||
#define __AUDIO_DEVICE_LABEL_H
|
||||
|
||||
#include <stddef.h>
|
||||
#include <string.h>
|
||||
#include <retro_inline.h>
|
||||
#include <compat/strl.h>
|
||||
|
||||
/* A device list entry can be a path with the hardware's name after
|
||||
* it in brackets - "/dev/dsp0 (HDA Intel)" - which is how oss and
|
||||
* audioio list their nodes so the user can tell them apart. The menu
|
||||
* stores the entry as shown, and it comes back to init as the device
|
||||
* string, so a driver that opens the string as a path takes the path
|
||||
* off the front first: everything before the first " (". A string
|
||||
* without one is a path already and is copied as it is. Returns the
|
||||
* length of the path. */
|
||||
static INLINE size_t audio_device_label_path(char *dst, size_t len,
|
||||
const char *entry)
|
||||
{
|
||||
const char *cut = strstr(entry, " (");
|
||||
size_t n = cut ? (size_t)(cut - entry) : strlen(entry);
|
||||
if (n >= len)
|
||||
n = len - 1;
|
||||
memcpy(dst, entry, n);
|
||||
dst[n] = '\0';
|
||||
return n;
|
||||
}
|
||||
|
||||
#endif
|
||||
+209
-341
@@ -118,22 +118,6 @@ static bool audio_driver_pipeline_transport_publish(uint32_t tempo_q16,
|
||||
* out of pipe_ring per pass (one batch slice), and the producer stages
|
||||
* at most this many when it has to convert a float batch first. */
|
||||
#define AUDIO_PIPE_SLICE_INT16S AUDIO_CHUNK_SIZE_NONBLOCKING
|
||||
|
||||
/* Multichannel fold staging (audio_driver_state_t::multi_fold), in
|
||||
* frames. Every multichannel entry slices its batch to at most this
|
||||
* many frames before folding, so the region is sized once at init and
|
||||
* the fold never grows it on the batch callback. Shared by the float
|
||||
* and int16 paths; sized for float, the larger. */
|
||||
#define AUDIO_MULTI_FOLD_FRAMES (AUDIO_CHUNK_SIZE_NONBLOCKING >> 1)
|
||||
|
||||
/* Recorder remap staging (audio_driver_state_t::record_remap): two
|
||||
* halves of one batch slice at the widest frame either side can carry
|
||||
* (a multichannel core is bounded at 2 + 2 * extra pairs, a recorder
|
||||
* layout at the same), so it too is a fixed region carved at init. In
|
||||
* int16 samples per half. */
|
||||
#define AUDIO_RECORD_REMAP_CHANNELS 16
|
||||
#define AUDIO_RECORD_REMAP_INT16S ((AUDIO_CHUNK_SIZE_NONBLOCKING >> 1) \
|
||||
* AUDIO_RECORD_REMAP_CHANNELS)
|
||||
/* The canonical wide frame on a multi-channel core's ring: a slot per
|
||||
* speaker position bit, FL and FR first. */
|
||||
#define AUDIO_PIPE_CANON_CHANNELS 11
|
||||
@@ -264,6 +248,8 @@ audio_driver_t audio_null = {
|
||||
audio_driver_t *audio_drivers[] = {
|
||||
#ifdef HAVE_ALSA
|
||||
&audio_alsa,
|
||||
#if !defined(__QNX__) && !defined(MIYOO) && defined(HAVE_THREADS)
|
||||
#endif
|
||||
#endif
|
||||
#ifdef HAVE_TINYALSA
|
||||
&audio_tinyalsa,
|
||||
@@ -307,11 +293,8 @@ audio_driver_t *audio_drivers[] = {
|
||||
#ifdef HAVE_DSOUND
|
||||
&audio_dsound,
|
||||
#endif
|
||||
#ifdef HAVE_SDL
|
||||
&audio_sdl1,
|
||||
#endif
|
||||
#ifdef HAVE_SDL2
|
||||
&audio_sdl2,
|
||||
#if defined(HAVE_SDL) || defined(HAVE_SDL2)
|
||||
&audio_sdl,
|
||||
#endif
|
||||
#ifdef HAVE_SDL3
|
||||
&audio_sdl3,
|
||||
@@ -340,15 +323,9 @@ audio_driver_t *audio_drivers[] = {
|
||||
#if defined(HAVE_AUDIOWORKLET)
|
||||
&audio_audioworklet,
|
||||
#endif
|
||||
#if defined(PSP)
|
||||
#if defined(PSP) || defined(VITA) || defined(ORBIS)
|
||||
&audio_psp,
|
||||
#endif
|
||||
#if defined(VITA)
|
||||
&audio_psp2,
|
||||
#endif
|
||||
#if defined(ORBIS)
|
||||
&audio_ps4,
|
||||
#endif
|
||||
#if defined(PS2)
|
||||
&audio_ps2,
|
||||
#endif
|
||||
@@ -373,6 +350,7 @@ microphone_driver_t microphone_null = {
|
||||
NULL,
|
||||
NULL,
|
||||
NULL,
|
||||
NULL,
|
||||
"null",
|
||||
NULL,
|
||||
NULL,
|
||||
@@ -383,13 +361,12 @@ microphone_driver_t microphone_null = {
|
||||
microphone_driver_t *microphone_drivers[] = {
|
||||
#ifdef HAVE_ALSA
|
||||
µphone_alsa,
|
||||
#if !defined(__QNX__) && !defined(MIYOO) && defined(HAVE_THREADS)
|
||||
#endif
|
||||
#endif
|
||||
#ifdef HAVE_WASAPI
|
||||
µphone_wasapi,
|
||||
#endif
|
||||
#ifdef HAVE_WDMKS
|
||||
µphone_wdmks,
|
||||
#endif
|
||||
#ifdef HAVE_SDL2
|
||||
µphone_sdl, /* Microphones are not supported in SDL 1 */
|
||||
#endif
|
||||
@@ -611,11 +588,27 @@ static bool audio_driver_resampler_realloc(audio_driver_state_t *audio_st,
|
||||
audio_st->resampler_quality, audio_st->src_ratio_orig, hq_oversampling);
|
||||
audio_st->resampler_hq = initialized && hq_oversampling
|
||||
&& audio_st->src_ratio_orig >= 2.0
|
||||
&& audio_st->resampler
|
||||
&& (audio_st->resampler->caps & RESAMPLER_CAP_HQ_OVERSAMPLE);
|
||||
&& audio_st->resampler == &sinc_resampler;
|
||||
return initialized;
|
||||
}
|
||||
|
||||
/* Map the shared resampler quality enum onto the integer sinc driver's own
|
||||
* quality enum (they use different orderings). */
|
||||
static enum sinc_int16_quality audio_sinc_int16_quality_map(
|
||||
enum resampler_quality q)
|
||||
{
|
||||
switch (q)
|
||||
{
|
||||
case RESAMPLER_QUALITY_LOWEST: return SINC_INT16_QUALITY_LOWEST;
|
||||
case RESAMPLER_QUALITY_LOWER: return SINC_INT16_QUALITY_LOWER;
|
||||
case RESAMPLER_QUALITY_HIGHER: return SINC_INT16_QUALITY_HIGHER;
|
||||
case RESAMPLER_QUALITY_HIGHEST: return SINC_INT16_QUALITY_HIGHEST;
|
||||
case RESAMPLER_QUALITY_NORMAL:
|
||||
case RESAMPLER_QUALITY_DONTCARE:
|
||||
default: return SINC_INT16_QUALITY_NORMAL;
|
||||
}
|
||||
}
|
||||
|
||||
size_t audio_driver_get_underruns(void)
|
||||
{
|
||||
audio_driver_state_t *audio_st = &audio_driver_st;
|
||||
@@ -657,13 +650,21 @@ static void audio_driver_extra_free(audio_driver_state_t *audio_st)
|
||||
/* An int16 resampler instance of the kind the front pair uses. */
|
||||
static void *audio_driver_int16_resampler_new(audio_driver_state_t *audio_st)
|
||||
{
|
||||
retro_resampler_int16_t rs;
|
||||
retro_resampler_int16_new(&rs,
|
||||
(audio_st->resampler && audio_st->resampler->short_ident)
|
||||
? audio_st->resampler->short_ident : NULL,
|
||||
audio_st->resampler_quality, audio_st->src_ratio_orig,
|
||||
audio_st->resampler_hq);
|
||||
return rs.data;
|
||||
const char *rs_ident = (audio_st->resampler && audio_st->resampler->short_ident)
|
||||
? audio_st->resampler->short_ident : "";
|
||||
if (string_is_equal(rs_ident, "sinc"))
|
||||
return sinc_resampler_int16_init_hq(audio_st->src_ratio_orig,
|
||||
audio_sinc_int16_quality_map(audio_st->resampler_quality),
|
||||
audio_st->resampler_hq);
|
||||
#ifdef HAVE_NEAREST_RESAMPLER
|
||||
if (string_is_equal(rs_ident, "nearest"))
|
||||
return nearest_resampler_int16_init();
|
||||
#endif
|
||||
#ifdef HAVE_CC_RESAMPLER
|
||||
if (string_is_equal(rs_ident, "cc"))
|
||||
return cc_resampler_int16_init(audio_st->src_ratio_orig);
|
||||
#endif
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Room for a batch of extras: the buffers, and resampler instances of
|
||||
@@ -984,50 +985,12 @@ static INLINE bool audio_driver_pipe_ahead(const audio_driver_state_t *audio_st,
|
||||
return d && d <= audio_st->pipe_ring.capacity;
|
||||
}
|
||||
|
||||
/* The main thread's side of the resume mark: a new sequence, armed at
|
||||
* @at or not armed at all. The main thread is the mark's only writer. */
|
||||
static void audio_driver_pipe_fade_publish(audio_driver_state_t *audio_st,
|
||||
bool armed, size_t at)
|
||||
{
|
||||
unsigned seq = ((unsigned)retro_atomic_load_relaxed_int(
|
||||
&audio_st->pipe_fade_in_mark) >> 1) + 1;
|
||||
if (armed)
|
||||
retro_atomic_store_release_size(&audio_st->pipe_fade_in_at, at);
|
||||
retro_atomic_store_release_int(&audio_st->pipe_fade_in_mark,
|
||||
(int)((seq << 1) | (armed ? 1u : 0u)));
|
||||
}
|
||||
|
||||
/* The consumer's side: the resume point a mark still asks for, and the
|
||||
* mark's sequence to record once it is acted on. The position is taken
|
||||
* between two reads of the mark that agree, so it is that mark's. */
|
||||
static bool audio_driver_pipe_fade_pending(audio_driver_state_t *audio_st,
|
||||
size_t *at, unsigned *seq)
|
||||
{
|
||||
for (;;)
|
||||
{
|
||||
unsigned mark = (unsigned)retro_atomic_load_acquire_int(
|
||||
&audio_st->pipe_fade_in_mark);
|
||||
if (!(mark & 1u) || (mark >> 1) == audio_st->pipe_fade_in_seen)
|
||||
return false;
|
||||
*at = retro_atomic_load_acquire_size(&audio_st->pipe_fade_in_at);
|
||||
if ((unsigned)retro_atomic_load_acquire_int(
|
||||
&audio_st->pipe_fade_in_mark) == mark)
|
||||
{
|
||||
*seq = mark >> 1;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* The ring's positions restart with it; so do the marks in them. Both
|
||||
* owners are parked. */
|
||||
/* The ring's positions restart with it; so do the marks in them. */
|
||||
static void audio_driver_pipe_marks_clear(audio_driver_state_t *audio_st)
|
||||
{
|
||||
audio_st->pipe_discard_seen =
|
||||
retro_atomic_load_acquire_int(&audio_st->pipe_discard_gen);
|
||||
audio_st->pipe_fade_in_seen =
|
||||
(unsigned)retro_atomic_load_acquire_int(
|
||||
&audio_st->pipe_fade_in_mark) >> 1;
|
||||
retro_atomic_store_release_int(&audio_st->pipe_fade_in_set, 0);
|
||||
audio_st->pipe_arm_fade = false;
|
||||
}
|
||||
#endif
|
||||
@@ -1053,23 +1016,6 @@ static bool audio_driver_deinit_internal(bool audio_enable)
|
||||
if (n)
|
||||
RARCH_LOG("[Audio] Driver \"%s\": %u period%s of silence for want of audio this session.\n",
|
||||
audio_driver_get_ident(), (unsigned)n, n == 1 ? "" : "s");
|
||||
#ifdef HAVE_THREADS
|
||||
/* And the consumer's side of it: whether the pipe ever ran
|
||||
* short of the core, which is the other place silence comes
|
||||
* from, and how low it got. */
|
||||
if (audio_st->pipe_threaded
|
||||
&& retro_atomic_load_acquire_size(&audio_st->pipe_held_min1))
|
||||
{
|
||||
size_t waits = retro_atomic_load_acquire_size(&audio_st->pipe_source_waits);
|
||||
size_t low = retro_atomic_load_acquire_size(&audio_st->pipe_held_min1) - 1;
|
||||
RARCH_LOG("[Audio] Pipeline: waited for the core %u time%s after priming,"
|
||||
" %.2f ms at the longest; the pipe held %u frames (%.1f ms) at its lowest.\n",
|
||||
(unsigned)waits, waits == 1 ? "" : "s",
|
||||
(double)retro_atomic_load_acquire_size(&audio_st->pipe_source_wait_max_us) / 1000.0,
|
||||
(unsigned)low,
|
||||
audio_st->input > 0.0f ? (double)low * 1000.0 / (double)audio_st->input : 0.0);
|
||||
}
|
||||
#endif
|
||||
if (audio_st->context_audio_data)
|
||||
audio->free(audio_st->context_audio_data);
|
||||
audio_st->context_audio_data = NULL;
|
||||
@@ -1121,10 +1067,10 @@ static bool audio_driver_deinit_internal(bool audio_enable)
|
||||
audio_st->pipe_wide = NULL;
|
||||
audio_st->pipe_wide_bytes = 0;
|
||||
audio_st->pipe_channels = 2;
|
||||
/* multi_fold is a region of arena_float, freed with it below. */
|
||||
free(audio_st->multi_fold);
|
||||
audio_st->multi_fold = NULL;
|
||||
audio_st->multi_fold_frames = 0;
|
||||
/* record_remap is a region of arena_int16, freed with it above. */
|
||||
free(audio_st->record_remap);
|
||||
audio_st->record_remap = NULL;
|
||||
audio_st->record_remap_frames = 0;
|
||||
audio_st->core_layout = AUDIO_LAYOUT_STEREO;
|
||||
@@ -3458,14 +3404,18 @@ static void audio_driver_flush(audio_driver_state_t *audio_st,
|
||||
* batch. The resampler sees identical bytes either way, so output is
|
||||
* bit-exact.
|
||||
*
|
||||
* Nothing downstream requires the pointer to be aligned: every
|
||||
* resampler reads data_in scalar-wise or with unaligned loads, and so
|
||||
* does the bypass clamp. */
|
||||
* The pointer must still be suitably aligned: the CC resampler's ARM
|
||||
* NEON assembly loads its input with an explicit alignment hint
|
||||
* (`vld1.f32 d16, [r1, :64]!` in cc_resampler_neon.S), which faults on
|
||||
* an under-aligned address. input_data is memalign_alloc(64); a
|
||||
* core-owned buffer carries no such guarantee, so fall back to the copy
|
||||
* unless the pointer is at least 16-byte aligned. */
|
||||
{
|
||||
bool synth_on = midi_driver_synth_active() && audio_st->synth_buf;
|
||||
bool copy_input = !is_float
|
||||
|| (audio_volume_gain != 1.0f)
|
||||
|| synth_on
|
||||
|| (((uintptr_t)data & 0xf) != 0)
|
||||
#ifdef HAVE_DSP_FILTER
|
||||
|| (audio_st->dsp != NULL)
|
||||
#endif
|
||||
@@ -4104,13 +4054,10 @@ bool audio_driver_init_internal(void *settings_data, bool audio_cb_inited)
|
||||
* in addition to its own. */
|
||||
size_t i16_rewind = AUDIO_ARENA_NEXT(i16_in_scratch,
|
||||
max_buffer_samples, AUDIO_ARENA_ALIGN_INT16);
|
||||
size_t i16_record = AUDIO_ARENA_NEXT(i16_rewind,
|
||||
max_buffer_samples, AUDIO_ARENA_ALIGN_INT16);
|
||||
size_t i16_total = i16_rewind + max_buffer_samples;
|
||||
#else
|
||||
size_t i16_record = AUDIO_ARENA_NEXT(i16_in_scratch,
|
||||
max_buffer_samples, AUDIO_ARENA_ALIGN_INT16);
|
||||
size_t i16_total = i16_in_scratch + max_buffer_samples;
|
||||
#endif
|
||||
size_t i16_total = i16_record + 2 * AUDIO_RECORD_REMAP_INT16S;
|
||||
size_t f32_input = 0;
|
||||
size_t f32_synth = AUDIO_ARENA_NEXT(f32_input,
|
||||
max_buffer_samples, AUDIO_ARENA_ALIGN_FLOAT);
|
||||
@@ -4119,13 +4066,10 @@ bool audio_driver_init_internal(void *settings_data, bool audio_cb_inited)
|
||||
#ifdef HAVE_REWIND
|
||||
size_t f32_rewind = AUDIO_ARENA_NEXT(f32_out,
|
||||
outsamples_max, AUDIO_ARENA_ALIGN_FLOAT);
|
||||
size_t f32_fold = AUDIO_ARENA_NEXT(f32_rewind,
|
||||
max_buffer_samples, AUDIO_ARENA_ALIGN_FLOAT);
|
||||
size_t f32_total = f32_rewind + max_buffer_samples;
|
||||
#else
|
||||
size_t f32_fold = AUDIO_ARENA_NEXT(f32_out,
|
||||
outsamples_max, AUDIO_ARENA_ALIGN_FLOAT);
|
||||
size_t f32_total = f32_out + outsamples_max;
|
||||
#endif
|
||||
size_t f32_total = f32_fold + AUDIO_MULTI_FOLD_FRAMES * 2;
|
||||
int16_t *arena_int16 = (int16_t*)memalign_alloc(64,
|
||||
i16_total * sizeof(int16_t));
|
||||
float *arena_float = (float*)memalign_alloc(64,
|
||||
@@ -4186,10 +4130,6 @@ bool audio_driver_init_internal(void *settings_data, bool audio_cb_inited)
|
||||
audio_driver_st.rewind_buf_f = arena_float + f32_rewind;
|
||||
#endif
|
||||
audio_driver_st.output_samples_buf_length = outsamples_max * sizeof(float);
|
||||
audio_driver_st.multi_fold = arena_float + f32_fold;
|
||||
audio_driver_st.multi_fold_frames = AUDIO_MULTI_FOLD_FRAMES;
|
||||
audio_driver_st.record_remap = arena_int16 + i16_record;
|
||||
audio_driver_st.record_remap_frames = AUDIO_RECORD_REMAP_INT16S;
|
||||
|
||||
if (!audio_enable)
|
||||
{
|
||||
@@ -4289,9 +4229,6 @@ bool audio_driver_init_internal(void *settings_data, bool audio_cb_inited)
|
||||
retro_atomic_store_release_int(&audio_driver_st.pipe_ctrl_avail, -1);
|
||||
audio_driver_st.pipe_underruns_seen = 0;
|
||||
audio_driver_st.pipe_priming = true;
|
||||
retro_atomic_size_init(&audio_driver_st.pipe_source_waits, 0);
|
||||
retro_atomic_size_init(&audio_driver_st.pipe_source_wait_max_us, 0);
|
||||
retro_atomic_size_init(&audio_driver_st.pipe_held_min1, 0);
|
||||
if (audio_driver_st.pipe_channels > 2)
|
||||
{
|
||||
size_t wide = audio_driver_st.pipe_pass_frames * AUDIO_PIPE_CANON_CHANNELS * sizeof(float);
|
||||
@@ -4331,7 +4268,7 @@ bool audio_driver_init_internal(void *settings_data, bool audio_cb_inited)
|
||||
audio_driver_st.pipe_park_ready = true;
|
||||
AUDIO_FLAGS_SET(&audio_driver_st, AUDIO_FLAG_PIPELINE_THREADED);
|
||||
audio_driver_st.pipe_threaded = true;
|
||||
retro_atomic_int_init(&audio_driver_st.pipe_consumer_gone, 0);
|
||||
audio_driver_st.pipe_consumer_gone = false;
|
||||
RARCH_LOG("[Audio] Threaded pipeline: ring holds %u frames of %s core audio.\n",
|
||||
(unsigned)(audio_driver_st.pipe_ring.capacity / audio_driver_st.pipe_frame_bytes),
|
||||
audio_driver_st.pipe_float ? "float" : "int16");
|
||||
@@ -4531,14 +4468,35 @@ bool audio_driver_init_internal(void *settings_data, bool audio_cb_inited)
|
||||
&& audio_driver_st.resampler->short_ident)
|
||||
{
|
||||
const char *rs_ident = audio_driver_st.resampler->short_ident;
|
||||
retro_resampler_int16_t rs;
|
||||
retro_resampler_int16_new(&rs, rs_ident,
|
||||
audio_driver_st.resampler_quality,
|
||||
audio_driver_st.src_ratio_orig, audio_driver_st.resampler_hq);
|
||||
audio_driver_st.resampler_data_int16 = rs.data;
|
||||
audio_driver_st.resampler_int16_process = rs.process;
|
||||
audio_driver_st.resampler_int16_free = rs.free;
|
||||
audio_driver_st.resampler_int16_reset = rs.reset;
|
||||
if (string_is_equal(rs_ident, "sinc"))
|
||||
{
|
||||
audio_driver_st.resampler_data_int16 = sinc_resampler_int16_init_hq(
|
||||
audio_driver_st.src_ratio_orig,
|
||||
audio_sinc_int16_quality_map(audio_driver_st.resampler_quality),
|
||||
audio_driver_st.resampler_hq);
|
||||
audio_driver_st.resampler_int16_process = sinc_resampler_int16_process;
|
||||
audio_driver_st.resampler_int16_free = sinc_resampler_int16_free;
|
||||
audio_driver_st.resampler_int16_reset = sinc_resampler_int16_reset;
|
||||
}
|
||||
#ifdef HAVE_NEAREST_RESAMPLER
|
||||
else if (string_is_equal(rs_ident, "nearest"))
|
||||
{
|
||||
audio_driver_st.resampler_data_int16 = nearest_resampler_int16_init();
|
||||
audio_driver_st.resampler_int16_process = nearest_resampler_int16_process;
|
||||
audio_driver_st.resampler_int16_free = nearest_resampler_int16_free;
|
||||
audio_driver_st.resampler_int16_reset = nearest_resampler_int16_reset;
|
||||
}
|
||||
#endif
|
||||
#ifdef HAVE_CC_RESAMPLER
|
||||
else if (string_is_equal(rs_ident, "cc"))
|
||||
{
|
||||
audio_driver_st.resampler_data_int16 = cc_resampler_int16_init(
|
||||
audio_driver_st.src_ratio_orig);
|
||||
audio_driver_st.resampler_int16_process = cc_resampler_int16_process;
|
||||
audio_driver_st.resampler_int16_free = cc_resampler_int16_free;
|
||||
audio_driver_st.resampler_int16_reset = cc_resampler_int16_reset;
|
||||
}
|
||||
#endif
|
||||
if (audio_driver_st.resampler_int16_process)
|
||||
RARCH_LOG("[Audio] %s resampler: integer s16 path %s.\n",
|
||||
rs_ident,
|
||||
@@ -4604,7 +4562,18 @@ bool audio_driver_init_internal(void *settings_data, bool audio_cb_inited)
|
||||
? (double)audio_driver_st.buffer_size / frame_bytes
|
||||
* 1000.0 / out_rate
|
||||
: 0.0;
|
||||
const char *ident = audio_driver_get_ident();
|
||||
const char *ident = audio_driver_st.current_audio->ident;
|
||||
#ifdef HAVE_THREADS
|
||||
/* Name the driver the user chose, not the wrapper it runs
|
||||
* under. */
|
||||
if (string_is_equal(ident, "audio-thread"))
|
||||
{
|
||||
const audio_driver_t *inner = audio_thread_wrapped_driver(
|
||||
audio_driver_st.context_audio_data);
|
||||
if (inner)
|
||||
ident = inner->ident;
|
||||
}
|
||||
#endif
|
||||
RARCH_LOG("[Audio] Driver \"%s\" reports a %u-byte buffer: "
|
||||
"%.1f ms of %u-channel %s at %u Hz against a %u ms latency setting%s; "
|
||||
"rate control %s it near %.1f ms.\n",
|
||||
@@ -4642,8 +4611,7 @@ bool audio_driver_init_internal(void *settings_data, bool audio_cb_inited)
|
||||
* SIGFPE" workaround for the same hazard. Check it once, centrally,
|
||||
* and fall back to no rate control rather than making every driver
|
||||
* defend itself. */
|
||||
if ( !audio_driver_st.current_audio->write_avail
|
||||
|| !audio_driver_st.current_audio->buffer_size)
|
||||
if (!audio_driver_st.current_audio->buffer_size)
|
||||
RARCH_WARN("[Audio] Rate control was desired, but driver does not support needed features.\n");
|
||||
else if (audio_driver_st.buffer_size == 0)
|
||||
RARCH_WARN("[Audio] Rate control was desired, but the driver "
|
||||
@@ -4700,8 +4668,7 @@ error:
|
||||
|
||||
void audio_driver_pipeline_consumer_exit(void)
|
||||
{
|
||||
/* Release: the consumer's loop is done before the word that says so. */
|
||||
retro_atomic_store_release_int(&audio_driver_st.pipe_consumer_gone, 1);
|
||||
audio_driver_st.pipe_consumer_gone = true;
|
||||
}
|
||||
|
||||
void audio_driver_publish_runloop(void)
|
||||
@@ -5053,7 +5020,7 @@ void audio_driver_pause_fade(bool paused)
|
||||
retro_atomic_store_release_size(&audio_st->pipe_discard_to,
|
||||
retro_atomic_load_acquire_size(&audio_st->pipe_ring.head));
|
||||
retro_atomic_fetch_add_int(&audio_st->pipe_discard_gen, 1);
|
||||
audio_driver_pipe_fade_publish(audio_st, false, 0);
|
||||
retro_atomic_store_release_int(&audio_st->pipe_fade_in_set, 0);
|
||||
#endif
|
||||
|
||||
/* Only when something was playing is there a step to smooth. */
|
||||
@@ -5296,19 +5263,9 @@ static size_t audio_driver_pipe_target_frames(audio_driver_state_t *audio_st)
|
||||
}
|
||||
/* Never under one publish: the core delivers a frame at a time, and
|
||||
* a pipe holding less than that between publishes is a device that
|
||||
* runs dry between them whatever its own buffer holds.
|
||||
*
|
||||
* And where nothing regulates the fill - no rate control - one
|
||||
* publish is no margin at all: the pipe holds a frame just after
|
||||
* the core hands one over and nothing just before the next, so a
|
||||
* frame late by any amount reaches the device as silence. Two
|
||||
* publishes there, so a frame's worth is held at the worst phase,
|
||||
* which is what a late frame costs. */
|
||||
* runs dry between them whatever its own buffer holds. */
|
||||
if (target < audio_st->pipe_pass_frames)
|
||||
target = audio_st->pipe_pass_frames;
|
||||
if ( !(AUDIO_FLAGS_GET(audio_st) & AUDIO_FLAG_CONTROL)
|
||||
&& target < 2 * audio_st->pipe_pass_frames)
|
||||
target = 2 * audio_st->pipe_pass_frames;
|
||||
/* Within the ring less two publishes: one arriving, one of swing
|
||||
* in the fill between the core's publish and the consumer's pass. */
|
||||
ring_max = audio_st->pipe_ring.capacity / audio_st->pipe_frame_bytes;
|
||||
@@ -5318,29 +5275,6 @@ static size_t audio_driver_pipe_target_frames(audio_driver_state_t *audio_st)
|
||||
ring_max = 0;
|
||||
return target < ring_max ? target : ring_max;
|
||||
}
|
||||
|
||||
/* What priming fills the pipe to: the target and the device's buffer
|
||||
* on top, since the device starts empty and the pipe is to hold a
|
||||
* buffer's worth ahead of it. The same figure is the line above which
|
||||
* audio is late after an underrun: trimming to the target alone threw
|
||||
* the device's share away on the first pass after every prime, and the
|
||||
* pipe then ran a device buffer short of what it had just been filled
|
||||
* to. Zero when there is no target. */
|
||||
static size_t audio_driver_pipe_prime_frames(audio_driver_state_t *audio_st)
|
||||
{
|
||||
size_t target = audio_driver_pipe_target_frames(audio_st);
|
||||
size_t frame_bytes, device, room, limit;
|
||||
if (!target)
|
||||
return 0;
|
||||
frame_bytes = audio_driver_dev_frame_bytes(audio_st);
|
||||
device = (size_t)((double)audio_st->buffer_size / frame_bytes
|
||||
/ audio_st->src_ratio_orig);
|
||||
room = audio_st->pipe_ring.capacity / audio_st->pipe_frame_bytes;
|
||||
limit = room > audio_st->pipe_pass_frames
|
||||
? room - audio_st->pipe_pass_frames : 1;
|
||||
target += device;
|
||||
return target > limit ? limit : target;
|
||||
}
|
||||
#endif
|
||||
|
||||
/* Input and output do not overlap. Convert only the two populated slots. */
|
||||
@@ -5612,8 +5546,9 @@ static void audio_driver_submit_width(audio_driver_state_t *audio_st,
|
||||
* whatever the consumer flushes next: mark where that starts. */
|
||||
if (from_core && audio_st->fade_in_pending)
|
||||
{
|
||||
audio_driver_pipe_fade_publish(audio_st, true,
|
||||
retro_atomic_store_release_size(&audio_st->pipe_fade_in_at,
|
||||
retro_atomic_load_relaxed_size(&audio_st->pipe_ring.head));
|
||||
retro_atomic_store_release_int(&audio_st->pipe_fade_in_set, 1);
|
||||
audio_st->fade_in_pending = false;
|
||||
}
|
||||
|
||||
@@ -5652,8 +5587,7 @@ static void audio_driver_submit_width(audio_driver_state_t *audio_st,
|
||||
: (AUDIO_FLAGS_GET(audio_st) & AUDIO_FLAG_NONBLOCK) != 0)
|
||||
break;
|
||||
if ( !(AUDIO_FLAGS_GET(audio_st) & AUDIO_FLAG_STARTED)
|
||||
|| retro_atomic_load_acquire_int(
|
||||
&audio_st->pipe_consumer_gone))
|
||||
|| audio_st->pipe_consumer_gone)
|
||||
break;
|
||||
/* Sleep until the consumer has completed a pass. The
|
||||
* generation is taken first, then the wait window is opened
|
||||
@@ -6141,27 +6075,6 @@ bool audio_driver_pipeline_transport_step(struct audio_pipeline_stretch *stage,
|
||||
output_budget, finishing, complete, &progress);
|
||||
}
|
||||
|
||||
/* The consumer's account of the core, kept for the teardown line:
|
||||
* a pass that starts with the pipe at its lowest so far, and a wait
|
||||
* for source that ran after priming, with how long it took. */
|
||||
static void audio_driver_pipe_note_held(audio_driver_state_t *st, size_t held)
|
||||
{
|
||||
size_t cur = retro_atomic_load_relaxed_size(&st->pipe_held_min1);
|
||||
if (!cur || held + 1 < cur)
|
||||
retro_atomic_store_release_size(&st->pipe_held_min1, held + 1);
|
||||
}
|
||||
|
||||
static void audio_driver_pipe_note_wait(audio_driver_state_t *st,
|
||||
retro_time_t began)
|
||||
{
|
||||
retro_time_t took = cpu_features_get_time_usec() - began;
|
||||
retro_atomic_fetch_add_size(&st->pipe_source_waits, 1);
|
||||
if (took > 0 && (size_t)took
|
||||
> retro_atomic_load_relaxed_size(&st->pipe_source_wait_max_us))
|
||||
retro_atomic_store_release_size(&st->pipe_source_wait_max_us,
|
||||
(size_t)took);
|
||||
}
|
||||
|
||||
static bool audio_driver_transport_wait(audio_driver_state_t *st, size_t need,
|
||||
unsigned metadata_threshold)
|
||||
{
|
||||
@@ -6190,13 +6103,8 @@ static bool audio_driver_transport_wait(audio_driver_state_t *st, size_t need,
|
||||
|| retro_atomic_load_acquire_int(&st->pipe_wake))
|
||||
retro_eventcount_cancel_wait(&st->pipe_data);
|
||||
else
|
||||
{
|
||||
retro_time_t began = st->pipe_priming ? 0 : cpu_features_get_time_usec();
|
||||
retro_eventcount_commit_wait_timeout(&st->pipe_data, key,
|
||||
AUDIO_PIPE_WAIT_MAX_US);
|
||||
if (began)
|
||||
audio_driver_pipe_note_wait(st, began);
|
||||
}
|
||||
if (retro_atomic_load_acquire_int(&st->pipe_wake))
|
||||
{
|
||||
retro_atomic_store_release_int(&st->pipe_wake, 0);
|
||||
@@ -6229,28 +6137,26 @@ static void audio_driver_transport_consume(audio_driver_state_t *st)
|
||||
size_t seen = audio->underruns(st->context_audio_data);
|
||||
if (seen != st->pipe_underruns_seen)
|
||||
{
|
||||
size_t target = audio_driver_pipe_prime_frames(st);
|
||||
size_t target = audio_driver_pipe_target_frames(st);
|
||||
if (!audio_driver_pipeline_transport_discard(held > target ? held - target : 0))
|
||||
return;
|
||||
st->pipe_underruns_seen = seen;
|
||||
held = retro_spsc_read_avail(&st->pipe_ring) / st->pipe_frame_bytes;
|
||||
/* Silence with the pipe short is the core having been late,
|
||||
* and the cushion the pipe held ahead of the device is what
|
||||
* the stall burned. Nothing rebuilds it on this path - no
|
||||
* rate control, and a core paced to the display never runs
|
||||
* ahead - so from here every ordinary bit of jitter would be
|
||||
* a hole. Prime again: the same wait the first pass made,
|
||||
* while the device is silent anyway, so the next stall meets
|
||||
* a full cushion. */
|
||||
if (held < target)
|
||||
st->pipe_priming = true;
|
||||
}
|
||||
}
|
||||
if (st->pipe_priming)
|
||||
{
|
||||
size_t need = audio_driver_pipe_prime_frames(st);
|
||||
if (!need)
|
||||
need = 1;
|
||||
size_t target = audio_driver_pipe_target_frames(st);
|
||||
size_t need = 1;
|
||||
if (target)
|
||||
{
|
||||
size_t room = st->pipe_ring.capacity / st->pipe_frame_bytes;
|
||||
size_t device = (size_t)((double)st->buffer_size
|
||||
/ audio_driver_dev_frame_bytes(st) / st->src_ratio_orig);
|
||||
size_t limit = room > st->pipe_pass_frames ? room - st->pipe_pass_frames : 1;
|
||||
need = target + device;
|
||||
if (need > limit) need = limit;
|
||||
}
|
||||
if (!audio_driver_transport_wait(st, need * st->pipe_frame_bytes,
|
||||
AUDIO_PIPELINE_LAYOUT_CAPACITY))
|
||||
return;
|
||||
@@ -6260,8 +6166,6 @@ static void audio_driver_transport_consume(audio_driver_state_t *st)
|
||||
&& audio_pipeline_stretch_needs_input(st->pipe_transport)
|
||||
&& !audio_driver_transport_wait(st, st->pipe_frame_bytes, 1))
|
||||
return;
|
||||
if (!st->pipe_priming)
|
||||
audio_driver_pipe_note_held(st, held);
|
||||
/* What a pause left in the ring is stale behind its tail, and so is
|
||||
* what the stage holds: taken out unplayed, up to where the pause was
|
||||
* published. */
|
||||
@@ -6287,23 +6191,20 @@ static void audio_driver_transport_consume(audio_driver_state_t *st)
|
||||
/* Never across the point the core's audio resumes at: there the
|
||||
* stage drops the silence it holds and the ramp is armed for what
|
||||
* it renders next. */
|
||||
if (retro_atomic_load_acquire_int(&st->pipe_fade_in_set))
|
||||
{
|
||||
size_t at;
|
||||
unsigned seq;
|
||||
if (audio_driver_pipe_fade_pending(st, &at, &seq))
|
||||
size_t at = retro_atomic_load_acquire_size(&st->pipe_fade_in_at);
|
||||
if (audio_driver_pipe_ahead(st, tail, at))
|
||||
{
|
||||
if (audio_driver_pipe_ahead(st, tail, at))
|
||||
{
|
||||
size_t upto = (at - tail) / st->pipe_frame_bytes;
|
||||
if (input_budget > upto)
|
||||
input_budget = upto;
|
||||
}
|
||||
else
|
||||
{
|
||||
audio_driver_transport_discard(0, false);
|
||||
st->pipe_arm_fade = true;
|
||||
st->pipe_fade_in_seen = seq;
|
||||
}
|
||||
size_t upto = (at - tail) / st->pipe_frame_bytes;
|
||||
if (input_budget > upto)
|
||||
input_budget = upto;
|
||||
}
|
||||
else
|
||||
{
|
||||
audio_driver_transport_discard(0, false);
|
||||
st->pipe_arm_fade = true;
|
||||
retro_atomic_store_release_int(&st->pipe_fade_in_set, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6370,9 +6271,18 @@ static void audio_driver_pipeline_consume(audio_driver_state_t *audio_st)
|
||||
size_t need = audio_st->pipe_frame_bytes;
|
||||
if (audio_st->pipe_priming)
|
||||
{
|
||||
size_t prime = audio_driver_pipe_prime_frames(audio_st);
|
||||
if (prime)
|
||||
need = prime * audio_st->pipe_frame_bytes;
|
||||
size_t target = audio_driver_pipe_target_frames(audio_st);
|
||||
if (target)
|
||||
{
|
||||
size_t frame_bytes = audio_driver_dev_frame_bytes(audio_st);
|
||||
size_t device = (size_t)((double)audio_st->buffer_size / frame_bytes
|
||||
/ audio_st->src_ratio_orig);
|
||||
size_t room = audio_st->pipe_ring.capacity / audio_st->pipe_frame_bytes;
|
||||
target += device;
|
||||
if (target > room - audio_st->pipe_pass_frames)
|
||||
target = room - audio_st->pipe_pass_frames;
|
||||
need = target * audio_st->pipe_frame_bytes;
|
||||
}
|
||||
}
|
||||
while (retro_spsc_read_avail(&audio_st->pipe_ring) < need)
|
||||
{
|
||||
@@ -6409,25 +6319,15 @@ static void audio_driver_pipeline_consume(audio_driver_state_t *audio_st)
|
||||
retro_eventcount_cancel_wait(&audio_st->pipe_data);
|
||||
continue;
|
||||
}
|
||||
if (!retro_eventcount_commit_wait_timeout(&audio_st->pipe_data, key,
|
||||
AUDIO_PIPE_WAIT_MAX_US))
|
||||
{
|
||||
retro_time_t began = audio_st->pipe_priming
|
||||
? 0 : cpu_features_get_time_usec();
|
||||
bool woken = retro_eventcount_commit_wait_timeout(&audio_st->pipe_data,
|
||||
key, AUDIO_PIPE_WAIT_MAX_US);
|
||||
if (began)
|
||||
audio_driver_pipe_note_wait(audio_st, began);
|
||||
if (!woken)
|
||||
{
|
||||
/* Nothing came within the bound. A producer that has
|
||||
* stopped is the wrapper's business, not this pass's. */
|
||||
if (retro_atomic_load_acquire_int(&audio_st->pipe_data_gen) == gen)
|
||||
return;
|
||||
}
|
||||
/* Nothing came within the bound. A producer that has stopped
|
||||
* is the wrapper's business, not this pass's. */
|
||||
if (retro_atomic_load_acquire_int(&audio_st->pipe_data_gen) == gen)
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (!audio_st->pipe_priming)
|
||||
audio_driver_pipe_note_held(audio_st,
|
||||
retro_spsc_read_avail(&audio_st->pipe_ring) / audio_st->pipe_frame_bytes);
|
||||
audio_st->pipe_priming = false;
|
||||
}
|
||||
|
||||
@@ -6499,7 +6399,7 @@ static void audio_driver_pipeline_consume(audio_driver_state_t *audio_st)
|
||||
size_t seen = audio->underruns(audio_st->context_audio_data);
|
||||
if (seen != audio_st->pipe_underruns_seen)
|
||||
{
|
||||
size_t target = audio_driver_pipe_prime_frames(audio_st) * audio_st->pipe_frame_bytes;
|
||||
size_t target = audio_driver_pipe_target_frames(audio_st) * audio_st->pipe_frame_bytes;
|
||||
size_t held = retro_spsc_read_avail(&audio_st->pipe_ring);
|
||||
bool discarded = false;
|
||||
audio_st->pipe_underruns_seen = seen;
|
||||
@@ -6521,17 +6421,6 @@ static void audio_driver_pipeline_consume(audio_driver_state_t *audio_st)
|
||||
audio_driver_reset_resamplers(audio_st);
|
||||
audio_driver_state_unlock();
|
||||
}
|
||||
/* Silence with the pipe short is the core having been late,
|
||||
* and what the stall burned is the cushion the pipe held
|
||||
* ahead of the device; nothing on this path rebuilds it.
|
||||
* Prime again on the next pass - the wait the first pass
|
||||
* made, while the device is silent anyway - so the next
|
||||
* stall meets a full cushion rather than an empty pipe. */
|
||||
if (held < target)
|
||||
{
|
||||
audio_st->pipe_priming = true;
|
||||
return;
|
||||
}
|
||||
if (have > held / audio_st->pipe_frame_bytes)
|
||||
have = held / audio_st->pipe_frame_bytes;
|
||||
}
|
||||
@@ -6577,22 +6466,19 @@ static void audio_driver_pipeline_consume(audio_driver_state_t *audio_st)
|
||||
}
|
||||
/* Never across the point the core's audio resumes at: the ramp is
|
||||
* armed for the chunk that starts there. */
|
||||
if (retro_atomic_load_acquire_int(&audio_st->pipe_fade_in_set))
|
||||
{
|
||||
size_t at;
|
||||
unsigned seq;
|
||||
if (audio_driver_pipe_fade_pending(audio_st, &at, &seq))
|
||||
size_t at = retro_atomic_load_acquire_size(&audio_st->pipe_fade_in_at);
|
||||
if (audio_driver_pipe_ahead(audio_st, tail, at))
|
||||
{
|
||||
if (audio_driver_pipe_ahead(audio_st, tail, at))
|
||||
{
|
||||
size_t upto = (at - tail) / audio_st->pipe_frame_bytes;
|
||||
if (have > upto)
|
||||
have = upto;
|
||||
}
|
||||
else
|
||||
{
|
||||
audio_st->pipe_arm_fade = true;
|
||||
audio_st->pipe_fade_in_seen = seq;
|
||||
}
|
||||
size_t upto = (at - tail) / audio_st->pipe_frame_bytes;
|
||||
if (have > upto)
|
||||
have = upto;
|
||||
}
|
||||
else
|
||||
{
|
||||
audio_st->pipe_arm_fade = true;
|
||||
retro_atomic_store_release_int(&audio_st->pipe_fade_in_set, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6665,11 +6551,13 @@ static void audio_driver_record_push(audio_driver_state_t *audio_st,
|
||||
capacity = frames > (AUDIO_CHUNK_SIZE_NONBLOCKING >> 1)
|
||||
? (AUDIO_CHUNK_SIZE_NONBLOCKING >> 1) : frames;
|
||||
need = capacity * (channels > rchannels ? channels : rchannels);
|
||||
/* The region is carved out of arena_int16 at init for
|
||||
* AUDIO_RECORD_REMAP_CHANNELS per frame; wider than that is not a
|
||||
* layout either side can declare, so this is a check, not a grow. */
|
||||
if (!audio_st->record_remap || need > audio_st->record_remap_frames)
|
||||
return;
|
||||
if (need > audio_st->record_remap_frames)
|
||||
{
|
||||
int16_t *nb = (int16_t*)realloc(audio_st->record_remap, need * 2 * sizeof(int16_t));
|
||||
if (!nb) return;
|
||||
audio_st->record_remap = nb;
|
||||
audio_st->record_remap_frames = need;
|
||||
}
|
||||
while (frames)
|
||||
{
|
||||
size_t n = frames > capacity ? capacity : frames;
|
||||
@@ -6845,11 +6733,18 @@ size_t audio_driver_sample_batch(const int16_t *data, size_t frames)
|
||||
static bool audio_driver_multi_fold_room(audio_driver_state_t *audio_st,
|
||||
size_t frames, size_t sample)
|
||||
{
|
||||
/* The region is carved out of arena_float by
|
||||
* audio_driver_init_internal() at AUDIO_MULTI_FOLD_FRAMES frames;
|
||||
* callers slice to that bound, so this is a check, never a grow. */
|
||||
if (frames > audio_st->multi_fold_frames)
|
||||
{
|
||||
/* float and int16 batches share the buffer: sized for float,
|
||||
* the larger */
|
||||
void *n = realloc(audio_st->multi_fold, frames * 2 * sizeof(float));
|
||||
if (!n)
|
||||
return false;
|
||||
audio_st->multi_fold = n;
|
||||
audio_st->multi_fold_frames = frames;
|
||||
}
|
||||
(void)sample;
|
||||
return audio_st->multi_fold && frames <= audio_st->multi_fold_frames;
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool audio_driver_multi_layout_ok(unsigned channels, unsigned layout)
|
||||
@@ -7871,7 +7766,6 @@ bool audio_driver_mixer_add_stream(audio_mixer_stream_params_t *params)
|
||||
if (audio_driver_mixer_use_s16(audio_driver_st.stat_core_is_float))
|
||||
voice = audio_mixer_play_s16(handle, looped,
|
||||
GAIN_TO_Q16(params->volume),
|
||||
audio_driver_st.resampler_ident,
|
||||
audio_driver_st.resampler_quality, stop_cb);
|
||||
else
|
||||
voice = audio_mixer_play(handle, looped, params->volume,
|
||||
@@ -7962,7 +7856,6 @@ static void audio_driver_mixer_play_stream_internal(
|
||||
audio_mixer_play_s16(audio_driver_st.mixer_streams[i].handle,
|
||||
(type == AUDIO_STREAM_STATE_PLAYING_LOOPED) ? true : false,
|
||||
AUDIO_MIXER_GAIN_UNITY,
|
||||
audio_driver_st.resampler_ident,
|
||||
audio_driver_st.resampler_quality,
|
||||
audio_driver_st.mixer_streams[i].stop_cb);
|
||||
else
|
||||
@@ -8408,7 +8301,7 @@ bool audio_driver_start(bool is_shutdown)
|
||||
}
|
||||
|
||||
RARCH_DBG("[Audio] Started audio driver \"%s\" (is_shutdown=%s)\n",
|
||||
audio_driver_get_ident(), is_shutdown ? "true" : "false");
|
||||
audio->ident, is_shutdown ? "true" : "false");
|
||||
|
||||
return true;
|
||||
|
||||
@@ -8842,8 +8735,7 @@ bool audio_driver_stop(void)
|
||||
audio_driver_state_unlock();
|
||||
}
|
||||
AUDIO_FLAGS_CLEAR(audio_st, AUDIO_FLAG_STARTED);
|
||||
RARCH_DBG("[Audio] Stopped audio driver \"%s\".\n",
|
||||
audio_driver_get_ident());
|
||||
RARCH_DBG("[Audio] Stopped audio driver \"%s\".\n", audio->ident);
|
||||
}
|
||||
|
||||
return stopped;
|
||||
@@ -9012,7 +8904,7 @@ bool audio_compute_buffer_statistics(audio_statistics_t *stats)
|
||||
}
|
||||
|
||||
#ifdef HAVE_MENU
|
||||
void audio_driver_menu_sample(bool by_clock)
|
||||
void audio_driver_menu_sample(void)
|
||||
{
|
||||
static int16_t samples_buf[1024] = {0};
|
||||
settings_t *settings = config_get_ptr();
|
||||
@@ -9038,40 +8930,10 @@ void audio_driver_menu_sample(bool by_clock)
|
||||
|| info->sample_rate <= 0.0 || info->fps <= 0.0)
|
||||
return;
|
||||
frames = floor(info->sample_rate / info->fps);
|
||||
if (by_clock)
|
||||
{
|
||||
/* What has played since the last feed, carrying the part of a
|
||||
* frame that did not make a whole one. The first feed is one
|
||||
* content frame; a gap longer than a tenth of a second is a
|
||||
* stall or a return to the menu, not time to make up. */
|
||||
retro_time_t now = cpu_features_get_time_usec();
|
||||
if (audio_st->menu_feed_last)
|
||||
{
|
||||
retro_time_t elapsed = now - audio_st->menu_feed_last;
|
||||
double owed;
|
||||
if (elapsed < 0)
|
||||
elapsed = 0;
|
||||
else if (elapsed > 100000)
|
||||
elapsed = 100000;
|
||||
owed = info->sample_rate
|
||||
* (double)elapsed / 1000000.0 + audio_st->menu_feed_frac;
|
||||
frames = floor(owed);
|
||||
audio_st->menu_feed_frac = owed - frames;
|
||||
}
|
||||
audio_st->menu_feed_last = now;
|
||||
}
|
||||
else
|
||||
{
|
||||
audio_st->menu_feed_last = 0;
|
||||
audio_st->menu_feed_frac = 0.0;
|
||||
}
|
||||
/* Validate before conversion and the stereo sample-count multiply. */
|
||||
if (!(frames >= 0.0 && frames <= UINT_MAX / 2))
|
||||
return;
|
||||
sample_count = (unsigned)frames * 2;
|
||||
/* Less than a frame has played since the last feed */
|
||||
if (by_clock && !sample_count)
|
||||
return;
|
||||
|
||||
if ((AUDIO_FLAGS_GET(audio_st) & AUDIO_FLAG_SUSPENDED))
|
||||
check_flush = false;
|
||||
@@ -9560,12 +9422,32 @@ static bool mic_driver_open_mic_internal(retro_microphone_t* microphone)
|
||||
&& microphone->resampler
|
||||
&& microphone->resampler->short_ident)
|
||||
{
|
||||
retro_resampler_int16_t rs;
|
||||
retro_resampler_int16_new(&rs, microphone->resampler->short_ident,
|
||||
mic_st->resampler_quality, microphone->orig_ratio, false);
|
||||
microphone->resampler_data_int16 = rs.data;
|
||||
microphone->resampler_int16_process = rs.process;
|
||||
microphone->resampler_int16_free = rs.free;
|
||||
const char *rs_ident = microphone->resampler->short_ident;
|
||||
if (string_is_equal(rs_ident, "sinc"))
|
||||
{
|
||||
microphone->resampler_data_int16 = sinc_resampler_int16_init(
|
||||
microphone->orig_ratio,
|
||||
audio_sinc_int16_quality_map(mic_st->resampler_quality));
|
||||
microphone->resampler_int16_process = sinc_resampler_int16_process;
|
||||
microphone->resampler_int16_free = sinc_resampler_int16_free;
|
||||
}
|
||||
#ifdef HAVE_NEAREST_RESAMPLER
|
||||
else if (string_is_equal(rs_ident, "nearest"))
|
||||
{
|
||||
microphone->resampler_data_int16 = nearest_resampler_int16_init();
|
||||
microphone->resampler_int16_process = nearest_resampler_int16_process;
|
||||
microphone->resampler_int16_free = nearest_resampler_int16_free;
|
||||
}
|
||||
#endif
|
||||
#ifdef HAVE_CC_RESAMPLER
|
||||
else if (string_is_equal(rs_ident, "cc"))
|
||||
{
|
||||
microphone->resampler_data_int16 = cc_resampler_int16_init(
|
||||
microphone->orig_ratio);
|
||||
microphone->resampler_int16_process = cc_resampler_int16_process;
|
||||
microphone->resampler_int16_free = cc_resampler_int16_free;
|
||||
}
|
||||
#endif
|
||||
if (!microphone->resampler_data_int16)
|
||||
{
|
||||
microphone->resampler_int16_process = NULL;
|
||||
@@ -9943,23 +9825,9 @@ static void microphone_driver_capture_thread(void *data)
|
||||
continue;
|
||||
}
|
||||
|
||||
/* What the device has now, which can be less than was asked for:
|
||||
* a driver returns short whenever its bounded wait expires with
|
||||
* something in hand, and alsa returns a period even when the
|
||||
* slice spans several. read() blocks until it has everything it
|
||||
* was asked for, so reading the whole slice on a short count
|
||||
* parks the worker inside the driver - and teardown joins this
|
||||
* thread unconditionally. Read what is there. */
|
||||
{
|
||||
size_t want = slice * sample_size;
|
||||
size_t got = mic_st->driver->wait_readable(mic_st->driver_context,
|
||||
microphone->microphone_context, want);
|
||||
|
||||
if (got < sample_size)
|
||||
continue;
|
||||
if (got < want)
|
||||
slice = got / sample_size;
|
||||
}
|
||||
if (!mic_st->driver->wait_readable(mic_st->driver_context,
|
||||
microphone->microphone_context, slice * sample_size))
|
||||
continue;
|
||||
|
||||
/* The flush - device read, up-channel, resample, ring write -
|
||||
* runs without the lock: SPSC, this thread is the producer. The
|
||||
|
||||
+14
-52
@@ -31,10 +31,12 @@
|
||||
* calls into it - so the type has to exist there even though nothing
|
||||
* uses it. */
|
||||
#include <rthreads/retro_eventcount.h>
|
||||
/* Declarations only, with or without threads: the state holds lock and
|
||||
* condition pointers either way. Declaring the types here instead would
|
||||
* repeat typedefs a C89 compiler refuses once a file includes both. */
|
||||
#ifdef HAVE_THREADS
|
||||
#include <rthreads/rthreads.h>
|
||||
#else
|
||||
typedef struct slock slock_t;
|
||||
typedef struct scond scond_t;
|
||||
#endif
|
||||
#include <retro_inline.h>
|
||||
#include <libretro.h>
|
||||
#include <retro_miscellaneous.h>
|
||||
@@ -411,12 +413,6 @@ typedef struct audio_driver
|
||||
bool (*device_clock_ppm)(void *data, double *ppm);
|
||||
} audio_driver_t;
|
||||
|
||||
/* What a driver's device-clock word holds until it has an estimate.
|
||||
* Every estimate is kept inside +-100000 ppm, so the estimate and
|
||||
* whether there is one share one int: one store publishes, one load
|
||||
* reads, and no reader can pair a flag with a stale value. */
|
||||
#define AUDIO_CLOCK_PPM_NONE (-0x7FFFFFFF - 1)
|
||||
|
||||
/* The layout the user asked for, one of the AUDIO_LAYOUT_ masks. A
|
||||
* driver opening a device reads this; a driver without the layout
|
||||
* hook is not asked. */
|
||||
@@ -550,10 +546,8 @@ typedef struct
|
||||
/* Written once by the wrapper thread as it leaves its loop, read by
|
||||
* the producer's wait. Its own field, not a bit in flags: the main
|
||||
* thread read-modify-writes flags and a second writer would lose
|
||||
* bits. An atomic and not a volatile bool: volatile orders nothing
|
||||
* between the two, so the producer had no guarantee of seeing the
|
||||
* consumer's last word at all. */
|
||||
retro_atomic_int_t pipe_consumer_gone;
|
||||
* bits. */
|
||||
volatile bool pipe_consumer_gone;
|
||||
/* Throttle channel for audio_sync without vsync: the consumer bumps
|
||||
* pipe_gen after every pass and notifies pipe_space; a producer that
|
||||
* found the ring full waits for the generation to change. No lock:
|
||||
@@ -691,15 +685,6 @@ typedef struct
|
||||
* pipe's target then, not just a frame. Consumer thread only after
|
||||
* init. */
|
||||
bool pipe_priming;
|
||||
/* How the consumer fared against the core after priming, said once
|
||||
* at teardown beside the driver's silence count: the passes that
|
||||
* found the pipe short and had to wait for the core, the longest
|
||||
* such wait, and the least the pipe held at the start of a pass
|
||||
* (kept as frames + 1, so zero is no pass yet). Consumer writes,
|
||||
* the main thread reads at teardown. */
|
||||
retro_atomic_size_t pipe_source_waits;
|
||||
retro_atomic_size_t pipe_source_wait_max_us;
|
||||
retro_atomic_size_t pipe_held_min1;
|
||||
/* The audio thread's own copy of AUDIO_FLAG_PIPELINE_THREADED. Set
|
||||
* before the wrapper thread is released and cleared after it is
|
||||
* joined, so the thread never reads the flags word - which the main
|
||||
@@ -769,13 +754,6 @@ typedef struct
|
||||
retro_time_t avg_flush_delta;
|
||||
double avg_expected_delta;
|
||||
|
||||
/* The menu's silence at Menu Frame Rate 'Display Rate' is measured
|
||||
* out by the clock rather than by the content's frame: when it was
|
||||
* last fed (0 = not feeding by the clock), and the fraction of a
|
||||
* frame the last feed left owing. */
|
||||
retro_time_t menu_feed_last;
|
||||
double menu_feed_frac;
|
||||
|
||||
/* Rate-limit state for the DRC compute.
|
||||
*
|
||||
* The DRC ratio is updated approximately once per game-frame's worth
|
||||
@@ -917,7 +895,7 @@ typedef struct
|
||||
unsigned out_channels;
|
||||
/* The multi-channel batch entry (RETRO_ENVIRONMENT_GET_AUDIO_
|
||||
* SAMPLE_BATCH_MULTI): the layout the core last delivered, and
|
||||
* the stereo fold of a batch slice, a fixed region of arena_float. The
|
||||
* the stereo fold of a batch, grown to the largest batch seen. The
|
||||
* pipeline carries stereo; a core's wider frame is folded here at
|
||||
* the boundary, and the device's upmix widens the stereo again.
|
||||
* core_layout is stereo until the core delivers something else,
|
||||
@@ -1002,16 +980,12 @@ typedef struct
|
||||
* has been published since. The core's first audio after a resume
|
||||
* starts at pipe_fade_in_at: the consumer never takes a chunk across
|
||||
* it and arms the resume ramp on reaching it, through pipe_arm_fade,
|
||||
* consumer thread only. pipe_fade_in_mark is (sequence << 1) | armed,
|
||||
* written by the main thread alone; the consumer records the sequence
|
||||
* it has acted on in pipe_fade_in_seen instead of clearing the mark,
|
||||
* so a mark published while it reads is never cleared unseen. */
|
||||
* consumer thread only. */
|
||||
retro_atomic_size_t pipe_discard_to;
|
||||
retro_atomic_int_t pipe_discard_gen;
|
||||
int pipe_discard_seen;
|
||||
retro_atomic_size_t pipe_fade_in_at;
|
||||
retro_atomic_int_t pipe_fade_in_mark;
|
||||
unsigned pipe_fade_in_seen;
|
||||
retro_atomic_int_t pipe_fade_in_set;
|
||||
bool pipe_arm_fade;
|
||||
uint8_t pipe_transport_follow;
|
||||
/* Mutually exclusive with pipe_transport; shares its output storage. */
|
||||
@@ -1416,18 +1390,7 @@ size_t audio_driver_sample_batch_rewind(
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_MENU
|
||||
/**
|
||||
* audio_driver_menu_sample:
|
||||
* @by_clock : feed the silence the clock says has played since the last
|
||||
* feed, rather than one content frame of it.
|
||||
*
|
||||
* Feeds the device silence while the core is not running behind the
|
||||
* menu, with the menu sounds, the mixer and thumbnail audio mixed in.
|
||||
* One content frame per call holds a blocking writer to the content's
|
||||
* rate; by the clock, with a non-blocking writer, the device is kept fed
|
||||
* at whatever rate the menu runs.
|
||||
**/
|
||||
void audio_driver_menu_sample(bool by_clock);
|
||||
void audio_driver_menu_sample(void);
|
||||
#endif
|
||||
|
||||
extern audio_driver_t audio_rsound;
|
||||
@@ -1439,8 +1402,7 @@ extern audio_driver_t audio_roar;
|
||||
extern audio_driver_t audio_openal;
|
||||
extern audio_driver_t audio_opensl;
|
||||
extern audio_driver_t audio_jack;
|
||||
extern audio_driver_t audio_sdl1;
|
||||
extern audio_driver_t audio_sdl2;
|
||||
extern audio_driver_t audio_sdl;
|
||||
extern audio_driver_t audio_sdl3;
|
||||
extern audio_driver_t audio_xa;
|
||||
extern audio_driver_t audio_pulse;
|
||||
@@ -1463,11 +1425,11 @@ extern audio_driver_t audio_ps3;
|
||||
extern audio_driver_t audio_gx;
|
||||
extern audio_driver_t audio_ax;
|
||||
extern audio_driver_t audio_psp;
|
||||
extern audio_driver_t audio_psp2;
|
||||
extern audio_driver_t audio_ps4;
|
||||
extern audio_driver_t audio_ps2;
|
||||
extern audio_driver_t audio_ctr_csnd;
|
||||
extern audio_driver_t audio_ctr_dsp;
|
||||
#ifdef HAVE_THREADS
|
||||
#endif
|
||||
extern audio_driver_t audio_switch;
|
||||
extern audio_driver_t audio_switch_libnx_audren;
|
||||
extern audio_driver_t audio_rwebaudio;
|
||||
|
||||
+125
-190
@@ -18,15 +18,12 @@
|
||||
#include <string.h>
|
||||
|
||||
#include <lists/string_list.h>
|
||||
#include <retro_atomic.h>
|
||||
#include <rthreads/retro_eventcount.h>
|
||||
#include <rthreads/rthreads.h>
|
||||
#include <features/features_cpu.h>
|
||||
|
||||
#include "audio_thread_wrapper.h"
|
||||
#include "audio_driver.h"
|
||||
#include "../verbosity.h"
|
||||
#include "../frontend/thread_elevation.h"
|
||||
|
||||
/* How long a handshake between the main thread and the audio thread
|
||||
* may run before it is reported. Both are sub-millisecond on a device
|
||||
@@ -46,48 +43,29 @@
|
||||
* request signals the condition and cuts the wait short. */
|
||||
#define AUDIO_THREAD_IDLE_WAIT_US 1000
|
||||
|
||||
enum audio_thread_init_state
|
||||
{
|
||||
AUDIO_THREAD_INIT_PENDING = 0,
|
||||
AUDIO_THREAD_INIT_OK,
|
||||
AUDIO_THREAD_INIT_FAILED,
|
||||
AUDIO_THREAD_INIT_ABANDONED
|
||||
};
|
||||
|
||||
typedef struct audio_thread
|
||||
{
|
||||
const audio_driver_t *driver;
|
||||
void *driver_data;
|
||||
|
||||
sthread_t *thread;
|
||||
slock_t *lock;
|
||||
scond_t *cond;
|
||||
const char *device;
|
||||
unsigned *new_rate;
|
||||
|
||||
/* Both threads park here: the audio thread between passes it has
|
||||
* nothing to do on and while stopped, the main thread for the
|
||||
* stop acknowledgement and for init() to return. Every change to
|
||||
* the words below is written first and notified after, so a waiter's
|
||||
* re-check between prepare and commit sees it. */
|
||||
retro_eventcount_t ec;
|
||||
/* The thread's two loop conditions, tested on every pass without
|
||||
* any lock. alive only ever goes false. */
|
||||
retro_atomic_int_t alive;
|
||||
retro_atomic_int_t stopped;
|
||||
/* The thread is parked in a stopped loop and the driver is stopped;
|
||||
* block() waits for it. */
|
||||
retro_atomic_int_t stopped_ack;
|
||||
/* How init() ended, and who owns this struct: the thread moves it
|
||||
* from PENDING to OK or FAILED, the frontend from PENDING to
|
||||
* ABANDONED when it gives up waiting. One compare-and-swap each, so
|
||||
* exactly one side wins - the other reads what happened and acts on
|
||||
* that. An abandoned thread frees the struct itself when init()
|
||||
* finally returns. */
|
||||
retro_atomic_int_t init_state;
|
||||
int inited;
|
||||
/* The frontend stopped waiting for init(): this thread owns its own
|
||||
* state from there and frees it when init() finally returns. */
|
||||
bool abandoned;
|
||||
|
||||
/* Initialization options. */
|
||||
unsigned out_rate;
|
||||
unsigned latency;
|
||||
|
||||
bool alive;
|
||||
bool stopped;
|
||||
bool stopped_ack;
|
||||
bool is_paused;
|
||||
bool is_shutdown;
|
||||
bool use_float;
|
||||
@@ -99,38 +77,6 @@ typedef struct audio_thread
|
||||
bool prefer_fast_cores;
|
||||
} audio_thread_t;
|
||||
|
||||
/* Audio thread: parks while stopped, acknowledging the stop so block()
|
||||
* can return. block() clears the ack before it raises the flag, so an
|
||||
* ack found clear inside the wait window means a start and a second
|
||||
* stop landed since this pass acknowledged, and that stop is answered
|
||||
* before parking. The ack is stored and notified before the window,
|
||||
* never inside it: a notify from inside would cut the thread's own
|
||||
* wait short. */
|
||||
static void audio_thread_park_stopped(audio_thread_t *thr)
|
||||
{
|
||||
for (;;)
|
||||
{
|
||||
int key;
|
||||
if (!retro_atomic_load_acquire_int(&thr->stopped))
|
||||
return;
|
||||
retro_atomic_store_release_int(&thr->stopped_ack, 1);
|
||||
retro_eventcount_notify(&thr->ec);
|
||||
|
||||
key = retro_eventcount_prepare_wait(&thr->ec);
|
||||
if (!retro_atomic_load_acquire_int(&thr->stopped))
|
||||
{
|
||||
retro_eventcount_cancel_wait(&thr->ec);
|
||||
return;
|
||||
}
|
||||
if (!retro_atomic_load_acquire_int(&thr->stopped_ack))
|
||||
{
|
||||
retro_eventcount_cancel_wait(&thr->ec);
|
||||
continue;
|
||||
}
|
||||
retro_eventcount_commit_wait(&thr->ec, key);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The thread that manages the life of the audio driver.
|
||||
* The wrapped audio driver lives and dies with this function.
|
||||
@@ -140,31 +86,15 @@ static void audio_thread_loop(void *data)
|
||||
bool is_shutdown;
|
||||
audio_thread_t *thr = (audio_thread_t*)data;
|
||||
|
||||
if (!thr)
|
||||
return;
|
||||
|
||||
sthread_setname("ra-audio");
|
||||
|
||||
/* Best effort and never fatal: a refusal leaves the default. */
|
||||
if (thr->raise_priority)
|
||||
{
|
||||
const char *via = NULL;
|
||||
const char *added = NULL;
|
||||
switch (thread_elevation_raise_current(&via, &added))
|
||||
{
|
||||
case THREAD_ELEVATION_GRANTED:
|
||||
RARCH_LOG("[Audio] Audio thread priority raised.\n");
|
||||
break;
|
||||
case THREAD_ELEVATION_PENDING:
|
||||
/* Finishing on a thread of its own; this one does not wait. */
|
||||
RARCH_LOG("[Audio] Audio thread priority not raised directly; asking %s.\n", via);
|
||||
break;
|
||||
default:
|
||||
RARCH_LOG("[Audio] Audio thread priority not raised; the system refused or has no such class.\n");
|
||||
break;
|
||||
}
|
||||
if (added)
|
||||
RARCH_LOG("[Audio] Audio thread runs on %s.\n", added);
|
||||
if (sthread_raise_current_priority())
|
||||
RARCH_LOG("[Audio] Audio thread priority raised.\n");
|
||||
else
|
||||
RARCH_LOG("[Audio] Audio thread priority not raised; the system refused or has no such class.\n");
|
||||
}
|
||||
|
||||
if (thr->prefer_fast_cores)
|
||||
@@ -173,32 +103,38 @@ static void audio_thread_loop(void *data)
|
||||
RARCH_LOG("[Audio] Audio thread placed on the performance cores.\n");
|
||||
}
|
||||
|
||||
if (!thr)
|
||||
return;
|
||||
|
||||
thr->driver_data = thr->driver->init(
|
||||
thr->device, thr->out_rate, thr->latency,
|
||||
thr->new_rate);
|
||||
/* Read by the frontend once it sees the outcome below */
|
||||
thr->use_float = false;
|
||||
if (thr->driver_data && thr->driver->use_float)
|
||||
slock_lock(thr->lock);
|
||||
thr->inited = thr->driver_data ? 1 : -1;
|
||||
if (thr->inited > 0 && thr->driver->use_float)
|
||||
thr->use_float = thr->driver->use_float(thr->driver_data);
|
||||
thr->layout = AUDIO_LAYOUT_STEREO;
|
||||
if (thr->driver_data && thr->driver->layout)
|
||||
if (thr->inited > 0 && thr->driver->layout)
|
||||
thr->layout = thr->driver->layout(thr->driver_data);
|
||||
|
||||
/* Publish the outcome, unless the frontend gave up waiting first:
|
||||
* then nobody is waiting for this any more and nobody else will
|
||||
* free it. */
|
||||
if (!retro_atomic_cas_int(&thr->init_state, AUDIO_THREAD_INIT_PENDING,
|
||||
thr->driver_data ? AUDIO_THREAD_INIT_OK : AUDIO_THREAD_INIT_FAILED))
|
||||
scond_signal(thr->cond);
|
||||
{
|
||||
if (thr->driver_data && thr->driver->free)
|
||||
thr->driver->free(thr->driver_data);
|
||||
retro_eventcount_free(&thr->ec);
|
||||
free(thr);
|
||||
return;
|
||||
}
|
||||
retro_eventcount_notify(&thr->ec);
|
||||
bool abandoned = thr->abandoned;
|
||||
slock_unlock(thr->lock);
|
||||
|
||||
if (!thr->driver_data)
|
||||
/* Nobody is waiting for this any more, and nobody else will free
|
||||
* it: init() took longer than the frontend was willing to wait. */
|
||||
if (abandoned)
|
||||
{
|
||||
if (thr->driver_data && thr->driver->free)
|
||||
thr->driver->free(thr->driver_data);
|
||||
slock_free(thr->lock);
|
||||
scond_free(thr->cond);
|
||||
free(thr);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (thr->inited < 0)
|
||||
return;
|
||||
|
||||
/* Wait until we start to avoid calling
|
||||
@@ -208,8 +144,15 @@ static void audio_thread_loop(void *data)
|
||||
* this loop rather than the one below. block() waits for the
|
||||
* acknowledgement whichever loop the thread is in, so this one
|
||||
* gives it too. */
|
||||
audio_thread_park_stopped(thr);
|
||||
slock_lock(thr->lock);
|
||||
while (thr->stopped)
|
||||
{
|
||||
thr->stopped_ack = true;
|
||||
scond_signal(thr->cond);
|
||||
scond_wait(thr->cond, thr->lock);
|
||||
}
|
||||
is_shutdown = thr->is_shutdown;
|
||||
slock_unlock(thr->lock);
|
||||
|
||||
/* The loop below only calls the driver's start() when it comes out
|
||||
* of a stop; the initial start has to be made here, on this thread,
|
||||
@@ -218,37 +161,41 @@ static void audio_thread_loop(void *data)
|
||||
|
||||
for (;;)
|
||||
{
|
||||
/* Tested without the lock: a pass that is neither leaving nor
|
||||
* parking has nothing to say to the main thread, and that is
|
||||
* every pass while audio is playing. A request that lands just
|
||||
* after either test is taken on the next pass, which is where it
|
||||
* was taken before. */
|
||||
if (!retro_atomic_load_acquire_int(&thr->alive))
|
||||
slock_lock(thr->lock);
|
||||
|
||||
if (!thr->alive)
|
||||
{
|
||||
retro_atomic_store_release_int(&thr->stopped_ack, 1);
|
||||
retro_eventcount_notify(&thr->ec);
|
||||
scond_signal(thr->cond);
|
||||
thr->stopped_ack = true;
|
||||
slock_unlock(thr->lock);
|
||||
break;
|
||||
}
|
||||
|
||||
if (retro_atomic_load_acquire_int(&thr->stopped))
|
||||
if (thr->stopped)
|
||||
{
|
||||
thr->driver->stop(thr->driver_data);
|
||||
audio_thread_park_stopped(thr);
|
||||
while (thr->stopped)
|
||||
{
|
||||
/* If we stop right after start,
|
||||
* we might not be able to properly ack.
|
||||
* Signal in the loop instead. */
|
||||
thr->stopped_ack = true;
|
||||
scond_signal(thr->cond);
|
||||
|
||||
scond_wait(thr->cond, thr->lock);
|
||||
}
|
||||
thr->driver->start(thr->driver_data, thr->is_shutdown);
|
||||
}
|
||||
|
||||
slock_unlock(thr->lock);
|
||||
|
||||
if (!audio_driver_callback())
|
||||
{
|
||||
/* The re-check between prepare and commit is what keeps a
|
||||
* request that lands here from going unseen until the
|
||||
* timeout */
|
||||
int key = retro_eventcount_prepare_wait(&thr->ec);
|
||||
if ( retro_atomic_load_acquire_int(&thr->alive)
|
||||
&& !retro_atomic_load_acquire_int(&thr->stopped))
|
||||
retro_eventcount_commit_wait_timeout(&thr->ec, key,
|
||||
slock_lock(thr->lock);
|
||||
if (thr->alive && !thr->stopped)
|
||||
scond_wait_timeout(thr->cond, thr->lock,
|
||||
AUDIO_THREAD_IDLE_WAIT_US);
|
||||
else
|
||||
retro_eventcount_cancel_wait(&thr->ec);
|
||||
slock_unlock(thr->lock);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -265,19 +212,23 @@ static void audio_thread_block(audio_thread_t *thr)
|
||||
if (!thr)
|
||||
return;
|
||||
|
||||
if (retro_atomic_load_acquire_int(&thr->stopped))
|
||||
if (thr->stopped)
|
||||
return;
|
||||
|
||||
slock_lock(thr->lock);
|
||||
/* alive goes false when the thread is told to leave its loop, or
|
||||
* leaves it on its own after a failed write. Either way it will not
|
||||
* acknowledge anything again, and the wait below would never end.
|
||||
* There is nothing running to park, so there is nothing to wait
|
||||
* for. */
|
||||
if (!retro_atomic_load_acquire_int(&thr->alive))
|
||||
if (!thr->alive)
|
||||
{
|
||||
slock_unlock(thr->lock);
|
||||
return;
|
||||
retro_atomic_store_release_int(&thr->stopped_ack, 0);
|
||||
retro_atomic_store_release_int(&thr->stopped, 1);
|
||||
retro_eventcount_notify(&thr->ec);
|
||||
}
|
||||
thr->stopped_ack = false;
|
||||
thr->stopped = true;
|
||||
scond_signal(thr->cond);
|
||||
/* The thread may be asleep in the pipeline waiting for data; wake it
|
||||
* so it comes back to the loop and acknowledges now rather than
|
||||
* after its timeout. */
|
||||
@@ -287,22 +238,12 @@ static void audio_thread_block(audio_thread_t *thr)
|
||||
* abandoned: callers past it act as though the thread is parked,
|
||||
* and free() joins the thread either way, so a device that never
|
||||
* returns from a write still stops here. It is reported, so the log
|
||||
* names what the frontend is waiting on. A thread that leaves its
|
||||
* loop meanwhile acks on the way out. */
|
||||
* names what the frontend is waiting on. */
|
||||
{
|
||||
bool warned = false;
|
||||
for (;;)
|
||||
while (!thr->stopped_ack)
|
||||
{
|
||||
int key;
|
||||
if (retro_atomic_load_acquire_int(&thr->stopped_ack))
|
||||
break;
|
||||
key = retro_eventcount_prepare_wait(&thr->ec);
|
||||
if (retro_atomic_load_acquire_int(&thr->stopped_ack))
|
||||
{
|
||||
retro_eventcount_cancel_wait(&thr->ec);
|
||||
break;
|
||||
}
|
||||
if (retro_eventcount_commit_wait_timeout(&thr->ec, key,
|
||||
if (scond_wait_timeout(thr->cond, thr->lock,
|
||||
AUDIO_THREAD_HANDSHAKE_WARN_US))
|
||||
continue;
|
||||
if (!warned)
|
||||
@@ -314,6 +255,8 @@ static void audio_thread_block(audio_thread_t *thr)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
slock_unlock(thr->lock);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -325,8 +268,10 @@ static void audio_thread_unblock(audio_thread_t *thr)
|
||||
if (!thr)
|
||||
return;
|
||||
|
||||
retro_atomic_store_release_int(&thr->stopped, 0);
|
||||
retro_eventcount_notify(&thr->ec);
|
||||
slock_lock(thr->lock); /* Prevent the audio thread from touching this flag... */
|
||||
thr->stopped = false; /* ...so that the main thread can do it. */
|
||||
scond_signal(thr->cond); /* Then let the audio thread know that it's okay to resume. */
|
||||
slock_unlock(thr->lock); /* "As you were." */
|
||||
}
|
||||
|
||||
static void audio_thread_free(void *data)
|
||||
@@ -338,11 +283,11 @@ static void audio_thread_free(void *data)
|
||||
|
||||
if (thr->thread)
|
||||
{
|
||||
/* Let the thread out of whichever wait it is in; it leaves its
|
||||
* loop on seeing alive false. */
|
||||
retro_atomic_store_release_int(&thr->stopped, 0);
|
||||
retro_atomic_store_release_int(&thr->alive, 0);
|
||||
retro_eventcount_notify(&thr->ec);
|
||||
slock_lock(thr->lock); /* Let the audio thread finish what it's doing... */
|
||||
thr->stopped = false; /* Then stop it. "You're fired." */
|
||||
thr->alive = false;
|
||||
scond_signal(thr->cond); /* Let the thread know it's okay to continue */
|
||||
slock_unlock(thr->lock); /* At this point, it will exit its loop. */
|
||||
|
||||
/* It may be asleep in the pipeline waiting for data; wake it so
|
||||
* it sees alive == false now rather than after its timeout. */
|
||||
@@ -352,7 +297,10 @@ static void audio_thread_free(void *data)
|
||||
* (It will call the wrapped driver's free() function.) */
|
||||
}
|
||||
|
||||
retro_eventcount_free(&thr->ec);
|
||||
if (thr->lock)
|
||||
slock_free(thr->lock);
|
||||
if (thr->cond)
|
||||
scond_free(thr->cond);
|
||||
free(thr);
|
||||
/* The audio driver is done, clean up the thread itself. */
|
||||
}
|
||||
@@ -368,7 +316,7 @@ static bool audio_thread_alive(void *data)
|
||||
/* A thread that has ended after a failed write reports the device
|
||||
* as not alive, which is what it is; block() below is a no-op then,
|
||||
* and the answer has to come from somewhere. */
|
||||
if (!retro_atomic_load_acquire_int(&thr->alive))
|
||||
if (!thr->alive)
|
||||
return false;
|
||||
|
||||
audio_thread_block(thr);
|
||||
@@ -385,7 +333,9 @@ void audio_thread_apply_control(void *data,
|
||||
bool running;
|
||||
if (!thr || !control)
|
||||
return;
|
||||
running = !retro_atomic_load_acquire_int(&thr->stopped);
|
||||
slock_lock(thr->lock);
|
||||
running = !thr->stopped;
|
||||
slock_unlock(thr->lock);
|
||||
audio_thread_block(thr);
|
||||
control(userdata);
|
||||
if (running)
|
||||
@@ -419,10 +369,10 @@ static bool audio_thread_start(void *data, bool is_shutdown)
|
||||
|
||||
audio_driver_enable_callback();
|
||||
|
||||
/* Written before the release store that clears stopped, which the
|
||||
* thread acquire-loads before it reads is_shutdown */
|
||||
slock_lock(thr->lock);
|
||||
thr->is_paused = false;
|
||||
thr->is_shutdown = is_shutdown;
|
||||
slock_unlock(thr->lock);
|
||||
audio_thread_unblock(thr);
|
||||
|
||||
return true;
|
||||
@@ -540,8 +490,10 @@ static ssize_t audio_thread_write(void *data, const void *s, size_t len)
|
||||
_len = thr->driver->write(thr->driver_data, s, len);
|
||||
if (_len < 0)
|
||||
{
|
||||
retro_atomic_store_release_int(&thr->alive, 0);
|
||||
retro_eventcount_notify(&thr->ec);
|
||||
slock_lock(thr->lock);
|
||||
thr->alive = false;
|
||||
scond_signal(thr->cond);
|
||||
slock_unlock(thr->lock);
|
||||
}
|
||||
return _len;
|
||||
}
|
||||
@@ -648,13 +600,13 @@ bool audio_init_thread(const audio_driver_t **out_driver,
|
||||
thr->new_rate = new_rate;
|
||||
thr->latency = latency;
|
||||
|
||||
if (!retro_eventcount_init(&thr->ec))
|
||||
if (!(thr->cond = scond_new()))
|
||||
goto error;
|
||||
if (!(thr->lock = slock_new()))
|
||||
goto error;
|
||||
|
||||
retro_atomic_int_init(&thr->alive, 1);
|
||||
retro_atomic_int_init(&thr->stopped, 1);
|
||||
retro_atomic_int_init(&thr->stopped_ack, 0);
|
||||
retro_atomic_int_init(&thr->init_state, AUDIO_THREAD_INIT_PENDING);
|
||||
thr->alive = true;
|
||||
thr->stopped = true;
|
||||
|
||||
if (!(thr->thread = sthread_create(audio_thread_loop, thr)))
|
||||
goto error;
|
||||
@@ -665,44 +617,27 @@ bool audio_init_thread(const audio_driver_t **out_driver,
|
||||
* with no way out. Past the deadline the thread is told it owns its
|
||||
* own state and this returns without it - freeing it here would pull
|
||||
* it out from under a thread still using it. */
|
||||
slock_lock(thr->lock);
|
||||
{
|
||||
bool warned = false;
|
||||
retro_time_t giveup = cpu_features_get_time_usec()
|
||||
+ AUDIO_THREAD_HANDSHAKE_GIVEUP_US;
|
||||
for (;;)
|
||||
while (!thr->inited)
|
||||
{
|
||||
int key;
|
||||
retro_time_t now;
|
||||
if (retro_atomic_load_acquire_int(&thr->init_state)
|
||||
!= AUDIO_THREAD_INIT_PENDING)
|
||||
break;
|
||||
now = cpu_features_get_time_usec();
|
||||
retro_time_t now = cpu_features_get_time_usec();
|
||||
if (now >= giveup)
|
||||
{
|
||||
/* The thread may publish in this same instant: the
|
||||
* exchange decides, and a lost one means init() has just
|
||||
* returned and the state is this thread's after all. */
|
||||
if (retro_atomic_cas_int(&thr->init_state,
|
||||
AUDIO_THREAD_INIT_PENDING, AUDIO_THREAD_INIT_ABANDONED))
|
||||
{
|
||||
RARCH_ERR("[Audio] Driver \"%s\" did not return from init after %d seconds; going on without audio.\n",
|
||||
thr->driver->ident ? thr->driver->ident : "?",
|
||||
(int)(AUDIO_THREAD_HANDSHAKE_GIVEUP_US / 1000000));
|
||||
sthread_detach(thr->thread);
|
||||
*out_driver = NULL;
|
||||
*out_data = NULL;
|
||||
return false;
|
||||
}
|
||||
continue;
|
||||
thr->abandoned = true;
|
||||
slock_unlock(thr->lock);
|
||||
RARCH_ERR("[Audio] Driver \"%s\" did not return from init after %d seconds; going on without audio.\n",
|
||||
thr->driver->ident ? thr->driver->ident : "?",
|
||||
(int)(AUDIO_THREAD_HANDSHAKE_GIVEUP_US / 1000000));
|
||||
sthread_detach(thr->thread);
|
||||
*out_driver = NULL;
|
||||
*out_data = NULL;
|
||||
return false;
|
||||
}
|
||||
key = retro_eventcount_prepare_wait(&thr->ec);
|
||||
if (retro_atomic_load_acquire_int(&thr->init_state)
|
||||
!= AUDIO_THREAD_INIT_PENDING)
|
||||
{
|
||||
retro_eventcount_cancel_wait(&thr->ec);
|
||||
break;
|
||||
}
|
||||
if (retro_eventcount_commit_wait_timeout(&thr->ec, key,
|
||||
if (scond_wait_timeout(thr->cond, thr->lock,
|
||||
AUDIO_THREAD_HANDSHAKE_WARN_US))
|
||||
continue;
|
||||
if (!warned)
|
||||
@@ -714,9 +649,9 @@ bool audio_init_thread(const audio_driver_t **out_driver,
|
||||
}
|
||||
}
|
||||
}
|
||||
slock_unlock(thr->lock);
|
||||
|
||||
if (retro_atomic_load_acquire_int(&thr->init_state)
|
||||
!= AUDIO_THREAD_INIT_OK) /* Thread failed. */
|
||||
if (thr->inited < 0) /* Thread failed. */
|
||||
goto error;
|
||||
|
||||
*out_driver = &audio_thread;
|
||||
|
||||
+114
-115
@@ -29,7 +29,6 @@
|
||||
#include <stdlib.h>
|
||||
|
||||
#include <lists/string_list.h>
|
||||
#include <retro_atomic.h>
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include "../../config.h"
|
||||
@@ -61,25 +60,37 @@ typedef struct alsa_microphone_handle
|
||||
alsa_stream_info_t stream_info;
|
||||
} alsa_microphone_handle_t;
|
||||
|
||||
/* The microphone driver context carries nothing of its own: what
|
||||
* init() and free() set up and tear down is ALSA's global state, and
|
||||
* the frontend only needs a handle to hand back. The driver itself is
|
||||
* that handle. */
|
||||
extern microphone_driver_t microphone_alsa;
|
||||
typedef struct alsa_microphone
|
||||
{
|
||||
bool nonblock;
|
||||
} alsa_microphone_t;
|
||||
|
||||
static void *alsa_microphone_init(void)
|
||||
{
|
||||
alsa_microphone_t *alsa = (alsa_microphone_t*)calloc(1, sizeof(alsa_microphone_t));
|
||||
|
||||
if (!alsa)
|
||||
{
|
||||
RARCH_ERR("[ALSA] Failed to allocate driver context.\n");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
RARCH_LOG("[ALSA] Using ALSA version %s.\n", snd_asoundlib_version());
|
||||
|
||||
return (void*)µphone_alsa;
|
||||
return alsa;
|
||||
}
|
||||
|
||||
static void alsa_microphone_close_mic(void *driver_context, void *mic_context);
|
||||
static void alsa_microphone_free(void *driver_context)
|
||||
{
|
||||
alsa_microphone_t *alsa = (alsa_microphone_t*)driver_context;
|
||||
/* The mic frontend should've closed all mics before calling free(). */
|
||||
if (driver_context)
|
||||
|
||||
if (alsa)
|
||||
{
|
||||
snd_config_update_free_global();
|
||||
free(alsa);
|
||||
}
|
||||
}
|
||||
|
||||
static bool alsa_microphone_start_mic(void *driver_context, void *mic_context);
|
||||
@@ -106,15 +117,13 @@ static int alsa_microphone_read(void *driver_context, void *mic_context, void *s
|
||||
{
|
||||
snd_pcm_sframes_t size;
|
||||
snd_pcm_state_t state;
|
||||
alsa_microphone_t *alsa = (alsa_microphone_t*)driver_context;
|
||||
alsa_microphone_handle_t *mic = (alsa_microphone_handle_t*)mic_context;
|
||||
uint8_t *buf = (uint8_t*)s;
|
||||
snd_pcm_sframes_t read = 0;
|
||||
int errnum = 0;
|
||||
bool eagain_retry = true;
|
||||
int laps = ALSA_WAIT_READABLE_LAPS;
|
||||
int timeout_ms;
|
||||
|
||||
if (!driver_context || !mic || !buf)
|
||||
if (!alsa || !mic || !buf)
|
||||
return -1;
|
||||
|
||||
size = BYTES_TO_FRAMES(len, mic->stream_info.frame_bits);
|
||||
@@ -137,55 +146,88 @@ static int alsa_microphone_read(void *driver_context, void *mic_context, void *s
|
||||
}
|
||||
}
|
||||
|
||||
timeout_ms = alsa_microphone_wait_ms(mic);
|
||||
|
||||
while (size)
|
||||
if (alsa->nonblock)
|
||||
{
|
||||
snd_pcm_sframes_t frames;
|
||||
int rc = snd_pcm_wait(mic->pcm, timeout_ms);
|
||||
|
||||
/* Nothing delivered in the time two periods take. A device that
|
||||
* has stopped must not park the caller, so give up after a
|
||||
* bounded number of these and return what there is. */
|
||||
if (rc == 0)
|
||||
while (size)
|
||||
{
|
||||
if (--laps < 0)
|
||||
break;
|
||||
continue;
|
||||
}
|
||||
snd_pcm_sframes_t frames = snd_pcm_readi(mic->pcm, buf, size);
|
||||
|
||||
if (rc == -EPIPE || rc == -ESTRPIPE || rc == -EINTR)
|
||||
{
|
||||
if (snd_pcm_recover(mic->pcm, rc, 1) < 0)
|
||||
return -1;
|
||||
continue;
|
||||
}
|
||||
|
||||
frames = snd_pcm_readi(mic->pcm, buf, size);
|
||||
|
||||
if (frames == -EPIPE || frames == -EINTR || frames == -ESTRPIPE)
|
||||
{
|
||||
if (snd_pcm_recover(mic->pcm, frames, 1) < 0)
|
||||
return -1;
|
||||
|
||||
break;
|
||||
}
|
||||
else if (frames == -EAGAIN)
|
||||
{
|
||||
/* Definitely not supposed to happen. */
|
||||
if (eagain_retry)
|
||||
if (frames == -EPIPE || frames == -EINTR || frames == -ESTRPIPE)
|
||||
{
|
||||
eagain_retry = false;
|
||||
errnum = snd_pcm_recover(mic->pcm, frames, 0);
|
||||
if (errnum < 0)
|
||||
{
|
||||
RARCH_ERR("[ALSA] Failed to read from microphone: %s.\n", snd_strerror(frames));
|
||||
RARCH_ERR("[ALSA] Additionally, recovery failed with: %s.\n", snd_strerror(errnum));
|
||||
return -1;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
else if (frames == -EAGAIN)
|
||||
break;
|
||||
else if (frames < 0)
|
||||
return -1;
|
||||
|
||||
read += frames;
|
||||
buf += FRAMES_TO_BYTES(frames, mic->stream_info.frame_bits);
|
||||
size -= frames;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
bool eagain_retry = true;
|
||||
int laps = ALSA_WAIT_READABLE_LAPS;
|
||||
int timeout_ms = alsa_microphone_wait_ms(mic);
|
||||
|
||||
while (size)
|
||||
{
|
||||
snd_pcm_sframes_t frames;
|
||||
int rc = snd_pcm_wait(mic->pcm, timeout_ms);
|
||||
|
||||
/* Nothing delivered in the time two periods take. A device that
|
||||
* has stopped must not park the caller, so give up after a
|
||||
* bounded number of these and return what there is. */
|
||||
if (rc == 0)
|
||||
{
|
||||
if (--laps < 0)
|
||||
break;
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
}
|
||||
else if (frames < 0)
|
||||
return -1;
|
||||
|
||||
read += frames;
|
||||
buf += FRAMES_TO_BYTES(frames, mic->stream_info.frame_bits);
|
||||
size -= frames;
|
||||
if (rc == -EPIPE || rc == -ESTRPIPE || rc == -EINTR)
|
||||
{
|
||||
if (snd_pcm_recover(mic->pcm, rc, 1) < 0)
|
||||
return -1;
|
||||
continue;
|
||||
}
|
||||
|
||||
frames = snd_pcm_readi(mic->pcm, buf, size);
|
||||
|
||||
if (frames == -EPIPE || frames == -EINTR || frames == -ESTRPIPE)
|
||||
{
|
||||
if (snd_pcm_recover(mic->pcm, frames, 1) < 0)
|
||||
return -1;
|
||||
|
||||
break;
|
||||
}
|
||||
else if (frames == -EAGAIN)
|
||||
{
|
||||
/* Definitely not supposed to happen. */
|
||||
if (eagain_retry)
|
||||
{
|
||||
eagain_retry = false;
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
}
|
||||
else if (frames < 0)
|
||||
return -1;
|
||||
|
||||
read += frames;
|
||||
buf += FRAMES_TO_BYTES(frames, mic->stream_info.frame_bits);
|
||||
size -= frames;
|
||||
}
|
||||
}
|
||||
|
||||
return FRAMES_TO_BYTES(read, mic->stream_info.frame_bits);
|
||||
@@ -202,6 +244,12 @@ static bool alsa_microphone_mic_alive(const void *driver_context, const void *mi
|
||||
return snd_pcm_state(mic->pcm) == SND_PCM_STATE_RUNNING;
|
||||
}
|
||||
|
||||
static void alsa_microphone_set_nonblock_state(void *driver_context, bool nonblock)
|
||||
{
|
||||
alsa_microphone_t *alsa = (alsa_microphone_t*)driver_context;
|
||||
alsa->nonblock = nonblock;
|
||||
}
|
||||
|
||||
static struct string_list *alsa_microphone_device_list_new(const void *data)
|
||||
{
|
||||
return alsa_device_list_type_new("Input");
|
||||
@@ -219,9 +267,10 @@ static void *alsa_microphone_open_mic(void *driver_context,
|
||||
unsigned latency,
|
||||
unsigned *new_rate)
|
||||
{
|
||||
alsa_microphone_t *alsa = (alsa_microphone_t*)driver_context;
|
||||
alsa_microphone_handle_t *mic = NULL;
|
||||
|
||||
if (!driver_context) /* If we weren't given a valid ALSA context... */
|
||||
if (!alsa) /* If we weren't given a valid ALSA context... */
|
||||
return NULL;
|
||||
|
||||
/* If the microphone context couldn't be allocated... */
|
||||
@@ -238,7 +287,7 @@ static void *alsa_microphone_open_mic(void *driver_context,
|
||||
error:
|
||||
RARCH_ERR("[ALSA] Failed to initialize microphone.\n");
|
||||
|
||||
alsa_microphone_close_mic(driver_context, mic);
|
||||
alsa_microphone_close_mic(alsa, mic);
|
||||
|
||||
return NULL;
|
||||
|
||||
@@ -351,6 +400,7 @@ microphone_driver_t microphone_alsa = {
|
||||
alsa_microphone_init,
|
||||
alsa_microphone_free,
|
||||
alsa_microphone_read,
|
||||
alsa_microphone_set_nonblock_state,
|
||||
"alsa",
|
||||
alsa_microphone_device_list_new,
|
||||
alsa_microphone_device_list_free,
|
||||
@@ -372,10 +422,6 @@ typedef struct alsa
|
||||
/* Frames snd_pcm_writei() accepted since open; less what the device
|
||||
* still holds, it is what the device has consumed. */
|
||||
uint64_t frames_written;
|
||||
/* Xruns the device reported on the write path: -EPIPE is the
|
||||
* device having run out of audio, which -EINTR and -ESTRPIPE
|
||||
* beside it are not. */
|
||||
retro_atomic_size_t underruns;
|
||||
bool nonblock;
|
||||
/* Stopped, as the frontend sees it: alive() is its inverse. Held
|
||||
* says how: the stream paused with its buffer kept, or dropped,
|
||||
@@ -420,20 +466,8 @@ typedef struct alsa
|
||||
double clk_a_sx, clk_a_sy, clk_a_sxx, clk_a_sxy, clk_a_n;
|
||||
int clk_a_ppm;
|
||||
int clk_a_valid;
|
||||
/* Raised wherever the device stops and starts again - a pause, a
|
||||
* resume, an xrun recovery - from whichever thread that happens on.
|
||||
* Its position stood still meanwhile while time did not, so both
|
||||
* fits start again from the writer's next sample; the last
|
||||
* published estimate stands until the new window publishes one. */
|
||||
retro_atomic_int_t clk_rebase;
|
||||
} alsa_t;
|
||||
|
||||
static int alsa_recover(alsa_t *alsa, int err)
|
||||
{
|
||||
retro_atomic_store_release_int(&alsa->clk_rebase, 1);
|
||||
return snd_pcm_recover(alsa->pcm, err, 1);
|
||||
}
|
||||
|
||||
/* The layout the device actually has, from its channel map; the
|
||||
* requested one where the map could not be read; stereo where the
|
||||
* device would not open wider. */
|
||||
@@ -515,7 +549,6 @@ static bool alsa_start(void *data, bool is_shutdown)
|
||||
}
|
||||
alsa->is_paused = false;
|
||||
alsa->held = false;
|
||||
retro_atomic_store_release_int(&alsa->clk_rebase, 1);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -540,9 +573,7 @@ static ssize_t alsa_write(void *data, const void *buf_, size_t len)
|
||||
|
||||
if (frames == -EPIPE || frames == -EINTR || frames == -ESTRPIPE)
|
||||
{
|
||||
if (frames == -EPIPE)
|
||||
retro_atomic_fetch_add_size(&alsa->underruns, 1);
|
||||
if (alsa_recover(alsa, frames) < 0)
|
||||
if (snd_pcm_recover(alsa->pcm, frames, 1) < 0)
|
||||
return -1;
|
||||
|
||||
break;
|
||||
@@ -580,9 +611,7 @@ static ssize_t alsa_write(void *data, const void *buf_, size_t len)
|
||||
|
||||
if (frames == -EPIPE || frames == -EINTR || frames == -ESTRPIPE)
|
||||
{
|
||||
if (frames == -EPIPE)
|
||||
retro_atomic_fetch_add_size(&alsa->underruns, 1);
|
||||
if (alsa_recover(alsa, frames) < 0)
|
||||
if (snd_pcm_recover(alsa->pcm, frames, 1) < 0)
|
||||
return -1;
|
||||
break;
|
||||
}
|
||||
@@ -594,7 +623,7 @@ static ssize_t alsa_write(void *data, const void *buf_, size_t len)
|
||||
rc = snd_pcm_wait(alsa->pcm, wait_ms);
|
||||
if (rc == -EPIPE || rc == -ESTRPIPE || rc == -EINTR)
|
||||
{
|
||||
if (alsa_recover(alsa, rc) < 0)
|
||||
if (snd_pcm_recover(alsa->pcm, rc, 1) < 0)
|
||||
return -1;
|
||||
}
|
||||
continue;
|
||||
@@ -633,7 +662,6 @@ static bool alsa_stop(void *data)
|
||||
{
|
||||
alsa->is_paused = true;
|
||||
alsa->held = true;
|
||||
retro_atomic_store_release_int(&alsa->clk_rebase, 1);
|
||||
return true;
|
||||
}
|
||||
ret = snd_pcm_drop(alsa->pcm);
|
||||
@@ -644,7 +672,6 @@ static bool alsa_stop(void *data)
|
||||
}
|
||||
alsa->is_paused = true;
|
||||
alsa->held = false;
|
||||
retro_atomic_store_release_int(&alsa->clk_rebase, 1);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -743,7 +770,7 @@ static size_t alsa_wait_writable(void *data, size_t len)
|
||||
|
||||
if (avail == -EPIPE || avail == -ESTRPIPE || avail == -EINTR)
|
||||
{
|
||||
if (alsa_recover(alsa, (int)avail) < 0)
|
||||
if (snd_pcm_recover(alsa->pcm, (int)avail, 1) < 0)
|
||||
return 0;
|
||||
if (--laps < 0)
|
||||
return 0;
|
||||
@@ -759,7 +786,7 @@ static size_t alsa_wait_writable(void *data, size_t len)
|
||||
rc = snd_pcm_start(alsa->pcm);
|
||||
if (rc == -EPIPE || rc == -ESTRPIPE || rc == -EINTR)
|
||||
{
|
||||
if (alsa_recover(alsa, rc) < 0)
|
||||
if (snd_pcm_recover(alsa->pcm, rc, 1) < 0)
|
||||
return 0;
|
||||
}
|
||||
else if (rc < 0)
|
||||
@@ -771,7 +798,7 @@ static size_t alsa_wait_writable(void *data, size_t len)
|
||||
return 0;
|
||||
if (rc == -EPIPE || rc == -ESTRPIPE || rc == -EINTR)
|
||||
{
|
||||
if (alsa_recover(alsa, rc) < 0)
|
||||
if (snd_pcm_recover(alsa->pcm, rc, 1) < 0)
|
||||
return 0;
|
||||
}
|
||||
else if (rc < 0)
|
||||
@@ -849,16 +876,6 @@ static void alsa_clock_sample(alsa_t *alsa)
|
||||
if (!sys_ns)
|
||||
return;
|
||||
|
||||
/* A load and a store rather than an exchange, which the volatile
|
||||
* fallback of retro_atomic lacks: a raise landing between the two
|
||||
* is one that has just been served. */
|
||||
if (retro_atomic_load_acquire_int(&alsa->clk_rebase))
|
||||
{
|
||||
retro_atomic_store_relaxed_int(&alsa->clk_rebase, 0);
|
||||
alsa->clk_have_anchor = 0;
|
||||
alsa->clk_a_have_anchor = 0;
|
||||
}
|
||||
|
||||
/* The hardware's own clock against the system's, where the driver
|
||||
* counts one. This needs no rate and no delay - it is two clocks
|
||||
* compared directly. */
|
||||
@@ -973,12 +990,6 @@ static size_t alsa_frames_consumed(void *data)
|
||||
return (size_t)(alsa->frames_written - (uint64_t)delay);
|
||||
}
|
||||
|
||||
static size_t alsa_underruns(void *data)
|
||||
{
|
||||
alsa_t *alsa = (alsa_t*)data;
|
||||
return alsa ? retro_atomic_load_acquire_size(&alsa->underruns) : 0;
|
||||
}
|
||||
|
||||
audio_driver_t audio_alsa = {
|
||||
alsa_init,
|
||||
alsa_write,
|
||||
@@ -996,7 +1007,7 @@ audio_driver_t audio_alsa = {
|
||||
NULL, /* write_raw */
|
||||
alsa_wait_writable,
|
||||
alsa_frames_consumed,
|
||||
alsa_underruns,
|
||||
NULL, /* underruns */
|
||||
alsa_layout,
|
||||
NULL, /* frames_consumed_fallback */
|
||||
alsa_device_clock_ppm
|
||||
@@ -1167,10 +1178,6 @@ typedef struct ealsa
|
||||
size_t period_frames;
|
||||
size_t buffer_frames;
|
||||
uint64_t frames_written; /* handed to the device since it opened */
|
||||
/* Xruns the device reported on the write path: EPIPE is the device
|
||||
* having run out of audio, which the ESTRPIPE beside it - a resume
|
||||
* after a suspend - is not. */
|
||||
retro_atomic_size_t underruns;
|
||||
bool nonblock;
|
||||
bool has_float;
|
||||
bool can_pause;
|
||||
@@ -1592,8 +1599,6 @@ static ssize_t tinyalsa_write(void *data, const void *buf, size_t len)
|
||||
{
|
||||
/* An underrun, or a resume after a suspend: prepared
|
||||
* again and the frames go out on the next turn. */
|
||||
if (errno == EPIPE)
|
||||
retro_atomic_fetch_add_size(&ea->underruns, 1);
|
||||
if (!ealsa_recover(ea))
|
||||
return -1;
|
||||
continue;
|
||||
@@ -1777,12 +1782,6 @@ static void tinyalsa_device_list_free(void *data, void *array_list_data)
|
||||
string_list_free(s);
|
||||
}
|
||||
|
||||
static size_t tinyalsa_underruns(void *data)
|
||||
{
|
||||
tinyalsa_t *ea = (tinyalsa_t*)data;
|
||||
return ea ? retro_atomic_load_acquire_size(&ea->underruns) : 0;
|
||||
}
|
||||
|
||||
audio_driver_t audio_tinyalsa = {
|
||||
tinyalsa_init,
|
||||
tinyalsa_write,
|
||||
@@ -1800,7 +1799,7 @@ audio_driver_t audio_tinyalsa = {
|
||||
NULL, /* write_raw */
|
||||
tinyalsa_wait_writable,
|
||||
tinyalsa_frames_consumed,
|
||||
tinyalsa_underruns,
|
||||
NULL, /* underruns */
|
||||
tinyalsa_layout
|
||||
};
|
||||
|
||||
|
||||
@@ -16,12 +16,6 @@
|
||||
|
||||
#define ALSA_PCM_NEW_HW_PARAMS_API
|
||||
#define ALSA_PCM_NEW_SW_PARAMS_API
|
||||
#include <errno.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <retro_atomic.h>
|
||||
|
||||
#include <sys/asoundlib.h>
|
||||
#include <retro_math.h>
|
||||
|
||||
@@ -44,12 +38,6 @@ typedef struct alsa
|
||||
unsigned buf_size;
|
||||
unsigned buf_count;
|
||||
unsigned rate;
|
||||
/* Times the device reported it had run out of audio. Counted where
|
||||
* the status says so, on the write path, which is why it is counted
|
||||
* and not said: a log line there costs part of the margin it is
|
||||
* reporting on. The frontend shows it and logs the total once at
|
||||
* teardown. */
|
||||
retro_atomic_size_t underruns;
|
||||
bool nonblock;
|
||||
bool has_float;
|
||||
bool can_pause;
|
||||
@@ -182,7 +170,6 @@ static void *alsa_qsa_init(const char *device,
|
||||
alsa->buffer[i] = alsa->buffer_chunk + i * alsa->buf_size;
|
||||
|
||||
alsa->has_float = false;
|
||||
retro_atomic_size_init(&alsa->underruns, 0);
|
||||
alsa->can_pause = true;
|
||||
RARCH_LOG("[ALSA QSA] Can pause: %s.\n",
|
||||
alsa->can_pause ? "yes" : "no");
|
||||
@@ -219,7 +206,7 @@ static int check_pcm_status(void *data, int channel_type)
|
||||
}
|
||||
else if (status.status == SND_PCM_STATUS_UNDERRUN)
|
||||
{
|
||||
retro_atomic_fetch_add_size(&alsa->underruns, 1);
|
||||
RARCH_LOG("[ALSA QSA] check_pcm_status: SNDP_CM_STATUS_UNDERRUN.\n");
|
||||
if ((ret = snd_pcm_channel_prepare(alsa->pcm, channel_type)) < 0)
|
||||
{
|
||||
RARCH_ERR("[ALSA QSA] Invalid state detected for underrun on snd_pcm_channel_prepare: %s.\n",
|
||||
@@ -435,12 +422,6 @@ static size_t alsa_qsa_buffer_size(void *data)
|
||||
return alsa->buf_size * alsa->buf_count;
|
||||
}
|
||||
|
||||
static size_t alsa_qsa_underruns(void *data)
|
||||
{
|
||||
alsa_qsa_t *alsa = (alsa_qsa_t*)data;
|
||||
return alsa ? retro_atomic_load_acquire_size(&alsa->underruns) : 0;
|
||||
}
|
||||
|
||||
audio_driver_t audio_alsa = {
|
||||
alsa_qsa_init,
|
||||
alsa_qsa_write,
|
||||
@@ -455,8 +436,5 @@ audio_driver_t audio_alsa = {
|
||||
NULL,
|
||||
alsa_qsa_write_avail,
|
||||
alsa_qsa_buffer_size,
|
||||
NULL, /* write_raw */
|
||||
NULL, /* wait_writable */
|
||||
NULL, /* frames_consumed */
|
||||
alsa_qsa_underruns
|
||||
NULL /* write_raw */
|
||||
};
|
||||
+14
-31
@@ -802,7 +802,8 @@ typedef struct ra_asio
|
||||
double clk_sxx;
|
||||
double clk_sxy;
|
||||
double clk_n;
|
||||
retro_atomic_int_t clk_ppm; /* AUDIO_CLOCK_PPM_NONE until known */
|
||||
retro_atomic_int_t clk_ppm;
|
||||
retro_atomic_int_t clk_valid;
|
||||
/* Frames the device has taken: a period per callback, silence
|
||||
* included. Written by the callback thread, read by the writer, so
|
||||
* atomic: an aligned word is what the machine will not tear, which
|
||||
@@ -810,12 +811,6 @@ typedef struct ra_asio
|
||||
* - and the underrun counter beside it is already atomic for the
|
||||
* same reason. */
|
||||
retro_atomic_size_t consumed;
|
||||
/* The frontend's reinit latch, taken on the main thread at init.
|
||||
* The ASIO callbacks run on the driver's own thread, where reaching
|
||||
* for a frontend singleton is the thing the worker-read audit
|
||||
* exists to catch; wasapi captures the same pointer at
|
||||
* sthread_create. */
|
||||
retro_atomic_int_t *reinit_request;
|
||||
/* Set by the message callback on kAsioResetRequest or
|
||||
* kAsioBufferSizeChange: the buffers are to be disposed and created
|
||||
* anew - the driver's preferred size may have changed - when the
|
||||
@@ -1055,11 +1050,8 @@ static void asio_cb_sample_rate_changed(ASIOSampleRate rate)
|
||||
RARCH_WARN("[ASIO] The device changed its sample rate to %.0f Hz; audio will reinitialise.\n",
|
||||
(double)rate);
|
||||
if (g_asio)
|
||||
{
|
||||
retro_atomic_store_release_int(&g_asio->rebuild_pending, 1);
|
||||
if (g_asio->reinit_request)
|
||||
retro_atomic_store_release_int(g_asio->reinit_request, 1);
|
||||
}
|
||||
retro_atomic_store_release_int(&audio_state_get_ptr()->reinit_request, 1);
|
||||
}
|
||||
|
||||
static long asio_cb_message(long selector, long value,
|
||||
@@ -1093,11 +1085,8 @@ static long asio_cb_message(long selector, long value,
|
||||
RARCH_WARN("[ASIO] Driver requests %s; audio will reinitialise.\n",
|
||||
selector == kAsioResetRequest ? "a reset" : "a buffer size change");
|
||||
if (g_asio)
|
||||
{
|
||||
retro_atomic_store_release_int(&g_asio->rebuild_pending, 1);
|
||||
if (g_asio->reinit_request)
|
||||
retro_atomic_store_release_int(g_asio->reinit_request, 1);
|
||||
}
|
||||
retro_atomic_store_release_int(&audio_state_get_ptr()->reinit_request, 1);
|
||||
return 1L;
|
||||
case kAsioResyncRequest:
|
||||
/* The driver lost its place - a system pause, a clock that
|
||||
@@ -1192,6 +1181,7 @@ static ASIOTime * asio_cb_buffer_switch_time_info(
|
||||
if (ppm > -100000.0 && ppm < 100000.0)
|
||||
{
|
||||
retro_atomic_store_release_int(&ad->clk_ppm, (int)ppm);
|
||||
retro_atomic_store_release_int(&ad->clk_valid, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1613,8 +1603,6 @@ static void *ra_asio_init(const char *device, unsigned rate,
|
||||
bool rate_moved = false;
|
||||
g_asio_persistent = NULL;
|
||||
|
||||
ad->reinit_request = &audio_state_get_ptr()->reinit_request;
|
||||
|
||||
/* Nothing was freed when this was parked, so a callback still
|
||||
* in flight from then was harmless - it read a cleared g_asio
|
||||
* and left. What follows here is not harmless: the ring is
|
||||
@@ -1637,7 +1625,8 @@ static void *ra_asio_init(const char *device, unsigned rate,
|
||||
* drift. */
|
||||
ad->clk_have_anchor = 0;
|
||||
ad->clk_n = 0.0;
|
||||
retro_atomic_store_release_int(&ad->clk_ppm, AUDIO_CLOCK_PPM_NONE);
|
||||
retro_atomic_store_release_int(&ad->clk_valid, 0);
|
||||
retro_atomic_store_release_int(&ad->clk_ppm, 0);
|
||||
|
||||
/* The rate, settled before anything is sized from it.
|
||||
*
|
||||
@@ -1738,10 +1727,9 @@ static void *ra_asio_init(const char *device, unsigned rate,
|
||||
ad = (ra_asio_t *)calloc(1, sizeof(ra_asio_t));
|
||||
if (!ad)
|
||||
return NULL;
|
||||
/* Taken here, on the main thread, for the driver's callbacks. */
|
||||
ad->reinit_request = &audio_state_get_ptr()->reinit_request;
|
||||
retro_atomic_size_init(&ad->underruns, 0);
|
||||
retro_atomic_int_init(&ad->clk_ppm, AUDIO_CLOCK_PPM_NONE);
|
||||
retro_atomic_int_init(&ad->clk_ppm, 0);
|
||||
retro_atomic_int_init(&ad->clk_valid, 0);
|
||||
ad->clk_have_anchor = 0;
|
||||
retro_atomic_size_init(&ad->consumed, 0);
|
||||
|
||||
@@ -2060,7 +2048,6 @@ static void ra_asio_set_nonblock_state(void *data, bool state)
|
||||
|
||||
static void ra_asio_free(void *data)
|
||||
{
|
||||
int clk_ppm;
|
||||
ra_asio_t *ad = (ra_asio_t *)data;
|
||||
if (!ad)
|
||||
return;
|
||||
@@ -2069,10 +2056,10 @@ static void ra_asio_free(void *data)
|
||||
* was doing, against the rate this driver asked for. Logged beside
|
||||
* the underrun count rather than acted on: until this has been read
|
||||
* off real interfaces it is a measurement, not an input. */
|
||||
clk_ppm = retro_atomic_load_acquire_int(&ad->clk_ppm);
|
||||
if (clk_ppm != AUDIO_CLOCK_PPM_NONE)
|
||||
if (retro_atomic_load_acquire_int(&ad->clk_valid))
|
||||
RARCH_LOG("[ASIO] Device clock, from the driver's time information:"
|
||||
" %+d ppm against %u Hz.\n", clk_ppm, ad->sample_rate);
|
||||
" %+d ppm against %u Hz.\n",
|
||||
retro_atomic_load_acquire_int(&ad->clk_ppm), ad->sample_rate);
|
||||
else
|
||||
RARCH_LOG("[ASIO] Device clock: the driver reported no usable time"
|
||||
" information.\n");
|
||||
@@ -2200,13 +2187,9 @@ static size_t ra_asio_underruns(void *data)
|
||||
static bool ra_asio_device_clock_ppm(void *data, double *ppm)
|
||||
{
|
||||
ra_asio_t *ad = (ra_asio_t*)data;
|
||||
int v;
|
||||
if (!ad)
|
||||
if (!ad || !retro_atomic_load_acquire_int(&ad->clk_valid))
|
||||
return false;
|
||||
v = retro_atomic_load_acquire_int(&ad->clk_ppm);
|
||||
if (v == AUDIO_CLOCK_PPM_NONE)
|
||||
return false;
|
||||
*ppm = (double)v;
|
||||
*ppm = (double)retro_atomic_load_acquire_int(&ad->clk_ppm);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
+5
-106
@@ -20,7 +20,6 @@
|
||||
#include <sys/ioctl.h>
|
||||
#include <unistd.h>
|
||||
#include <errno.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <sys/audioio.h>
|
||||
|
||||
@@ -28,12 +27,7 @@
|
||||
#include "../../config.h"
|
||||
#endif
|
||||
|
||||
#include <compat/strl.h>
|
||||
#include <lists/string_list.h>
|
||||
|
||||
#include <retro_miscellaneous.h>
|
||||
#include "../audio_driver.h"
|
||||
#include "../audio_device_label.h"
|
||||
#include "../../verbosity.h"
|
||||
|
||||
#define DEFAULT_DEV "/dev/audio"
|
||||
@@ -42,19 +36,12 @@ static void *audioio_init(const char *device, unsigned rate, unsigned latency,
|
||||
unsigned *new_out_rate)
|
||||
{
|
||||
struct audio_info info;
|
||||
char audiodev[PATH_MAX_LENGTH];
|
||||
const char *audiodev = device ? device : DEFAULT_DEV;
|
||||
int *fd = (int*)calloc(1, sizeof(int));
|
||||
|
||||
if (!fd)
|
||||
return NULL;
|
||||
|
||||
/* The device may be a list entry, "/dev/audio0 (name)": the path
|
||||
* is what is opened. */
|
||||
if (device)
|
||||
audio_device_label_path(audiodev, sizeof(audiodev), device);
|
||||
else
|
||||
strlcpy(audiodev, DEFAULT_DEV, sizeof(audiodev));
|
||||
|
||||
AUDIO_INITINFO(&info);
|
||||
|
||||
#ifdef AUMODE_PLAY_ALL
|
||||
@@ -191,104 +178,20 @@ static size_t audioio_buffer_size(void *data)
|
||||
int *fd = (int*)data;
|
||||
|
||||
if (ioctl(*fd, AUDIO_GETINFO, &info) < 0)
|
||||
return 0;
|
||||
return false;
|
||||
|
||||
return info.play.buffer_size;
|
||||
}
|
||||
|
||||
#ifdef AUDIO_GETBUFINFO
|
||||
/* audio(4)'s play.seek is the count of bytes pending, so what the
|
||||
* device will take is its buffer less what is still queued in it.
|
||||
*
|
||||
* AUDIO_GETBUFINFO is NetBSD's, and so is seek: the Solaris
|
||||
* audio_prinfo carries buffer_size and samples and no seek, which is
|
||||
* why this is not offered there. Reporting the whole buffer instead -
|
||||
* which is what happened before - reads to rate control as a device
|
||||
* that never drains, and it answers a device that is always empty
|
||||
* with its full upward correction, for as long as it is on. The
|
||||
* interface asks for nothing rather than a constant, and nothing is
|
||||
* what the other branch gives. */
|
||||
static size_t audioio_write_avail(void *data)
|
||||
{
|
||||
struct audio_info info;
|
||||
int *fd = (int*)data;
|
||||
|
||||
if (ioctl(*fd, AUDIO_GETBUFINFO, &info) < 0)
|
||||
return 0;
|
||||
if (info.play.seek >= info.play.buffer_size)
|
||||
return 0;
|
||||
return info.play.buffer_size - info.play.seek;
|
||||
return audioio_buffer_size(data);
|
||||
}
|
||||
#endif
|
||||
|
||||
/* The device is opened with AUDIO_ENCODING_SLINEAR at 16 bits; the
|
||||
* audio(4) encodings are integer PCM, mu-law and A-law, none float. */
|
||||
static bool audioio_use_float(void *data) { return false; }
|
||||
|
||||
/* The nodes an audioio kernel offers: /dev/audio and /dev/audio0..15,
|
||||
* as oss.c walks /dev/dsp*. AUDIO_GETDEV names the hardware behind a
|
||||
* node on every audioio kernel, so the label carries it. A node that
|
||||
* cannot be opened for output is not offered; a busy one is, since it
|
||||
* exists and may be free later. Needs no driver instance, which is
|
||||
* what the menu has when init failed and the user needs to pick
|
||||
* another device. */
|
||||
static void *audioio_device_list_new(void *data)
|
||||
{
|
||||
int i;
|
||||
union string_list_elem_attr attr;
|
||||
struct string_list *sl = string_list_new();
|
||||
|
||||
(void)data;
|
||||
attr.i = 0;
|
||||
if (!sl)
|
||||
return NULL;
|
||||
|
||||
for (i = -1; i < 16; i++)
|
||||
{
|
||||
char path[32];
|
||||
char label[160];
|
||||
int fd;
|
||||
|
||||
if (i < 0)
|
||||
strlcpy(path, DEFAULT_DEV, sizeof(path));
|
||||
else
|
||||
snprintf(path, sizeof(path), "/dev/audio%d", i);
|
||||
|
||||
fd = open(path, O_WRONLY | O_NONBLOCK);
|
||||
if (fd < 0 && errno != EBUSY)
|
||||
continue;
|
||||
|
||||
strlcpy(label, path, sizeof(label));
|
||||
if (fd >= 0)
|
||||
{
|
||||
audio_device_t dev;
|
||||
if (ioctl(fd, AUDIO_GETDEV, &dev) == 0 && dev.name[0])
|
||||
{
|
||||
size_t _len = strlcat(label, " (", sizeof(label));
|
||||
_len += strlcpy(label + _len, dev.name, sizeof(label) - _len);
|
||||
strlcpy(label + _len, ")", sizeof(label) - _len);
|
||||
}
|
||||
close(fd);
|
||||
}
|
||||
string_list_append(sl, label, attr);
|
||||
}
|
||||
|
||||
if (!sl->size)
|
||||
{
|
||||
string_list_free(sl);
|
||||
return NULL;
|
||||
}
|
||||
return sl;
|
||||
}
|
||||
|
||||
static void audioio_device_list_free(void *data, void *array_list_data)
|
||||
{
|
||||
struct string_list *sl = (struct string_list*)array_list_data;
|
||||
(void)data;
|
||||
if (sl)
|
||||
string_list_free(sl);
|
||||
}
|
||||
|
||||
audio_driver_t audio_audioio = {
|
||||
audioio_init,
|
||||
audioio_write,
|
||||
@@ -299,13 +202,9 @@ audio_driver_t audio_audioio = {
|
||||
audioio_free,
|
||||
audioio_use_float,
|
||||
"audioio",
|
||||
audioio_device_list_new,
|
||||
audioio_device_list_free,
|
||||
#ifdef AUDIO_GETBUFINFO
|
||||
NULL,
|
||||
NULL,
|
||||
audioio_write_avail,
|
||||
#else
|
||||
NULL, /* write_avail - no way to measure the fill here */
|
||||
#endif
|
||||
audioio_buffer_size,
|
||||
NULL /* write_raw */
|
||||
};
|
||||
@@ -16,9 +16,7 @@
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
#include <malloc.h>
|
||||
#include <boolean.h>
|
||||
#include <retro_atomic.h>
|
||||
|
||||
#include <emscripten/wasm_worker.h>
|
||||
#include <emscripten/webaudio.h>
|
||||
@@ -67,11 +65,6 @@ typedef struct audioworklet_data
|
||||
* ring_init false and the driver does not come up. */
|
||||
retro_spsc_t ring;
|
||||
size_t ring_size;
|
||||
/* Quanta the worklet had to fill with silence because the ring came
|
||||
* up short, or because it could not get at the ring at all. Counted
|
||||
* here and not said: this is the render callback, and a line from
|
||||
* it costs part of the quantum it is reporting on. */
|
||||
retro_atomic_size_t underruns;
|
||||
bool ring_init;
|
||||
unsigned rate;
|
||||
unsigned latency;
|
||||
@@ -119,9 +112,7 @@ static bool audioworklet_process_cb(int numInputs, const AudioSampleFrame *input
|
||||
/* busyspin is safe as of emscripten 4.0.4 */
|
||||
if (!emscripten_lock_busyspin_wait_acquire(&audioworklet->buffer_lock, 2.5))
|
||||
{
|
||||
/* The output arrays arrive zeroed, so this quantum goes out
|
||||
* as silence. */
|
||||
retro_atomic_fetch_add_size(&audioworklet->underruns, 1);
|
||||
printf("[WARN] [AudioWorklet] Worklet: could not acquire lock\n");
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -145,10 +136,6 @@ static bool audioworklet_process_cb(int numInputs, const AudioSampleFrame *input
|
||||
if (writing_frames < outputs[0].samplesPerChannel)
|
||||
{
|
||||
int zero_frames = outputs[0].samplesPerChannel - writing_frames;
|
||||
/* Only where audio was expected: a driver that is not running is
|
||||
* not being starved. */
|
||||
if (audioworklet->driver_running)
|
||||
retro_atomic_fetch_add_size(&audioworklet->underruns, 1);
|
||||
memset(outputs[0].data + writing_frames, 0, zero_frames * sizeof(float));
|
||||
memset(outputs[0].data + writing_frames + outputs[0].samplesPerChannel, 0, zero_frames * sizeof(float));
|
||||
}
|
||||
@@ -300,10 +287,7 @@ static void *audioworklet_init(const char *device, unsigned rate,
|
||||
return NULL;
|
||||
audioworklet->worklet_stack = memalign(16, WORKLET_STACK_SIZE);
|
||||
if (!audioworklet->worklet_stack)
|
||||
{
|
||||
free(audioworklet);
|
||||
return NULL;
|
||||
}
|
||||
audioworklet_static_data = audioworklet;
|
||||
|
||||
audioworklet->latency = latency;
|
||||
@@ -317,7 +301,6 @@ static void *audioworklet_init(const char *device, unsigned rate,
|
||||
* below take their existing error exits. */
|
||||
if (!audioworklet->ring_init)
|
||||
audioworklet->init_error = true;
|
||||
retro_atomic_size_init(&audioworklet->underruns, 0);
|
||||
emscripten_lock_init(&audioworklet->buffer_lock);
|
||||
#ifdef PROXY_TO_PTHREAD
|
||||
emscripten_lock_init(&audioworklet->trywrite_lock);
|
||||
@@ -624,13 +607,6 @@ static size_t audioworklet_buffer_size(void *data)
|
||||
|
||||
static bool audioworklet_use_float(void *data) { return true; }
|
||||
|
||||
static size_t audioworklet_underruns(void *data)
|
||||
{
|
||||
audioworklet_data_t *audioworklet = (audioworklet_data_t*)data;
|
||||
return audioworklet
|
||||
? retro_atomic_load_acquire_size(&audioworklet->underruns) : 0;
|
||||
}
|
||||
|
||||
audio_driver_t audio_audioworklet = {
|
||||
audioworklet_init,
|
||||
audioworklet_write,
|
||||
@@ -645,8 +621,5 @@ audio_driver_t audio_audioworklet = {
|
||||
NULL,
|
||||
audioworklet_write_avail,
|
||||
audioworklet_buffer_size,
|
||||
NULL, /* write_raw */
|
||||
NULL, /* wait_writable */
|
||||
NULL, /* frames_consumed */
|
||||
audioworklet_underruns
|
||||
NULL /* write_raw */
|
||||
};
|
||||
+66
-58
@@ -328,12 +328,6 @@ typedef struct coreaudio
|
||||
* on that path, read by the frontend's overlay, and by it once at
|
||||
* teardown for the log. Never logged from here. */
|
||||
retro_atomic_size_t underruns;
|
||||
/* The frontend's reinit latch, taken on the main thread at init:
|
||||
* the default-device listeners run on HAL threads, where reaching
|
||||
* for a frontend singleton is the thing the worker-read audit
|
||||
* exists to catch. wasapi captures the same pointer at
|
||||
* sthread_create. */
|
||||
retro_atomic_int_t *reinit_request;
|
||||
|
||||
/* The largest number_frames the callback may be handed, as the
|
||||
* device and the unit describe themselves, and the largest it was
|
||||
@@ -353,19 +347,19 @@ typedef struct coreaudio
|
||||
/* Render callbacks handed invalid output buffers. */
|
||||
retro_atomic_size_t format_errors;
|
||||
|
||||
/* The worst a callback came up short by and how full the ring was
|
||||
* that time, in frames: the shortfall in the high 16 bits, the ring
|
||||
* in the low 16, each saturating at 0xFFFF. One word, so the two
|
||||
* always come from the same callback. Written by the render thread
|
||||
* and read once at teardown - never logged from there. A buzz is a
|
||||
* callback finding less than a period and padding the rest with
|
||||
* silence, so these two numbers say whether that is what is
|
||||
* happening and by how much, which "it buzzes" cannot. Out here rather than beside period_frames, which is the
|
||||
/* The worst a callback came up short by, in samples, and how full
|
||||
* the ring was that time. Written by the render thread with two
|
||||
* atomic stores and read once at teardown - never logged from
|
||||
* there. A buzz is a callback finding less than a period and
|
||||
* padding the rest with silence, so these two numbers say whether
|
||||
* that is what is happening and by how much, which "it buzzes"
|
||||
* cannot. Out here rather than beside period_frames, which is the
|
||||
* HAL's and macOS-only: the callback and the teardown log read
|
||||
* these on every Apple platform, so declaring them under
|
||||
* !TARGET_OS_IPHONE left the iOS and tvOS builds referring to
|
||||
* members that were not there. */
|
||||
retro_atomic_int_t worst_short;
|
||||
retro_atomic_size_t worst_short;
|
||||
retro_atomic_size_t worst_short_avail;
|
||||
|
||||
/* The output unit: ComponentInstance or AudioComponentInstance,
|
||||
* both of which are this type on every SDK. */
|
||||
@@ -420,12 +414,14 @@ typedef struct coreaudio
|
||||
uint64_t ct_anchor_host;
|
||||
int ct_have_anchor;
|
||||
double ct_sx, ct_sy, ct_sxx, ct_sxy, ct_n;
|
||||
retro_atomic_int_t ct_ppm; /* AUDIO_CLOCK_PPM_NONE until known */
|
||||
retro_atomic_int_t ct_ppm;
|
||||
retro_atomic_int_t ct_valid;
|
||||
/* What the HAL says itself, where it fills it in: mRateScalar is
|
||||
* the device clock against nominal, already computed by someone
|
||||
* with better information than this fit has. Kept beside it rather
|
||||
* than instead of it, so the two can be compared on hardware. */
|
||||
retro_atomic_int_t ct_scalar_ppm; /* AUDIO_CLOCK_PPM_NONE until known */
|
||||
retro_atomic_int_t ct_scalar_ppm;
|
||||
retro_atomic_int_t ct_scalar_valid;
|
||||
unsigned output_rate; /* Hardware output rate */
|
||||
/* The layout the output unit's input bus was set to, as the
|
||||
* frontend's mask, and its channel count: the unit routes each
|
||||
@@ -578,8 +574,6 @@ static void coreaudio_free(void *data)
|
||||
{
|
||||
coreaudio_t *dev = (coreaudio_t*)data;
|
||||
size_t n;
|
||||
int clk_ppm;
|
||||
unsigned worst;
|
||||
|
||||
if (!dev)
|
||||
return;
|
||||
@@ -589,12 +583,11 @@ static void coreaudio_free(void *data)
|
||||
* shortfall against the ring and the period, which is what says
|
||||
* whether a buzz is the ring being too small for the device's
|
||||
* period rather than the source being late. */
|
||||
worst = (unsigned)retro_atomic_load_acquire_int(&dev->worst_short);
|
||||
if ((n = retro_atomic_load_acquire_size(&dev->underruns)))
|
||||
RARCH_LOG("[CoreAudio] The callback came up short %u time%s; at worst it wanted %u samples more than the %u it found, against a %u-sample ring and a %u-frame device period.\n",
|
||||
(unsigned)n, n == 1 ? "" : "s",
|
||||
(worst >> 16) * dev->channels,
|
||||
(worst & 0xFFFFu) * dev->channels,
|
||||
(unsigned)retro_atomic_load_acquire_size(&dev->worst_short),
|
||||
(unsigned)retro_atomic_load_acquire_size(&dev->worst_short_avail),
|
||||
(unsigned)dev->usable,
|
||||
#if !TARGET_OS_IPHONE
|
||||
(unsigned)dev->period_frames
|
||||
@@ -610,18 +603,17 @@ static void coreaudio_free(void *data)
|
||||
* measurements, and a clock estimate that is wrong is worse than
|
||||
* one that is absent. Where both appear they should agree, and a
|
||||
* disagreement is the interesting result. */
|
||||
clk_ppm = retro_atomic_load_acquire_int(&dev->ct_ppm);
|
||||
if (clk_ppm != AUDIO_CLOCK_PPM_NONE)
|
||||
if (retro_atomic_load_acquire_int(&dev->ct_valid))
|
||||
RARCH_LOG("[CoreAudio] Device clock, fitted from the callback's"
|
||||
" timestamps: %+d ppm against %u Hz.\n",
|
||||
clk_ppm, dev->output_rate);
|
||||
retro_atomic_load_acquire_int(&dev->ct_ppm), dev->output_rate);
|
||||
else
|
||||
RARCH_LOG("[CoreAudio] Device clock: not enough usable timestamps"
|
||||
" to fit one.\n");
|
||||
clk_ppm = retro_atomic_load_acquire_int(&dev->ct_scalar_ppm);
|
||||
if (clk_ppm != AUDIO_CLOCK_PPM_NONE)
|
||||
if (retro_atomic_load_acquire_int(&dev->ct_scalar_valid))
|
||||
RARCH_LOG("[CoreAudio] Device clock, as the HAL reports it"
|
||||
" (mRateScalar): %+d ppm.\n", clk_ppm);
|
||||
" (mRateScalar): %+d ppm.\n",
|
||||
retro_atomic_load_acquire_int(&dev->ct_scalar_ppm));
|
||||
|
||||
/* What the device was said to be able to ask for, and what it did
|
||||
* ask for. Said whether or not anything went wrong, because the
|
||||
@@ -683,6 +675,7 @@ static OSStatus coreaudio_audio_write_cb(void *userdata,
|
||||
{
|
||||
retro_atomic_store_release_int(&dev->ct_scalar_ppm,
|
||||
(int)((time_stamp->mRateScalar - 1.0) * 1000000.0));
|
||||
retro_atomic_store_release_int(&dev->ct_scalar_valid, 1);
|
||||
}
|
||||
|
||||
if ( (f & kAudioTimeStampSampleTimeValid)
|
||||
@@ -727,6 +720,7 @@ static OSStatus coreaudio_audio_write_cb(void *userdata,
|
||||
if (ppm > -100000.0 && ppm < 100000.0)
|
||||
{
|
||||
retro_atomic_store_release_int(&dev->ct_ppm, (int)ppm);
|
||||
retro_atomic_store_release_int(&dev->ct_valid, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -819,18 +813,12 @@ static OSStatus coreaudio_audio_write_cb(void *userdata,
|
||||
*action_flags |= kAudioUnitRenderAction_OutputIsSilence;
|
||||
retro_atomic_fetch_add_size(&dev->underruns, 1);
|
||||
{
|
||||
/* Whole frames both: avail was rounded down above, and
|
||||
* frames_needed is a multiple of the channel count. */
|
||||
size_t shortfall = (frames_needed - avail) / dev->channels;
|
||||
size_t have = avail / dev->channels;
|
||||
if (shortfall > 0xFFFF)
|
||||
shortfall = 0xFFFF;
|
||||
if (have > 0xFFFF)
|
||||
have = 0xFFFF;
|
||||
if (shortfall > ((unsigned)retro_atomic_load_acquire_int(
|
||||
&dev->worst_short) >> 16))
|
||||
retro_atomic_store_release_int(&dev->worst_short,
|
||||
(int)((shortfall << 16) | have));
|
||||
size_t shortfall = frames_needed - avail;
|
||||
if (shortfall > retro_atomic_load_acquire_size(&dev->worst_short))
|
||||
{
|
||||
retro_atomic_store_release_size(&dev->worst_short, shortfall);
|
||||
retro_atomic_store_release_size(&dev->worst_short_avail, avail);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -1383,11 +1371,11 @@ static void *coreaudio_init(const char *device,
|
||||
}
|
||||
dev->ct_have_anchor = 0;
|
||||
dev->ct_n = 0.0;
|
||||
retro_atomic_int_init(&dev->ct_ppm, AUDIO_CLOCK_PPM_NONE);
|
||||
retro_atomic_int_init(&dev->ct_scalar_ppm, AUDIO_CLOCK_PPM_NONE);
|
||||
retro_atomic_int_init(&dev->ct_ppm, 0);
|
||||
retro_atomic_int_init(&dev->ct_valid, 0);
|
||||
retro_atomic_int_init(&dev->ct_scalar_ppm, 0);
|
||||
retro_atomic_int_init(&dev->ct_scalar_valid, 0);
|
||||
retro_atomic_size_init(&dev->underruns, 0);
|
||||
/* Taken here, on the main thread, for the HAL's listeners. */
|
||||
dev->reinit_request = &audio_state_get_ptr()->reinit_request;
|
||||
/* What coreaudio_run() waits for before starting; the HAL's pull on
|
||||
* macOS, and on iOS the ring's own quarter, which is what the unit
|
||||
* was asked to use. */
|
||||
@@ -1399,7 +1387,8 @@ static void *coreaudio_init(const char *device,
|
||||
retro_atomic_size_init(&dev->max_pull_observed, 0);
|
||||
retro_atomic_size_init(&dev->oversized_pulls, 0);
|
||||
retro_atomic_size_init(&dev->format_errors, 0);
|
||||
retro_atomic_int_init(&dev->worst_short, 0);
|
||||
retro_atomic_size_init(&dev->worst_short, 0);
|
||||
retro_atomic_size_init(&dev->worst_short_avail, 0);
|
||||
dev->write_ptr = 0;
|
||||
dev->read_ptr = 0;
|
||||
|
||||
@@ -1801,16 +1790,19 @@ static void coreaudio_device_list_free(void *data, void *array_list_data)
|
||||
static bool coreaudio_device_clock_ppm(void *data, double *ppm)
|
||||
{
|
||||
coreaudio_t *dev = (coreaudio_t*)data;
|
||||
int v;
|
||||
if (!dev)
|
||||
return false;
|
||||
v = retro_atomic_load_acquire_int(&dev->ct_scalar_ppm);
|
||||
if (v == AUDIO_CLOCK_PPM_NONE)
|
||||
v = retro_atomic_load_acquire_int(&dev->ct_ppm);
|
||||
if (v == AUDIO_CLOCK_PPM_NONE)
|
||||
return false;
|
||||
*ppm = (double)v;
|
||||
return true;
|
||||
if (retro_atomic_load_acquire_int(&dev->ct_scalar_valid))
|
||||
{
|
||||
*ppm = (double)retro_atomic_load_acquire_int(&dev->ct_scalar_ppm);
|
||||
return true;
|
||||
}
|
||||
if (retro_atomic_load_acquire_int(&dev->ct_valid))
|
||||
{
|
||||
*ppm = (double)retro_atomic_load_acquire_int(&dev->ct_ppm);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static size_t coreaudio_frames_consumed(void *data)
|
||||
@@ -1904,8 +1896,9 @@ audio_driver_t audio_coreaudio = {
|
||||
* built again against whatever the default now is. */
|
||||
static void coreaudio_output_default_moved(coreaudio_t *dev)
|
||||
{
|
||||
if (dev && dev->follows_default && dev->reinit_request)
|
||||
retro_atomic_store_release_int(dev->reinit_request, 1);
|
||||
if (dev && dev->follows_default)
|
||||
retro_atomic_store_release_int(
|
||||
&audio_state_get_ptr()->reinit_request, 1);
|
||||
}
|
||||
|
||||
static OSStatus coreaudio_output_default_listener(ca_obj_id_t obj,
|
||||
@@ -2005,7 +1998,7 @@ typedef struct coreaudio_mic
|
||||
/* The reader's park, notified by the capture callback after each
|
||||
* slice it writes (and on the ones it cannot: a full ring still
|
||||
* moves the epoch, so a parked reader re-checks). An eventcount,
|
||||
* as in sdl2_audio's mic: the callback's notify is an atomic bump
|
||||
* as in sdl_audio's mic: the callback's notify is an atomic bump
|
||||
* and a load with no reader parked - no lock ever on the capture
|
||||
* thread - and the reader's timed waits re-check the ring inside
|
||||
* the prepare/commit window, so a slice landing between the check
|
||||
@@ -2017,6 +2010,7 @@ typedef struct coreaudio_mic
|
||||
void *cb_buf;
|
||||
size_t cb_buf_size;
|
||||
unsigned sample_rate;
|
||||
bool nonblock;
|
||||
bool use_float;
|
||||
#if !TARGET_OS_IPHONE
|
||||
AudioDeviceID device;
|
||||
@@ -2028,10 +2022,12 @@ typedef struct coreaudio_mic
|
||||
/* Driver-wide context: created by init(), destroyed by free(). The
|
||||
* frontend holds it for as long as the driver is selected, longer than
|
||||
* any microphone opened through it, so it is never the same allocation
|
||||
* as one. It tracks the one microphone open through it. */
|
||||
* as one. It latches the non-blocking state so a mic opened later
|
||||
* inherits it. */
|
||||
typedef struct coreaudio_mic_driver
|
||||
{
|
||||
coreaudio_mic_t *mic;
|
||||
bool nonblock;
|
||||
} coreaudio_mic_driver_t;
|
||||
|
||||
static void coreaudio_mic_close(void *driver_context, void *mic_context);
|
||||
@@ -2360,7 +2356,7 @@ static int coreaudio_mic_read(void *driver_context, void *mic_context,
|
||||
|
||||
avail = retro_spsc_read_avail(&mic->ring);
|
||||
n = avail < len ? avail : len;
|
||||
if (!n)
|
||||
if (!n && !mic->nonblock)
|
||||
{
|
||||
/* Blocking: one slice's worth of wait for the callback, timed,
|
||||
* with the ring re-checked inside the window. */
|
||||
@@ -2377,6 +2373,16 @@ static int coreaudio_mic_read(void *driver_context, void *mic_context,
|
||||
return (int)n;
|
||||
}
|
||||
|
||||
static void coreaudio_mic_set_nonblock_state(void *driver_context, bool state)
|
||||
{
|
||||
coreaudio_mic_driver_t *drv = (coreaudio_mic_driver_t*)driver_context;
|
||||
if (!drv)
|
||||
return;
|
||||
drv->nonblock = state;
|
||||
if (drv->mic)
|
||||
drv->mic->nonblock = state;
|
||||
}
|
||||
|
||||
static struct string_list *coreaudio_mic_device_list_new(const void *driver_context)
|
||||
{
|
||||
#if TARGET_OS_IPHONE
|
||||
@@ -2467,6 +2473,7 @@ static void *coreaudio_mic_open(void *driver_context, const char *device,
|
||||
retro_atomic_int_init(&mic->running, 0);
|
||||
retro_atomic_int_init(&mic->initialized, 0);
|
||||
mic->sample_rate = rate;
|
||||
mic->nonblock = drv->nonblock;
|
||||
if (!retro_eventcount_init(&mic->park))
|
||||
goto error;
|
||||
|
||||
@@ -2680,6 +2687,7 @@ microphone_driver_t microphone_coreaudio = {
|
||||
coreaudio_mic_init,
|
||||
coreaudio_mic_free,
|
||||
coreaudio_mic_read,
|
||||
coreaudio_mic_set_nonblock_state,
|
||||
"coreaudio",
|
||||
coreaudio_mic_device_list_new,
|
||||
coreaudio_mic_device_list_free,
|
||||
|
||||
+11
-156
@@ -17,7 +17,6 @@
|
||||
#include <string.h>
|
||||
|
||||
#include <retro_atomic.h>
|
||||
#include <features/features_cpu.h>
|
||||
#include <malloc.h>
|
||||
|
||||
#include "../audio_driver.h"
|
||||
@@ -25,17 +24,6 @@
|
||||
#include "../../retroarch.h"
|
||||
#include "../../verbosity.h"
|
||||
|
||||
/* libctru defines this from 1.5 on; it is the SoC clock by sixteen. */
|
||||
#ifndef SYSCLOCK_ARM11
|
||||
#define SYSCLOCK_ARM11 (16756991u * 8u * 2u)
|
||||
#endif
|
||||
|
||||
/* The rate the channel is set to and the rate the frontend is told,
|
||||
* which have to be the one number: the channel interpolates with
|
||||
* NDSP_INTERP_NONE, so anything the frontend resamples to that the
|
||||
* channel is not playing at is error with nothing to correct it. */
|
||||
#define CTR_DSP_AUDIO_RATE 32728
|
||||
|
||||
typedef struct
|
||||
{
|
||||
ndspWaveBuf dsp_buf; /* TODO/FIXME - find out alignment */
|
||||
@@ -54,27 +42,6 @@ typedef struct
|
||||
* that once every four seconds and would miss ninety wraps. */
|
||||
uint32_t last_pos;
|
||||
retro_atomic_size_t consumed;
|
||||
/* The DSP's clock against the rate the channel is set to, fitted
|
||||
* from the sample position this callback already reads and
|
||||
* svcGetSystemTick(), which counts in the ARM11's domain rather
|
||||
* than the DSP's. Accumulated in the callback, which owns all of
|
||||
* it, and published as one int in ppm.
|
||||
*
|
||||
* cpu_features_get_perf_counter() and not
|
||||
* cpu_features_get_time_usec(): the counter is svcGetSystemTick()
|
||||
* here, the ARM11's own tick; the clock is osGetTime(), which
|
||||
* resolves in milliseconds - a hundred parts per million of
|
||||
* quantisation on a ten second window, against a figure measured
|
||||
* in single parts. SYSCLOCK_ARM11 is the counter's scale, since
|
||||
* features_cpu.h offers no frequency beside it.
|
||||
*
|
||||
* A measurement. Nothing acts on it. */
|
||||
uint64_t clk_pos;
|
||||
uint64_t clk_anchor_pos;
|
||||
uint64_t clk_anchor_tick;
|
||||
int clk_have_anchor;
|
||||
double clk_sx, clk_sy, clk_sxx, clk_sxy, clk_n;
|
||||
retro_atomic_int_t clk_ppm; /* AUDIO_CLOCK_PPM_NONE until known */
|
||||
bool nonblock;
|
||||
bool playing;
|
||||
} ctr_dsp_audio_t;
|
||||
@@ -83,75 +50,18 @@ typedef struct
|
||||
#define CTR_DSP_AUDIO_COUNT_MASK (CTR_DSP_AUDIO_COUNT - 1u)
|
||||
#define CTR_DSP_AUDIO_SIZE (CTR_DSP_AUDIO_COUNT * sizeof(int16_t) * 2)
|
||||
|
||||
/* One (position, tick) pair into the fit. Runs on the DSP callback's
|
||||
* own thread, which owns every field it touches here. */
|
||||
static void ctr_dsp_audio_clock_sample(ctr_dsp_audio_t *ctr, uint32_t frames)
|
||||
{
|
||||
uint64_t tick = (uint64_t)cpu_features_get_perf_counter();
|
||||
double x, y, denom;
|
||||
|
||||
ctr->clk_pos += frames;
|
||||
|
||||
/* Taken on the first frame, on a position that went backwards, and
|
||||
* again once the window has run long enough to be worth
|
||||
* restarting. */
|
||||
if ( !ctr->clk_have_anchor
|
||||
|| tick <= ctr->clk_anchor_tick
|
||||
|| (tick - ctr->clk_anchor_tick) > (uint64_t)SYSCLOCK_ARM11 * 30)
|
||||
{
|
||||
ctr->clk_anchor_pos = ctr->clk_pos;
|
||||
ctr->clk_anchor_tick = tick;
|
||||
ctr->clk_have_anchor = 1;
|
||||
ctr->clk_sx = ctr->clk_sy = ctr->clk_sxx = ctr->clk_sxy
|
||||
= ctr->clk_n = 0.0;
|
||||
retro_atomic_store_release_int(&ctr->clk_ppm, AUDIO_CLOCK_PPM_NONE);
|
||||
return;
|
||||
}
|
||||
|
||||
/* Seconds and frames from the anchor: a fit on the raw values
|
||||
* loses its answer to cancellation. */
|
||||
x = (double)(tick - ctr->clk_anchor_tick) / (double)SYSCLOCK_ARM11;
|
||||
y = (double)(ctr->clk_pos - ctr->clk_anchor_pos);
|
||||
|
||||
ctr->clk_sx += x;
|
||||
ctr->clk_sy += y;
|
||||
ctr->clk_sxx += x * x;
|
||||
ctr->clk_sxy += x * y;
|
||||
ctr->clk_n += 1.0;
|
||||
|
||||
/* A second of window at least, as the interface asks. */
|
||||
if (ctr->clk_n < 4.0 || x < 1.0)
|
||||
return;
|
||||
|
||||
denom = ctr->clk_n * ctr->clk_sxx - ctr->clk_sx * ctr->clk_sx;
|
||||
if (denom <= 0.0)
|
||||
return;
|
||||
|
||||
{
|
||||
double slope = (ctr->clk_n * ctr->clk_sxy - ctr->clk_sx * ctr->clk_sy)
|
||||
/ denom;
|
||||
double ppm = (slope / (double)CTR_DSP_AUDIO_RATE - 1.0) * 1000000.0;
|
||||
if (ppm > -100000.0 && ppm < 100000.0)
|
||||
{
|
||||
retro_atomic_store_release_int(&ctr->clk_ppm, (int)ppm);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void ctr_dsp_audio_frame_cb(void *data)
|
||||
{
|
||||
ctr_dsp_audio_t *ctr = (ctr_dsp_audio_t*)data;
|
||||
uint32_t pos = ndspChnGetSamplePos(ctr->channel);
|
||||
|
||||
/* Unsigned subtraction and the mask do the unwrap: the difference is
|
||||
* right across the wrap without a comparison. Only this callback
|
||||
* writes either field. */
|
||||
uint32_t frames = (pos - ctr->last_pos) & CTR_DSP_AUDIO_COUNT_MASK;
|
||||
|
||||
retro_atomic_fetch_add_size(&ctr->consumed, (size_t)frames);
|
||||
retro_atomic_fetch_add_size(&ctr->consumed,
|
||||
(size_t)((pos - ctr->last_pos) & CTR_DSP_AUDIO_COUNT_MASK));
|
||||
ctr->last_pos = pos;
|
||||
|
||||
ctr_dsp_audio_clock_sample(ctr, frames);
|
||||
|
||||
LightEvent_Signal(&ctr->frame_event);
|
||||
}
|
||||
|
||||
@@ -184,11 +94,13 @@ static void *ctr_dsp_audio_init(const char *device, unsigned rate, unsigned late
|
||||
ndspExit();
|
||||
return NULL;
|
||||
}
|
||||
retro_atomic_int_init(&ctr->clk_ppm, AUDIO_CLOCK_PPM_NONE);
|
||||
LightEvent_Init(&ctr->frame_event, RESET_ONESHOT);
|
||||
ndspSetCallback(ctr_dsp_audio_frame_cb, ctr);
|
||||
|
||||
*new_rate = CTR_DSP_AUDIO_RATE;
|
||||
if (!ctr)
|
||||
return NULL;
|
||||
|
||||
*new_rate = 32730;
|
||||
|
||||
ctr->channel = 0;
|
||||
|
||||
@@ -198,7 +110,7 @@ static void *ctr_dsp_audio_init(const char *device, unsigned rate, unsigned late
|
||||
ndspChnReset(ctr->channel);
|
||||
ndspChnSetFormat(ctr->channel, NDSP_FORMAT_STEREO_PCM16);
|
||||
ndspChnSetInterp(ctr->channel, NDSP_INTERP_NONE);
|
||||
ndspChnSetRate(ctr->channel, (float)CTR_DSP_AUDIO_RATE);
|
||||
ndspChnSetRate(ctr->channel, 32728.0f);
|
||||
ndspChnWaveBufClear(ctr->channel);
|
||||
|
||||
ctr->dsp_buf.data_pcm16 = linearAlloc(CTR_DSP_AUDIO_SIZE);
|
||||
@@ -232,38 +144,6 @@ static void ctr_dsp_audio_free(void *data)
|
||||
* advance before giving up on it. */
|
||||
#define CTR_DSP_AUDIO_WAIT_LAPS 20
|
||||
|
||||
/* LightEvent_Wait() has no timeout, so a DSP that has stopped
|
||||
* signalling its frame event - reset, or the channel dropped coming
|
||||
* back from sleep - never returns from it. Never reached while the DSP
|
||||
* runs: it signals every frame, so this only decides how long a
|
||||
* stopped one takes to be noticed. */
|
||||
#define CTR_DSP_AUDIO_STALL_TIMEOUT_NS 256000000LL
|
||||
#define CTR_DSP_AUDIO_WAIT_WRITABLE_LAPS 8
|
||||
|
||||
/* LightEvent_WaitTimeout() is libctru 2's; on 1.x the wait is the
|
||||
* try-wait polled in slices short against the DSP's frame. Non-zero is
|
||||
* the timeout either way. */
|
||||
#ifdef USE_CTRULIB_2
|
||||
#define ctr_dsp_audio_frame_wait(ctr, ns) \
|
||||
LightEvent_WaitTimeout(&(ctr)->frame_event, (ns))
|
||||
#else
|
||||
#define CTR_DSP_AUDIO_POLL_SLICE_NS 500000LL
|
||||
|
||||
static int ctr_dsp_audio_frame_wait(ctr_dsp_audio_t *ctr, s64 timeout_ns)
|
||||
{
|
||||
s64 left = timeout_ns;
|
||||
|
||||
while (left > 0)
|
||||
{
|
||||
if (LightEvent_TryWait(&ctr->frame_event))
|
||||
return 0;
|
||||
svcSleepThread(CTR_DSP_AUDIO_POLL_SLICE_NS);
|
||||
left -= CTR_DSP_AUDIO_POLL_SLICE_NS;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
#endif
|
||||
|
||||
static ssize_t ctr_dsp_audio_write(void *data, const void *buf, size_t len)
|
||||
{
|
||||
u32 pos;
|
||||
@@ -385,9 +265,6 @@ static size_t ctr_dsp_audio_wait_writable(void *data, size_t len)
|
||||
{
|
||||
ctr_dsp_audio_t *ctr = (ctr_dsp_audio_t*)data;
|
||||
uint32_t want = (uint32_t)(len >> 2);
|
||||
/* Each wait ends on a timeout; this ends the loop when the DSP
|
||||
* keeps calling back but never frees enough. */
|
||||
int laps = CTR_DSP_AUDIO_WAIT_WRITABLE_LAPS;
|
||||
|
||||
if (want > (CTR_DSP_AUDIO_COUNT >> 1))
|
||||
want = CTR_DSP_AUDIO_COUNT >> 1;
|
||||
@@ -396,18 +273,13 @@ static size_t ctr_dsp_audio_wait_writable(void *data, size_t len)
|
||||
{
|
||||
uint32_t avail;
|
||||
if (!ctr->playing)
|
||||
break;
|
||||
return 0;
|
||||
avail = (ndspChnGetSamplePos(ctr->channel) - ctr->pos)
|
||||
& CTR_DSP_AUDIO_COUNT_MASK;
|
||||
if (avail >= want)
|
||||
return avail * 2 * sizeof(int16_t);
|
||||
if (--laps < 0)
|
||||
break;
|
||||
/* Non-zero is the timeout. */
|
||||
if (ctr_dsp_audio_frame_wait(ctr, CTR_DSP_AUDIO_STALL_TIMEOUT_NS))
|
||||
break;
|
||||
LightEvent_Wait(&ctr->frame_event);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Both in bytes of int16 stereo, as the interface asks: the ring is
|
||||
@@ -419,19 +291,6 @@ static size_t ctr_dsp_audio_buffer_size(void *data)
|
||||
return CTR_DSP_AUDIO_COUNT * 2 * sizeof(int16_t);
|
||||
}
|
||||
|
||||
static bool ctr_dsp_audio_device_clock_ppm(void *data, double *ppm)
|
||||
{
|
||||
ctr_dsp_audio_t *ctr = (ctr_dsp_audio_t*)data;
|
||||
int v;
|
||||
if (!ctr)
|
||||
return false;
|
||||
v = retro_atomic_load_acquire_int(&ctr->clk_ppm);
|
||||
if (v == AUDIO_CLOCK_PPM_NONE)
|
||||
return false;
|
||||
*ppm = (double)v;
|
||||
return true;
|
||||
}
|
||||
|
||||
audio_driver_t audio_ctr_dsp = {
|
||||
ctr_dsp_audio_init,
|
||||
ctr_dsp_audio_write,
|
||||
@@ -448,9 +307,5 @@ audio_driver_t audio_ctr_dsp = {
|
||||
ctr_dsp_audio_buffer_size,
|
||||
NULL, /* write_raw */
|
||||
ctr_dsp_audio_wait_writable,
|
||||
ctr_dsp_audio_frames_consumed,
|
||||
NULL, /* underruns */
|
||||
NULL, /* layout */
|
||||
NULL, /* frames_consumed_fallback */
|
||||
ctr_dsp_audio_device_clock_ppm
|
||||
ctr_dsp_audio_frames_consumed
|
||||
};
|
||||
+1
-11
@@ -151,9 +151,6 @@ typedef struct dsound
|
||||
* 48 kHz it holds twelve hours, and the estimate treats a wrap as a
|
||||
* restart. */
|
||||
retro_atomic_int_t frames_played;
|
||||
/* Blocks the mixer thread had room for and nothing to put in, so
|
||||
* the device played the silence written into them instead. */
|
||||
retro_atomic_size_t underruns;
|
||||
DWORD last_read_ptr;
|
||||
} dsound_t;
|
||||
|
||||
@@ -385,7 +382,6 @@ static DWORD CALLBACK dsound_thread(PVOID data)
|
||||
* fill block with silence. */
|
||||
memset(region.chunk1, 0, region.size1);
|
||||
memset(region.chunk2, 0, region.size2);
|
||||
retro_atomic_fetch_add_size(&ds->underruns, 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -986,12 +982,6 @@ static uint32_t dsound_layout(void *data)
|
||||
return ds ? ds->layout : AUDIO_LAYOUT_STEREO;
|
||||
}
|
||||
|
||||
static size_t dsound_underruns(void *data)
|
||||
{
|
||||
dsound_t *ds = (dsound_t*)data;
|
||||
return ds ? retro_atomic_load_acquire_size(&ds->underruns) : 0;
|
||||
}
|
||||
|
||||
audio_driver_t audio_dsound = {
|
||||
dsound_init,
|
||||
dsound_write,
|
||||
@@ -1009,6 +999,6 @@ audio_driver_t audio_dsound = {
|
||||
NULL, /* write_raw */
|
||||
dsound_wait_writable,
|
||||
dsound_frames_consumed,
|
||||
dsound_underruns,
|
||||
NULL, /* underruns */
|
||||
dsound_layout
|
||||
};
|
||||
+1
-143
@@ -21,13 +21,11 @@
|
||||
#ifdef GEKKO
|
||||
#include <gccore.h>
|
||||
#include <ogcsys.h>
|
||||
#include <ogc/lwp_watchdog.h>
|
||||
#else
|
||||
#include <cafe/ai.h>
|
||||
#endif
|
||||
|
||||
#include <boolean.h>
|
||||
#include <features/features_cpu.h>
|
||||
#include <retro_inline.h>
|
||||
|
||||
#include <defines/gx_defines.h>
|
||||
@@ -57,40 +55,6 @@ typedef struct
|
||||
* way. 32-bit so the read cannot tear: at 48 kHz it wraps in about
|
||||
* a day, which the estimator's differences handle. */
|
||||
volatile uint32_t consumed;
|
||||
/* Chunks the DMA started on that the writer had not reached: the
|
||||
* callback zeroes each chunk as it retires it, so what goes out is
|
||||
* silence. Volatile rather than atomic for the reason consumed is. */
|
||||
volatile uint32_t underruns;
|
||||
/* One (position, time) pair per chunk for the device clock, taken
|
||||
* in the callback because that is where the position is exact:
|
||||
* sampling consumed from the frontend would be a chunk out, which
|
||||
* over any usable window is more error than the figure being
|
||||
* measured. clk_seq brackets the pair so the reader can tell it
|
||||
* was not caught mid-write.
|
||||
*
|
||||
* The fit itself is done in device_clock_ppm(), on the frontend's
|
||||
* thread. This callback is a DMA interrupt, and floating point
|
||||
* there is not a thing to be doing.
|
||||
*
|
||||
* clk_tb is the PPC timebase, which is what
|
||||
* cpu_features_get_perf_counter() reads here - a counter, not a
|
||||
* clock. ticks_to_microsecs() is its scale; features_cpu.h offers
|
||||
* the counter and no frequency beside it. */
|
||||
volatile uint32_t clk_pos;
|
||||
volatile uint32_t clk_tb;
|
||||
volatile uint32_t clk_seq;
|
||||
/* The fit, touched only by device_clock_ppm(). Sums in seconds and
|
||||
* frames from the anchor, because a fit on raw values loses its
|
||||
* answer to cancellation. The anchor is retaken well inside the
|
||||
* 32-bit timebase's wrap. */
|
||||
uint32_t clk_last_seq;
|
||||
uint32_t clk_anchor_pos;
|
||||
uint32_t clk_anchor_tb;
|
||||
int clk_have_anchor;
|
||||
int clk_valid;
|
||||
int clk_ppm;
|
||||
double clk_sx, clk_sy, clk_sxx, clk_sxy, clk_n;
|
||||
unsigned rate;
|
||||
bool nonblock;
|
||||
bool is_paused;
|
||||
} gx_audio_t;
|
||||
@@ -116,23 +80,11 @@ static void gx_audio_dma_callback(void)
|
||||
wa->dma_busy = wa->dma_next;
|
||||
wa->dma_next = (wa->dma_next + 1) & (BLOCKS - 1);
|
||||
|
||||
/* The chunk the DMA is about to take is one the writer has not
|
||||
* filled, and the callback erased it as it retired. */
|
||||
if (wa->dma_next == wa->dma_write)
|
||||
wa->underruns++;
|
||||
|
||||
DCFlushRange(wa->data[wa->dma_next], CHUNK_SIZE);
|
||||
|
||||
AIInitDMA((uint32_t)wa->data[wa->dma_next], CHUNK_SIZE);
|
||||
/* A chunk the DMA has finished with: device time. */
|
||||
wa->consumed += CHUNK_FRAMES;
|
||||
|
||||
/* The pair the clock fit reads, published between two bumps of the
|
||||
* sequence so a reader can tell a torn one. */
|
||||
wa->clk_seq++;
|
||||
wa->clk_pos = wa->consumed;
|
||||
wa->clk_tb = (uint32_t)cpu_features_get_perf_counter();
|
||||
wa->clk_seq++;
|
||||
OSSignalCond(wa->dma_cond);
|
||||
}
|
||||
|
||||
@@ -167,13 +119,11 @@ static void *gx_audio_init(const char *device,
|
||||
{
|
||||
AISetDSPSampleRate(AI_SAMPLERATE_32KHZ);
|
||||
*new_rate = 32000;
|
||||
wa->rate = 32000;
|
||||
}
|
||||
else /* Ranges 32001-39999 (in settings going up from 32000) and 48000-max (default high) -> set to 48000 hz */
|
||||
{
|
||||
AISetDSPSampleRate(AI_SAMPLERATE_48KHZ);
|
||||
*new_rate = 48000;
|
||||
wa->rate = 48000;
|
||||
}
|
||||
|
||||
wa->dma_write = BLOCKS - 1;
|
||||
@@ -362,94 +312,6 @@ static size_t gx_audio_wait_writable(void *data, size_t len)
|
||||
* so the slot cannot be NULL either. */
|
||||
static bool gx_audio_use_float(void *data) { return false; }
|
||||
|
||||
static size_t gx_audio_underruns(void *data)
|
||||
{
|
||||
gx_audio_t *wa = (gx_audio_t*)data;
|
||||
return wa ? (size_t)wa->underruns : 0;
|
||||
}
|
||||
|
||||
/* The AI's own clock against the rate this driver reports, in parts
|
||||
* per million. What the hardware does with the rate it was asked for
|
||||
* is not the rate it was asked for: the AI divides its source clock by
|
||||
* a figure that does not land on 48000, and nothing downstream has
|
||||
* ever been told. A measurement; nothing acts on it. */
|
||||
static bool gx_audio_device_clock_ppm(void *data, double *ppm)
|
||||
{
|
||||
gx_audio_t *wa = (gx_audio_t*)data;
|
||||
uint32_t s1 = 0, s2 = 0, pos = 0, tb = 0;
|
||||
int tries = 4;
|
||||
|
||||
if (!wa || !wa->rate)
|
||||
return false;
|
||||
|
||||
/* A pair the callback was not in the middle of writing. */
|
||||
do
|
||||
{
|
||||
s1 = wa->clk_seq;
|
||||
pos = wa->clk_pos;
|
||||
tb = wa->clk_tb;
|
||||
s2 = wa->clk_seq;
|
||||
} while ((s1 != s2 || (s1 & 1u)) && --tries > 0);
|
||||
|
||||
if (s1 == s2 && !(s1 & 1u) && s1 != wa->clk_last_seq)
|
||||
{
|
||||
wa->clk_last_seq = s1;
|
||||
|
||||
/* Retaken on the first pair, when the DMA has restarted and the
|
||||
* position went backwards, and well inside the wrap of the
|
||||
* 32-bit timebase this samples. The frame bound catches the
|
||||
* case the tick bound cannot: nothing reads this while the
|
||||
* statistics are off, and a gap long enough to wrap the
|
||||
* timebase comes back looking like a short interval. */
|
||||
if ( !wa->clk_have_anchor
|
||||
|| pos - wa->clk_anchor_pos > 0x80000000u
|
||||
|| (uint32_t)(pos - wa->clk_anchor_pos) > wa->rate * 31u
|
||||
|| (uint64_t)ticks_to_microsecs(
|
||||
(uint64_t)(uint32_t)(tb - wa->clk_anchor_tb)) > 30000000ull)
|
||||
{
|
||||
wa->clk_anchor_pos = pos;
|
||||
wa->clk_anchor_tb = tb;
|
||||
wa->clk_have_anchor = 1;
|
||||
wa->clk_valid = 0;
|
||||
wa->clk_sx = wa->clk_sy = wa->clk_sxx = wa->clk_sxy = wa->clk_n = 0.0;
|
||||
}
|
||||
else
|
||||
{
|
||||
double x = (double)(uint64_t)ticks_to_microsecs(
|
||||
(uint64_t)(uint32_t)(tb - wa->clk_anchor_tb)) / 1000000.0;
|
||||
double y = (double)(uint32_t)(pos - wa->clk_anchor_pos);
|
||||
|
||||
wa->clk_sx += x;
|
||||
wa->clk_sy += y;
|
||||
wa->clk_sxx += x * x;
|
||||
wa->clk_sxy += x * y;
|
||||
wa->clk_n += 1.0;
|
||||
|
||||
/* A second of window at least, as the interface asks. */
|
||||
if (wa->clk_n >= 4.0 && x >= 1.0)
|
||||
{
|
||||
double denom = wa->clk_n * wa->clk_sxx - wa->clk_sx * wa->clk_sx;
|
||||
if (denom > 0.0)
|
||||
{
|
||||
double slope = (wa->clk_n * wa->clk_sxy
|
||||
- wa->clk_sx * wa->clk_sy) / denom;
|
||||
double est = (slope / (double)wa->rate - 1.0) * 1000000.0;
|
||||
if (est > -100000.0 && est < 100000.0)
|
||||
{
|
||||
wa->clk_ppm = (int)est;
|
||||
wa->clk_valid = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!wa->clk_valid)
|
||||
return false;
|
||||
*ppm = (double)wa->clk_ppm;
|
||||
return true;
|
||||
}
|
||||
|
||||
audio_driver_t audio_gx = {
|
||||
gx_audio_init,
|
||||
gx_audio_write,
|
||||
@@ -466,9 +328,5 @@ audio_driver_t audio_gx = {
|
||||
gx_audio_buffer_size,
|
||||
NULL, /* write_raw */
|
||||
gx_audio_wait_writable,
|
||||
gx_audio_frames_consumed,
|
||||
gx_audio_underruns,
|
||||
NULL, /* layout */
|
||||
NULL, /* frames_consumed_fallback */
|
||||
gx_audio_device_clock_ppm
|
||||
gx_audio_frames_consumed
|
||||
};
|
||||
+14
-23
@@ -43,11 +43,6 @@ typedef struct jack
|
||||
unsigned channels; /* ports, and samples a frame in the ring */
|
||||
uint32_t layout; /* the frontend's mask, one port a position */
|
||||
jack_ringbuffer_t *buffer;
|
||||
/* The frontend's reinit latch, taken on the main thread at init:
|
||||
* the server's callbacks run on its threads, where reaching for a
|
||||
* frontend singleton is the thing the worker-read audit exists to
|
||||
* catch. wasapi does the same at sthread_create. */
|
||||
retro_atomic_int_t *reinit_request;
|
||||
#ifdef HAVE_THREADS
|
||||
/* Parked on by the writer; notified from JACK's process and
|
||||
* shutdown callbacks. An eventcount, not a lock and condition
|
||||
@@ -107,7 +102,8 @@ typedef struct jack
|
||||
int clk_have_anchor;
|
||||
double clk_sx, clk_sy, clk_sxx, clk_sxy, clk_n;
|
||||
unsigned clk_rate;
|
||||
retro_atomic_int_t clk_ppm; /* AUDIO_CLOCK_PPM_NONE until known */
|
||||
retro_atomic_int_t clk_ppm;
|
||||
retro_atomic_int_t clk_valid;
|
||||
/* The server saying its rate or its period has changed. Both make
|
||||
* everything downstream wrong - what the resampler produces, what
|
||||
* the ring was sized for, how long a write waits - so both ask the
|
||||
@@ -149,13 +145,9 @@ static size_t ja_read_deinterleaved(jack_t *jd, float *dst[JACK_MAX_PORTS], jack
|
||||
static bool ja_device_clock_ppm(void *data, double *ppm)
|
||||
{
|
||||
jack_t *jd = (jack_t*)data;
|
||||
int v;
|
||||
if (!jd)
|
||||
if (!jd || !retro_atomic_load_acquire_int(&jd->clk_valid))
|
||||
return false;
|
||||
v = retro_atomic_load_acquire_int(&jd->clk_ppm);
|
||||
if (v == AUDIO_CLOCK_PPM_NONE)
|
||||
return false;
|
||||
*ppm = (double)v;
|
||||
*ppm = (double)retro_atomic_load_acquire_int(&jd->clk_ppm);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -229,6 +221,7 @@ static int ja_process_cb(jack_nframes_t nframes, void *data)
|
||||
if (ppm > -100000.0 && ppm < 100000.0)
|
||||
{
|
||||
retro_atomic_store_release_int(&jd->clk_ppm, (int)ppm);
|
||||
retro_atomic_store_release_int(&jd->clk_valid, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -294,8 +287,8 @@ static int ja_sample_rate_cb(jack_nframes_t nframes, void *data)
|
||||
if (jd)
|
||||
{
|
||||
retro_atomic_store_release_int(&jd->reconfigure, 1);
|
||||
if (jd->reinit_request)
|
||||
retro_atomic_store_release_int(jd->reinit_request, 1);
|
||||
retro_atomic_store_release_int(
|
||||
&audio_state_get_ptr()->reinit_request, 1);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
@@ -306,8 +299,8 @@ static int ja_buffer_size_cb(jack_nframes_t nframes, void *data)
|
||||
if (jd)
|
||||
{
|
||||
retro_atomic_store_release_int(&jd->reconfigure, 1);
|
||||
if (jd->reinit_request)
|
||||
retro_atomic_store_release_int(jd->reinit_request, 1);
|
||||
retro_atomic_store_release_int(
|
||||
&audio_state_get_ptr()->reinit_request, 1);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
@@ -448,15 +441,14 @@ static void *ja_init(const char *device,
|
||||
goto error;
|
||||
#endif
|
||||
|
||||
jd->reinit_request = &audio_state_get_ptr()->reinit_request;
|
||||
|
||||
jd->client = jack_client_open("RetroArch", JackNullOption, NULL);
|
||||
if (!jd->client)
|
||||
goto error;
|
||||
|
||||
*new_rate = jack_get_sample_rate(jd->client);
|
||||
jd->clk_rate = *new_rate;
|
||||
retro_atomic_int_init(&jd->clk_ppm, AUDIO_CLOCK_PPM_NONE);
|
||||
retro_atomic_int_init(&jd->clk_ppm, 0);
|
||||
retro_atomic_int_init(&jd->clk_valid, 0);
|
||||
|
||||
jack_set_process_callback(jd->client, ja_process_cb, jd);
|
||||
/* Registered before activation, as JACK requires of a client that
|
||||
@@ -705,7 +697,6 @@ static void ja_free(void *data)
|
||||
{
|
||||
jack_t *jd = (jack_t*)data;
|
||||
size_t x;
|
||||
int clk_ppm;
|
||||
|
||||
retro_atomic_store_release_int(&jd->shutdown, 1);
|
||||
|
||||
@@ -714,10 +705,10 @@ static void ja_free(void *data)
|
||||
* are measurements, and a clock estimate that is wrong is worse
|
||||
* than one that is absent. With a device driving the graph this is
|
||||
* that device; with something else driving it, it is that. */
|
||||
clk_ppm = retro_atomic_load_acquire_int(&jd->clk_ppm);
|
||||
if (clk_ppm != AUDIO_CLOCK_PPM_NONE)
|
||||
if (retro_atomic_load_acquire_int(&jd->clk_valid))
|
||||
RARCH_LOG("[JACK] Server clock, fitted from the cycle times:"
|
||||
" %+d ppm against %u Hz.\n", clk_ppm, jd->clk_rate);
|
||||
" %+d ppm against %u Hz.\n",
|
||||
retro_atomic_load_acquire_int(&jd->clk_ppm), jd->clk_rate);
|
||||
else
|
||||
RARCH_LOG("[JACK] Server clock: not enough usable cycle times to"
|
||||
" fit one.\n");
|
||||
|
||||
+3
-23
@@ -137,10 +137,6 @@ typedef struct al
|
||||
/* Frames the source has finished playing, for the sink rate
|
||||
* estimate; see al_frames_consumed(). */
|
||||
retro_atomic_size_t consumed;
|
||||
/* Times the source was found STOPPED with a write to make: it had
|
||||
* played its queue out and the device went quiet for want of
|
||||
* audio. INITIAL is the first write, which has not started it. */
|
||||
retro_atomic_size_t underruns;
|
||||
} al_t;
|
||||
|
||||
static void al_free(void *data)
|
||||
@@ -331,14 +327,8 @@ static void *al_init(const char *device, unsigned rate, unsigned latency,
|
||||
* OpenAL Soft extension, so asked for rather than assumed: core
|
||||
* OpenAL has nothing of the kind and Apple's does not carry it. */
|
||||
if (alcIsExtensionPresent(al->handle, "ALC_SOFT_device_clock"))
|
||||
{
|
||||
/* Through a union, as al_init_events() resolves its own: an
|
||||
* object pointer cast to a function pointer is not legal C. */
|
||||
union { void *p; al_get_integer64v_t f; } clk;
|
||||
clk.p = alcGetProcAddress(al->handle, "alcGetInteger64vSOFT");
|
||||
if (clk.p)
|
||||
al->clk_get = clk.f;
|
||||
}
|
||||
al->clk_get = (al_get_integer64v_t)alcGetProcAddress(
|
||||
al->handle, "alcGetInteger64vSOFT");
|
||||
RARCH_LOG("[OpenAL] Device clock: %s.\n",
|
||||
al->clk_get ? "reported by the implementation (ALC_SOFT_device_clock)"
|
||||
: "not offered by this implementation");
|
||||
@@ -614,11 +604,7 @@ static ssize_t al_write(void *data, const void *s, size_t len)
|
||||
ALint val;
|
||||
alGetSourcei(al->source, AL_SOURCE_STATE, &val);
|
||||
if (val != AL_PLAYING)
|
||||
{
|
||||
if (val == AL_STOPPED)
|
||||
retro_atomic_fetch_add_size(&al->underruns, 1);
|
||||
alSourcePlay(al->source);
|
||||
}
|
||||
}
|
||||
|
||||
return _len;
|
||||
@@ -798,12 +784,6 @@ static void al_device_list_free(void *u, void *slp)
|
||||
string_list_free(sl);
|
||||
}
|
||||
|
||||
static size_t al_underruns(void *data)
|
||||
{
|
||||
al_t *al = (al_t*)data;
|
||||
return al ? retro_atomic_load_acquire_size(&al->underruns) : 0;
|
||||
}
|
||||
|
||||
audio_driver_t audio_openal = {
|
||||
al_init,
|
||||
al_write,
|
||||
@@ -821,7 +801,7 @@ audio_driver_t audio_openal = {
|
||||
NULL, /* write_raw */
|
||||
al_wait_writable,
|
||||
al_frames_consumed,
|
||||
al_underruns,
|
||||
NULL, /* underruns */
|
||||
al_layout,
|
||||
NULL, /* frames_consumed_fallback */
|
||||
al_device_clock_ppm
|
||||
|
||||
+3
-77
@@ -34,7 +34,7 @@
|
||||
* reached in normal operation - the callback fires as each enqueued
|
||||
* block finishes - so the value only decides how long a device that
|
||||
* has stopped consuming takes to be noticed. Matches the flat
|
||||
* bail-out timeouts in sdl2_audio.c and wasapi.c. */
|
||||
* bail-out timeouts in sdl_audio.c and wasapi.c. */
|
||||
#define OPENSL_STALL_TIMEOUT_US 256000
|
||||
|
||||
/* Helper macros, COM-style. */
|
||||
@@ -69,10 +69,6 @@ typedef struct sl
|
||||
* level 21+); blocks then hold 32-bit float frames and the frontend
|
||||
* skips its float-to-int16 pass. */
|
||||
bool use_float;
|
||||
/* The layout asked for, and the channels the player was created
|
||||
* with. They agree unless the wider format was refused. */
|
||||
uint32_t layout;
|
||||
unsigned channels;
|
||||
bool nonblock;
|
||||
bool is_paused;
|
||||
} sl_t;
|
||||
@@ -108,9 +104,6 @@ typedef struct sl_shared
|
||||
* and the ASIO callback count. Written only by the callback, read by
|
||||
* the frontend through sl_frames_consumed(). */
|
||||
retro_atomic_size_t consumed;
|
||||
/* Callbacks that left the queue empty: the device has nothing to
|
||||
* play next and goes quiet until the writer enqueues again. */
|
||||
retro_atomic_size_t underruns;
|
||||
unsigned frames_per_block;
|
||||
bool park_ready;
|
||||
} sl_shared_t;
|
||||
@@ -135,8 +128,7 @@ static void opensl_callback(SLAndroidSimpleBufferQueueItf bq, void *ctx)
|
||||
/* A player torn down, or replaced, since this was dispatched. */
|
||||
if (!retro_atomic_load_acquire_int(&sh->live) || bq != sh->bq)
|
||||
return;
|
||||
if (retro_atomic_fetch_sub_int(&sh->buffered_blocks, 1) == 1)
|
||||
retro_atomic_fetch_add_size(&sh->underruns, 1);
|
||||
retro_atomic_fetch_sub_int(&sh->buffered_blocks, 1);
|
||||
/* A block the device has played: device time, whatever the writer
|
||||
* managed to supply. */
|
||||
retro_atomic_fetch_add_size(&sh->consumed, sh->frames_per_block);
|
||||
@@ -227,7 +219,6 @@ static void *sl_init(const char *device, unsigned rate, unsigned latency,
|
||||
{
|
||||
unsigned i;
|
||||
unsigned frames_per_block;
|
||||
unsigned channels;
|
||||
unsigned frame_size = 2 * sizeof(int16_t);
|
||||
SLDataFormat_PCM fmt_pcm = {0};
|
||||
#if defined(ANDROID) && defined(__ANDROID_API__) && (__ANDROID_API__ >= 21)
|
||||
@@ -263,14 +254,6 @@ static void *sl_init(const char *device, unsigned rate, unsigned latency,
|
||||
|
||||
RARCH_LOG("[OpenSL] Requested audio latency: %u ms.\n", latency);
|
||||
|
||||
/* Anything wider than stereo needs the PCM_EX format below, which
|
||||
* is API level 21 and up; without it the count is not one this
|
||||
* driver can ask for and the stereo path is what runs. */
|
||||
sl->layout = audio_driver_requested_layout();
|
||||
channels = audio_layout_channels(sl->layout);
|
||||
if (channels < 2 || channels > 8)
|
||||
channels = 2;
|
||||
|
||||
GOTO_IF_FAIL(slCreateEngine(&sl->engine_object, 0, NULL, 0, NULL, NULL));
|
||||
GOTO_IF_FAIL(SLObjectItf_Realize(sl->engine_object, SL_BOOLEAN_FALSE));
|
||||
GOTO_IF_FAIL(SLObjectItf_GetInterface(sl->engine_object, SL_IID_ENGINE, &sl->engine));
|
||||
@@ -316,46 +299,10 @@ static void *sl_init(const char *device, unsigned rate, unsigned latency,
|
||||
audio_sink.pLocator = &loc_outmix;
|
||||
|
||||
#if defined(ANDROID) && defined(__ANDROID_API__) && (__ANDROID_API__ >= 21)
|
||||
/* The layout the frontend wants, where the player takes it. The
|
||||
* frontend's mask is the channel mask OpenSL asks for - both are
|
||||
* the WAVEFORMATEXTENSIBLE bits - so it goes across as it is, and
|
||||
* the frames arrive in its ascending-bit order either way. Refused,
|
||||
* this falls through to the stereo attempts below unchanged. */
|
||||
if (channels > 2)
|
||||
{
|
||||
fmt_pcm_ex.formatType = SL_ANDROID_DATAFORMAT_PCM_EX;
|
||||
fmt_pcm_ex.numChannels = channels;
|
||||
fmt_pcm_ex.sampleRate = rate * 1000; /* milli-Hz */
|
||||
fmt_pcm_ex.bitsPerSample = 32;
|
||||
fmt_pcm_ex.containerSize = 32;
|
||||
fmt_pcm_ex.channelMask = sl->layout;
|
||||
fmt_pcm_ex.endianness = SL_BYTEORDER_LITTLEENDIAN;
|
||||
fmt_pcm_ex.representation = SL_ANDROID_PCM_REPRESENTATION_FLOAT;
|
||||
audio_src.pFormat = &fmt_pcm_ex;
|
||||
|
||||
res = SLEngineItf_CreateAudioPlayer(sl->engine, &sl->buffer_queue_object,
|
||||
&audio_src, &audio_sink, 1, &id, &req);
|
||||
if (res == SL_RESULT_SUCCESS)
|
||||
{
|
||||
sl->use_float = true;
|
||||
sl->channels = channels;
|
||||
frame_size = channels * sizeof(float);
|
||||
RARCH_LOG("[OpenSL] Float output, %u channels.\n", channels);
|
||||
}
|
||||
else
|
||||
{
|
||||
sl->buffer_queue_object = NULL;
|
||||
RARCH_LOG("[OpenSL] %u channels refused (0x%x), using stereo.\n",
|
||||
channels, (unsigned)res);
|
||||
}
|
||||
}
|
||||
|
||||
/* Float first. API level 21 added SLAndroidDataFormat_PCM_EX; a
|
||||
* runtime that refuses it (older device, or a libOpenSLES built
|
||||
* without it) fails player creation, and the 16-bit format below is
|
||||
* tried in that case. */
|
||||
if (!sl->use_float)
|
||||
{
|
||||
fmt_pcm_ex.formatType = SL_ANDROID_DATAFORMAT_PCM_EX;
|
||||
fmt_pcm_ex.numChannels = 2;
|
||||
fmt_pcm_ex.sampleRate = rate * 1000; /* milli-Hz */
|
||||
@@ -371,7 +318,6 @@ static void *sl_init(const char *device, unsigned rate, unsigned latency,
|
||||
if (res == SL_RESULT_SUCCESS)
|
||||
{
|
||||
sl->use_float = true;
|
||||
sl->channels = 2;
|
||||
frame_size = 2 * sizeof(float);
|
||||
RARCH_LOG("[OpenSL] Float output.\n");
|
||||
}
|
||||
@@ -381,7 +327,6 @@ static void *sl_init(const char *device, unsigned rate, unsigned latency,
|
||||
RARCH_LOG("[OpenSL] Float output refused (0x%x), using 16-bit.\n",
|
||||
(unsigned)res);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
if (!sl->use_float)
|
||||
@@ -398,7 +343,6 @@ static void *sl_init(const char *device, unsigned rate, unsigned latency,
|
||||
GOTO_IF_FAIL(SLEngineItf_CreateAudioPlayer(sl->engine, &sl->buffer_queue_object,
|
||||
&audio_src, &audio_sink,
|
||||
1, &id, &req));
|
||||
sl->channels = 2;
|
||||
frame_size = 2 * sizeof(int16_t);
|
||||
}
|
||||
GOTO_IF_FAIL(SLObjectItf_Realize(sl->buffer_queue_object, SL_BOOLEAN_FALSE));
|
||||
@@ -635,22 +579,6 @@ static bool sl_use_float(void *data)
|
||||
return sl->use_float;
|
||||
}
|
||||
|
||||
static size_t sl_underruns(void *data)
|
||||
{
|
||||
sl_t *sl = (sl_t*)data;
|
||||
if (!sl)
|
||||
return 0;
|
||||
return retro_atomic_load_acquire_size(&sl_shared.underruns);
|
||||
}
|
||||
|
||||
static uint32_t sl_layout(void *data)
|
||||
{
|
||||
sl_t *sl = (sl_t*)data;
|
||||
if (!sl || sl->channels != audio_layout_channels(sl->layout))
|
||||
return AUDIO_LAYOUT_STEREO;
|
||||
return sl->layout;
|
||||
}
|
||||
|
||||
audio_driver_t audio_opensl = {
|
||||
sl_init,
|
||||
sl_write,
|
||||
@@ -667,7 +595,5 @@ audio_driver_t audio_opensl = {
|
||||
sl_buffer_size,
|
||||
NULL, /* write_raw */
|
||||
sl_wait_writable,
|
||||
sl_frames_consumed,
|
||||
sl_underruns,
|
||||
sl_layout
|
||||
sl_frames_consumed
|
||||
};
|
||||
+1
-10
@@ -37,9 +37,7 @@
|
||||
#include "../../config.h"
|
||||
#endif
|
||||
|
||||
#include <retro_miscellaneous.h>
|
||||
#include "../audio_driver.h"
|
||||
#include "../audio_device_label.h"
|
||||
#include "../../verbosity.h"
|
||||
|
||||
#ifdef HAVE_OSS_BSD
|
||||
@@ -83,19 +81,12 @@ static void *oss_init(const char *device,
|
||||
unsigned *new_out_rate)
|
||||
{
|
||||
int frags, frag, channels, format, new_rate;
|
||||
char oss_device[PATH_MAX_LENGTH];
|
||||
oss_audio_t *ossaudio = (oss_audio_t*)calloc(1, sizeof(oss_audio_t));
|
||||
const char *oss_device = device ? device : DEFAULT_OSS_DEV;
|
||||
|
||||
if (!ossaudio)
|
||||
return NULL;
|
||||
|
||||
/* The device may be a list entry, "/dev/dsp0 (card)": the path is
|
||||
* what is opened. */
|
||||
if (device)
|
||||
audio_device_label_path(oss_device, sizeof(oss_device), device);
|
||||
else
|
||||
strlcpy(oss_device, DEFAULT_OSS_DEV, sizeof(oss_device));
|
||||
|
||||
if ((ossaudio->fd = open(oss_device, O_WRONLY)) < 0)
|
||||
{
|
||||
free(ossaudio);
|
||||
|
||||
+10
-14
@@ -61,10 +61,6 @@ typedef struct pipewire_audio
|
||||
* way. Written only by the callback, read by the frontend through
|
||||
* pwire_frames_consumed(). */
|
||||
retro_atomic_size_t consumed;
|
||||
/* Quanta the graph asked for and this driver had nothing at all
|
||||
* for, so the whole buffer went out as silence. A short read is
|
||||
* not one: that hands the graph fewer frames, not silence. */
|
||||
retro_atomic_size_t underruns;
|
||||
|
||||
/* The device clock, fitted from the time report the graph already
|
||||
* hands over.
|
||||
@@ -109,6 +105,7 @@ static size_t pwire_calc_frame_size(enum spa_audio_format fmt, uint32_t nchannel
|
||||
case SPA_AUDIO_FORMAT_F32_BE:
|
||||
case SPA_AUDIO_FORMAT_F32_LE:
|
||||
return 4 * nchannels;
|
||||
break;
|
||||
default:
|
||||
RARCH_ERR("[PipeWire] Bad spa_audio_format %d.\n", fmt);
|
||||
break;
|
||||
@@ -409,6 +406,13 @@ static bool pwire_microphone_mic_alive(const void *driver_context, const void *m
|
||||
return pw_stream_get_state(mic->stream, &error) == PW_STREAM_STATE_STREAMING;
|
||||
}
|
||||
|
||||
static void pwire_microphone_set_nonblock_state(void *driver_context, bool nonblock)
|
||||
{
|
||||
pipewire_core_t *pw = (pipewire_core_t*)driver_context;
|
||||
if (pw)
|
||||
pw->nonblock = nonblock;
|
||||
}
|
||||
|
||||
static struct string_list *pwire_microphone_device_list_new(const void *driver_context)
|
||||
{
|
||||
pipewire_core_t *pw = (pipewire_core_t*)driver_context;
|
||||
@@ -604,6 +608,7 @@ microphone_driver_t microphone_pipewire = {
|
||||
pwire_microphone_init,
|
||||
pwire_microphone_free,
|
||||
pwire_microphone_read,
|
||||
pwire_microphone_set_nonblock_state,
|
||||
"pipewire",
|
||||
pwire_microphone_device_list_new,
|
||||
pwire_microphone_device_list_free,
|
||||
@@ -657,11 +662,8 @@ static void pwire_playback_process_cb(void *data)
|
||||
avail = spa_ringbuffer_get_read_index(&audio->ring, &idx);
|
||||
|
||||
if (avail <= 0)
|
||||
{
|
||||
/* fill rest buffer with silence */
|
||||
memset(p, 0x00, n_bytes);
|
||||
retro_atomic_fetch_add_size(&audio->underruns, 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (avail < (int32_t)n_bytes)
|
||||
@@ -1262,12 +1264,6 @@ static size_t pwire_wait_writable(void *data, size_t len)
|
||||
return (size_t)avail;
|
||||
}
|
||||
|
||||
static size_t pwire_underruns(void *data)
|
||||
{
|
||||
pipewire_audio_t *audio = (pipewire_audio_t*)data;
|
||||
return audio ? retro_atomic_load_acquire_size(&audio->underruns) : 0;
|
||||
}
|
||||
|
||||
audio_driver_t audio_pipewire = {
|
||||
pwire_init,
|
||||
pwire_write,
|
||||
@@ -1285,7 +1281,7 @@ audio_driver_t audio_pipewire = {
|
||||
NULL, /* write_raw */
|
||||
pwire_wait_writable,
|
||||
pwire_frames_consumed,
|
||||
pwire_underruns,
|
||||
NULL, /* underruns */
|
||||
pwire_layout,
|
||||
NULL, /* frames_consumed_fallback */
|
||||
pwire_device_clock_ppm
|
||||
|
||||
+11
-96
@@ -20,32 +20,14 @@
|
||||
#include <kernel.h>
|
||||
#include <audsrv.h>
|
||||
|
||||
#include <retro_timers.h>
|
||||
|
||||
#include "../audio_driver.h"
|
||||
|
||||
#define AUDIO_BUFFER 128 * 1024
|
||||
#define AUDIO_CHANNELS 2
|
||||
#define AUDIO_BITS 16
|
||||
#define AUDIO_FRAME_BYTES (AUDIO_CHANNELS * (AUDIO_BITS / 8))
|
||||
|
||||
/* audsrv_wait_audio() is the SDK's own wait and has no bound, so a
|
||||
* server that stops draining parks the caller for good. Polling what
|
||||
* audsrv reports free ends either way: the laps cap the case where it
|
||||
* keeps draining but never frees enough, and the case where it has
|
||||
* stopped. Never reached while audio plays - a period frees long
|
||||
* before this does. */
|
||||
#define AUDIO_WAIT_LAPS 8
|
||||
#define AUDIO_WAIT_US 4000
|
||||
|
||||
typedef struct ps2_audio
|
||||
{
|
||||
/* Bytes handed to audsrv since the format was set. Less what its
|
||||
* ring still holds, that is what the SPU has played. */
|
||||
uint64_t bytes_written;
|
||||
/* What audsrv reported free before anything was queued, which is
|
||||
* its ring. write_avail() is measured against it. */
|
||||
size_t ring_size;
|
||||
/* TODO/FIXME - nonblock is not implemented */
|
||||
bool nonblock;
|
||||
bool running;
|
||||
@@ -55,7 +37,6 @@ static void *ps2_audio_init(const char *device,
|
||||
unsigned rate, unsigned latency,
|
||||
unsigned *new_rate)
|
||||
{
|
||||
int avail;
|
||||
struct audsrv_fmt_t format;
|
||||
ps2_audio_t *ps2 = (ps2_audio_t*)calloc(1, sizeof(ps2_audio_t));
|
||||
|
||||
@@ -69,10 +50,6 @@ static void *ps2_audio_init(const char *device,
|
||||
audsrv_set_format(&format);
|
||||
audsrv_set_volume(MAX_VOLUME);
|
||||
|
||||
/* Asked with nothing queued, so what is free is the whole ring. */
|
||||
avail = audsrv_available();
|
||||
ps2->ring_size = (avail > 0) ? (size_t)avail : (size_t)AUDIO_BUFFER;
|
||||
|
||||
return ps2;
|
||||
}
|
||||
|
||||
@@ -89,14 +66,10 @@ static void ps2_audio_free(void *data)
|
||||
|
||||
static ssize_t ps2_audio_write(void *data, const void *s, size_t len)
|
||||
{
|
||||
int ret;
|
||||
ps2_audio_t* ps2 = (ps2_audio_t*)data;
|
||||
if (!ps2->running)
|
||||
return -1;
|
||||
ret = audsrv_play_audio(s, len);
|
||||
if (ret > 0)
|
||||
ps2->bytes_written += (uint64_t)ret;
|
||||
return ret;
|
||||
return audsrv_play_audio(s, len);
|
||||
}
|
||||
|
||||
static bool ps2_audio_alive(void *data)
|
||||
@@ -134,72 +107,16 @@ static void ps2_audio_set_nonblock_state(void *data, bool toggle)
|
||||
ps2->nonblock = toggle;
|
||||
}
|
||||
|
||||
/* What audsrv says is free in its ring: a measurement, not the whole
|
||||
* buffer reported as free every frame, which is what this used to do
|
||||
* and what rate control answered with its full upward correction for
|
||||
* as long as it was on. */
|
||||
static size_t ps2_audio_write_avail(void *data)
|
||||
{
|
||||
int avail;
|
||||
ps2_audio_t *ps2 = (ps2_audio_t*)data;
|
||||
|
||||
if (!ps2 || !ps2->running)
|
||||
return 0;
|
||||
if ((avail = audsrv_available()) <= 0)
|
||||
return 0;
|
||||
if ((size_t)avail > ps2->ring_size)
|
||||
return ps2->ring_size;
|
||||
return (size_t)avail;
|
||||
}
|
||||
|
||||
/* Frames the SPU has played: what was handed over, less what audsrv
|
||||
* still holds for it. */
|
||||
static size_t ps2_audio_frames_consumed(void *data)
|
||||
{
|
||||
int queued;
|
||||
uint64_t played;
|
||||
ps2_audio_t *ps2 = (ps2_audio_t*)data;
|
||||
|
||||
if (!ps2)
|
||||
return 0;
|
||||
if ((queued = audsrv_queued()) < 0)
|
||||
queued = 0;
|
||||
played = ((uint64_t)queued >= ps2->bytes_written)
|
||||
? 0 : ps2->bytes_written - (uint64_t)queued;
|
||||
return (size_t)(played / AUDIO_FRAME_BYTES);
|
||||
}
|
||||
|
||||
static size_t ps2_audio_wait_writable(void *data, size_t len)
|
||||
{
|
||||
int laps = AUDIO_WAIT_LAPS;
|
||||
ps2_audio_t *ps2 = (ps2_audio_t*)data;
|
||||
|
||||
if (!ps2 || !ps2->running)
|
||||
return 0;
|
||||
/* Capped at half the ring, so the wait always has an end within a
|
||||
* draining server's reach. */
|
||||
if (len > ps2->ring_size / 2)
|
||||
len = ps2->ring_size / 2;
|
||||
|
||||
for (;;)
|
||||
{
|
||||
size_t avail = ps2_audio_write_avail(ps2);
|
||||
if (avail >= len)
|
||||
return avail;
|
||||
if (--laps < 0)
|
||||
break;
|
||||
retro_sleep_us(AUDIO_WAIT_US);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
/* No write_avail(): the driver hands audio to audsrv and has nothing
|
||||
* to measure its fill with. It used to report the whole buffer as free
|
||||
* whenever it was running, which the rate control read as an empty
|
||||
* device every frame and answered with its full upward correction,
|
||||
* permanently - audio a half percent fast, and sharp, for as long as
|
||||
* rate control was on. With no write_avail() the frontend disables
|
||||
* rate control for this driver and says so, which is the truth. */
|
||||
|
||||
static bool ps2_audio_use_float(void *data) { return false; }
|
||||
|
||||
static size_t ps2_audio_buffer_size(void *data)
|
||||
{
|
||||
ps2_audio_t *ps2 = (ps2_audio_t*)data;
|
||||
return ps2 ? ps2->ring_size : (size_t)AUDIO_BUFFER;
|
||||
}
|
||||
static size_t ps2_audio_buffer_size(void *data) { return AUDIO_BUFFER; }
|
||||
|
||||
audio_driver_t audio_ps2 = {
|
||||
ps2_audio_init,
|
||||
@@ -213,9 +130,7 @@ audio_driver_t audio_ps2 = {
|
||||
"ps2",
|
||||
NULL,
|
||||
NULL,
|
||||
ps2_audio_write_avail,
|
||||
NULL, /* write_avail */
|
||||
ps2_audio_buffer_size,
|
||||
NULL, /* write_raw */
|
||||
ps2_audio_wait_writable,
|
||||
ps2_audio_frames_consumed
|
||||
NULL /* write_raw */
|
||||
};
|
||||
+14
-92
@@ -16,9 +16,7 @@
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <malloc.h>
|
||||
|
||||
#include <retro_atomic.h>
|
||||
#include <retro_spsc.h>
|
||||
|
||||
#include <defines/ps3_defines.h>
|
||||
@@ -52,24 +50,7 @@ typedef struct
|
||||
uint32_t audio_port;
|
||||
bool nonblock;
|
||||
bool started;
|
||||
/* Set by the thread tearing the driver down, read by the output
|
||||
* thread's loop. An atomic and not a volatile bool: volatile orders
|
||||
* nothing between the two. */
|
||||
retro_atomic_int_t quit_thread;
|
||||
/* Blocks the output thread had nothing for and sent as silence. */
|
||||
retro_atomic_size_t underruns;
|
||||
/* One block, handed to audioAddData() as it comes out of the ring.
|
||||
* On the heap and not the output thread's stack: that thread is
|
||||
* created with 4 KiB, which a stereo block already half fills and
|
||||
* a six channel one would run straight past. Aligned as the stack
|
||||
* copy was. */
|
||||
float *out_block;
|
||||
size_t out_block_size;
|
||||
/* The layout asked for, and the channels the port was opened with.
|
||||
* They agree unless the port refused the count, which is what
|
||||
* layout() reports on. */
|
||||
uint32_t layout;
|
||||
unsigned channels;
|
||||
volatile bool quit_thread;
|
||||
} ps3_audio_t;
|
||||
|
||||
|
||||
@@ -79,30 +60,27 @@ static void ps3_event_loop(void *data)
|
||||
static void ps3_event_loop(uint64_t data)
|
||||
#endif
|
||||
{
|
||||
float out_tmp[AUDIO_BLOCK_SAMPLES * AUDIO_CHANNELS]
|
||||
__attribute__((aligned(16)));
|
||||
sys_event_queue_t id;
|
||||
sys_ipc_key_t key;
|
||||
sys_event_t event;
|
||||
ps3_audio_t *aud = (ps3_audio_t*)(uintptr_t)data;
|
||||
float *out_block = aud->out_block;
|
||||
size_t block = aud->out_block_size;
|
||||
|
||||
audioCreateNotifyEventQueue(&id, &key);
|
||||
audioSetNotifyEventQueue(key);
|
||||
|
||||
while (!retro_atomic_load_acquire_int(&aud->quit_thread))
|
||||
while (!aud->quit_thread)
|
||||
{
|
||||
sysEventQueueReceive(id, &event, PS3_SYS_NO_TIMEOUT);
|
||||
|
||||
if (retro_spsc_read_avail(&aud->ring) >= block)
|
||||
retro_spsc_read(&aud->ring, out_block, block);
|
||||
if (retro_spsc_read_avail(&aud->ring) >= sizeof(out_tmp))
|
||||
retro_spsc_read(&aud->ring, out_tmp, sizeof(out_tmp));
|
||||
else
|
||||
{
|
||||
memset(out_block, 0, block);
|
||||
retro_atomic_fetch_add_size(&aud->underruns, 1);
|
||||
}
|
||||
memset(out_tmp, 0, sizeof(out_tmp));
|
||||
sysLwCondSignal(&aud->cond);
|
||||
|
||||
audioAddData(aud->audio_port, out_block,
|
||||
audioAddData(aud->audio_port, out_tmp,
|
||||
AUDIO_BLOCK_SAMPLES, 1.0);
|
||||
}
|
||||
|
||||
@@ -114,7 +92,6 @@ static void *ps3_audio_init(const char *device,
|
||||
unsigned rate, unsigned latency,
|
||||
unsigned *new_rate)
|
||||
{
|
||||
unsigned channels;
|
||||
audioPortParam params;
|
||||
ps3_audio_t *data = NULL;
|
||||
#ifdef __PSL1GHT__
|
||||
@@ -135,16 +112,7 @@ static void *ps3_audio_init(const char *device,
|
||||
|
||||
audioInit();
|
||||
|
||||
/* The layout the frontend wants, where the port takes that many
|
||||
* channels: libaudio opens two, six or eight, so a four channel
|
||||
* mask is not one of them and opens as stereo. layout() reports
|
||||
* what was granted. */
|
||||
data->layout = audio_driver_requested_layout();
|
||||
channels = audio_layout_channels(data->layout);
|
||||
if (channels != 2 && channels != 6 && channels != 8)
|
||||
channels = AUDIO_CHANNELS;
|
||||
|
||||
params.numChannels = channels;
|
||||
params.numChannels = AUDIO_CHANNELS;
|
||||
params.numBlocks = AUDIO_BLOCKS;
|
||||
params.param_attrib = 0;
|
||||
#if 0
|
||||
@@ -156,40 +124,16 @@ static void *ps3_audio_init(const char *device,
|
||||
|
||||
if (audioPortOpen(¶ms, &data->audio_port) != CELL_OK)
|
||||
{
|
||||
/* A port that would not take the wider count still takes two. */
|
||||
if (channels == AUDIO_CHANNELS)
|
||||
{
|
||||
audioQuit();
|
||||
free(data);
|
||||
return NULL;
|
||||
}
|
||||
channels = AUDIO_CHANNELS;
|
||||
params.numChannels = channels;
|
||||
if (audioPortOpen(¶ms, &data->audio_port) != CELL_OK)
|
||||
{
|
||||
audioQuit();
|
||||
free(data);
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
data->channels = channels;
|
||||
|
||||
data->out_block_size = AUDIO_BLOCK_SAMPLES * channels * sizeof(float);
|
||||
data->out_block = (float*)memalign(16, data->out_block_size);
|
||||
if (!data->out_block)
|
||||
{
|
||||
audioPortClose(data->audio_port);
|
||||
audioQuit();
|
||||
free(data);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
data->ring_size = AUDIO_BLOCK_SAMPLES *
|
||||
channels * AUDIO_BLOCKS * sizeof(float);
|
||||
AUDIO_CHANNELS * AUDIO_BLOCKS * sizeof(float);
|
||||
data->ring_init = retro_spsc_init(&data->ring, data->ring_size);
|
||||
if (!data->ring_init)
|
||||
{
|
||||
free(data->out_block);
|
||||
audioPortClose(data->audio_port);
|
||||
audioQuit();
|
||||
free(data);
|
||||
@@ -201,8 +145,6 @@ static void *ps3_audio_init(const char *device,
|
||||
|
||||
audioPortStart(data->audio_port);
|
||||
data->started = true;
|
||||
retro_atomic_int_init(&data->quit_thread, 0);
|
||||
retro_atomic_size_init(&data->underruns, 0);
|
||||
sysThreadCreate(&data->thread, ps3_event_loop,
|
||||
#ifdef __PSL1GHT__
|
||||
data,
|
||||
@@ -248,8 +190,7 @@ static ssize_t ps3_audio_write(void *data, const void *s, size_t len)
|
||||
int laps = PS3_AUDIO_WAIT_LAPS;
|
||||
while (ps3_audio_write_avail(aud) < len)
|
||||
{
|
||||
if ( !aud->started
|
||||
|| retro_atomic_load_acquire_int(&aud->quit_thread))
|
||||
if (!aud->started || aud->quit_thread)
|
||||
return 0;
|
||||
ps3_audio_wait_block(aud);
|
||||
if (--laps < 0)
|
||||
@@ -303,7 +244,7 @@ static void ps3_audio_free(void *data)
|
||||
uint64_t val;
|
||||
ps3_audio_t *aud = data;
|
||||
|
||||
retro_atomic_store_release_int(&aud->quit_thread, 1);
|
||||
aud->quit_thread = true;
|
||||
ps3_audio_start(aud, false);
|
||||
sysThreadJoin(aud->thread, &val);
|
||||
|
||||
@@ -316,7 +257,6 @@ static void ps3_audio_free(void *data)
|
||||
sysLwMutexDestroy(&aud->cond_lock);
|
||||
sysLwCondDestroy(&aud->cond);
|
||||
|
||||
free(aud->out_block);
|
||||
free(data);
|
||||
}
|
||||
|
||||
@@ -353,8 +293,7 @@ static size_t ps3_audio_wait_writable(void *data, size_t len)
|
||||
|
||||
for (;;)
|
||||
{
|
||||
if ( !aud->started
|
||||
|| retro_atomic_load_acquire_int(&aud->quit_thread))
|
||||
if (!aud->started || aud->quit_thread)
|
||||
return 0;
|
||||
avail = ps3_audio_write_avail(aud);
|
||||
if (avail >= len)
|
||||
@@ -368,20 +307,6 @@ static size_t ps3_audio_wait_writable(void *data, size_t len)
|
||||
}
|
||||
}
|
||||
|
||||
static uint32_t ps3_audio_layout(void *data)
|
||||
{
|
||||
ps3_audio_t *aud = (ps3_audio_t*)data;
|
||||
if (!aud || aud->channels != audio_layout_channels(aud->layout))
|
||||
return AUDIO_LAYOUT_STEREO;
|
||||
return aud->layout;
|
||||
}
|
||||
|
||||
static size_t ps3_audio_underruns(void *data)
|
||||
{
|
||||
ps3_audio_t *aud = (ps3_audio_t*)data;
|
||||
return aud ? retro_atomic_load_acquire_size(&aud->underruns) : 0;
|
||||
}
|
||||
|
||||
audio_driver_t audio_ps3 = {
|
||||
ps3_audio_init,
|
||||
ps3_audio_write,
|
||||
@@ -397,8 +322,5 @@ audio_driver_t audio_ps3 = {
|
||||
ps3_audio_write_avail,
|
||||
ps3_audio_buffer_size,
|
||||
NULL, /* write_raw */
|
||||
ps3_audio_wait_writable,
|
||||
NULL, /* frames_consumed */
|
||||
ps3_audio_underruns,
|
||||
ps3_audio_layout
|
||||
ps3_audio_wait_writable
|
||||
};
|
||||
@@ -1,527 +0,0 @@
|
||||
/* RetroArch - A frontend for libretro.
|
||||
* Copyright (C) 2010-2014 - Hans-Kristian Arntzen
|
||||
* Copyright (C) 2011-2017 - Daniel De Matteis
|
||||
* Copyright (C) 2014-2017 - Ali Bouhlel
|
||||
*
|
||||
* RetroArch is free software: you can redistribute it and/or modify it under the terms
|
||||
* of the GNU General Public License as published by the Free Software Found-
|
||||
* ation, either version 3 of the License, or (at your option) any later version.
|
||||
*
|
||||
* RetroArch is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
|
||||
* PURPOSE. See the GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with RetroArch.
|
||||
* If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <rthreads/rthreads.h>
|
||||
#include <rthreads/retro_eventcount.h>
|
||||
#include <retro_atomic.h>
|
||||
|
||||
#include <libSceAudioOut.h>
|
||||
#include <defines/ps4_defines.h>
|
||||
|
||||
#include "../audio_driver.h"
|
||||
|
||||
typedef struct ps4_audio
|
||||
{
|
||||
/* The ring, followed by one period of silence for the worker
|
||||
* to output when the ring runs short. */
|
||||
uint32_t* buffer_u32;
|
||||
|
||||
sthread_t *worker_thread;
|
||||
/* The writer's park; the worker notifies after every period it
|
||||
* frees. Gated: the steady state, writer ahead and never waiting,
|
||||
* costs the worker nothing per period, where the old
|
||||
* signal-every-iteration took cond_lock each time. */
|
||||
retro_eventcount_t park;
|
||||
|
||||
/* For the sink rate estimate and the statistics overlay.
|
||||
* Only the worker writes either. */
|
||||
retro_atomic_size_t consumed;
|
||||
retro_atomic_size_t underruns;
|
||||
|
||||
int port;
|
||||
int rate;
|
||||
|
||||
/* Frames the ring holds. Set before the worker starts, and
|
||||
* fixed for the life of the driver. */
|
||||
unsigned ring;
|
||||
|
||||
/* The ring's index pair, the SPSC discipline by hand because the
|
||||
* worker consumes in place - ps4->buffer_u32 + read_pos goes straight
|
||||
* into the output syscall, and a retro_spsc would add a bounce
|
||||
* copy per period. Producer publishes write_pos with release
|
||||
* after the samples land; consumer publishes read_pos with
|
||||
* release after the syscall returns; each side reads the other's
|
||||
* index with acquire and its own relaxed. fifo_lock guarded
|
||||
* nothing else, and is gone. */
|
||||
retro_atomic_int_t read_pos;
|
||||
retro_atomic_int_t write_pos;
|
||||
|
||||
retro_atomic_int_t running;
|
||||
bool nonblock;
|
||||
} ps4_audio_t;
|
||||
|
||||
#define AUDIO_OUT_COUNT 512u
|
||||
|
||||
/* The ring is what the latency setting asks for, so it is a whole
|
||||
* number of periods rather than a power of two and the wrap is a
|
||||
* compare. The window the device is playing stays inside the ring
|
||||
* as held until the output call after it returns (see the worker),
|
||||
* so the writer's room is the ring less that window less one frame.
|
||||
* Six periods is the floor: five of room, which holds a delivery of
|
||||
* a video frame of audio - 1600 frames for 30 Hz content at 48 kHz -
|
||||
* beside the window in flight without dipping under a period each
|
||||
* refill. Anything at or above the default is unaffected. */
|
||||
#define AUDIO_RING_MIN (AUDIO_OUT_COUNT * 6u)
|
||||
#define AUDIO_RING_MAX (AUDIO_OUT_COUNT * 64u)
|
||||
|
||||
/* Frames held, and frames the writer may still place - one short of
|
||||
* the ring, so a full ring and an empty one do not both read as
|
||||
* write_pos == read_pos. */
|
||||
#define RING_HELD(w, r, n) \
|
||||
((unsigned)((w) >= (r) ? (w) - (r) : (n) - (r) + (w)))
|
||||
#define RING_FREE(w, r, n) ((n) - 1u - RING_HELD((w), (r), (n)))
|
||||
|
||||
/* Bound on any wait for the audio thread to consume: one wait, and how
|
||||
* many of them before the caller gets the pass back. The thread
|
||||
* consumes a period every period while the device runs. */
|
||||
#define PS4_AUDIO_WAIT_US 100000
|
||||
#define PS4_AUDIO_WAIT_LAPS 8
|
||||
|
||||
/* The only rate sceAudioOut opens at. */
|
||||
#define PS4_AUDIO_RATE 48000
|
||||
|
||||
/* The ring the latency setting asks for: that many milliseconds at
|
||||
* the rate the device opened at, in whole periods so no window the
|
||||
* worker hands over crosses the ring's end. The window the device is
|
||||
* playing is inside it, so this is the whole of the driver's
|
||||
* buffering and what buffer_size() reports. */
|
||||
static unsigned ps4_ring_frames(unsigned rate, unsigned latency)
|
||||
{
|
||||
unsigned frames = rate * latency / 1000u;
|
||||
frames = ((frames + AUDIO_OUT_COUNT - 1u) / AUDIO_OUT_COUNT)
|
||||
* AUDIO_OUT_COUNT;
|
||||
if (frames < AUDIO_RING_MIN)
|
||||
return AUDIO_RING_MIN;
|
||||
if (frames > AUDIO_RING_MAX)
|
||||
return AUDIO_RING_MAX;
|
||||
return frames;
|
||||
}
|
||||
|
||||
/* Return port used */
|
||||
static int ps4_configure_audio(unsigned rate, unsigned *new_rate)
|
||||
{
|
||||
/* Every other rate is refused, which leaves the session with no
|
||||
* audio at all; open at the one it takes and tell the frontend to
|
||||
* resample to it. */
|
||||
if (rate != PS4_AUDIO_RATE)
|
||||
*new_rate = PS4_AUDIO_RATE;
|
||||
return sceAudioOutOpen(0xff,
|
||||
SCE_AUDIO_OUT_PORT_TYPE_MAIN, 0, AUDIO_OUT_COUNT,
|
||||
PS4_AUDIO_RATE, SCE_AUDIO_OUT_MODE_STEREO);
|
||||
}
|
||||
|
||||
static void ps4_audio_mainloop(void *data)
|
||||
{
|
||||
ps4_audio_t* ps4 = (ps4_audio_t*)data;
|
||||
/* The worker's own cursor: the next window to hand over. The
|
||||
* published read_pos runs one window behind it while a ring
|
||||
* window is in flight. The output call returns once the window
|
||||
* handed to the previous call has been output, and with this
|
||||
* call's window queued and still to be read by the hardware -
|
||||
* the SDKs document passing NULL as the way to wait for that
|
||||
* last window - so the window a call hands over is the device's
|
||||
* until the call after it returns, and is released there. */
|
||||
uint16_t play_pos = (uint16_t)
|
||||
retro_atomic_load_relaxed_int(&ps4->read_pos);
|
||||
|
||||
while (retro_atomic_load_acquire_int(&ps4->running))
|
||||
{
|
||||
bool cond = false;
|
||||
uint16_t next_pos = play_pos;
|
||||
uint16_t write_pos = (uint16_t)
|
||||
retro_atomic_load_acquire_int(&ps4->write_pos);
|
||||
|
||||
/* A period in hand is a period to play. Holding one back
|
||||
* as a reserve only hands the device silence while the
|
||||
* ring has audio in it. */
|
||||
cond = RING_HELD(write_pos, play_pos, ps4->ring)
|
||||
< AUDIO_OUT_COUNT;
|
||||
|
||||
if (!cond)
|
||||
{
|
||||
next_pos += AUDIO_OUT_COUNT;
|
||||
if (next_pos >= ps4->ring)
|
||||
next_pos = 0;
|
||||
}
|
||||
else
|
||||
retro_atomic_fetch_add_size(&ps4->underruns, 1);
|
||||
|
||||
sceAudioOutOutput(ps4->port,
|
||||
ps4->buffer_u32
|
||||
+ (cond ? ps4->ring : play_pos));
|
||||
|
||||
retro_atomic_fetch_add_size(&ps4->consumed, AUDIO_OUT_COUNT);
|
||||
|
||||
/* The previous call's window has been output; this call's is
|
||||
* the one in flight, so everything before play_pos is the
|
||||
* writer's. When this call handed over silence, play_pos is
|
||||
* where read_pos already stands. */
|
||||
retro_atomic_store_release_int(&ps4->read_pos, play_pos);
|
||||
play_pos = next_pos;
|
||||
|
||||
retro_eventcount_notify(&ps4->park);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
static void *ps4_audio_init(const char *device,
|
||||
unsigned rate, unsigned latency,
|
||||
unsigned *new_rate)
|
||||
{
|
||||
int port;
|
||||
ps4_audio_t *ps4 = (ps4_audio_t*)calloc(1, sizeof(ps4_audio_t));
|
||||
|
||||
if (!ps4)
|
||||
return NULL;
|
||||
|
||||
/* The library is what opens the port, so it comes up first. */
|
||||
sceAudioOutInit();
|
||||
|
||||
if ((port = ps4_configure_audio(rate, new_rate)) < 0)
|
||||
{
|
||||
free(ps4);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
ps4->rate = *new_rate ? *new_rate : rate;
|
||||
ps4->ring = ps4_ring_frames(ps4->rate, latency);
|
||||
|
||||
/* Cache aligned, not necessary but helpful. */
|
||||
/* The ring, and one period of silence past its end. */
|
||||
ps4->buffer_u32 = (uint32_t*)calloc(ps4->ring + AUDIO_OUT_COUNT,
|
||||
sizeof(uint32_t));
|
||||
|
||||
retro_atomic_size_init(&ps4->consumed, 0);
|
||||
retro_atomic_size_init(&ps4->underruns, 0);
|
||||
retro_atomic_int_init(&ps4->read_pos, 0);
|
||||
retro_atomic_int_init(&ps4->write_pos, 0);
|
||||
ps4->port = port;
|
||||
|
||||
if ( !ps4->buffer_u32
|
||||
|| !retro_eventcount_init(&ps4->park))
|
||||
{
|
||||
sceAudioOutClose(port);
|
||||
free(ps4->buffer_u32);
|
||||
free(ps4);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
ps4->nonblock = false;
|
||||
/* running has to be set before the worker starts, so a worker that
|
||||
* never starts leaves a driver reporting itself alive with nothing
|
||||
* consuming the ring: writes wait out their laps and return zero,
|
||||
* and teardown acts on a state that was never true. */
|
||||
retro_atomic_int_init(&ps4->running, 1);
|
||||
if (!(ps4->worker_thread = sthread_create(ps4_audio_mainloop, ps4)))
|
||||
{
|
||||
retro_eventcount_free(&ps4->park);
|
||||
sceAudioOutClose(port);
|
||||
free(ps4->buffer_u32);
|
||||
free(ps4);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return ps4;
|
||||
}
|
||||
|
||||
static void ps4_audio_free(void *data)
|
||||
{
|
||||
ps4_audio_t* ps4 = (ps4_audio_t*)data;
|
||||
if (!ps4)
|
||||
return;
|
||||
|
||||
if (retro_atomic_load_acquire_int(&ps4->running))
|
||||
{
|
||||
if (ps4->worker_thread)
|
||||
{
|
||||
retro_atomic_store_release_int(&ps4->running, 0);
|
||||
sthread_join(ps4->worker_thread);
|
||||
}
|
||||
}
|
||||
retro_eventcount_free(&ps4->park);
|
||||
free(ps4->buffer_u32);
|
||||
ps4->worker_thread = NULL;
|
||||
|
||||
sceAudioOutClose(ps4->port);
|
||||
|
||||
free(ps4);
|
||||
|
||||
}
|
||||
|
||||
static ssize_t ps4_audio_write(void *data, const void *s, size_t len)
|
||||
{
|
||||
ps4_audio_t* ps4 = (ps4_audio_t*)data;
|
||||
uint16_t write_pos = (uint16_t)
|
||||
retro_atomic_load_relaxed_int(&ps4->write_pos);
|
||||
/* Frames, and wide enough for them: narrowed to uint16_t, a write of
|
||||
* more than 65535 frames wrapped to a small count that the room check
|
||||
* then waved through, and the copy below took len bytes for it. The
|
||||
* ring holds one frame short of its length, so no wait can free room
|
||||
* for more than that at once - take what fits and report the short
|
||||
* write rather than spending the laps on room that will not come. A
|
||||
* len that is not whole frames carries the frames it has. */
|
||||
size_t sample_count = len / sizeof(uint32_t);
|
||||
|
||||
if (!retro_atomic_load_acquire_int(&ps4->running))
|
||||
return -1;
|
||||
if (!sample_count)
|
||||
return 0;
|
||||
if (sample_count > (size_t)(ps4->ring - 1u))
|
||||
sample_count = ps4->ring - 1u;
|
||||
len = sample_count * sizeof(uint32_t);
|
||||
|
||||
/* The ring is counted in uint32_t frames (write_pos, read_pos,
|
||||
* ring); len is bytes. Both room checks below used to
|
||||
* compare the frame count against len, i.e. demanded four times the
|
||||
* room actually needed: non-blocking writes were refused - the audio
|
||||
* dropped - with plenty of space free, and blocking ones waited for
|
||||
* space that rate control was not trying to free. ps4_write_avail()
|
||||
* and ps4_wait_writable() already convert; compare frames to
|
||||
* frames here too. */
|
||||
if (ps4->nonblock)
|
||||
{
|
||||
if (RING_FREE(write_pos, (uint16_t)
|
||||
retro_atomic_load_acquire_int(&ps4->read_pos),
|
||||
ps4->ring) < sample_count)
|
||||
return 0;
|
||||
}
|
||||
|
||||
{
|
||||
/* The audio thread notifies every period it consumes. One that
|
||||
* has stopped consuming - the device suspended, the thread gone
|
||||
* - notifies nothing; each wait is bounded, the loop is
|
||||
* lap-bounded, and the write then returns having written
|
||||
* nothing rather than holding the caller. Room and liveness
|
||||
* are re-checked inside the eventcount's window, so a period
|
||||
* freed between the check and the park costs nothing. */
|
||||
int laps = PS4_AUDIO_WAIT_LAPS;
|
||||
while (RING_FREE(write_pos, (uint16_t)
|
||||
retro_atomic_load_acquire_int(&ps4->read_pos),
|
||||
ps4->ring) < sample_count)
|
||||
{
|
||||
int key;
|
||||
if (--laps < 0 || !retro_atomic_load_acquire_int(&ps4->running))
|
||||
return 0;
|
||||
key = retro_eventcount_prepare_wait(&ps4->park);
|
||||
if ( (RING_FREE(write_pos, (uint16_t)
|
||||
retro_atomic_load_acquire_int(&ps4->read_pos),
|
||||
ps4->ring) >= sample_count)
|
||||
|| !retro_atomic_load_acquire_int(&ps4->running))
|
||||
{
|
||||
retro_eventcount_cancel_wait(&ps4->park);
|
||||
continue;
|
||||
}
|
||||
if (!retro_eventcount_commit_wait_timeout(&ps4->park, key,
|
||||
PS4_AUDIO_WAIT_US))
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if ((write_pos + sample_count) > ps4->ring)
|
||||
{
|
||||
memcpy(ps4->buffer_u32 + write_pos, s,
|
||||
(ps4->ring - write_pos) * sizeof(uint32_t));
|
||||
memcpy(ps4->buffer_u32, (uint32_t*)s +
|
||||
(ps4->ring - write_pos),
|
||||
(write_pos + sample_count - ps4->ring) * sizeof(uint32_t));
|
||||
}
|
||||
else
|
||||
memcpy(ps4->buffer_u32 + write_pos, s, len);
|
||||
|
||||
write_pos = (uint16_t)(write_pos + sample_count);
|
||||
if (write_pos >= ps4->ring)
|
||||
write_pos -= ps4->ring;
|
||||
/* Release: the samples land before the index that publishes
|
||||
* them. */
|
||||
retro_atomic_store_release_int(&ps4->write_pos, write_pos);
|
||||
return len;
|
||||
}
|
||||
|
||||
static bool ps4_audio_alive(void *data)
|
||||
{
|
||||
ps4_audio_t* ps4 = (ps4_audio_t*)data;
|
||||
if (!ps4)
|
||||
return false;
|
||||
return retro_atomic_load_acquire_int(&ps4->running) != 0;
|
||||
}
|
||||
|
||||
static bool ps4_audio_stop(void *data)
|
||||
{
|
||||
ps4_audio_t* ps4 = (ps4_audio_t*)data;
|
||||
|
||||
if (ps4)
|
||||
{
|
||||
retro_atomic_store_release_int(&ps4->running, 0);
|
||||
/* A writer parked on the ring must see the flag drop; the
|
||||
* worker may already be gone and notify nothing further. */
|
||||
retro_eventcount_notify(&ps4->park);
|
||||
|
||||
if (ps4->worker_thread)
|
||||
{
|
||||
sthread_join(ps4->worker_thread);
|
||||
ps4->worker_thread = NULL;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool ps4_audio_start(void *data, bool is_shutdown)
|
||||
{
|
||||
ps4_audio_t* ps4 = (ps4_audio_t*)data;
|
||||
|
||||
if (ps4 && !retro_atomic_load_acquire_int(&ps4->running))
|
||||
{
|
||||
if (!ps4->worker_thread)
|
||||
{
|
||||
retro_atomic_store_release_int(&ps4->running, 1);
|
||||
if (!(ps4->worker_thread = sthread_create(ps4_audio_mainloop, ps4)))
|
||||
{
|
||||
retro_atomic_store_release_int(&ps4->running, 0);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void ps4_audio_set_nonblock_state(void *data, bool toggle)
|
||||
{
|
||||
ps4_audio_t* ps4 = (ps4_audio_t*)data;
|
||||
if (ps4)
|
||||
ps4->nonblock = toggle;
|
||||
}
|
||||
|
||||
static size_t ps4_write_avail(void *data)
|
||||
{
|
||||
size_t _len;
|
||||
ps4_audio_t* ps4 = (ps4_audio_t*)data;
|
||||
|
||||
if (!ps4 || !retro_atomic_load_acquire_int(&ps4->running))
|
||||
return 0;
|
||||
_len = RING_FREE((uint16_t)
|
||||
retro_atomic_load_relaxed_int(&ps4->write_pos), (uint16_t)
|
||||
retro_atomic_load_acquire_int(&ps4->read_pos), ps4->ring);
|
||||
return _len * sizeof(uint32_t);
|
||||
}
|
||||
|
||||
/* Sleep on the condition the output thread signals after every block
|
||||
* until the fifo has room for len, in the same units ps4_audio_write()
|
||||
* compares against, capped at half the fifo so the wait always ends.
|
||||
* Returns the free space as ps4_write_avail() reports it, or 0 when
|
||||
* the output thread is not running. */
|
||||
static size_t ps4_wait_writable(void *data, size_t len)
|
||||
{
|
||||
ps4_audio_t* ps4 = (ps4_audio_t*)data;
|
||||
size_t avail;
|
||||
int laps = PS4_AUDIO_WAIT_LAPS;
|
||||
/* len arrives in bytes; the ring is counted in uint32_t frames. */
|
||||
size_t want = len / sizeof(uint32_t);
|
||||
|
||||
if (want > ps4->ring / 2)
|
||||
want = ps4->ring / 2;
|
||||
|
||||
for (;;)
|
||||
{
|
||||
int key;
|
||||
if (!retro_atomic_load_acquire_int(&ps4->running))
|
||||
return 0;
|
||||
avail = RING_FREE((uint16_t)
|
||||
retro_atomic_load_relaxed_int(&ps4->write_pos), (uint16_t)
|
||||
retro_atomic_load_acquire_int(&ps4->read_pos), ps4->ring);
|
||||
if (avail >= want)
|
||||
break;
|
||||
/* Bounded per wait and overall: a thread that has stopped
|
||||
* consuming hands the pass back as no space coming from this
|
||||
* call. The room is re-checked inside the window. */
|
||||
if (--laps < 0)
|
||||
return 0;
|
||||
key = retro_eventcount_prepare_wait(&ps4->park);
|
||||
if (RING_FREE((uint16_t)
|
||||
retro_atomic_load_relaxed_int(&ps4->write_pos), (uint16_t)
|
||||
retro_atomic_load_acquire_int(&ps4->read_pos),
|
||||
ps4->ring) >= want
|
||||
|| !retro_atomic_load_acquire_int(&ps4->running))
|
||||
{
|
||||
retro_eventcount_cancel_wait(&ps4->park);
|
||||
continue;
|
||||
}
|
||||
if (!retro_eventcount_commit_wait_timeout(&ps4->park, key,
|
||||
PS4_AUDIO_WAIT_US))
|
||||
continue;
|
||||
}
|
||||
return avail * sizeof(uint32_t);
|
||||
}
|
||||
|
||||
/* sceAudioOut is opened in SCE_AUDIO_OUT_MODE_STEREO, which is 16-bit
|
||||
* PCM; float output would need a different port mode. */
|
||||
static bool ps4_audio_use_float(void *data) { return false; }
|
||||
static size_t ps4_buffer_size(void *data)
|
||||
{
|
||||
ps4_audio_t* ps4 = (ps4_audio_t*)data;
|
||||
if (!ps4)
|
||||
return 0;
|
||||
/* In bytes: the ring, in uint32_t frames of int16 stereo. The
|
||||
* window the device is playing is held inside it. */
|
||||
return ps4->ring * sizeof(uint32_t);
|
||||
}
|
||||
|
||||
/* Frames the device has taken since init: the output call returns
|
||||
* when it has taken the one before, so the worker counts a period
|
||||
* per completed call. */
|
||||
static size_t ps4_frames_consumed(void *data)
|
||||
{
|
||||
ps4_audio_t* ps4 = (ps4_audio_t*)data;
|
||||
if (!ps4)
|
||||
return 0;
|
||||
return retro_atomic_load_acquire_size(&ps4->consumed);
|
||||
}
|
||||
|
||||
/* Periods played as silence for want of audio. */
|
||||
static size_t ps4_underruns(void *data)
|
||||
{
|
||||
ps4_audio_t* ps4 = (ps4_audio_t*)data;
|
||||
if (!ps4)
|
||||
return 0;
|
||||
return retro_atomic_load_acquire_size(&ps4->underruns);
|
||||
}
|
||||
|
||||
audio_driver_t audio_ps4 = {
|
||||
ps4_audio_init,
|
||||
ps4_audio_write,
|
||||
ps4_audio_stop,
|
||||
ps4_audio_start,
|
||||
ps4_audio_alive,
|
||||
ps4_audio_set_nonblock_state,
|
||||
ps4_audio_free,
|
||||
ps4_audio_use_float,
|
||||
"orbis",
|
||||
NULL,
|
||||
NULL,
|
||||
ps4_write_avail,
|
||||
ps4_buffer_size,
|
||||
NULL, /* write_raw */
|
||||
ps4_wait_writable,
|
||||
ps4_frames_consumed,
|
||||
ps4_underruns
|
||||
};
|
||||
@@ -1,523 +0,0 @@
|
||||
/* RetroArch - A frontend for libretro.
|
||||
* Copyright (C) 2010-2014 - Hans-Kristian Arntzen
|
||||
* Copyright (C) 2011-2017 - Daniel De Matteis
|
||||
* Copyright (C) 2014-2017 - Ali Bouhlel
|
||||
*
|
||||
* RetroArch is free software: you can redistribute it and/or modify it under the terms
|
||||
* of the GNU General Public License as published by the Free Software Found-
|
||||
* ation, either version 3 of the License, or (at your option) any later version.
|
||||
*
|
||||
* RetroArch is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
|
||||
* PURPOSE. See the GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with RetroArch.
|
||||
* If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <stdint.h>
|
||||
#include <malloc.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <rthreads/rthreads.h>
|
||||
#include <rthreads/retro_eventcount.h>
|
||||
#include <retro_atomic.h>
|
||||
|
||||
#include <psp2/audioout.h>
|
||||
|
||||
#include "../audio_driver.h"
|
||||
|
||||
typedef struct psp2_audio
|
||||
{
|
||||
/* The ring, followed by one period of silence for the worker
|
||||
* to output when the ring runs short. */
|
||||
uint32_t* buffer_u32;
|
||||
|
||||
sthread_t *worker_thread;
|
||||
/* The writer's park; the worker notifies after every period it
|
||||
* frees. Gated: the steady state, writer ahead and never waiting,
|
||||
* costs the worker nothing per period, where the old
|
||||
* signal-every-iteration took cond_lock each time. */
|
||||
retro_eventcount_t park;
|
||||
|
||||
/* For the sink rate estimate and the statistics overlay.
|
||||
* Only the worker writes either. */
|
||||
retro_atomic_size_t consumed;
|
||||
retro_atomic_size_t underruns;
|
||||
|
||||
int port;
|
||||
int rate;
|
||||
|
||||
/* Frames the ring holds. Set before the worker starts, and
|
||||
* fixed for the life of the driver. */
|
||||
unsigned ring;
|
||||
|
||||
/* The ring's index pair, the SPSC discipline by hand because the
|
||||
* worker consumes in place - psp->buffer_u32 + read_pos goes straight
|
||||
* into the output syscall, and a retro_spsc would add a bounce
|
||||
* copy per period. Producer publishes write_pos with release
|
||||
* after the samples land; consumer publishes read_pos with
|
||||
* release after the syscall returns; each side reads the other's
|
||||
* index with acquire and its own relaxed. fifo_lock guarded
|
||||
* nothing else, and is gone. */
|
||||
retro_atomic_int_t read_pos;
|
||||
retro_atomic_int_t write_pos;
|
||||
|
||||
retro_atomic_int_t running;
|
||||
bool nonblock;
|
||||
} psp2_audio_t;
|
||||
|
||||
#define AUDIO_OUT_COUNT 512u
|
||||
|
||||
/* The ring is what the latency setting asks for, so it is a whole
|
||||
* number of periods rather than a power of two and the wrap is a
|
||||
* compare. The window the device is playing stays inside the ring
|
||||
* as held until the output call after it returns (see the worker),
|
||||
* so the writer's room is the ring less that window less one frame.
|
||||
* Six periods is the floor: five of room, which holds a delivery of
|
||||
* a video frame of audio - 1600 frames for 30 Hz content at 48 kHz -
|
||||
* beside the window in flight without dipping under a period each
|
||||
* refill. Anything at or above the default is unaffected. */
|
||||
#define AUDIO_RING_MIN (AUDIO_OUT_COUNT * 6u)
|
||||
#define AUDIO_RING_MAX (AUDIO_OUT_COUNT * 64u)
|
||||
|
||||
/* Frames held, and frames the writer may still place - one short of
|
||||
* the ring, so a full ring and an empty one do not both read as
|
||||
* write_pos == read_pos. */
|
||||
#define RING_HELD(w, r, n) \
|
||||
((unsigned)((w) >= (r) ? (w) - (r) : (n) - (r) + (w)))
|
||||
#define RING_FREE(w, r, n) ((n) - 1u - RING_HELD((w), (r), (n)))
|
||||
|
||||
/* Bound on any wait for the audio thread to consume: one wait, and how
|
||||
* many of them before the caller gets the pass back. The thread
|
||||
* consumes a period every period while the device runs. */
|
||||
#define PSP2_AUDIO_WAIT_US 100000
|
||||
#define PSP2_AUDIO_WAIT_LAPS 8
|
||||
|
||||
/* The only rate the main port opens at. */
|
||||
#define PSP2_AUDIO_RATE 48000
|
||||
|
||||
/* The ring the latency setting asks for: that many milliseconds at
|
||||
* the rate the device opened at, in whole periods so no window the
|
||||
* worker hands over crosses the ring's end. The window the device is
|
||||
* playing is inside it, so this is the whole of the driver's
|
||||
* buffering and what buffer_size() reports. */
|
||||
static unsigned psp2_ring_frames(unsigned rate, unsigned latency)
|
||||
{
|
||||
unsigned frames = rate * latency / 1000u;
|
||||
frames = ((frames + AUDIO_OUT_COUNT - 1u) / AUDIO_OUT_COUNT)
|
||||
* AUDIO_OUT_COUNT;
|
||||
if (frames < AUDIO_RING_MIN)
|
||||
return AUDIO_RING_MIN;
|
||||
if (frames > AUDIO_RING_MAX)
|
||||
return AUDIO_RING_MAX;
|
||||
return frames;
|
||||
}
|
||||
|
||||
/* Return port used */
|
||||
static int psp2_configure_audio(unsigned rate, unsigned *new_rate)
|
||||
{
|
||||
/* The main port refuses every other rate, which leaves the session
|
||||
* with no audio at all; open at the one it takes and tell the
|
||||
* frontend to resample to it. */
|
||||
if (rate != PSP2_AUDIO_RATE)
|
||||
*new_rate = PSP2_AUDIO_RATE;
|
||||
return sceAudioOutOpenPort(
|
||||
SCE_AUDIO_OUT_PORT_TYPE_MAIN, AUDIO_OUT_COUNT,
|
||||
PSP2_AUDIO_RATE, SCE_AUDIO_OUT_MODE_STEREO);
|
||||
}
|
||||
|
||||
static void psp2_audio_mainloop(void *data)
|
||||
{
|
||||
psp2_audio_t* psp = (psp2_audio_t*)data;
|
||||
/* The worker's own cursor: the next window to hand over. The
|
||||
* published read_pos runs one window behind it while a ring
|
||||
* window is in flight. The output call returns once the window
|
||||
* handed to the previous call has been output, and with this
|
||||
* call's window queued and still to be read by the hardware -
|
||||
* the SDKs document passing NULL as the way to wait for that
|
||||
* last window - so the window a call hands over is the device's
|
||||
* until the call after it returns, and is released there. */
|
||||
uint16_t play_pos = (uint16_t)
|
||||
retro_atomic_load_relaxed_int(&psp->read_pos);
|
||||
|
||||
while (retro_atomic_load_acquire_int(&psp->running))
|
||||
{
|
||||
bool cond = false;
|
||||
uint16_t next_pos = play_pos;
|
||||
uint16_t write_pos = (uint16_t)
|
||||
retro_atomic_load_acquire_int(&psp->write_pos);
|
||||
|
||||
/* A period in hand is a period to play. Holding one back
|
||||
* as a reserve only hands the device silence while the
|
||||
* ring has audio in it. */
|
||||
cond = RING_HELD(write_pos, play_pos, psp->ring)
|
||||
< AUDIO_OUT_COUNT;
|
||||
|
||||
if (!cond)
|
||||
{
|
||||
next_pos += AUDIO_OUT_COUNT;
|
||||
if (next_pos >= psp->ring)
|
||||
next_pos = 0;
|
||||
}
|
||||
else
|
||||
retro_atomic_fetch_add_size(&psp->underruns, 1);
|
||||
|
||||
sceAudioOutOutput(psp->port,
|
||||
psp->buffer_u32
|
||||
+ (cond ? psp->ring : play_pos));
|
||||
|
||||
retro_atomic_fetch_add_size(&psp->consumed, AUDIO_OUT_COUNT);
|
||||
|
||||
/* The previous call's window has been output; this call's is
|
||||
* the one in flight, so everything before play_pos is the
|
||||
* writer's. When this call handed over silence, play_pos is
|
||||
* where read_pos already stands. */
|
||||
retro_atomic_store_release_int(&psp->read_pos, play_pos);
|
||||
play_pos = next_pos;
|
||||
|
||||
retro_eventcount_notify(&psp->park);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
static void *psp2_audio_init(const char *device,
|
||||
unsigned rate, unsigned latency,
|
||||
unsigned *new_rate)
|
||||
{
|
||||
int port;
|
||||
psp2_audio_t *psp = (psp2_audio_t*)calloc(1, sizeof(psp2_audio_t));
|
||||
|
||||
if (!psp)
|
||||
return NULL;
|
||||
|
||||
if ((port = psp2_configure_audio(rate, new_rate)) < 0)
|
||||
{
|
||||
free(psp);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
psp->rate = *new_rate ? *new_rate : rate;
|
||||
psp->ring = psp2_ring_frames(psp->rate, latency);
|
||||
|
||||
/* Cache aligned, not necessary but helpful. */
|
||||
/* The ring, and one period of silence past its end. */
|
||||
psp->buffer_u32 = (uint32_t*)calloc(psp->ring + AUDIO_OUT_COUNT,
|
||||
sizeof(uint32_t));
|
||||
|
||||
retro_atomic_size_init(&psp->consumed, 0);
|
||||
retro_atomic_size_init(&psp->underruns, 0);
|
||||
retro_atomic_int_init(&psp->read_pos, 0);
|
||||
retro_atomic_int_init(&psp->write_pos, 0);
|
||||
psp->port = port;
|
||||
|
||||
if ( !psp->buffer_u32
|
||||
|| !retro_eventcount_init(&psp->park))
|
||||
{
|
||||
sceAudioOutReleasePort(port);
|
||||
free(psp->buffer_u32);
|
||||
free(psp);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
psp->nonblock = false;
|
||||
/* running has to be set before the worker starts, so a worker that
|
||||
* never starts leaves a driver reporting itself alive with nothing
|
||||
* consuming the ring: writes wait out their laps and return zero,
|
||||
* and teardown acts on a state that was never true. */
|
||||
retro_atomic_int_init(&psp->running, 1);
|
||||
if (!(psp->worker_thread = sthread_create(psp2_audio_mainloop, psp)))
|
||||
{
|
||||
retro_eventcount_free(&psp->park);
|
||||
sceAudioOutReleasePort(port);
|
||||
free(psp->buffer_u32);
|
||||
free(psp);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return psp;
|
||||
}
|
||||
|
||||
static void psp2_audio_free(void *data)
|
||||
{
|
||||
psp2_audio_t* psp = (psp2_audio_t*)data;
|
||||
if (!psp)
|
||||
return;
|
||||
|
||||
if (retro_atomic_load_acquire_int(&psp->running))
|
||||
{
|
||||
if (psp->worker_thread)
|
||||
{
|
||||
retro_atomic_store_release_int(&psp->running, 0);
|
||||
sthread_join(psp->worker_thread);
|
||||
}
|
||||
}
|
||||
retro_eventcount_free(&psp->park);
|
||||
free(psp->buffer_u32);
|
||||
psp->worker_thread = NULL;
|
||||
|
||||
sceAudioOutReleasePort(psp->port);
|
||||
|
||||
free(psp);
|
||||
|
||||
}
|
||||
|
||||
static ssize_t psp2_audio_write(void *data, const void *s, size_t len)
|
||||
{
|
||||
psp2_audio_t* psp = (psp2_audio_t*)data;
|
||||
uint16_t write_pos = (uint16_t)
|
||||
retro_atomic_load_relaxed_int(&psp->write_pos);
|
||||
/* Frames, and wide enough for them: narrowed to uint16_t, a write of
|
||||
* more than 65535 frames wrapped to a small count that the room check
|
||||
* then waved through, and the copy below took len bytes for it. The
|
||||
* ring holds one frame short of its length, so no wait can free room
|
||||
* for more than that at once - take what fits and report the short
|
||||
* write rather than spending the laps on room that will not come. A
|
||||
* len that is not whole frames carries the frames it has. */
|
||||
size_t sample_count = len / sizeof(uint32_t);
|
||||
|
||||
if (!retro_atomic_load_acquire_int(&psp->running))
|
||||
return -1;
|
||||
if (!sample_count)
|
||||
return 0;
|
||||
if (sample_count > (size_t)(psp->ring - 1u))
|
||||
sample_count = psp->ring - 1u;
|
||||
len = sample_count * sizeof(uint32_t);
|
||||
|
||||
/* The ring is counted in uint32_t frames (write_pos, read_pos,
|
||||
* ring); len is bytes. Both room checks below used to
|
||||
* compare the frame count against len, i.e. demanded four times the
|
||||
* room actually needed: non-blocking writes were refused - the audio
|
||||
* dropped - with plenty of space free, and blocking ones waited for
|
||||
* space that rate control was not trying to free. psp2_write_avail()
|
||||
* and psp2_wait_writable() already convert; compare frames to
|
||||
* frames here too. */
|
||||
if (psp->nonblock)
|
||||
{
|
||||
if (RING_FREE(write_pos, (uint16_t)
|
||||
retro_atomic_load_acquire_int(&psp->read_pos),
|
||||
psp->ring) < sample_count)
|
||||
return 0;
|
||||
}
|
||||
|
||||
{
|
||||
/* The audio thread notifies every period it consumes. One that
|
||||
* has stopped consuming - the device suspended, the thread gone
|
||||
* - notifies nothing; each wait is bounded, the loop is
|
||||
* lap-bounded, and the write then returns having written
|
||||
* nothing rather than holding the caller. Room and liveness
|
||||
* are re-checked inside the eventcount's window, so a period
|
||||
* freed between the check and the park costs nothing. */
|
||||
int laps = PSP2_AUDIO_WAIT_LAPS;
|
||||
while (RING_FREE(write_pos, (uint16_t)
|
||||
retro_atomic_load_acquire_int(&psp->read_pos),
|
||||
psp->ring) < sample_count)
|
||||
{
|
||||
int key;
|
||||
if (--laps < 0 || !retro_atomic_load_acquire_int(&psp->running))
|
||||
return 0;
|
||||
key = retro_eventcount_prepare_wait(&psp->park);
|
||||
if ( (RING_FREE(write_pos, (uint16_t)
|
||||
retro_atomic_load_acquire_int(&psp->read_pos),
|
||||
psp->ring) >= sample_count)
|
||||
|| !retro_atomic_load_acquire_int(&psp->running))
|
||||
{
|
||||
retro_eventcount_cancel_wait(&psp->park);
|
||||
continue;
|
||||
}
|
||||
if (!retro_eventcount_commit_wait_timeout(&psp->park, key,
|
||||
PSP2_AUDIO_WAIT_US))
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if ((write_pos + sample_count) > psp->ring)
|
||||
{
|
||||
memcpy(psp->buffer_u32 + write_pos, s,
|
||||
(psp->ring - write_pos) * sizeof(uint32_t));
|
||||
memcpy(psp->buffer_u32, (uint32_t*)s +
|
||||
(psp->ring - write_pos),
|
||||
(write_pos + sample_count - psp->ring) * sizeof(uint32_t));
|
||||
}
|
||||
else
|
||||
memcpy(psp->buffer_u32 + write_pos, s, len);
|
||||
|
||||
write_pos = (uint16_t)(write_pos + sample_count);
|
||||
if (write_pos >= psp->ring)
|
||||
write_pos -= psp->ring;
|
||||
/* Release: the samples land before the index that publishes
|
||||
* them. */
|
||||
retro_atomic_store_release_int(&psp->write_pos, write_pos);
|
||||
return len;
|
||||
}
|
||||
|
||||
static bool psp2_audio_alive(void *data)
|
||||
{
|
||||
psp2_audio_t* psp = (psp2_audio_t*)data;
|
||||
if (!psp)
|
||||
return false;
|
||||
return retro_atomic_load_acquire_int(&psp->running) != 0;
|
||||
}
|
||||
|
||||
static bool psp2_audio_stop(void *data)
|
||||
{
|
||||
psp2_audio_t* psp = (psp2_audio_t*)data;
|
||||
|
||||
if (psp)
|
||||
{
|
||||
retro_atomic_store_release_int(&psp->running, 0);
|
||||
/* A writer parked on the ring must see the flag drop; the
|
||||
* worker may already be gone and notify nothing further. */
|
||||
retro_eventcount_notify(&psp->park);
|
||||
|
||||
if (psp->worker_thread)
|
||||
{
|
||||
sthread_join(psp->worker_thread);
|
||||
psp->worker_thread = NULL;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool psp2_audio_start(void *data, bool is_shutdown)
|
||||
{
|
||||
psp2_audio_t* psp = (psp2_audio_t*)data;
|
||||
|
||||
if (psp && !retro_atomic_load_acquire_int(&psp->running))
|
||||
{
|
||||
if (!psp->worker_thread)
|
||||
{
|
||||
retro_atomic_store_release_int(&psp->running, 1);
|
||||
if (!(psp->worker_thread = sthread_create(psp2_audio_mainloop, psp)))
|
||||
{
|
||||
retro_atomic_store_release_int(&psp->running, 0);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void psp2_audio_set_nonblock_state(void *data, bool toggle)
|
||||
{
|
||||
psp2_audio_t* psp = (psp2_audio_t*)data;
|
||||
if (psp)
|
||||
psp->nonblock = toggle;
|
||||
}
|
||||
|
||||
static size_t psp2_write_avail(void *data)
|
||||
{
|
||||
size_t _len;
|
||||
psp2_audio_t* psp = (psp2_audio_t*)data;
|
||||
|
||||
if (!psp || !retro_atomic_load_acquire_int(&psp->running))
|
||||
return 0;
|
||||
_len = RING_FREE((uint16_t)
|
||||
retro_atomic_load_relaxed_int(&psp->write_pos), (uint16_t)
|
||||
retro_atomic_load_acquire_int(&psp->read_pos), psp->ring);
|
||||
return _len * sizeof(uint32_t);
|
||||
}
|
||||
|
||||
/* Sleep on the condition the output thread signals after every block
|
||||
* until the fifo has room for len, in the same units psp2_audio_write()
|
||||
* compares against, capped at half the fifo so the wait always ends.
|
||||
* Returns the free space as psp2_write_avail() reports it, or 0 when
|
||||
* the output thread is not running. */
|
||||
static size_t psp2_wait_writable(void *data, size_t len)
|
||||
{
|
||||
psp2_audio_t* psp = (psp2_audio_t*)data;
|
||||
size_t avail;
|
||||
int laps = PSP2_AUDIO_WAIT_LAPS;
|
||||
/* len arrives in bytes; the ring is counted in uint32_t frames. */
|
||||
size_t want = len / sizeof(uint32_t);
|
||||
|
||||
if (want > psp->ring / 2)
|
||||
want = psp->ring / 2;
|
||||
|
||||
for (;;)
|
||||
{
|
||||
int key;
|
||||
if (!retro_atomic_load_acquire_int(&psp->running))
|
||||
return 0;
|
||||
avail = RING_FREE((uint16_t)
|
||||
retro_atomic_load_relaxed_int(&psp->write_pos), (uint16_t)
|
||||
retro_atomic_load_acquire_int(&psp->read_pos), psp->ring);
|
||||
if (avail >= want)
|
||||
break;
|
||||
/* Bounded per wait and overall: a thread that has stopped
|
||||
* consuming hands the pass back as no space coming from this
|
||||
* call. The room is re-checked inside the window. */
|
||||
if (--laps < 0)
|
||||
return 0;
|
||||
key = retro_eventcount_prepare_wait(&psp->park);
|
||||
if (RING_FREE((uint16_t)
|
||||
retro_atomic_load_relaxed_int(&psp->write_pos), (uint16_t)
|
||||
retro_atomic_load_acquire_int(&psp->read_pos),
|
||||
psp->ring) >= want
|
||||
|| !retro_atomic_load_acquire_int(&psp->running))
|
||||
{
|
||||
retro_eventcount_cancel_wait(&psp->park);
|
||||
continue;
|
||||
}
|
||||
if (!retro_eventcount_commit_wait_timeout(&psp->park, key,
|
||||
PSP2_AUDIO_WAIT_US))
|
||||
continue;
|
||||
}
|
||||
return avail * sizeof(uint32_t);
|
||||
}
|
||||
|
||||
/* sceAudioOut takes 16-bit PCM only; there is no float output on the
|
||||
* Vita hardware or in the kernel API. */
|
||||
static bool psp2_audio_use_float(void *data) { return false; }
|
||||
static size_t psp2_buffer_size(void *data)
|
||||
{
|
||||
psp2_audio_t* psp = (psp2_audio_t*)data;
|
||||
if (!psp)
|
||||
return 0;
|
||||
/* In bytes: the ring, in uint32_t frames of int16 stereo. The
|
||||
* window the device is playing is held inside it. */
|
||||
return psp->ring * sizeof(uint32_t);
|
||||
}
|
||||
|
||||
/* Frames the device has taken since init: the output call returns
|
||||
* when it has taken the one before, so the worker counts a period
|
||||
* per completed call. */
|
||||
static size_t psp2_frames_consumed(void *data)
|
||||
{
|
||||
psp2_audio_t* psp = (psp2_audio_t*)data;
|
||||
if (!psp)
|
||||
return 0;
|
||||
return retro_atomic_load_acquire_size(&psp->consumed);
|
||||
}
|
||||
|
||||
/* Periods played as silence for want of audio. */
|
||||
static size_t psp2_underruns(void *data)
|
||||
{
|
||||
psp2_audio_t* psp = (psp2_audio_t*)data;
|
||||
if (!psp)
|
||||
return 0;
|
||||
return retro_atomic_load_acquire_size(&psp->underruns);
|
||||
}
|
||||
|
||||
audio_driver_t audio_psp2 = {
|
||||
psp2_audio_init,
|
||||
psp2_audio_write,
|
||||
psp2_audio_stop,
|
||||
psp2_audio_start,
|
||||
psp2_audio_alive,
|
||||
psp2_audio_set_nonblock_state,
|
||||
psp2_audio_free,
|
||||
psp2_audio_use_float,
|
||||
"vita",
|
||||
NULL,
|
||||
NULL,
|
||||
psp2_write_avail,
|
||||
psp2_buffer_size,
|
||||
NULL, /* write_raw */
|
||||
psp2_wait_writable,
|
||||
psp2_frames_consumed,
|
||||
psp2_underruns
|
||||
};
|
||||
+127
-203
@@ -16,7 +16,9 @@
|
||||
*/
|
||||
|
||||
#include <stdint.h>
|
||||
#if defined(VITA) || defined(PSP)
|
||||
#include <malloc.h>
|
||||
#endif
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
@@ -24,16 +26,26 @@
|
||||
#include <rthreads/retro_eventcount.h>
|
||||
#include <retro_atomic.h>
|
||||
|
||||
#if defined(VITA)
|
||||
#include <psp2/kernel/processmgr.h>
|
||||
#include <psp2/kernel/threadmgr.h>
|
||||
#include <psp2/kernel/sysmem.h>
|
||||
#include <psp2/audioout.h>
|
||||
#elif defined(PSP)
|
||||
#include <pspkernel.h>
|
||||
#include <pspaudio.h>
|
||||
#elif defined(ORBIS)
|
||||
#include <libSceAudioOut.h>
|
||||
#include <defines/ps4_defines.h>
|
||||
#include <verbosity.h>
|
||||
#endif
|
||||
|
||||
#include "../audio_driver.h"
|
||||
|
||||
typedef struct psp_audio
|
||||
{
|
||||
/* The ring, followed by one period of silence for the worker
|
||||
* to output when the ring runs short. */
|
||||
uint32_t* buffer_u32;
|
||||
uint32_t* buffer;
|
||||
uint32_t* zeroBuffer;
|
||||
|
||||
sthread_t *worker_thread;
|
||||
/* The writer's park; the worker notifies after every period it
|
||||
@@ -42,20 +54,13 @@ typedef struct psp_audio
|
||||
* signal-every-iteration took cond_lock each time. */
|
||||
retro_eventcount_t park;
|
||||
|
||||
/* For the sink rate estimate and the statistics overlay.
|
||||
* Only the worker writes either. */
|
||||
retro_atomic_size_t consumed;
|
||||
retro_atomic_size_t underruns;
|
||||
SceUID thread;
|
||||
|
||||
int port;
|
||||
int rate;
|
||||
|
||||
/* Frames the ring holds. Set before the worker starts, and
|
||||
* fixed for the life of the driver. */
|
||||
unsigned ring;
|
||||
|
||||
/* The ring's index pair, the SPSC discipline by hand because the
|
||||
* worker consumes in place - psp->buffer_u32 + read_pos goes straight
|
||||
* worker consumes in place - psp->buffer + read_pos goes straight
|
||||
* into the output syscall, and a retro_spsc would add a bounce
|
||||
* copy per period. Producer publishes write_pos with release
|
||||
* after the samples land; consumer publishes read_pos with
|
||||
@@ -70,25 +75,8 @@ typedef struct psp_audio
|
||||
} psp_audio_t;
|
||||
|
||||
#define AUDIO_OUT_COUNT 512u
|
||||
|
||||
/* The ring is what the latency setting asks for, so it is a whole
|
||||
* number of periods rather than a power of two and the wrap is a
|
||||
* compare. The window the device is playing stays inside the ring
|
||||
* as held until the output call after it returns (see the worker),
|
||||
* so the writer's room is the ring less that window less one frame.
|
||||
* Six periods is the floor: five of room, which holds a delivery of
|
||||
* a video frame of audio - 1600 frames for 30 Hz content at 48 kHz -
|
||||
* beside the window in flight without dipping under a period each
|
||||
* refill. Anything at or above the default is unaffected. */
|
||||
#define AUDIO_RING_MIN (AUDIO_OUT_COUNT * 6u)
|
||||
#define AUDIO_RING_MAX (AUDIO_OUT_COUNT * 64u)
|
||||
|
||||
/* Frames held, and frames the writer may still place - one short of
|
||||
* the ring, so a full ring and an empty one do not both read as
|
||||
* write_pos == read_pos. */
|
||||
#define RING_HELD(w, r, n) \
|
||||
((unsigned)((w) >= (r) ? (w) - (r) : (n) - (r) + (w)))
|
||||
#define RING_FREE(w, r, n) ((n) - 1u - RING_HELD((w), (r), (n)))
|
||||
#define AUDIO_BUFFER_SIZE (1u<<13u)
|
||||
#define AUDIO_BUFFER_SIZE_MASK (AUDIO_BUFFER_SIZE-1)
|
||||
|
||||
/* Bound on any wait for the audio thread to consume: one wait, and how
|
||||
* many of them before the caller gets the pass back. The thread
|
||||
@@ -96,92 +84,62 @@ typedef struct psp_audio
|
||||
#define PSP_AUDIO_WAIT_US 100000
|
||||
#define PSP_AUDIO_WAIT_LAPS 8
|
||||
|
||||
/* The rate the SRC always takes, for a setting it will not. */
|
||||
#define PSP_AUDIO_RATE_FALLBACK 48000
|
||||
|
||||
/* The ring the latency setting asks for: that many milliseconds at
|
||||
* the rate the device opened at, in whole periods so no window the
|
||||
* worker hands over crosses the ring's end. The window the device is
|
||||
* playing is inside it, so this is the whole of the driver's
|
||||
* buffering and what buffer_size() reports. */
|
||||
static unsigned psp_ring_frames(unsigned rate, unsigned latency)
|
||||
{
|
||||
unsigned frames = rate * latency / 1000u;
|
||||
frames = ((frames + AUDIO_OUT_COUNT - 1u) / AUDIO_OUT_COUNT)
|
||||
* AUDIO_OUT_COUNT;
|
||||
if (frames < AUDIO_RING_MIN)
|
||||
return AUDIO_RING_MIN;
|
||||
if (frames > AUDIO_RING_MAX)
|
||||
return AUDIO_RING_MAX;
|
||||
return frames;
|
||||
}
|
||||
|
||||
/* Return port used */
|
||||
static int psp_configure_audio(unsigned rate, unsigned *new_rate)
|
||||
static int psp_configure_audio(unsigned rate)
|
||||
{
|
||||
int port = sceAudioSRCChReserve(AUDIO_OUT_COUNT, rate, 2);
|
||||
/* The SRC takes a fixed set of rates and refuses the rest outright,
|
||||
* which leaves the session with no audio at all. Open at a rate it
|
||||
* takes and tell the frontend to resample to that instead. */
|
||||
if (port < 0)
|
||||
{
|
||||
if ((port = sceAudioSRCChReserve(AUDIO_OUT_COUNT,
|
||||
PSP_AUDIO_RATE_FALLBACK, 2)) >= 0)
|
||||
*new_rate = PSP_AUDIO_RATE_FALLBACK;
|
||||
}
|
||||
return port;
|
||||
#if defined(VITA)
|
||||
return sceAudioOutOpenPort(
|
||||
SCE_AUDIO_OUT_PORT_TYPE_MAIN, AUDIO_OUT_COUNT,
|
||||
rate, SCE_AUDIO_OUT_MODE_STEREO);
|
||||
#elif defined(ORBIS)
|
||||
return sceAudioOutOpen(0xff,
|
||||
SCE_AUDIO_OUT_PORT_TYPE_MAIN, 0, AUDIO_OUT_COUNT,
|
||||
rate, SCE_AUDIO_OUT_MODE_STEREO);
|
||||
#else
|
||||
return sceAudioSRCChReserve(AUDIO_OUT_COUNT, rate, 2);
|
||||
#endif
|
||||
}
|
||||
|
||||
static void psp_audio_mainloop(void *data)
|
||||
{
|
||||
psp_audio_t* psp = (psp_audio_t*)data;
|
||||
/* The worker's own cursor: the next window to hand over. The
|
||||
* published read_pos runs one window behind it while a ring
|
||||
* window is in flight. The output call returns once the window
|
||||
* handed to the previous call has been output, and with this
|
||||
* call's window queued and still to be read by the hardware -
|
||||
* the SDKs document passing NULL as the way to wait for that
|
||||
* last window - so the window a call hands over is the device's
|
||||
* until the call after it returns, and is released there. */
|
||||
uint16_t play_pos = (uint16_t)
|
||||
retro_atomic_load_relaxed_int(&psp->read_pos);
|
||||
|
||||
while (retro_atomic_load_acquire_int(&psp->running))
|
||||
{
|
||||
bool cond = false;
|
||||
uint16_t next_pos = play_pos;
|
||||
uint16_t read_pos = (uint16_t)
|
||||
retro_atomic_load_relaxed_int(&psp->read_pos);
|
||||
#if defined(VITA) || defined(ORBIS)
|
||||
uint16_t read_pos_2 = read_pos;
|
||||
#endif
|
||||
uint16_t write_pos = (uint16_t)
|
||||
retro_atomic_load_acquire_int(&psp->write_pos);
|
||||
|
||||
/* A period in hand is a period to play. Holding one back
|
||||
* as a reserve only hands the device silence while the
|
||||
* ring has audio in it. */
|
||||
cond = RING_HELD(write_pos, play_pos, psp->ring)
|
||||
< AUDIO_OUT_COUNT;
|
||||
cond = ((uint16_t)(write_pos - read_pos) & AUDIO_BUFFER_SIZE_MASK)
|
||||
< (AUDIO_OUT_COUNT * 2);
|
||||
|
||||
if (!cond)
|
||||
{
|
||||
next_pos += AUDIO_OUT_COUNT;
|
||||
if (next_pos >= psp->ring)
|
||||
next_pos = 0;
|
||||
read_pos += AUDIO_OUT_COUNT;
|
||||
read_pos &= AUDIO_BUFFER_SIZE_MASK;
|
||||
/* Release: the period is free for the writer only after
|
||||
* this store; the syscall below reads the old window, which
|
||||
* the writer cannot touch until it sees the new index. */
|
||||
retro_atomic_store_release_int(&psp->read_pos, read_pos);
|
||||
}
|
||||
else
|
||||
retro_atomic_fetch_add_size(&psp->underruns, 1);
|
||||
|
||||
sceAudioSRCOutputBlocking(PSP_AUDIO_VOLUME_MAX,
|
||||
psp->buffer_u32
|
||||
+ (cond ? psp->ring : play_pos));
|
||||
|
||||
retro_atomic_fetch_add_size(&psp->consumed, AUDIO_OUT_COUNT);
|
||||
|
||||
/* The previous call's window has been output; this call's is
|
||||
* the one in flight, so everything before play_pos is the
|
||||
* writer's. When this call handed over silence, play_pos is
|
||||
* where read_pos already stands. */
|
||||
retro_atomic_store_release_int(&psp->read_pos, play_pos);
|
||||
play_pos = next_pos;
|
||||
|
||||
retro_eventcount_notify(&psp->park);
|
||||
|
||||
#if defined(VITA) || defined(ORBIS)
|
||||
sceAudioOutOutput(psp->port,
|
||||
cond ? (psp->zeroBuffer)
|
||||
: (psp->buffer + read_pos_2));
|
||||
#else
|
||||
sceAudioSRCOutputBlocking(PSP_AUDIO_VOLUME_MAX,
|
||||
cond
|
||||
? (psp->zeroBuffer)
|
||||
: (psp->buffer + read_pos));
|
||||
#endif
|
||||
}
|
||||
|
||||
return;
|
||||
@@ -197,49 +155,37 @@ static void *psp_audio_init(const char *device,
|
||||
if (!psp)
|
||||
return NULL;
|
||||
|
||||
if ((port = psp_configure_audio(rate, new_rate)) < 0)
|
||||
if ((port = psp_configure_audio(rate)) < 0)
|
||||
{
|
||||
free(psp);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
psp->rate = *new_rate ? *new_rate : rate;
|
||||
psp->ring = psp_ring_frames(psp->rate, latency);
|
||||
|
||||
#if defined(ORBIS)
|
||||
sceAudioOutInit();
|
||||
#endif
|
||||
/* Cache aligned, not necessary but helpful. */
|
||||
/* The ring, and one period of silence past its end. */
|
||||
psp->buffer_u32 = (uint32_t*)calloc(psp->ring + AUDIO_OUT_COUNT,
|
||||
sizeof(uint32_t));
|
||||
psp->buffer = (uint32_t*)malloc(AUDIO_BUFFER_SIZE * sizeof(uint32_t));
|
||||
memset(psp->buffer, 0, AUDIO_BUFFER_SIZE * sizeof(uint32_t));
|
||||
|
||||
psp->zeroBuffer = (uint32_t*)malloc(AUDIO_OUT_COUNT * sizeof(uint32_t));
|
||||
memset(psp->zeroBuffer, 0, AUDIO_OUT_COUNT * sizeof(uint32_t));
|
||||
|
||||
retro_atomic_size_init(&psp->consumed, 0);
|
||||
retro_atomic_size_init(&psp->underruns, 0);
|
||||
retro_atomic_int_init(&psp->read_pos, 0);
|
||||
retro_atomic_int_init(&psp->write_pos, 0);
|
||||
psp->port = port;
|
||||
|
||||
if ( !psp->buffer_u32
|
||||
|| !retro_eventcount_init(&psp->park))
|
||||
if (!retro_eventcount_init(&psp->park))
|
||||
{
|
||||
sceAudioSRCChRelease();
|
||||
free(psp->buffer_u32);
|
||||
free(psp->buffer);
|
||||
free(psp->zeroBuffer);
|
||||
free(psp);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
psp->nonblock = false;
|
||||
/* running has to be set before the worker starts, so a worker that
|
||||
* never starts leaves a driver reporting itself alive with nothing
|
||||
* consuming the ring: writes wait out their laps and return zero,
|
||||
* and teardown acts on a state that was never true. */
|
||||
retro_atomic_int_init(&psp->running, 1);
|
||||
if (!(psp->worker_thread = sthread_create(psp_audio_mainloop, psp)))
|
||||
{
|
||||
retro_eventcount_free(&psp->park);
|
||||
sceAudioSRCChRelease();
|
||||
free(psp->buffer_u32);
|
||||
free(psp);
|
||||
return NULL;
|
||||
}
|
||||
psp->worker_thread = sthread_create(psp_audio_mainloop, psp);
|
||||
|
||||
return psp;
|
||||
}
|
||||
@@ -259,10 +205,17 @@ static void psp_audio_free(void *data)
|
||||
}
|
||||
}
|
||||
retro_eventcount_free(&psp->park);
|
||||
free(psp->buffer_u32);
|
||||
free(psp->buffer);
|
||||
psp->worker_thread = NULL;
|
||||
free(psp->zeroBuffer);
|
||||
|
||||
sceAudioSRCChRelease();
|
||||
#if defined(VITA)
|
||||
sceAudioOutReleasePort(psp->port);
|
||||
#elif defined(ORBIS)
|
||||
sceAudioOutClose(psp->port);
|
||||
#else
|
||||
sceAudioSRCChRelease();
|
||||
#endif
|
||||
|
||||
free(psp);
|
||||
|
||||
@@ -273,25 +226,13 @@ static ssize_t psp_audio_write(void *data, const void *s, size_t len)
|
||||
psp_audio_t* psp = (psp_audio_t*)data;
|
||||
uint16_t write_pos = (uint16_t)
|
||||
retro_atomic_load_relaxed_int(&psp->write_pos);
|
||||
/* Frames, and wide enough for them: narrowed to uint16_t, a write of
|
||||
* more than 65535 frames wrapped to a small count that the room check
|
||||
* then waved through, and the copy below took len bytes for it. The
|
||||
* ring holds one frame short of its length, so no wait can free room
|
||||
* for more than that at once - take what fits and report the short
|
||||
* write rather than spending the laps on room that will not come. A
|
||||
* len that is not whole frames carries the frames it has. */
|
||||
size_t sample_count = len / sizeof(uint32_t);
|
||||
uint16_t sample_count = len / sizeof(uint32_t);
|
||||
|
||||
if (!retro_atomic_load_acquire_int(&psp->running))
|
||||
return -1;
|
||||
if (!sample_count)
|
||||
return 0;
|
||||
if (sample_count > (size_t)(psp->ring - 1u))
|
||||
sample_count = psp->ring - 1u;
|
||||
len = sample_count * sizeof(uint32_t);
|
||||
|
||||
/* The ring is counted in uint32_t frames (write_pos, read_pos,
|
||||
* ring); len is bytes. Both room checks below used to
|
||||
* AUDIO_BUFFER_SIZE); len is bytes. Both room checks below used to
|
||||
* compare the frame count against len, i.e. demanded four times the
|
||||
* room actually needed: non-blocking writes were refused - the audio
|
||||
* dropped - with plenty of space free, and blocking ones waited for
|
||||
@@ -300,9 +241,9 @@ static ssize_t psp_audio_write(void *data, const void *s, size_t len)
|
||||
* frames here too. */
|
||||
if (psp->nonblock)
|
||||
{
|
||||
if (RING_FREE(write_pos, (uint16_t)
|
||||
retro_atomic_load_acquire_int(&psp->read_pos),
|
||||
psp->ring) < sample_count)
|
||||
if (AUDIO_BUFFER_SIZE - ((uint16_t)(write_pos - (uint16_t)
|
||||
retro_atomic_load_acquire_int(&psp->read_pos))
|
||||
& AUDIO_BUFFER_SIZE_MASK) < sample_count)
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -315,17 +256,17 @@ static ssize_t psp_audio_write(void *data, const void *s, size_t len)
|
||||
* are re-checked inside the eventcount's window, so a period
|
||||
* freed between the check and the park costs nothing. */
|
||||
int laps = PSP_AUDIO_WAIT_LAPS;
|
||||
while (RING_FREE(write_pos, (uint16_t)
|
||||
retro_atomic_load_acquire_int(&psp->read_pos),
|
||||
psp->ring) < sample_count)
|
||||
while (AUDIO_BUFFER_SIZE - ((uint16_t)(write_pos - (uint16_t)
|
||||
retro_atomic_load_acquire_int(&psp->read_pos))
|
||||
& AUDIO_BUFFER_SIZE_MASK) < sample_count)
|
||||
{
|
||||
int key;
|
||||
if (--laps < 0 || !retro_atomic_load_acquire_int(&psp->running))
|
||||
return 0;
|
||||
key = retro_eventcount_prepare_wait(&psp->park);
|
||||
if ( (RING_FREE(write_pos, (uint16_t)
|
||||
retro_atomic_load_acquire_int(&psp->read_pos),
|
||||
psp->ring) >= sample_count)
|
||||
if ( (AUDIO_BUFFER_SIZE - ((uint16_t)(write_pos - (uint16_t)
|
||||
retro_atomic_load_acquire_int(&psp->read_pos))
|
||||
& AUDIO_BUFFER_SIZE_MASK) >= sample_count)
|
||||
|| !retro_atomic_load_acquire_int(&psp->running))
|
||||
{
|
||||
retro_eventcount_cancel_wait(&psp->park);
|
||||
@@ -337,20 +278,19 @@ static ssize_t psp_audio_write(void *data, const void *s, size_t len)
|
||||
}
|
||||
}
|
||||
|
||||
if ((write_pos + sample_count) > psp->ring)
|
||||
if ((write_pos + sample_count) > AUDIO_BUFFER_SIZE)
|
||||
{
|
||||
memcpy(psp->buffer_u32 + write_pos, s,
|
||||
(psp->ring - write_pos) * sizeof(uint32_t));
|
||||
memcpy(psp->buffer_u32, (uint32_t*)s +
|
||||
(psp->ring - write_pos),
|
||||
(write_pos + sample_count - psp->ring) * sizeof(uint32_t));
|
||||
memcpy(psp->buffer + write_pos, s,
|
||||
(AUDIO_BUFFER_SIZE - write_pos) * sizeof(uint32_t));
|
||||
memcpy(psp->buffer, (uint32_t*)s +
|
||||
(AUDIO_BUFFER_SIZE - write_pos),
|
||||
(write_pos + sample_count - AUDIO_BUFFER_SIZE) * sizeof(uint32_t));
|
||||
}
|
||||
else
|
||||
memcpy(psp->buffer_u32 + write_pos, s, len);
|
||||
memcpy(psp->buffer + write_pos, s, len);
|
||||
|
||||
write_pos = (uint16_t)(write_pos + sample_count);
|
||||
if (write_pos >= psp->ring)
|
||||
write_pos -= psp->ring;
|
||||
write_pos += sample_count;
|
||||
write_pos &= AUDIO_BUFFER_SIZE_MASK;
|
||||
/* Release: the samples land before the index that publishes
|
||||
* them. */
|
||||
retro_atomic_store_release_int(&psp->write_pos, write_pos);
|
||||
@@ -369,6 +309,9 @@ static bool psp_audio_stop(void *data)
|
||||
{
|
||||
psp_audio_t* psp = (psp_audio_t*)data;
|
||||
|
||||
#if defined(ORBIS)
|
||||
return false;
|
||||
#else
|
||||
if (psp)
|
||||
{
|
||||
retro_atomic_store_release_int(&psp->running, 0);
|
||||
@@ -383,6 +326,7 @@ static bool psp_audio_stop(void *data)
|
||||
}
|
||||
}
|
||||
return true;
|
||||
#endif
|
||||
}
|
||||
|
||||
static bool psp_audio_start(void *data, bool is_shutdown)
|
||||
@@ -394,11 +338,7 @@ static bool psp_audio_start(void *data, bool is_shutdown)
|
||||
if (!psp->worker_thread)
|
||||
{
|
||||
retro_atomic_store_release_int(&psp->running, 1);
|
||||
if (!(psp->worker_thread = sthread_create(psp_audio_mainloop, psp)))
|
||||
{
|
||||
retro_atomic_store_release_int(&psp->running, 0);
|
||||
return false;
|
||||
}
|
||||
psp->worker_thread = sthread_create(psp_audio_mainloop, psp);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -419,9 +359,10 @@ static size_t psp_write_avail(void *data)
|
||||
|
||||
if (!psp || !retro_atomic_load_acquire_int(&psp->running))
|
||||
return 0;
|
||||
_len = RING_FREE((uint16_t)
|
||||
retro_atomic_load_relaxed_int(&psp->write_pos), (uint16_t)
|
||||
retro_atomic_load_acquire_int(&psp->read_pos), psp->ring);
|
||||
_len = AUDIO_BUFFER_SIZE - ((uint16_t)((uint16_t)
|
||||
retro_atomic_load_relaxed_int(&psp->write_pos) - (uint16_t)
|
||||
retro_atomic_load_acquire_int(&psp->read_pos))
|
||||
& AUDIO_BUFFER_SIZE_MASK);
|
||||
return _len * sizeof(uint32_t);
|
||||
}
|
||||
|
||||
@@ -438,17 +379,18 @@ static size_t psp_wait_writable(void *data, size_t len)
|
||||
/* len arrives in bytes; the ring is counted in uint32_t frames. */
|
||||
size_t want = len / sizeof(uint32_t);
|
||||
|
||||
if (want > psp->ring / 2)
|
||||
want = psp->ring / 2;
|
||||
if (want > AUDIO_BUFFER_SIZE / 2)
|
||||
want = AUDIO_BUFFER_SIZE / 2;
|
||||
|
||||
for (;;)
|
||||
{
|
||||
int key;
|
||||
if (!retro_atomic_load_acquire_int(&psp->running))
|
||||
return 0;
|
||||
avail = RING_FREE((uint16_t)
|
||||
retro_atomic_load_relaxed_int(&psp->write_pos), (uint16_t)
|
||||
retro_atomic_load_acquire_int(&psp->read_pos), psp->ring);
|
||||
avail = AUDIO_BUFFER_SIZE - ((uint16_t)((uint16_t)
|
||||
retro_atomic_load_relaxed_int(&psp->write_pos) - (uint16_t)
|
||||
retro_atomic_load_acquire_int(&psp->read_pos))
|
||||
& AUDIO_BUFFER_SIZE_MASK);
|
||||
if (avail >= want)
|
||||
break;
|
||||
/* Bounded per wait and overall: a thread that has stopped
|
||||
@@ -457,10 +399,10 @@ static size_t psp_wait_writable(void *data, size_t len)
|
||||
if (--laps < 0)
|
||||
return 0;
|
||||
key = retro_eventcount_prepare_wait(&psp->park);
|
||||
if (RING_FREE((uint16_t)
|
||||
retro_atomic_load_relaxed_int(&psp->write_pos), (uint16_t)
|
||||
retro_atomic_load_acquire_int(&psp->read_pos),
|
||||
psp->ring) >= want
|
||||
if ((AUDIO_BUFFER_SIZE - ((uint16_t)((uint16_t)
|
||||
retro_atomic_load_relaxed_int(&psp->write_pos) - (uint16_t)
|
||||
retro_atomic_load_acquire_int(&psp->read_pos))
|
||||
& AUDIO_BUFFER_SIZE_MASK)) >= want
|
||||
|| !retro_atomic_load_acquire_int(&psp->running))
|
||||
{
|
||||
retro_eventcount_cancel_wait(&psp->park);
|
||||
@@ -478,32 +420,10 @@ static size_t psp_wait_writable(void *data, size_t len)
|
||||
static bool psp_audio_use_float(void *data) { return false; }
|
||||
static size_t psp_buffer_size(void *data)
|
||||
{
|
||||
psp_audio_t* psp = (psp_audio_t*)data;
|
||||
if (!psp)
|
||||
return 0;
|
||||
/* In bytes: the ring, in uint32_t frames of int16 stereo. The
|
||||
* window the device is playing is held inside it. */
|
||||
return psp->ring * sizeof(uint32_t);
|
||||
}
|
||||
|
||||
/* Frames the device has taken since init: the output call returns
|
||||
* when it has taken the one before, so the worker counts a period
|
||||
* per completed call. */
|
||||
static size_t psp_frames_consumed(void *data)
|
||||
{
|
||||
psp_audio_t* psp = (psp_audio_t*)data;
|
||||
if (!psp)
|
||||
return 0;
|
||||
return retro_atomic_load_acquire_size(&psp->consumed);
|
||||
}
|
||||
|
||||
/* Periods played as silence for want of audio. */
|
||||
static size_t psp_underruns(void *data)
|
||||
{
|
||||
psp_audio_t* psp = (psp_audio_t*)data;
|
||||
if (!psp)
|
||||
return 0;
|
||||
return retro_atomic_load_acquire_size(&psp->underruns);
|
||||
/* In bytes: the ring holds AUDIO_BUFFER_SIZE uint32_t frames of int16
|
||||
* stereo. The comment beside this had the multiplication and left it
|
||||
* out. */
|
||||
return AUDIO_BUFFER_SIZE * sizeof(uint32_t);
|
||||
}
|
||||
|
||||
audio_driver_t audio_psp = {
|
||||
@@ -515,13 +435,17 @@ audio_driver_t audio_psp = {
|
||||
psp_audio_set_nonblock_state,
|
||||
psp_audio_free,
|
||||
psp_audio_use_float,
|
||||
#if defined(VITA)
|
||||
"vita",
|
||||
#elif defined(ORBIS)
|
||||
"orbis",
|
||||
#else
|
||||
"psp",
|
||||
#endif
|
||||
NULL,
|
||||
NULL,
|
||||
psp_write_avail,
|
||||
psp_buffer_size,
|
||||
NULL, /* write_raw */
|
||||
psp_wait_writable,
|
||||
psp_frames_consumed,
|
||||
psp_underruns
|
||||
psp_wait_writable
|
||||
};
|
||||
+8
-111
@@ -63,24 +63,6 @@ typedef struct
|
||||
* lowers it at once. */
|
||||
retro_atomic_size_t writable_cached;
|
||||
retro_atomic_size_t sink_frames_cached;
|
||||
/* Times the server ran out of audio for this stream, from its own
|
||||
* underflow callback. One atomic add there, read by the frontend. */
|
||||
retro_atomic_size_t underruns;
|
||||
/* The sink's clock against the rate the stream was opened at,
|
||||
* fitted from the timing info the server already hands over:
|
||||
* read_index is what the sink has played, in bytes, and timestamp
|
||||
* is when that was true. A fit over every sample rather than two
|
||||
* points, because noise on a single anchor divides by the window
|
||||
* and reads as drift.
|
||||
*
|
||||
* Accumulated in the latency-update callback, on the mainloop's
|
||||
* thread, and published as one int in ppm. Nothing acts on it. */
|
||||
unsigned frame_bytes;
|
||||
uint64_t clk_anchor_pos;
|
||||
int64_t clk_anchor_us;
|
||||
int clk_have_anchor;
|
||||
double clk_sx, clk_sy, clk_sxx, clk_sxy, clk_n;
|
||||
retro_atomic_int_t clk_ppm; /* AUDIO_CLOCK_PPM_NONE until known */
|
||||
} pa_t;
|
||||
|
||||
/* A note for the eventcount census: this driver stays off it, on
|
||||
@@ -236,64 +218,6 @@ static void pulse_stream_request_cb(pa_stream *s, size_t len, void *data)
|
||||
pa_threaded_mainloop_signal(pa->mainloop, 0);
|
||||
}
|
||||
|
||||
/* One (position, time) pair from the server's timing info into the
|
||||
* fit. read_index is the sink's play position in bytes; it can step
|
||||
* back on a rewind or a flush, which restarts the fit rather than
|
||||
* reading as an enormous negative drift. */
|
||||
static void pulse_clock_sample(pa_t *pa, const pa_timing_info *ti)
|
||||
{
|
||||
uint64_t pos;
|
||||
int64_t us;
|
||||
double x, y, denom;
|
||||
|
||||
if (!pa->frame_bytes || ti->read_index < 0)
|
||||
return;
|
||||
|
||||
pos = (uint64_t)ti->read_index / pa->frame_bytes;
|
||||
us = (int64_t)ti->timestamp.tv_sec * 1000000
|
||||
+ (int64_t)ti->timestamp.tv_usec;
|
||||
|
||||
if (!pa->clk_have_anchor || pos < pa->clk_anchor_pos
|
||||
|| us <= pa->clk_anchor_us)
|
||||
{
|
||||
pa->clk_anchor_pos = pos;
|
||||
pa->clk_anchor_us = us;
|
||||
pa->clk_have_anchor = 1;
|
||||
pa->clk_sx = pa->clk_sy = pa->clk_sxx = pa->clk_sxy = pa->clk_n = 0.0;
|
||||
retro_atomic_store_release_int(&pa->clk_ppm, AUDIO_CLOCK_PPM_NONE);
|
||||
return;
|
||||
}
|
||||
|
||||
/* Seconds and frames from the anchor: a fit on the raw values
|
||||
* loses its answer to cancellation. */
|
||||
x = (double)(us - pa->clk_anchor_us) / 1000000.0;
|
||||
y = (double)(pos - pa->clk_anchor_pos);
|
||||
|
||||
pa->clk_sx += x;
|
||||
pa->clk_sy += y;
|
||||
pa->clk_sxx += x * x;
|
||||
pa->clk_sxy += x * y;
|
||||
pa->clk_n += 1.0;
|
||||
|
||||
/* A second of window at least, as the interface says. */
|
||||
if (pa->clk_n < 4.0 || x < 1.0)
|
||||
return;
|
||||
|
||||
denom = pa->clk_n * pa->clk_sxx - pa->clk_sx * pa->clk_sx;
|
||||
if (denom <= 0.0)
|
||||
return;
|
||||
|
||||
{
|
||||
double slope = (pa->clk_n * pa->clk_sxy - pa->clk_sx * pa->clk_sy)
|
||||
/ denom;
|
||||
double ppm = (slope / (double)pa->rate - 1.0) * 1000000.0;
|
||||
if (ppm > -100000.0 && ppm < 100000.0)
|
||||
{
|
||||
retro_atomic_store_release_int(&pa->clk_ppm, (int)ppm);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void pulse_stream_latency_update_cb(pa_stream *s, void *data)
|
||||
{
|
||||
pa_t *pa = (pa_t*)data;
|
||||
@@ -302,25 +226,22 @@ static void pulse_stream_latency_update_cb(pa_stream *s, void *data)
|
||||
* the server's queue; NULL until timing data has arrived. */
|
||||
const pa_timing_info *ti = pa_stream_get_timing_info(s);
|
||||
if (ti && pa->rate)
|
||||
{
|
||||
retro_atomic_store_release_size(&pa->sink_frames_cached,
|
||||
(size_t)((uint64_t)ti->sink_usec * pa->rate / 1000000));
|
||||
pulse_clock_sample(pa, ti);
|
||||
}
|
||||
pa_threaded_mainloop_signal(pa->mainloop, 0);
|
||||
}
|
||||
|
||||
/* The server telling us this stream ran dry. Counted, not logged: it
|
||||
* arrives on the mainloop's thread while audio is playing, which is
|
||||
* the one place a log line costs what it is reporting on. */
|
||||
static void pulse_underrun_update_cb(pa_stream *s, void *data)
|
||||
{
|
||||
#if 0
|
||||
pa_t *pa = (pa_t*)data;
|
||||
|
||||
(void)s;
|
||||
|
||||
if (pa)
|
||||
retro_atomic_fetch_add_size(&pa->underruns, 1);
|
||||
RARCH_LOG("[PulseAudio] Underrun (Buffer: %u, Writable size: %u).\n",
|
||||
(unsigned)pa->buffer_size,
|
||||
(unsigned)pa_stream_writable_size(pa->stream));
|
||||
#endif
|
||||
}
|
||||
|
||||
static void pulse_buffer_attr_cb(pa_stream *s, void *data)
|
||||
@@ -378,8 +299,6 @@ static void *pulse_init(const char *device, unsigned rate,
|
||||
return NULL;
|
||||
retro_atomic_size_init(&pa->writable_cached, 0);
|
||||
retro_atomic_size_init(&pa->sink_frames_cached, 0);
|
||||
retro_atomic_size_init(&pa->underruns, 0);
|
||||
retro_atomic_int_init(&pa->clk_ppm, AUDIO_CLOCK_PPM_NONE);
|
||||
|
||||
memset(&spec, 0, sizeof(spec));
|
||||
|
||||
@@ -432,8 +351,7 @@ static void *pulse_init(const char *device, unsigned rate,
|
||||
spec.channels = (uint8_t)audio_layout_channels(pa->layout);
|
||||
spec.rate = rate;
|
||||
pulse_channel_map(pa->layout, &map);
|
||||
pa->rate = rate;
|
||||
pa->frame_bytes = (unsigned)pa_frame_size(&spec);
|
||||
pa->rate = rate;
|
||||
|
||||
pa->stream = pa_stream_new(pa->context, "audio", &spec, &map);
|
||||
if (!pa->stream)
|
||||
@@ -860,25 +778,6 @@ static void pulse_device_list_free(void *data, void *array_list_data)
|
||||
string_list_free(s);
|
||||
}
|
||||
|
||||
static size_t pulse_underruns(void *data)
|
||||
{
|
||||
pa_t *pa = (pa_t*)data;
|
||||
return pa ? retro_atomic_load_acquire_size(&pa->underruns) : 0;
|
||||
}
|
||||
|
||||
static bool pulse_device_clock_ppm(void *data, double *ppm)
|
||||
{
|
||||
pa_t *pa = (pa_t*)data;
|
||||
int v;
|
||||
if (!pa)
|
||||
return false;
|
||||
v = retro_atomic_load_acquire_int(&pa->clk_ppm);
|
||||
if (v == AUDIO_CLOCK_PPM_NONE)
|
||||
return false;
|
||||
*ppm = (double)v;
|
||||
return true;
|
||||
}
|
||||
|
||||
audio_driver_t audio_pulse = {
|
||||
pulse_init,
|
||||
pulse_write,
|
||||
@@ -896,8 +795,6 @@ audio_driver_t audio_pulse = {
|
||||
NULL, /* write_raw */
|
||||
pulse_wait_writable,
|
||||
pulse_frames_consumed,
|
||||
pulse_underruns,
|
||||
pulse_layout,
|
||||
NULL, /* frames_consumed_fallback */
|
||||
pulse_device_clock_ppm
|
||||
NULL, /* underruns */
|
||||
pulse_layout
|
||||
};
|
||||
@@ -143,6 +143,7 @@ static void ra_free(void *data)
|
||||
* float PCM codec in the versions this driver has been built against;
|
||||
* revisit only if one appears. */
|
||||
static bool ra_use_float(void *data) { return false; }
|
||||
static size_t ra_write_avail(void *data) { return 0; }
|
||||
|
||||
audio_driver_t audio_roar = {
|
||||
ra_init,
|
||||
@@ -156,10 +157,7 @@ audio_driver_t audio_roar = {
|
||||
"roar",
|
||||
NULL,
|
||||
NULL,
|
||||
/* write_avail - the VS API reports neither the room in the stream
|
||||
* nor what the server still holds. NULL disables rate control;
|
||||
* a constant would instead feed it a fill that never changes. */
|
||||
NULL,
|
||||
ra_write_avail,
|
||||
NULL, /* buffer_size */
|
||||
NULL /* write_raw */
|
||||
};
|
||||
+4
-21
@@ -24,7 +24,6 @@
|
||||
#include <retro_atomic.h>
|
||||
|
||||
#include "../audio_driver.h"
|
||||
#include "../../verbosity.h"
|
||||
#include "rsound.h"
|
||||
|
||||
typedef struct rsd
|
||||
@@ -33,9 +32,9 @@ typedef struct rsd
|
||||
|
||||
/* Single producer (the core thread in rs_write), single consumer
|
||||
* (librsound's worker in rsound_audio_cb): a lock-free retro_spsc
|
||||
* ring, so the handoff between them needs nothing of librsound's.
|
||||
* retro_spsc rounds capacity up to a power of two; fifo_size is the
|
||||
* size asked for and the producer never fills past it. */
|
||||
* ring, so neither side takes librsound's callback lock to touch
|
||||
* it. retro_spsc rounds capacity up to a power of two; fifo_size
|
||||
* is the size asked for and the producer never fills past it. */
|
||||
retro_spsc_t ring;
|
||||
bool ring_init;
|
||||
/* Only the bounded waits a full ring puts the writer into; the ring
|
||||
@@ -66,10 +65,6 @@ typedef struct rsd
|
||||
* volatile orders nothing between threads and the re-check below
|
||||
* has to see the store that came before the notify. */
|
||||
retro_atomic_int_t has_error;
|
||||
/* Callbacks librsound asked to fill and the ring came up short
|
||||
* for; librsound zero-fills the shortfall, so what goes out is
|
||||
* silence the device played for want of audio. */
|
||||
retro_atomic_size_t underruns;
|
||||
} rsd_t;
|
||||
|
||||
/* Room in the ring against the size asked for: the physical room less
|
||||
@@ -88,9 +83,6 @@ static ssize_t rsound_audio_cb(void *data, size_t bytes, void *userdata)
|
||||
size_t write_size = retro_spsc_read(&rsd->ring, data, bytes);
|
||||
retro_eventcount_notify(&rsd->park);
|
||||
|
||||
if (write_size < bytes)
|
||||
retro_atomic_fetch_add_size(&rsd->underruns, 1);
|
||||
|
||||
return write_size;
|
||||
}
|
||||
|
||||
@@ -117,7 +109,6 @@ static void *rs_init(const char *device, unsigned rate, unsigned latency,
|
||||
goto error;
|
||||
|
||||
retro_atomic_int_init(&rsd->has_error, 0);
|
||||
retro_atomic_size_init(&rsd->underruns, 0);
|
||||
/* Checked: on a backend that parks through a condition variable the
|
||||
* init allocates, and a failure leaves an object whose commit_wait
|
||||
* would hand scond_wait a NULL cond. */
|
||||
@@ -374,12 +365,6 @@ static size_t rs_wait_writable(void *data, size_t len)
|
||||
}
|
||||
static bool rs_use_float(void *data) { return false; }
|
||||
|
||||
static size_t rs_underruns(void *data)
|
||||
{
|
||||
rsd_t *rsd = (rsd_t*)data;
|
||||
return rsd ? retro_atomic_load_acquire_size(&rsd->underruns) : 0;
|
||||
}
|
||||
|
||||
audio_driver_t audio_rsound = {
|
||||
rs_init,
|
||||
rs_write,
|
||||
@@ -395,7 +380,5 @@ audio_driver_t audio_rsound = {
|
||||
rs_write_avail,
|
||||
rs_buffer_size,
|
||||
NULL, /* write_raw */
|
||||
rs_wait_writable,
|
||||
NULL, /* frames_consumed */
|
||||
rs_underruns
|
||||
rs_wait_writable
|
||||
};
|
||||
Loaded 100 of 2112 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user