Compare commits

...
Author SHA1 Message Date
LibretroAdmin 54e6ef1869 task_queue: a wait sees a task between finishing and the finished stack
A finishing task's settle counts it finished, stops counting it as
running and then pushes it onto the finished stack. The threaded
task_queue_wait() asked whether anything was left by the running count
against the scheduled count, the finished stack and the retire list -
so in the moment between the running count dropping and the push it
saw nothing, and returned before the task was retired or its callback
run. Where a handler's last pass ends just as the waiter looks, the
wait gave up on a task it was waiting for.

macOS's TSan run of task_queue_park_test hit it: 'task_queue_wait()
returned with a parked task still running', then the next lane's task
found the last one's state still on the stack (a race report in
horizon_handler, and 'the horizon handler did not run three times').
The finished count is raised before the running count drops and
lowered only once the gather has the task on the retire list, so the
busy test reads it too.

With a 30 ms sleep put between the two steps of the settle, the old
test fails exactly as on macOS and the new one passes. Every
task_queue sample passes plain, under ASan and UBSan, and under TSan.
2026-11-13 00:02:56 +01:00
LibretroAdmin a26d6b552b retro_deque: push_front_growing
push_back_growing's counterpart, for a queue with no limit of its own
that takes its newest at the front (a most-recently-used list): when
the queue holds its limit it first reserves twice as much, and only
that push allocates.

The sample pushes 100 at the front of a queue made for 4: in order,
five allocations.
2026-11-13 00:00:19 +01:00
LibretroAdmin c4174413a1 vfs: stdio streams keep their position, so a write does not ask for it
A buffered write asked stdio where the stream stood, to keep its size,
on every call. The stream keeps that position itself: set by a seek
from the start, moved by a relative seek and by what each read and
write moves, and unknown after a seek from the end, a failed seek, a
read error or a short write, when the next write or tell asks stdio
once. Tell answers from it too.

vfs_write_mix_test checks the position and the size after every kind
of operation, and fails 2030 checks with reads not moving it.
2026-11-12 23:59:19 +01:00
LibretroAdmin 990384fdb7 settings_table.reference: video_skip_dupe_frames
16565589 added the Skip Duplicate Frames setting, a bool,
video_skip_dupe_frames, default off; the settings check found it in
the three emitted tables and not in the reference. Regenerated with
tools/settings_check.py table --write; the three new rows are the
whole change, and the table check and the unity builds pass.
2026-11-12 23:39:58 +01:00
LibretroAdmin c7a5f6edb8 vfs: Windows write streams on a handle of their own; stdio streams settle before truncate, punch and flush
On Windows NT, streams opened to write, read and write, or update are
opened on a handle of their own like read-only ones, created or opened
as the C runtime would and shared the same way. The 64 KiB buffer holds
either what was read or one run of writes not yet in the file: writes
that carry on from the run join it, anything else writes the run out in
one WriteFile at its offset, and a write as large as the buffer goes
straight to the file. Reads, truncates, holes, flushes and closes write
the run out first, and a seek from the end counts it.
VFS_WIN32_NO_NATIVE keeps the C runtime.

On a stdio stream, truncating and punching a hole first write out what
stdio holds and afterwards seek back to the stream's position, so
writes made before them are cut or zeroed with the rest and nothing
read ahead outlives them. Flushing a stream that reads as well as
writes does the same, since msvcrt's fflush drops the read-ahead
without seeking back.

vfs_write_mix_test drives writes, seeks, reads, truncates, holes,
flushes, reads at offsets and reopening in every write mode against a
copy of the file, and runs in the Linux samples job and both Windows
lanes. The VFS samples build Linux with _GNU_SOURCE as RetroArch does,
so hole punching runs there. vfs_bulk_read_test checks where a plain
write stream opens, vfs_close_test counts a native write stream's
close as the descriptor close it is, and vfs_native_bench times writes.
2026-11-12 23:37:09 +01:00
sonninnos 68d25aa5bf Add option for quick scrolling with left+right (#19860) 2026-11-13 00:29:44 +02:00
LibretroAdmin 434b5525b6 retro_deque: a queue with no limit of its own, and no old-style casts in C++
Two things for the code that moves to it from std::deque and
std::queue.

push_back_growing: where the old code relied on a queue with no limit,
a push into a queue that holds its limit first reserves twice as much.
Only that push allocates; a queue given at init the most it is expected
to hold never does. Nothing is dropped that was not dropped before.

The header's casts are C++'s when it is included from C++
(RETRO_DEQUE_CAST), so a C++ unit built with -Wold-style-cast, or with
it as an error, includes the header as it is, without turning the
warning off around each use.

The sample grows a queue from 4 to 128 in order, and counts the
allocations: five, at the doublings.
2026-11-12 22:49:05 +01:00
LibretroAdmin 6bda23daad retro_deque: a pointer type as the element
RETRO_DEQUE_DEFINE(threads, struct thread*) did not compile in C++ and
meant the wrong thing in C: the functions take "const type *value", and
with a pointer type written out that is a pointer to a pointer to const,
not a pointer to a const element.

The macro names the element's type once (name_value_t) and uses that
name, so const is the element's whatever the type is spelled as.

The sample has a queue of pointers, pushed from a pointer and from a
const pointer.
2026-11-12 22:42:47 +01:00
LibretroAdmin d4fa1c6401 cheevos, archive_zstd: read at the offset in one call
The CISO hasher and the zstd archive reader read each block with
filestream_read_at, as the RVZ hasher does, and seek and read only where
the VFS has no reads at offsets. The CISO handle no longer tracks the
file position.

archive_zst_test runs its plain-file read, extract and window checks a
second time against a v6 VFS whose read_at counts its calls, and fails
an extract that never reads at an offset.
2026-11-12 22:41:04 +01:00
LibretroAdmin 75b43c75a9 vfs_close_test: a native Windows stream's handle close is its descriptor close
9ce7ef23 opens read-only streams on Windows NT on a handle of their own
by default, and closes them with CloseHandle(). vfs_close_test counts
and fails closes through fclose() and close() only, so on Windows
filestream_exists() closed nothing it could see, and its check that
one close happened failed. Under _WIN32 the test hooks CloseHandle()
too, counted and failed as the descriptor close it now is; nothing
else in these checks closes a handle.

The Windows build passes under Wine, with dvd_blocks_test,
disc_dump_test and macos_command_test after it, and the Linux build
under ASan and UBSan.
2026-11-12 22:39:41 +01:00
LibretroAdmin 8714cee2fe libretro-common: retro_deque, a double-ended queue of one type in one block
What std::deque is used for - push and pop at either end, the n-th
element, insert and erase in the middle, in order - without what it
costs. std::deque keeps its elements in blocks of a few dozen and takes
a block from the heap, or gives one back, each time its contents cross a
block's end: a queue pushed at one end and popped at the other does that
for ever, however few elements it holds. libretro-common's generic_queue
allocates a node for every push.

retro_deque is a ring in one block, a power of two long, set aside when
the queue is made for as many elements as the caller says it holds.
Pushing, popping, inserting and erasing never allocate; a push into a
queue at its limit is refused with one compare; reserve is the one call
besides init that allocates. Insert and erase move whichever side of the
place is shorter.

The functions are made for the element's type by a macro
(RETRO_DEQUE_DEFINE(name, type)), so an element is copied as what it is
and not through memcpy with a size known only at run time. C89, header
only; MSVC compiles it.

Against std::deque built with the same flags (g++ -O3), on the two uses
it replaces first, the PS3 core's event queues and its scheduler's
deadlines:

  an event sent and taken   4.2 ns   std::deque 5.7 ns, and an
                                     allocation every 16 events
  scheduler step, 4 asleep  8.4 ns   11.9 ns
                  16       14.1 ns   21.3 ns
                  64       40.0 ns   60.8 ns

  send     21 instructions, no call   41 and a call
  take     22, no call                41 and a call
  insert   92, no call               338 and 13 calls
  expire   21, no call                46 and a call

The sample runs two million pushes, pops, inserts and erases against a
model, a struct element, the limit, reserve, and a queue marched along
for a million elements in its one block; built as C89, pedantic. In the
libretro-common samples workflow; passes under ASan and UBSan.
2026-11-12 22:38:18 +01:00
LibretroAdmin fd17192acb formats: no file I/O - image_texture's path loaders go through streams/
formats/image_texture.c loaded images by path through the data_transfer
file adapter. Its four path loaders (image_texture_load, _load_ex,
_load_request, _load_request_ex) move to streams/image_texture_file.c,
declared in streams/image_texture_file.h, the same split data_transfer,
cdfs and the DVD reader got. The names and behaviour are unchanged;
callers include the new header.

formats/image_texture.c keeps the buffer decoders and makes
image_texture_load_buffer_probe public so the adapter can reach it.
image_texture_load_set stays in formats/image_texture_set.c: it does no
file I/O of its own, and gfx_surface_still tests it against a stubbed
load_request.

griffin, Makefile.common, the companion test scripts and the sample
Makefiles that build the loaders add streams/image_texture_file.c; the
overlay task sample, which stubs image loading, does not.
2026-11-12 22:27:29 +01:00
LibretroAdmin 1656558977 video: add Skip Duplicate Frames
A repeat the core sends as a NULL frame is left unpresented, so the
display receives the content's own frame rate and frame generation or
interpolation tools see the real cadence. It stays out of the way where
the presents are the effect (Black Frame Insertion, shader sub-frames,
Threaded Video Frame Repeat, a headset) and with the menu open,
fast-forward or a cached frame shown again.

While repeats are being skipped, the runloop holds the loop to the
content's period with its timer wherever only VSync or the threaded
hold would have, since neither can block on a frame that is never
presented, and the driver's release stands aside. Lanes in
samples/runloop/pacing and samples/gfx/threaded_video pin both halves.

Idea from beetle-psx-libretro#1013 by Razor221.
2026-11-12 21:11:42 +01:00
LibretroAdmin 13677e89f8 menu: drop the unreachable statements after Pace Repeats From The Display's case 2026-11-12 21:11:38 +01:00
LibretroAdmin a1b7f2204a rflac: 16-bit output rounded to the nearest step, not cut down
A FLAC stream wider than 16 bits was taken to 16 bits by shifting its
left-justified samples right by 16, which rounds down: every sample of
a 24-bit stream came out half a step low on average. Every 16-bit
output path (independent, left/side, side/right and mid/side stereo,
the 33-bit side channel of 32-bit streams, mono and more channels;
scalar, SSE2 and NEON) now rounds to the nearest step, halves up, and
clamps the top. A stream of 16 bits or fewer is unchanged, and the
decode is no slower (60 s of 24-bit stereo: 19.2 ms before, 19.1 ms
after, best of 15).

The 33-bit side channel's reconstruction shifted negative int64 values
left, which is undefined and which UBSan reports on the new fixtures;
those shifts go through uint64_t.

New sample formats/rflac: rflac_s16_test decodes FLAC streams of 16,
24 and 32 bits (stereo fixtures holding all four channel codings,
mono, six channels) and checks every sample against the PCM they were
made from, rebuilt from the same integer formulas as mkfixtures.py
(flac and metaflac made them). It is built with the target's SIMD and
again with RFLAC_NO_SIMD; the NEON loops were run under qemu-aarch64.
It fails before this change.
2026-11-12 21:10:28 +01:00
sonninnos d928778305 Win32: Fix remember windowed size saving and adjusting (#20043) 2026-11-12 22:02:24 +02:00
LibretroAdmin fbc6073586 vfs: native Windows streams size a refill by how they are being read
A read that misses the buffer of a native read-only Windows stream
refills all 64 KiB where it carries on from the buffer's end or starts
the file, and after a seek elsewhere reads only the request, rounded up
to 4 KiB; a read that then carries on from there refills in full again.
A stream read at random places reads what it asks for, and one read
straight through is unchanged.

vfs_seek_read_mix_test seeks and reads a stream the way parsers do -
random places, back into what was read, past the end, runs after a
seek, sizes from 1 byte to past 64 KiB - checking every byte and the
position after each read.
2026-11-12 20:51:50 +01:00
LibretroAdmin d5eedad5ff vfs: Windows reads at offsets take the synchronous handle when it is free
A call through filestream_read_at or filestream_write_at takes the
stream's synchronous second handle - Windows' cached-read fast path for
a lone caller - when no other call holds it, one CAS on a flag; a call
that finds it held takes the overlapped handle, whose calls Windows
runs side by side. A native stream's own handle serves as the
synchronous one.
2026-11-12 20:14:31 +01:00
LibretroAdmin 92e1e205ce rflac: rflac_seek_point gives the seek point's frame; FLAC seeks land
rflac_seek gives the byte a seek point is at, but not the frame it is
at, which rflac_seek_resumed needs. audio_transfer told it 0, so the
decoder took the point for the start of the stream and the remainder
was decoded on from the point as if from frame 0: a seek in a FLAC
buffer with a seek table landed as many frames past its target as the
point was from the start.

rflac_seek_point gives both (rflac_seek is it, without the frame), and
audio_transfer resumes at the point's own frame. The audio_transfer_flac
sample gains a fixture with a seek table (made with flac and metaflac;
ffmpeg writes none) and a lane of seeks between and onto its points,
forward and back, which failed before.
2026-11-12 20:06:20 +01:00
LibretroAdmin b42806c02c vfs: Windows reads and writes at offsets on an overlapped handle
The second handle filestream_read_at and filestream_write_at go through
is opened for overlapped I/O - the relative NtCreateFile without
FILE_SYNCHRONOUS_IO_*, the path reopen with FILE_FLAG_OVERLAPPED - so
Windows no longer runs its calls one at a time and threads reading at
offsets read side by side. Each call waits on an event of its own:
one of eight a stream keeps, claimed by a bit with one CAS and made at
first use, or a passing one beyond eight; a call that completes at
once never waits. Native read-only streams take this handle too
instead of sharing their own, and synchronous, one.

vfs_positional_wide_test runs the positional test from twelve threads,
past the eight events; the cross-libc Windows job runs it.
2026-11-12 20:01:26 +01:00
LibretroAdmin 9ce7ef2311 vfs: read-only Windows streams on their own handle by default
Read-only streams on Windows NT take their own handle, position and
buffer over the C runtime's FILE by default; VFS_WIN32_NO_NATIVE keeps
the C runtime. Measured with vfs_native_bench on Windows: 1-byte
sequential reads 16.0 -> 6.8 ns, 16-byte 16.6 -> 7.5 ns, 256-byte 40.0
-> 30.4 ns; 4 KiB and up, random reads, reads at offsets, threads and
small-file opens the same within run-to-run spread.

The cross-libc Windows job's second VFS pass is now the C runtime one
(make VFS_NATIVE=0); vfs_native_bench_native is the default build and
vfs_native_bench the C runtime one.
2026-11-12 19:46:42 +01:00
LibretroAdmin cf816b8940 vfs: read-only Windows streams on a handle of their own (VFS_WIN32_NATIVE, opt-in)
Built with VFS_WIN32_NATIVE, a read-only stream on Windows NT opens with
CreateFileW instead of a C runtime FILE: it keeps its own position and
a 64 KiB buffer, reads at that position with ReadFile, and serves
filestream_read_at from the same handle, with no second one. Writable,
mapped, descriptor and scheme streams, and 9x, are as before, and so is
every build without the define.

vfs_native_bench and vfs_native_bench_native time the two on the same
reads - sequential at six sizes, random by seek and by read_at, read_at
from four threads, small-file opens - for deciding the default on
Windows. The cross-libc Windows job runs the VFS stream, path, large
file, read-ahead and positional tests built with it too.
2026-11-12 19:22:20 +01:00
LibretroAdmin 68b07f6a1a formats: no file I/O - data_transfer, cdfs and the DVD file reader go through streams/
data_transfer reads only through its reader callback. The path entry
points keep their names, data_transfer_open_prefix and
data_transfer_open_window, in streams/data_transfer_file.c
(<streams/data_transfer_file.h>), which opens the file through
filestream, reads it at offsets and closes it with the transfer;
data_transfer_open_prefix_reader and data_transfer_set_release are the
core's side of that.

cdfs reads only through the stream a track holds. cdfs_open_track,
cdfs_open_data_track and cdfs_open_raw_track are in
streams/cdfs_file.c (<streams/cdfs_file.h>); the length-based sector
probe they use is cdfs_track_sector_size_from_length.

rdvd_vfs, which binds files to the DVD source, and rdvd_stream, which
drives it, are streams/rdvd_vfs.c and streams/rdvd_stream.c, their
headers under <streams/>.

cdfs_file_test covers the path openers: a cooked iso and a raw bin
sized by length, a cue sheet resolving its bin beside it, an INDEX 01
offset, the data track, refusals.
2026-11-12 19:22:20 +01:00
140 changed files with 4271 additions and 812 deletions

No files matched your search

@@ -235,6 +235,27 @@ jobs:
./vfs_positional_nommap_test.exe
./vfs_positional_ntverify_test.exe
./vfs_positional_pathreopen_test.exe
# twelve threads: more than a stream keeps events for
./vfs_positional_wide_test.exe
# seeks and reads of every size, every byte checked, through
# the stream's own buffer
./vfs_seek_read_mix_test.exe
# writes, seeks, reads, truncates and holes in every write
# mode through the stream's own buffer, checked against a copy
./vfs_write_mix_test.exe
# The same with streams on the C runtime (VFS_WIN32_NO_NATIVE):
# the stream contract, the path an open takes, large files,
# offsets from threads, writes.
make clean MMAP=0
make MMAP=0 VFS_NATIVE=0
./vfs_bulk_read_test.exe
./vfs_seek_contract_test.exe
./vfs_large_file_test.exe
./vfs_readahead_test.exe
./vfs_descriptor_rw_test.exe
./vfs_positional_test.exe
./vfs_positional_nommap_test.exe
./vfs_write_mix_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
+2
View File
@@ -60,6 +60,7 @@ jobs:
./cdfs_dir_record_test
./cdfs_pure_api_test
./cdfs_io_error_test
./cdfs_file_test
env:
ASAN_OPTIONS: detect_leaks=1
UBSAN_OPTIONS: print_stacktrace=1:halt_on_error=1
@@ -72,3 +73,4 @@ jobs:
WINEDEBUG=-all wine ./cdfs_dir_record_test.exe
WINEDEBUG=-all wine ./cdfs_pure_api_test.exe
WINEDEBUG=-all wine ./cdfs_io_error_test.exe
WINEDEBUG=-all wine ./cdfs_file_test.exe
@@ -304,6 +304,12 @@ jobs:
# the same run as vfs_positional_test here.
vfs_positional_pathreopen_test
vfs_positional_ntverify_test
vfs_positional_wide_test
# seeks and reads of every size, every byte checked
vfs_seek_read_mix_test
# writes, seeks, reads, truncates and holes in every write
# mode, checked against a copy of the file
vfs_write_mix_test
cdrom_cuesheet_overflow_test
rdvd_h262_test
rdvd_lpcm_test
@@ -318,6 +324,8 @@ jobs:
cdfs_dir_record_test
cdfs_pure_api_test
cdfs_io_error_test
# cdfs tracks opened by filename: iso, bin, cue beside its bin
cdfs_file_test
chd_meta_overflow_test
chd_stream_threads_test
chd_stream_ahead_test
@@ -462,6 +470,9 @@ jobs:
# Twenty threads change one record two million times in all
# while three copy it: no change lost, no copy torn.
retro_cas_record_test
# A double-ended queue of one type in one block, against a
# model: two million changes, none wrong, none allocating.
retro_deque_test
sthread_ex_test
fast_cores_test
memshm_test
@@ -491,6 +502,11 @@ jobs:
# The FLAC arm, sample-exact in every input mode, a grown
# demuxed packet set and a raised WebM wall both resuming.
audio_transfer_flac_test
# rflac's 16-bit output against the PCM its fixtures were
# made from: 16 bits exact, 24 and 32 rounded to the nearest
# step, through every stereo coding, with SIMD and without.
rflac_s16_test
rflac_s16_nosimd_test
# The HDR video blits: PQ and HLG to linear scRGB against
# double-precision references, and HLG tone-mapped as HDR.
image_hdr_blit_test
@@ -545,6 +561,10 @@ jobs:
# self-contained regression tests came to be compiled every
# run and never executed.
declare -a NORUN_TARGETS=(
# Benchmarks: C runtime streams (VFS_WIN32_NO_NATIVE)
# against the stream's own handle, for timing on Windows.
vfs_native_bench
vfs_native_bench_native
# Fixture-driven checks run in Linux-samples-record.yml.
flv_remux_test
rtmp_socket_native_test
+2
View File
@@ -1,4 +1,6 @@
# Future
- LIBRETRO-COMMON/FLAC: 24- and 32-bit FLAC decoded to 16 bits is rounded to the nearest step instead of cut down (every sample was half a step low on average); 16-bit FLAC is unchanged
- LIBRETRO-COMMON/FLAC: A seek in a FLAC that has a seek table lands on the frame asked for (it landed as far past it as the seek point was from the start); rflac_seek_point gives the seek point's frame
- D3D11: Software-rendering cores draw straight into lent staging textures, copied to the frame texture on the GPU instead of by the CPU
- VULKAN: Keep the pipeline cache on disk between runs, one file per GPU, so stock and slang shader pipelines are not recompiled by the driver on every launch, preset load or driver reinit
- LINUX: Game Mode implemented natively (I/O priority plus a power-profiles-daemon performance hold); Feral libgamemode is no longer loaded and deps/feralgamemode is removed
+5 -5
View File
@@ -528,7 +528,7 @@ ifneq ($(HAVE_STRL), 1)
OBJ += $(LIBRETRO_COMM_DIR)/compat/compat_strl.o
endif
OBJ += $(LIBRETRO_COMM_DIR)/formats/image_texture.o \
OBJ += $(LIBRETRO_COMM_DIR)/formats/image_texture.o $(LIBRETRO_COMM_DIR)/streams/image_texture_file.o \
$(LIBRETRO_COMM_DIR)/formats/image_texture_scale.o \
$(LIBRETRO_COMM_DIR)/formats/image_texture_set.o \
$(LIBRETRO_COMM_DIR)/formats/image/image_yuv_blit.o
@@ -2655,9 +2655,9 @@ ifeq ($(HAVE_RMPEG1), 1)
$(LIBRETRO_COMM_DIR)/formats/dvd/rdvd_packet.o \
$(LIBRETRO_COMM_DIR)/formats/dvd/rdvd_spu.o \
$(LIBRETRO_COMM_DIR)/formats/dvd/rdvd_ifo.o \
$(LIBRETRO_COMM_DIR)/formats/dvd/rdvd_stream.o \
$(LIBRETRO_COMM_DIR)/formats/dvd/rdvd_source.o \
$(LIBRETRO_COMM_DIR)/formats/dvd/rdvd_vfs.o
$(LIBRETRO_COMM_DIR)/streams/rdvd_stream.o \
$(LIBRETRO_COMM_DIR)/streams/rdvd_vfs.o
# MPEG-1 audio is layer II, which rmp3 already decodes; a consumer that
# wants sound from an MPEG-1 stream needs HAVE_RMP3 as well.
endif
@@ -2673,7 +2673,7 @@ OBJ += $(LIBRETRO_COMM_DIR)/formats/bmp/rbmp_encode.o \
$(LIBRETRO_COMM_DIR)/formats/image_transfer.o \
$(LIBRETRO_COMM_DIR)/formats/video_player.o \
$(LIBRETRO_COMM_DIR)/formats/audio_transfer.o \
$(LIBRETRO_COMM_DIR)/formats/data_transfer.o \
$(LIBRETRO_COMM_DIR)/formats/data_transfer.o $(LIBRETRO_COMM_DIR)/streams/data_transfer_file.o \
$(LIBRETRO_COMM_DIR)/memmap/memmap.o \
$(LIBRETRO_COMM_DIR)/formats/m3u/rm3u.o
@@ -2889,7 +2889,7 @@ ifeq ($(HAVE_NETWORKING), 1)
cheevos/cheevos_client.o \
cheevos/cheevos_menu.o \
cheevos/cheevos_badge.o \
$(LIBRETRO_COMM_DIR)/formats/cdfs/cdfs.o \
$(LIBRETRO_COMM_DIR)/formats/cdfs/cdfs.o $(LIBRETRO_COMM_DIR)/streams/cdfs_file.o \
deps/rcheevos/src/rc_client.o \
deps/rcheevos/src/rc_compat.o \
deps/rcheevos/src/rc_libretro.o \
+1
View File
@@ -23,6 +23,7 @@
#include <streams/file_stream.h>
#include <features/features_cpu.h>
#include <formats/cdfs.h>
#include <streams/cdfs_file.h>
#include <compat/strl.h>
#include <retro_miscellaneous.h>
#include <retro_math.h>
+4 -6
View File
@@ -33,7 +33,6 @@
typedef struct
{
int64_t pos; /* in the disc image */
uint64_t file_pos; /* where the file stands; seeks to it are skipped */
ciso_t *ciso;
RFILE *file;
} rcheevos_ciso_t;
@@ -51,12 +50,12 @@ static int rcheevos_ciso_pump(rcheevos_ciso_t *handle, bool opening)
if (err != CISO_PENDING)
return err;
if ( handle->file_pos == req.offset
|| filestream_seek(handle->file, (int64_t)req.offset,
/* At the offset in one call; seek and read where there is none */
if ((got = filestream_read_at(handle->file, req.dst,
(int64_t)req.length, (int64_t)req.offset)) < 0
&& filestream_seek(handle->file, (int64_t)req.offset,
RETRO_VFS_SEEK_POSITION_START) == 0)
got = filestream_read(handle->file, req.dst, (int64_t)req.length);
/* unknown after a failed read: seek next time */
handle->file_pos = got > 0 ? req.offset + (uint64_t)got : ~(uint64_t)0;
ciso_feed(handle->ciso, got > 0 ? (size_t)got : 0);
}
}
@@ -81,7 +80,6 @@ static void* RC_CCONV rcheevos_ciso_open(const char *path)
if (!handle)
return NULL;
handle->pos = 0;
handle->file_pos = 0;
handle->ciso = NULL;
/* the descriptor path: no stdio buffer between the file and the
* buffers rc_hash reads into */
+6
View File
@@ -571,6 +571,10 @@
* times. Off: the fixed-timer pacing threaded video always had. */
#define DEFAULT_VIDEO_THREADED_DISPLAY_PACING false
/* Leave a frame the core marks as a repeat unpresented, so the display
* sees the content's own frame rate. Off: every repeat is presented. */
#define DEFAULT_VIDEO_SKIP_DUPE_FRAMES false
/* The Direct3D 9 drivers on a Direct3D9Ex device: a window presented
* through the flip model, and the frame latency settings honoured.
* Off: the discard swap effect and the runtime's own queue, as before. */
@@ -971,6 +975,8 @@
#define DEFAULT_MENU_SHOW_SUBLABELS_CURRENT_SELECTION_ONLY false
#define DEFAULT_MENU_SHOW_CONFIRM true
#define DEFAULT_MENU_DYNAMIC_WALLPAPER_ENABLE true
#define DEFAULT_MENU_SCROLL_QUICK false
#define DEFAULT_MENU_SCROLL_FAST false
#define DEFAULT_MENU_SCROLL_DELAY 256
+2
View File
@@ -754,6 +754,7 @@ typedef struct settings
bool video_threaded_display_pacing;
bool video_d3d9ex_present;
bool video_present_timing_from_display;
bool video_skip_dupe_frames;
bool video_font_enable;
bool video_disable_composition;
bool video_post_filter_record;
@@ -924,6 +925,7 @@ typedef struct settings
bool menu_linear_filter;
bool menu_texture_mipmapping;
bool menu_horizontal_animation;
bool menu_scroll_quick;
bool menu_scroll_fast;
bool menu_show_online_updater;
bool menu_show_full_paths;
+1
View File
@@ -30,6 +30,7 @@
#include "stb_image.h"
#else
#include <formats/image.h>
#include <streams/image_texture_file.h>
#endif
#include <libretro.h>
+1
View File
@@ -27,6 +27,7 @@
#include <retro_miscellaneous.h>
#include <streams/file_stream.h>
#include <formats/data_transfer.h>
#include <streams/data_transfer_file.h>
#include <streams/file_stream.h>
#include <formats/rwebm.h>
#include <formats/rwebm_video.h>
+45 -34
View File
@@ -677,11 +677,9 @@ static void win32_save_position(void)
{
bool ui_menubar_enable = settings->bools.ui_menubar_enable;
bool window_show_decor = settings->bools.video_window_show_decorations;
unsigned win_w =
VIDEO_SCALE_W(g_win32->pos_dims);
unsigned win_h =
VIDEO_SCALE_H(g_win32->pos_dims);
settings->uints.window_position_pos = g_win32->pos;
unsigned win_w = VIDEO_SCALE_W(g_win32->pos_dims);
unsigned win_h = VIDEO_SCALE_H(g_win32->pos_dims);
/* The frame the window reports includes whatever chrome it
* is wearing; the setting holds the client area, so take the
* chrome off both axes before the pair is stored. */
@@ -695,11 +693,12 @@ static void win32_save_position(void)
}
if (ui_menubar_enable)
{
int menu_bar_height = GetSystemMetrics(SM_CYMENU);
int menu_bar_height = GetSystemMetrics(SM_CYMENU);
win_h -= menu_bar_height;
}
settings->uints.window_position_dims =
VIDEO_SCALE_PACK(win_w, win_h);
settings->uints.window_position_pos = g_win32->pos;
settings->uints.window_position_dims = VIDEO_SCALE_PACK(win_w, win_h);
}
}
}
@@ -2154,8 +2153,10 @@ void win32_check_window(void *data,
if (g_win32_flags & WIN32_CMN_FLAG_RESIZED)
{
*resize = true;
*dims = VIDEO_SCALE_PACK(g_win32_resize_width, g_win32_resize_height);
*dims = VIDEO_SCALE_PACK(g_win32_resize_width, g_win32_resize_height);
g_win32_flags &= ~WIN32_CMN_FLAG_RESIZED;
/* Save the current position and size if set so. */
win32_save_position();
}
}
#endif
@@ -2235,37 +2236,50 @@ bool win32_has_focus(void *data)
{
settings_t *settings = config_get_ptr();
/* Ensure window size is big enough for core geometry. */
/* Apply/enforce windowed mode size. */
if ( settings
&& !settings->bools.video_fullscreen
&& !settings->bools.video_window_save_positions)
&& !settings->bools.video_fullscreen)
{
unsigned extra_width = 0;
unsigned extra_height = 0;
unsigned min_width = 0;
unsigned min_height = 0;
bool window_save_positions = settings->bools.video_window_save_positions;
unsigned extra_w = 0;
unsigned extra_h = 0;
unsigned win_w = 0;
unsigned win_h = 0;
win32_get_av_info_geometry(&min_width, &min_height);
/* Follow windowed mode size options in realtime when remembering position. */
if (window_save_positions)
{
win_w = VIDEO_SCALE_W(settings->uints.window_position_dims);
win_h = VIDEO_SCALE_H(settings->uints.window_position_dims);
}
/* Ensure window size is big enough for core geometry. */
else
win32_get_av_info_geometry(&win_w, &win_h);
if (settings->bools.video_window_show_decorations)
{
int border_thickness = GetSystemMetrics(SM_CXSIZEFRAME);
int title_bar_height = GetSystemMetrics(SM_CYCAPTION);
extra_width += border_thickness * 2;
extra_height += border_thickness * 2 + title_bar_height;
extra_w += border_thickness * 2;
extra_h += border_thickness * 2 + title_bar_height;
}
if (settings->bools.ui_menubar_enable)
extra_height += GetSystemMetrics(SM_CYMENU);
extra_h += GetSystemMetrics(SM_CYMENU);
if ( ( g_win32_resize_width < min_width
|| g_win32_resize_height < min_height)
&& min_width - g_win32_resize_width < MIN_WIDTH / 1.5f
&& min_height - g_win32_resize_height < MIN_HEIGHT / 1.5f)
if ( ( window_save_positions
&& ( win_w != g_win32_resize_width
|| win_h != g_win32_resize_height))
|| ( !window_save_positions
&& ( win_w > g_win32_resize_width
|| win_h > g_win32_resize_height)
&& win_w - g_win32_resize_width < MIN_WIDTH / 1.5f
&& win_h - g_win32_resize_height < MIN_HEIGHT / 1.5f)
)
SetWindowPos(main_window.hwnd, NULL, 0, 0,
min_width + extra_width,
min_height + extra_height,
win_w + extra_w,
win_h + extra_h,
SWP_NOMOVE);
}
@@ -2439,7 +2453,7 @@ bool win32_set_style(MONITORINFOEX *current_mon, HMONITOR *hm_to_use,
{
win32_common_state_t *g_win32 = (win32_common_state_t*)&win32_st;
bool position_set_from_config = false;
bool video_window_save_positions = settings->bools.video_window_save_positions;
bool window_save_positions = settings->bools.video_window_save_positions;
bool window_show_decor = settings->bools.video_window_show_decorations;
*style = WS_OVERLAPPEDWINDOW | WS_CLIPSIBLINGS | WS_CLIPCHILDREN;
@@ -2454,18 +2468,15 @@ bool win32_set_style(MONITORINFOEX *current_mon, HMONITOR *hm_to_use,
AdjustWindowRect(rect, *style, FALSE);
if (video_window_save_positions)
if (window_save_positions)
{
/* Set position from config */
int border_thickness = window_show_decor ? GetSystemMetrics(SM_CXSIZEFRAME) : 0;
int title_bar_height = window_show_decor ? GetSystemMetrics(SM_CYCAPTION) : 0;
unsigned window_position_width =
VIDEO_SCALE_W(settings->uints.window_position_dims);
unsigned window_position_height =
VIDEO_SCALE_H(settings->uints.window_position_dims);
unsigned window_position_width = VIDEO_SCALE_W(settings->uints.window_position_dims);
unsigned window_position_height = VIDEO_SCALE_H(settings->uints.window_position_dims);
g_win32->pos =
settings->uints.window_position_pos;
g_win32->pos = settings->uints.window_position_pos;
g_win32->pos_set = true;
g_win32->pos_dims = VIDEO_SCALE_PACK(
window_position_width + border_thickness * 2,
+1
View File
@@ -27,6 +27,7 @@
#include <retro_math.h>
#include <compat/strl.h>
#include <formats/image.h>
#include <streams/image_texture_file.h>
#ifdef HAVE_CONFIG_H
#include "../../config.h"
+1
View File
@@ -37,6 +37,7 @@
#include <encodings/utf.h>
#include <lists/string_list.h>
#include <formats/image.h>
#include <streams/image_texture_file.h>
#include <dxgi.h>
+1
View File
@@ -42,6 +42,7 @@
#include <encodings/utf.h>
#include <lists/string_list.h>
#include <formats/image.h>
#include <streams/image_texture_file.h>
#include <formats/image_yuv_blit.h>
#include "../common/video_import_color.h"
+1
View File
@@ -40,6 +40,7 @@
#include <encodings/utf.h>
#include <lists/string_list.h>
#include <formats/image.h>
#include <streams/image_texture_file.h>
#include <formats/image_yuv_blit.h>
#include "../common/video_import_color.h"
+1
View File
@@ -26,6 +26,7 @@
#define CINTERFACE
#include <formats/image.h>
#include <streams/image_texture_file.h>
#include <compat/strl.h>
#include <compat/posix_string.h>
#include <file/file_path.h>
+1
View File
@@ -31,6 +31,7 @@
#endif
#include <formats/image.h>
#include <streams/image_texture_file.h>
#include <compat/strl.h>
#include <compat/posix_string.h>
#include <file/file_path.h>
+1
View File
@@ -45,6 +45,7 @@
#include <gfx/scaler/scaler.h>
#include <gfx/math/matrix_4x4.h>
#include <formats/image.h>
#include <streams/image_texture_file.h>
#include <retro_inline.h>
#include <retro_miscellaneous.h>
#include <retro_math.h>
+1
View File
@@ -24,6 +24,7 @@
#include <encodings/utf.h>
#include <formats/image.h>
#include <streams/image_texture_file.h>
#include <file/file_path.h>
#include <string/stdstring.h>
+1
View File
@@ -34,6 +34,7 @@
#include <gfx/scaler/scaler.h>
#include <gfx/video_frame.h>
#include <formats/image.h>
#include <streams/image_texture_file.h>
#include <formats/image_yuv_blit.h>
#include <retro_inline.h>
#include <retro_miscellaneous.h>
+1
View File
@@ -25,6 +25,7 @@
#include <compat/strl.h>
#include <string/stdstring.h>
#include <formats/image.h>
#include <streams/image_texture_file.h>
#include <retro_miscellaneous.h>
#include "slang_process.h"
+1
View File
@@ -22,6 +22,7 @@
#include <compat/strl.h>
#include <formats/image.h>
#include <streams/image_texture_file.h>
#include <string/stdstring.h>
#include <gfx/math/matrix_4x4.h>
#include <retro_miscellaneous.h>
+1
View File
@@ -28,6 +28,7 @@
#include <streams/file_stream.h>
#include <formats/image.h>
#include <formats/data_transfer.h>
#include <streams/data_transfer_file.h>
#include <formats/video_player.h>
#include <retro_atomic.h>
#include <features/features_cpu.h>
+1
View File
@@ -19,6 +19,7 @@
#include <retro_atomic.h>
#include <retro_miscellaneous.h>
#include <formats/image_yuv_blit.h>
#include <streams/image_texture_file.h>
#include "gfx_surface.h"
#include "gfx_instrument.h"
+28
View File
@@ -9749,6 +9749,34 @@ void video_driver_frame(const void *data, unsigned width,
if (data)
video_st->main_flags &= ~VIDEO_FLAG_FF_FRAME_OWED;
/* Skip Duplicate Frames: a repeat from the core is left unpresented,
* so the display gets the content's own rate. Only a repeat the core
* sent, not the cached frame sent again for pause or the menu, and
* not where the presents themselves are the effect: Black Frame
* Insertion, shader sub-frames, the threaded presenter's repeats or
* a headset's interval. The hold tells the runloop to pace these
* iterations itself (runloop_pace_dupe_skip_engages). */
if ( !data
&& render_frame
&& settings->bools.video_skip_dupe_frames
&& !video_st->frame_repeat
&& !menu_is_alive
&& !video_info.input_driver_nonblock_state
&& !video_info.black_frame_insertion
&& video_info.shader_subframes <= 1
&& !video_st->headset_interval
#ifdef HAVE_THREADS
&& !( video_st->thread_wrapper_active
&& video_info.threaded_present_repeat)
#endif
)
{
render_frame = false;
video_st->dupe_skip_hold = VIDEO_DUPE_SKIP_HOLD;
}
else if (video_st->dupe_skip_hold && !video_st->frame_repeat)
video_st->dupe_skip_hold--;
/* What the shader chains read as FrameTimeDelta, from the reading
* taken at the top of this function. Measured between the frames
* the driver is handed, so a frame dropped by fast-forward
+9
View File
@@ -2054,6 +2054,10 @@ typedef struct
unsigned driver_pacing_interval_us;
uint8_t driver_pacing_mode;
uint8_t driver_pacing_reason;
/* Skip Duplicate Frames: the core frames left before the runloop
* stops pacing for skipped repeats, rearmed by each one skipped.
* Main thread. */
uint8_t dupe_skip_hold;
bool threaded;
@@ -2606,6 +2610,11 @@ float video_driver_get_aspect_ratio(void);
/* The aspect ratio, as the bits of the float in an atomic word. A
* union, not a cast through a pointer, so it stays inside what the
* standard allows and MSVC's aliasing rules. */
/* Core frames a skipped repeat keeps the runloop's dupe-skip pacing on
* for: long enough to span the gaps between repeats down to 1 in 8. */
#define VIDEO_DUPE_SKIP_HOLD 8
#ifdef HAVE_THREADS
#define VIDEO_CACHE_KEEPER ((size_t)sthread_get_current_thread_id())
#else
+3
View File
@@ -229,6 +229,7 @@ ACHIEVEMENTS
#define RC_CLIENT_SUPPORTS_HASH 1
#include "../libretro-common/formats/cdfs/cdfs.c"
#include "../libretro-common/streams/cdfs_file.c"
#include "../cheevos/cheevos.c"
#include "../cheevos/cheevos_client.c"
@@ -575,6 +576,7 @@ VIDEO IMAGE
============================================================ */
#include "../libretro-common/formats/image_texture.c"
#include "../libretro-common/streams/image_texture_file.c"
#include "../libretro-common/formats/image/image_yuv_blit.c"
#include "../libretro-common/formats/image_texture_scale.c"
#include "../libretro-common/formats/image_texture_set.c"
@@ -613,6 +615,7 @@ VIDEO IMAGE
#include "../libretro-common/formats/image_transfer.c"
#include "../libretro-common/formats/video_player.c"
#include "../libretro-common/formats/data_transfer.c"
#include "../libretro-common/streams/data_transfer_file.c"
#ifdef HAVE_RPNG
#include "../libretro-common/formats/png/rpng.c"
#include "../libretro-common/formats/png/rpng_apng.c"
+8 -2
View File
@@ -110,6 +110,7 @@ static int zstd_src_next(struct zstd_file_context *ctx,
return rzstd_blocks_step(ctx->zs, data, len);
for (;;)
{
int64_t got;
size_t want = rzstd_blocks_want(ctx->zs);
/* all of it out, and nothing after it */
if (!want)
@@ -117,8 +118,13 @@ static int zstd_src_next(struct zstd_file_context *ctx,
? RZSTD_PROCESS_END : RZSTD_PROCESS_ERROR;
if (want > RZSTD_WANT_MAX)
return RZSTD_PROCESS_ERROR;
filestream_seek(f, ctx->in_at, RETRO_VFS_SEEK_POSITION_START);
if (filestream_read(f, ctx->in, (int64_t)want) != (int64_t)want)
/* At the offset in one call; seek and read where there is none */
if ((got = filestream_read_at(f, ctx->in, (int64_t)want,
ctx->in_at)) < 0
&& filestream_seek(f, ctx->in_at,
RETRO_VFS_SEEK_POSITION_START) == 0)
got = filestream_read(f, ctx->in, (int64_t)want);
if (got != (int64_t)want)
return RZSTD_PROCESS_ERROR;
ctx->in_at += (int64_t)want;
if (rzstd_blocks_give(ctx->zs, ctx->in, want, data, len)
+9 -3
View File
@@ -3635,12 +3635,18 @@ static uint32_t audio_transfer_flac_seek_to(struct audio_transfer_flac *fl,
#endif
&& fl->data)
{
uint64_t at = 0;
if (rflac_seek(fl->handle, frame, &at) == RFLAC_PROCESS_NEXT
uint64_t at = 0;
uint64_t point = 0;
if (rflac_seek_point(fl->handle, frame, &at, &point)
== RFLAC_PROCESS_NEXT
&& at < fl->size)
{
uint64_t landed;
rflac_seek_resumed(fl->handle, 0);
/* Resumed at the seek point's own frame. Told 0 instead, the
* decoder took the point for the start, and the remainder below
* was decoded on from the point as if from frame 0: the seek
* landed point frames past its target. */
rflac_seek_resumed(fl->handle, point);
rflac_set_in(fl->handle, (const uint8_t*)fl->data + at,
fl->size - (size_t)at);
rflac_set_eof(fl->handle);
+12 -179
View File
@@ -1,31 +1,21 @@
#include <retro_posix_source.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <formats/cdfs.h>
#include <retro_miscellaneous.h>
#include <compat/strl.h>
#include <file/file_path.h>
#include <string/stdstring.h>
#ifdef HAVE_CHD
#include <streams/chd_stream.h>
#endif
/* This TU is deliberately kept as a single translation unit but is
* organised in two layers:
*
* - the pure layer (everything through cdfs_parse_cue): ISO-9660
* sector/directory/file logic plus cue-sheet parsing. It only ever
* reads through an already-open track->stream or a caller-supplied
* memory buffer, and never opens a path. Entry points:
* cdfs_track_from_stream(), cdfs_parse_cue(), cdfs_open_file() and
* the read/seek family.
*
* - the path adapter layer (cdfs_open_cue_track onward): resolves
* filenames, opens cue/bin/iso/chd files via intfstream, then hands
* the resulting stream to the pure layer. file_path.h and the VFS
* are only used here. Callers that already hold a stream (or an
* in-memory image) can skip this layer entirely. */
/* ISO-9660 sector/directory/file logic plus cue-sheet parsing. It only
* ever reads through an already-open track->stream or a caller-supplied
* memory buffer, and never opens a path: streams/cdfs_file.c resolves
* filenames, opens cue/bin/iso/chd files and hands the stream in here.
* Entry points: cdfs_track_from_stream(), cdfs_parse_cue(),
* cdfs_open_file() and the read/seek family. */
static void cdfs_determine_sector_size(cdfs_track_t* track)
{
@@ -97,9 +87,9 @@ static void cdfs_determine_sector_size(cdfs_track_t* track)
}
}
static void cdfs_determine_sector_size_from_file_size(cdfs_track_t* track)
void cdfs_track_sector_size_from_length(cdfs_track_t* track)
{
/* attempt to determine stream_sector_size from file size */
/* attempt to determine stream_sector_size from the stream's length */
int64_t _len = intfstream_get_size(track->stream);
if (_len <= 0)
return;
@@ -686,163 +676,6 @@ bool cdfs_parse_cue(const char *cue_text, size_t len,
return false;
}
/* --- Path adapter layer ------------------------------------------------ */
static cdfs_track_t* cdfs_open_cue_track(
const char* path, unsigned int track_index)
{
cdfs_cue_info_t info;
char track_path[PATH_MAX_LENGTH];
int64_t _len;
bool parsed = false;
char *cue_contents = NULL;
cdfs_track_t *track = NULL;
intfstream_t *cue_stream = intfstream_open_file(path,
RETRO_VFS_FILE_ACCESS_READ, RETRO_VFS_FILE_ACCESS_HINT_NONE);
if (!cue_stream)
return NULL;
/* A cue sheet is a small text file: read it with one bulk read and
* release the handle before the (potentially slow) bin open below,
* instead of interleaving parsing with per-line stream reads. */
_len = intfstream_get_size(cue_stream);
if (_len > 0 && (cue_contents = (char*)malloc((size_t)_len)))
parsed = (intfstream_read(cue_stream, cue_contents, _len) == _len);
intfstream_close(cue_stream);
free(cue_stream);
if (parsed)
parsed = cdfs_parse_cue(cue_contents, (size_t)_len,
track_index, &info);
if (cue_contents)
free(cue_contents);
if (!parsed)
return NULL;
/* Resolve the FILE name from the cue against the cue's own
* directory unless it already carries a path. */
if ( strchr(info.bin_name, '/')
|| strchr(info.bin_name, '\\'))
strlcpy(track_path, info.bin_name, sizeof(track_path));
else
{
fill_pathname_basedir(track_path, path, sizeof(track_path));
strlcat(track_path, info.bin_name, sizeof(track_path));
}
track = cdfs_track_from_stream(intfstream_open_file(
track_path, RETRO_VFS_FILE_ACCESS_READ,
RETRO_VFS_FILE_ACCESS_HINT_NONE),
info.first_sector_offset, info.first_sector_index);
if (track && track->stream_sector_size == 0)
{
track->stream_sector_size = info.sector_size;
if (info.sector_size == 2352)
track->stream_sector_header_size = 16;
else if (info.sector_size == 2336)
track->stream_sector_header_size = 8;
}
return track;
}
#ifdef HAVE_CHD
static cdfs_track_t* cdfs_open_chd_track(const char* path, int32_t track_index)
{
cdfs_track_t *track;
intfstream_t *intf_stream = intfstream_open_chd_track(path,
RETRO_VFS_FILE_ACCESS_READ, RETRO_VFS_FILE_ACCESS_HINT_NONE,
track_index);
if (!intf_stream)
return NULL;
track = cdfs_track_from_stream(intf_stream,
intfstream_get_offset_to_start(intf_stream),
intfstream_get_first_sector(intf_stream));
if (track && track->stream_sector_header_size == 0)
{
track->stream_sector_size = intfstream_get_frame_size(intf_stream);
if (track->stream_sector_size == 2352)
track->stream_sector_header_size = 16;
else if (track->stream_sector_size == 2336)
track->stream_sector_header_size = 8;
}
return track;
}
#endif
struct cdfs_track_t* cdfs_open_track(const char* path,
unsigned int track_index)
{
const char* ext = path_get_extension(path);
if (string_is_equal_noncase(ext, "cue"))
return cdfs_open_cue_track(path, track_index);
#ifdef HAVE_CHD
if (string_is_equal_noncase(ext, "chd"))
return cdfs_open_chd_track(path, track_index);
#endif
/* if opening track 1, try opening as a raw track */
if (track_index == 1)
return cdfs_open_raw_track(path);
/* unsupported file type */
return NULL;
}
struct cdfs_track_t* cdfs_open_data_track(const char* path)
{
const char* ext = path_get_extension(path);
if (string_is_equal_noncase(ext, "cue"))
return cdfs_open_cue_track(path, 0);
#ifdef HAVE_CHD
if (string_is_equal_noncase(ext, "chd"))
return cdfs_open_chd_track(path, CHDSTREAM_TRACK_PRIMARY);
#endif
/* unsupported file type - try opening as a raw track */
return cdfs_open_raw_track(path);
}
cdfs_track_t* cdfs_open_raw_track(const char* path)
{
const char *ext = path_get_extension(path);
cdfs_track_t *track = NULL;
if ( string_is_equal_noncase(ext, "bin")
|| string_is_equal_noncase(ext, "iso"))
{
intfstream_t* file = intfstream_open_file(path,
RETRO_VFS_FILE_ACCESS_READ, RETRO_VFS_FILE_ACCESS_HINT_NONE);
track = cdfs_track_from_stream(file, 0, 0);
if (track && track->stream_sector_size == 0)
{
cdfs_determine_sector_size_from_file_size(track);
if (track->stream_sector_size == 0)
{
cdfs_close_track(track);
track = NULL;
}
}
}
return track;
}
void cdfs_close_track(cdfs_track_t* track)
{
if (track)
+47 -91
View File
@@ -29,8 +29,8 @@
* avail() for the buffer and how much of it is real, and the
* complete()/failed()/capped() terminals.
*
* - prefix (data_transfer_open_prefix): a file whose length is known
* at open. Address space for the whole length is reserved up
* - prefix (data_transfer_open_prefix_reader): a source whose length
* is known at open, read at offsets through a callback. Address space for the whole length is reserved up
* front so the base never moves, and physical pages are committed
* DT_COMMIT_STEP at a time as the fill advances, in DT_READ_CHUNK
* reads. commit_cap bounds the committed bytes and turns overrun
@@ -39,7 +39,7 @@
* sequential consumer holds a constant window over a file of any
* size.
*
* - window (data_transfer_open_window): the same reservation used
* - window (data_transfer_open_window_reader): the same reservation used
* cyclically, for sequential consumers that loop. A permanent
* head of 'keep' bytes plus a moving [wlo, whi) that feed()
* advances behind the consumer and extends ahead of it;
@@ -59,8 +59,11 @@
* growable buffer that shares this file and header but touches no
* data_transfer_t and no page machinery.
*
* File access goes through filestream/VFS throughout, so 64-bit
* offsets and VFS-only paths work. Where the platform cannot reserve
* No file I/O here: every read is the reader's. streams/
* data_transfer_file.c opens paths through filestream/VFS - 64-bit
* offsets, VFS-only paths - and hands the reader in, keeping the
* data_transfer_open_prefix/_window names. Where the platform cannot
* reserve
* address space, every mode degrades to a plain allocation of
* min(len, commit_cap) - the whole file when there is no cap - and a
* window simply holds the whole file; reserve_supported() and
@@ -68,8 +71,8 @@
*
* NOT IMPLEMENTED
*
* - Asynchrony. Every read is a synchronous filestream_read on the
* calling thread: iterate() blocks for the length of its budget,
* - Asynchrony. Every read is a synchronous call of the reader on
* the calling thread: iterate() blocks for the length of its budget,
* and there is nothing in flight for free() to cancel. Pacing is
* by byte budget and the caller does the time-slicing. The module
* began as a wrapper over nbio, which is where its vocabulary came
@@ -162,7 +165,6 @@
#include <stdlib.h>
#include <memmap.h>
#include <streams/file_stream.h>
#include <string.h>
#include <formats/data_transfer.h>
@@ -302,9 +304,10 @@ struct data_transfer_inert { size_t lo, hi; };
struct data_transfer
{
/* file-backed reader (open_prefix, and open_window over it) */
RFILE *f;
/* the reader every prefix and window fills from, and what frees
* its user data with the transfer, if anything */
data_transfer_read_at_t read_at;
data_transfer_release_t release;
void *read_ud;
uint8_t *map; /* reserved (or fallback-allocated) buffer */
size_t map_len; /* reserved bytes (page-rounded), 0 = fallback */
@@ -392,17 +395,12 @@ void data_transfer_arena_release(data_transfer_arena_t *a)
static int data_transfer_read_at(data_transfer_t *dt, size_t off,
uint8_t *dst, size_t n);
/* open_prefix's body. allow_pool is 0 for open_window, which builds
* on a prefix handle but reads map_len in punch/peek and so needs it
* to be the file's own rounded length rather than a pool slot's. */
static data_transfer_t *data_transfer_open_reader_ex(const char *path,
size_t commit_cap, int allow_pool, data_transfer_read_at_t read_at,
size_t length, void *ud);
static data_transfer_t *data_transfer_open_prefix_ex(const char *path,
size_t commit_cap, int allow_pool)
{
return data_transfer_open_reader_ex(path,commit_cap,allow_pool,NULL,0,NULL);
}
/* The prefix body. allow_pool is 0 for a window, which builds on a
* prefix handle but reads map_len in punch/peek and so needs it to be
* the source's own rounded length rather than a pool slot's. */
static data_transfer_t *data_transfer_open_reader_ex(size_t commit_cap,
int allow_pool, data_transfer_read_at_t read_at, size_t length,
void *ud);
/* The fill proper. data_transfer_iterate() refuses to run it on a
* window; open_window's no-reservation path genuinely needs it and
* calls it here directly. */
@@ -575,16 +573,19 @@ static data_transfer_t *data_transfer_start_window(data_transfer_t *dt, size_t k
return dt;
}
data_transfer_t *data_transfer_open_window(const char *path, size_t keep)
{
return data_transfer_start_window(data_transfer_open_prefix_ex(path,0,0),keep);
}
data_transfer_t *data_transfer_open_window_reader(size_t length,size_t keep,
data_transfer_read_at_t read_at,void *ud)
{
if (!read_at) return NULL;
return data_transfer_start_window(
data_transfer_open_reader_ex(NULL,0,0,read_at,length,ud),keep);
data_transfer_open_reader_ex(0,0,read_at,length,ud),keep);
}
void data_transfer_set_release(data_transfer_t *dt,
data_transfer_release_t release)
{
if (dt)
dt->release = release;
}
bool data_transfer_reserve_supported(void)
@@ -901,44 +902,18 @@ bool data_transfer_window_feed(data_transfer_t *dt, size_t tell,
margin, 0, NULL);
}
static data_transfer_t *data_transfer_open_reader_ex(const char *path,
size_t commit_cap, int allow_pool, data_transfer_read_at_t read_at,
size_t length, void *ud)
static data_transfer_t *data_transfer_open_reader_ex(size_t commit_cap,
int allow_pool, data_transfer_read_at_t read_at, size_t length,
void *ud)
{
data_transfer_t *dt;
RFILE *f = NULL;
int64_t l;
/* filestream: 64-bit lengths and offsets everywhere (a plain long
* ftell caps at 2 GB on LLP64 and 32-bit targets), and the read
* routes through the VFS interface when one is registered, so
* paths only the VFS can open - archive members, Android
* content:// documents, frontend overrides - work like any
* other. */
if (read_at)
{
if (!length || (uint64_t)length > INT64_MAX) return NULL;
l = (int64_t)length;
}
else
{
if (!(f = filestream_open(path,RETRO_VFS_FILE_ACCESS_READ,
RETRO_VFS_FILE_ACCESS_HINT_NONE))) return NULL;
l = filestream_get_size(f);
if (l <= 0 || (uint64_t)l > SIZE_MAX)
{
filestream_close(f);
return NULL;
}
}
if (!read_at || !length || (uint64_t)length > INT64_MAX)
return NULL;
if (!(dt = (data_transfer_t*)calloc(1, sizeof(*dt))))
{
if (f) filestream_close(f);
return NULL;
}
dt->f = f;
dt->read_at = read_at;
dt->read_ud = ud;
dt->len = (size_t)l;
dt->len = length;
dt->cap = commit_cap;
{
@@ -1019,7 +994,6 @@ static data_transfer_t *data_transfer_open_reader_ex(const char *path,
dt->cap = w;
if (!(dt->map = (uint8_t*)malloc(w ? w : 1)))
{
if (f) filestream_close(f);
free(dt);
return NULL;
}
@@ -1028,10 +1002,10 @@ static data_transfer_t *data_transfer_open_reader_ex(const char *path,
return dt;
}
data_transfer_t *data_transfer_open_prefix(const char *path,
size_t commit_cap)
data_transfer_t *data_transfer_open_prefix_reader(size_t length,
size_t commit_cap, data_transfer_read_at_t read_at, void *ud)
{
return data_transfer_open_prefix_ex(path, commit_cap, 1);
return data_transfer_open_reader_ex(commit_cap, 1, read_at, length, ud);
}
/* Grow the physical backing to cover at least 'need' bytes.
@@ -1161,8 +1135,6 @@ static size_t data_transfer_prefix_iterate(data_transfer_t *dt,
size_t end = dt->inert[lo].hi;
size_t release_end;
if (dt->cap && end > dt->cap) end = dt->cap;
if (filestream_seek(dt->f, (int64_t)end, RETRO_VFS_SEEK_POSITION_START) < 0)
{ dt->done = dt->failed = 1; break; }
/* Commit growth may have armed pages past the preceding
* read. Keep an inert interval unreadable after crossing
* it, including in strict builds and after refill. */
@@ -1224,9 +1196,8 @@ static size_t data_transfer_prefix_iterate(data_transfer_t *dt,
continue;
}
{
int64_t r = dt->read_at
? dt->read_at(dt->read_ud,dt->avail,dt->map+dt->avail,chunk)
: filestream_read(dt->f,dt->map+dt->avail,(int64_t)chunk);
int64_t r = dt->read_at(dt->read_ud, dt->avail,
dt->map + dt->avail, chunk);
got = r > 0 && (uint64_t)r <= chunk ? (size_t)r : 0;
}
dt->avail += got;
@@ -1256,29 +1227,14 @@ static size_t data_transfer_prefix_iterate(data_transfer_t *dt,
static int data_transfer_read_at(data_transfer_t *dt, size_t off,
uint8_t *dst, size_t n)
{
int64_t save, r;
if (dt->read_at)
return dt->read_at(dt->read_ud,off,dst,n) == (int64_t)n;
/* At the offset, the cursor and read-ahead untouched; seek and read
* where the file has no positional reads */
if ((r = filestream_read_at(dt->f, dst, (int64_t)n, (int64_t)off)) >= 0)
return r == (int64_t)n;
save = filestream_tell(dt->f);
if (save < 0
|| (save != (int64_t)off
&& filestream_seek(dt->f, (int64_t)off,
RETRO_VFS_SEEK_POSITION_START) < 0))
return 0;
r = filestream_read(dt->f, dst, (int64_t)n);
if (!dt->window || !dt->map_len)
filestream_seek(dt->f, save, RETRO_VFS_SEEK_POSITION_START);
return r == (int64_t)n;
return dt->read_at(dt->read_ud, off, dst, n) == (int64_t)n;
}
bool data_transfer_prefix_inert(data_transfer_t *dt, size_t from, size_t to)
{
size_t lo, hi, first, last, count;
if (!dt || !dt->f || dt->window || dt->failed || from > to || to > dt->len)
if (!dt || !dt->read_at || dt->window || dt->failed || from > to
|| to > dt->len)
return false;
if (!dt->map_len || dt->pooled) return true;
lo = (from / dt->page + (from % dt->page != 0)) * dt->page;
@@ -1320,7 +1276,7 @@ bool data_transfer_prefix_inert(data_transfer_t *dt, size_t from, size_t to)
void data_transfer_discard(data_transfer_t *dt, size_t up_to)
{
size_t lo;
if (!dt || !dt->f || !dt->map_len || !dt->page)
if (!dt || !dt->read_at || !dt->map_len || !dt->page)
return; /* source or fallback: bytes stay */
if (dt->window)
return; /* not this surface - see below */
@@ -1342,7 +1298,7 @@ bool data_transfer_refill(data_transfer_t *dt, size_t from)
size_t lo, end, at, i;
if (!dt)
return false;
if (!dt->f || !dt->map_len || dt->window || from >= dt->low)
if (!dt->read_at || !dt->map_len || dt->window || from >= dt->low)
return !dt->failed; /* nothing was released there */
lo = (from / dt->page) * dt->page;
end = dt->low;
@@ -1378,7 +1334,7 @@ bool data_transfer_refill(data_transfer_t *dt, size_t from)
/* The prefix surface is inert on a window handle.
*
* A window keeps a live RFILE and a reservation, so these three would
* A window keeps a live reader and a reservation, so these three would
* otherwise pass their own guards on one and then disagree with the
* window's bookkeeping. discard() is the dangerous one: its 'low'
* frontier knows nothing of keep/wlo/whi/wfreed, so it releases from
@@ -1399,7 +1355,7 @@ size_t data_transfer_iterate_while(data_transfer_t *dt, size_t max_bytes,
return 0;
if (dt->window)
return dt->avail; /* not this surface - see above */
if (dt->f || dt->src_cb)
if (dt->read_at || dt->src_cb)
return data_transfer_prefix_iterate(dt, max_bytes,
should_continue, ud);
return dt->avail;
@@ -1447,8 +1403,8 @@ void data_transfer_free(data_transfer_t *dt)
{
if (!dt)
return;
if (dt->f)
filestream_close(dt->f);
if (dt->release)
dt->release(dt->read_ud);
free(dt->inert);
if (dt->map)
{
+146 -188
View File
@@ -2154,6 +2154,13 @@ static uint32_t rflac__decode_samples_with_residual__rice__scalar(rflac_bs* bs,
}
#if defined(RFLAC_SUPPORT_SSE2)
/* rflac__s16_round for four lanes, but for the clamp, which the saturating
* pack after it does */
static INLINE __m128i rflac__mm_round_s16_epi32(__m128i x)
{
return _mm_srai_epi32(_mm_add_epi32(_mm_srai_epi32(x, 15), _mm_set1_epi32(1)), 1);
}
static INLINE __m128i rflac__mm_packs_interleaved_epi32(__m128i a, __m128i b)
{
__m128i r;
@@ -4387,6 +4394,17 @@ static rflac* rflac_open_with_metadata_private(rflac_read_proc onRead,
return pFlac;
}
/* A sample left-justified in 32 bits, to 16: rounded to the nearest step
* (halves up) and clamped, where a shift alone rounded down - every sample
* of a 24-bit stream half a step low on average. Shifting by 15 first keeps
* the rounding add from overflowing; only +32768 can come out, at the very
* top, and is clamped. A stream of 16 bits or fewer is unchanged by it. */
static INLINE int16_t rflac__s16_round(uint32_t x)
{
int32_t r = (((int32_t)x >> 15) + 1) >> 1;
return (int16_t)(r > 32767 ? 32767 : r);
}
static INLINE void rflac_read_pcm_frames_s16__decode_left_side__scalar(
rflac* pFlac, uint64_t frameCount, uint32_t unusedBitsPerSample,
const int32_t* pInputSamples0, const int32_t* pInputSamples1,
@@ -4416,24 +4434,14 @@ static INLINE void rflac_read_pcm_frames_s16__decode_left_side__scalar(
uint32_t right2 = left2 - side2;
uint32_t right3 = left3 - side3;
left0 >>= 16;
left1 >>= 16;
left2 >>= 16;
left3 >>= 16;
right0 >>= 16;
right1 >>= 16;
right2 >>= 16;
right3 >>= 16;
pOutputSamples[i*8+0] = (int16_t)left0;
pOutputSamples[i*8+1] = (int16_t)right0;
pOutputSamples[i*8+2] = (int16_t)left1;
pOutputSamples[i*8+3] = (int16_t)right1;
pOutputSamples[i*8+4] = (int16_t)left2;
pOutputSamples[i*8+5] = (int16_t)right2;
pOutputSamples[i*8+6] = (int16_t)left3;
pOutputSamples[i*8+7] = (int16_t)right3;
pOutputSamples[i*8+0] = rflac__s16_round(left0);
pOutputSamples[i*8+1] = rflac__s16_round(right0);
pOutputSamples[i*8+2] = rflac__s16_round(left1);
pOutputSamples[i*8+3] = rflac__s16_round(right1);
pOutputSamples[i*8+4] = rflac__s16_round(left2);
pOutputSamples[i*8+5] = rflac__s16_round(right2);
pOutputSamples[i*8+6] = rflac__s16_round(left3);
pOutputSamples[i*8+7] = rflac__s16_round(right3);
}
for (i = (frameCount4 << 2); i < frameCount; ++i)
@@ -4442,11 +4450,8 @@ static INLINE void rflac_read_pcm_frames_s16__decode_left_side__scalar(
uint32_t side = pInputSamples1U32[i] << shift1;
uint32_t right = left - side;
left >>= 16;
right >>= 16;
pOutputSamples[i*2+0] = (int16_t)left;
pOutputSamples[i*2+1] = (int16_t)right;
pOutputSamples[i*2+0] = rflac__s16_round(left);
pOutputSamples[i*2+1] = rflac__s16_round(right);
}
}
@@ -4468,8 +4473,8 @@ static INLINE void rflac_read_pcm_frames_s16__decode_left_side__sse2(
__m128i side = _mm_slli_epi32(_mm_loadu_si128((const __m128i*)pInputSamples1 + i), shift1);
__m128i right = _mm_sub_epi32(left, side);
left = _mm_srai_epi32(left, 16);
right = _mm_srai_epi32(right, 16);
left = rflac__mm_round_s16_epi32(left);
right = rflac__mm_round_s16_epi32(right);
_mm_storeu_si128((__m128i*)(pOutputSamples + i*8), rflac__mm_packs_interleaved_epi32(left, right));
}
@@ -4479,11 +4484,8 @@ static INLINE void rflac_read_pcm_frames_s16__decode_left_side__sse2(
uint32_t side = pInputSamples1U32[i] << shift1;
uint32_t right = left - side;
left >>= 16;
right >>= 16;
pOutputSamples[i*2+0] = (int16_t)left;
pOutputSamples[i*2+1] = (int16_t)right;
pOutputSamples[i*2+0] = rflac__s16_round(left);
pOutputSamples[i*2+1] = rflac__s16_round(right);
}
}
#endif
@@ -4509,14 +4511,14 @@ static INLINE void rflac_read_pcm_frames_s16__decode_left_side__neon(
uint32x4_t side = vshlq_u32(vld1q_u32(pInputSamples1U32 + i*4), shift1_4);
uint32x4_t right = vsubq_u32(left, side);
left = vshrq_n_u32(left, 16);
right = vshrq_n_u32(right, 16);
left = vreinterpretq_u32_s32(vrshrq_n_s32(vreinterpretq_s32_u32(left), 16));
right = vreinterpretq_u32_s32(vrshrq_n_s32(vreinterpretq_s32_u32(right), 16));
{
/* interleaving store; replaces vzip + contiguous store */
uint16x4x2_t v;
v.val[0] = vmovn_u32(left);
v.val[1] = vmovn_u32(right);
v.val[0] = vreinterpret_u16_s16(vqmovn_s32(vreinterpretq_s32_u32(left)));
v.val[1] = vreinterpret_u16_s16(vqmovn_s32(vreinterpretq_s32_u32(right)));
vst2_u16((uint16_t*)pOutputSamples + i*8, v);
}
}
@@ -4527,11 +4529,8 @@ static INLINE void rflac_read_pcm_frames_s16__decode_left_side__neon(
uint32_t side = pInputSamples1U32[i] << shift1;
uint32_t right = left - side;
left >>= 16;
right >>= 16;
pOutputSamples[i*2+0] = (int16_t)left;
pOutputSamples[i*2+1] = (int16_t)right;
pOutputSamples[i*2+0] = rflac__s16_round(left);
pOutputSamples[i*2+1] = rflac__s16_round(right);
}
}
#endif
@@ -4585,24 +4584,14 @@ static INLINE void rflac_read_pcm_frames_s16__decode_right_side__scalar(
uint32_t left2 = right2 + side2;
uint32_t left3 = right3 + side3;
left0 >>= 16;
left1 >>= 16;
left2 >>= 16;
left3 >>= 16;
right0 >>= 16;
right1 >>= 16;
right2 >>= 16;
right3 >>= 16;
pOutputSamples[i*8+0] = (int16_t)left0;
pOutputSamples[i*8+1] = (int16_t)right0;
pOutputSamples[i*8+2] = (int16_t)left1;
pOutputSamples[i*8+3] = (int16_t)right1;
pOutputSamples[i*8+4] = (int16_t)left2;
pOutputSamples[i*8+5] = (int16_t)right2;
pOutputSamples[i*8+6] = (int16_t)left3;
pOutputSamples[i*8+7] = (int16_t)right3;
pOutputSamples[i*8+0] = rflac__s16_round(left0);
pOutputSamples[i*8+1] = rflac__s16_round(right0);
pOutputSamples[i*8+2] = rflac__s16_round(left1);
pOutputSamples[i*8+3] = rflac__s16_round(right1);
pOutputSamples[i*8+4] = rflac__s16_round(left2);
pOutputSamples[i*8+5] = rflac__s16_round(right2);
pOutputSamples[i*8+6] = rflac__s16_round(left3);
pOutputSamples[i*8+7] = rflac__s16_round(right3);
}
for (i = (frameCount4 << 2); i < frameCount; ++i)
@@ -4611,11 +4600,8 @@ static INLINE void rflac_read_pcm_frames_s16__decode_right_side__scalar(
uint32_t right = pInputSamples1U32[i] << shift1;
uint32_t left = right + side;
left >>= 16;
right >>= 16;
pOutputSamples[i*2+0] = (int16_t)left;
pOutputSamples[i*2+1] = (int16_t)right;
pOutputSamples[i*2+0] = rflac__s16_round(left);
pOutputSamples[i*2+1] = rflac__s16_round(right);
}
}
@@ -4638,8 +4624,8 @@ static INLINE void rflac_read_pcm_frames_s16__decode_right_side__sse2(
__m128i right = _mm_slli_epi32(_mm_loadu_si128((const __m128i*)pInputSamples1 + i), shift1);
__m128i left = _mm_add_epi32(right, side);
left = _mm_srai_epi32(left, 16);
right = _mm_srai_epi32(right, 16);
left = rflac__mm_round_s16_epi32(left);
right = rflac__mm_round_s16_epi32(right);
_mm_storeu_si128((__m128i*)(pOutputSamples + i*8), rflac__mm_packs_interleaved_epi32(left, right));
}
@@ -4649,11 +4635,8 @@ static INLINE void rflac_read_pcm_frames_s16__decode_right_side__sse2(
uint32_t right = pInputSamples1U32[i] << shift1;
uint32_t left = right + side;
left >>= 16;
right >>= 16;
pOutputSamples[i*2+0] = (int16_t)left;
pOutputSamples[i*2+1] = (int16_t)right;
pOutputSamples[i*2+0] = rflac__s16_round(left);
pOutputSamples[i*2+1] = rflac__s16_round(right);
}
}
#endif
@@ -4679,14 +4662,14 @@ static INLINE void rflac_read_pcm_frames_s16__decode_right_side__neon(
uint32x4_t right = vshlq_u32(vld1q_u32(pInputSamples1U32 + i*4), shift1_4);
uint32x4_t left = vaddq_u32(right, side);
left = vshrq_n_u32(left, 16);
right = vshrq_n_u32(right, 16);
left = vreinterpretq_u32_s32(vrshrq_n_s32(vreinterpretq_s32_u32(left), 16));
right = vreinterpretq_u32_s32(vrshrq_n_s32(vreinterpretq_s32_u32(right), 16));
{
/* interleaving store; replaces vzip + contiguous store */
uint16x4x2_t v;
v.val[0] = vmovn_u32(left);
v.val[1] = vmovn_u32(right);
v.val[0] = vreinterpret_u16_s16(vqmovn_s32(vreinterpretq_s32_u32(left)));
v.val[1] = vreinterpret_u16_s16(vqmovn_s32(vreinterpretq_s32_u32(right)));
vst2_u16((uint16_t*)pOutputSamples + i*8, v);
}
}
@@ -4696,11 +4679,8 @@ static INLINE void rflac_read_pcm_frames_s16__decode_right_side__neon(
uint32_t right = pInputSamples1U32[i] << shift1;
uint32_t left = right + side;
left >>= 16;
right >>= 16;
pOutputSamples[i*2+0] = (int16_t)left;
pOutputSamples[i*2+1] = (int16_t)right;
pOutputSamples[i*2+0] = rflac__s16_round(left);
pOutputSamples[i*2+1] = rflac__s16_round(right);
}
}
#endif
@@ -4775,24 +4755,14 @@ static INLINE void rflac_read_pcm_frames_s16__decode_mid_side__scalar(
temp2R = (mid2 - side2) << shift;
temp3R = (mid3 - side3) << shift;
temp0L >>= 16;
temp1L >>= 16;
temp2L >>= 16;
temp3L >>= 16;
temp0R >>= 16;
temp1R >>= 16;
temp2R >>= 16;
temp3R >>= 16;
pOutputSamples[i*8+0] = (int16_t)temp0L;
pOutputSamples[i*8+1] = (int16_t)temp0R;
pOutputSamples[i*8+2] = (int16_t)temp1L;
pOutputSamples[i*8+3] = (int16_t)temp1R;
pOutputSamples[i*8+4] = (int16_t)temp2L;
pOutputSamples[i*8+5] = (int16_t)temp2R;
pOutputSamples[i*8+6] = (int16_t)temp3L;
pOutputSamples[i*8+7] = (int16_t)temp3R;
pOutputSamples[i*8+0] = rflac__s16_round(temp0L);
pOutputSamples[i*8+1] = rflac__s16_round(temp0R);
pOutputSamples[i*8+2] = rflac__s16_round(temp1L);
pOutputSamples[i*8+3] = rflac__s16_round(temp1R);
pOutputSamples[i*8+4] = rflac__s16_round(temp2L);
pOutputSamples[i*8+5] = rflac__s16_round(temp2R);
pOutputSamples[i*8+6] = rflac__s16_round(temp3L);
pOutputSamples[i*8+7] = rflac__s16_round(temp3R);
}
}
else
@@ -4833,24 +4803,14 @@ static INLINE void rflac_read_pcm_frames_s16__decode_mid_side__scalar(
temp2R = ((int32_t)(mid2 - side2) >> 1);
temp3R = ((int32_t)(mid3 - side3) >> 1);
temp0L >>= 16;
temp1L >>= 16;
temp2L >>= 16;
temp3L >>= 16;
temp0R >>= 16;
temp1R >>= 16;
temp2R >>= 16;
temp3R >>= 16;
pOutputSamples[i*8+0] = (int16_t)temp0L;
pOutputSamples[i*8+1] = (int16_t)temp0R;
pOutputSamples[i*8+2] = (int16_t)temp1L;
pOutputSamples[i*8+3] = (int16_t)temp1R;
pOutputSamples[i*8+4] = (int16_t)temp2L;
pOutputSamples[i*8+5] = (int16_t)temp2R;
pOutputSamples[i*8+6] = (int16_t)temp3L;
pOutputSamples[i*8+7] = (int16_t)temp3R;
pOutputSamples[i*8+0] = rflac__s16_round(temp0L);
pOutputSamples[i*8+1] = rflac__s16_round(temp0R);
pOutputSamples[i*8+2] = rflac__s16_round(temp1L);
pOutputSamples[i*8+3] = rflac__s16_round(temp1R);
pOutputSamples[i*8+4] = rflac__s16_round(temp2L);
pOutputSamples[i*8+5] = rflac__s16_round(temp2R);
pOutputSamples[i*8+6] = rflac__s16_round(temp3L);
pOutputSamples[i*8+7] = rflac__s16_round(temp3R);
}
}
@@ -4861,8 +4821,8 @@ static INLINE void rflac_read_pcm_frames_s16__decode_mid_side__scalar(
mid = (mid << 1) | (side & 0x01);
pOutputSamples[i*2+0] = (int16_t)(((uint32_t)((int32_t)(mid + side) >> 1) << unusedBitsPerSample) >> 16);
pOutputSamples[i*2+1] = (int16_t)(((uint32_t)((int32_t)(mid - side) >> 1) << unusedBitsPerSample) >> 16);
pOutputSamples[i*2+0] = rflac__s16_round((uint32_t)(((uint32_t)((int32_t)(mid + side) >> 1) << unusedBitsPerSample)));
pOutputSamples[i*2+1] = rflac__s16_round((uint32_t)(((uint32_t)((int32_t)(mid - side) >> 1) << unusedBitsPerSample)));
}
}
@@ -4892,8 +4852,8 @@ static INLINE void rflac_read_pcm_frames_s16__decode_mid_side__sse2(
left = _mm_srai_epi32(_mm_add_epi32(mid, side), 1);
right = _mm_srai_epi32(_mm_sub_epi32(mid, side), 1);
left = _mm_srai_epi32(left, 16);
right = _mm_srai_epi32(right, 16);
left = rflac__mm_round_s16_epi32(left);
right = rflac__mm_round_s16_epi32(right);
_mm_storeu_si128((__m128i*)(pOutputSamples + i*8), rflac__mm_packs_interleaved_epi32(left, right));
}
@@ -4904,8 +4864,8 @@ static INLINE void rflac_read_pcm_frames_s16__decode_mid_side__sse2(
mid = (mid << 1) | (side & 0x01);
pOutputSamples[i*2+0] = (int16_t)(((int32_t)(mid + side) >> 1) >> 16);
pOutputSamples[i*2+1] = (int16_t)(((int32_t)(mid - side) >> 1) >> 16);
pOutputSamples[i*2+0] = rflac__s16_round((uint32_t)(((int32_t)(mid + side) >> 1)));
pOutputSamples[i*2+1] = rflac__s16_round((uint32_t)(((int32_t)(mid - side) >> 1)));
}
}
else
@@ -4923,8 +4883,8 @@ static INLINE void rflac_read_pcm_frames_s16__decode_mid_side__sse2(
left = _mm_slli_epi32(_mm_add_epi32(mid, side), shift);
right = _mm_slli_epi32(_mm_sub_epi32(mid, side), shift);
left = _mm_srai_epi32(left, 16);
right = _mm_srai_epi32(right, 16);
left = rflac__mm_round_s16_epi32(left);
right = rflac__mm_round_s16_epi32(right);
_mm_storeu_si128((__m128i*)(pOutputSamples + i*8), rflac__mm_packs_interleaved_epi32(left, right));
}
@@ -4936,8 +4896,8 @@ static INLINE void rflac_read_pcm_frames_s16__decode_mid_side__sse2(
mid = (mid << 1) | (side & 0x01);
pOutputSamples[i*2+0] = (int16_t)(((mid + side) << shift) >> 16);
pOutputSamples[i*2+1] = (int16_t)(((mid - side) << shift) >> 16);
pOutputSamples[i*2+0] = rflac__s16_round((uint32_t)(((mid + side) << shift)));
pOutputSamples[i*2+1] = rflac__s16_round((uint32_t)(((mid - side) << shift)));
}
}
}
@@ -4972,14 +4932,14 @@ static INLINE void rflac_read_pcm_frames_s16__decode_mid_side__neon(
left = vshrq_n_s32(vreinterpretq_s32_u32(vaddq_u32(mid, side)), 1);
right = vshrq_n_s32(vreinterpretq_s32_u32(vsubq_u32(mid, side)), 1);
left = vshrq_n_s32(left, 16);
right = vshrq_n_s32(right, 16);
left = vrshrq_n_s32(left, 16);
right = vrshrq_n_s32(right, 16);
{
/* interleaving store; replaces vzip + contiguous store */
int16x4x2_t v;
v.val[0] = vmovn_s32(left);
v.val[1] = vmovn_s32(right);
v.val[0] = vqmovn_s32(left);
v.val[1] = vqmovn_s32(right);
vst2_s16(pOutputSamples + i*8, v);
}
}
@@ -4991,8 +4951,8 @@ static INLINE void rflac_read_pcm_frames_s16__decode_mid_side__neon(
mid = (mid << 1) | (side & 0x01);
pOutputSamples[i*2+0] = (int16_t)(((int32_t)(mid + side) >> 1) >> 16);
pOutputSamples[i*2+1] = (int16_t)(((int32_t)(mid - side) >> 1) >> 16);
pOutputSamples[i*2+0] = rflac__s16_round((uint32_t)(((int32_t)(mid + side) >> 1)));
pOutputSamples[i*2+1] = rflac__s16_round((uint32_t)(((int32_t)(mid - side) >> 1)));
}
}
else
@@ -5015,14 +4975,14 @@ static INLINE void rflac_read_pcm_frames_s16__decode_mid_side__neon(
left = vreinterpretq_s32_u32(vshlq_u32(vaddq_u32(mid, side), shift4));
right = vreinterpretq_s32_u32(vshlq_u32(vsubq_u32(mid, side), shift4));
left = vshrq_n_s32(left, 16);
right = vshrq_n_s32(right, 16);
left = vrshrq_n_s32(left, 16);
right = vrshrq_n_s32(right, 16);
{
/* interleaving store; replaces vzip + contiguous store */
int16x4x2_t v;
v.val[0] = vmovn_s32(left);
v.val[1] = vmovn_s32(right);
v.val[0] = vqmovn_s32(left);
v.val[1] = vqmovn_s32(right);
vst2_s16(pOutputSamples + i*8, v);
}
}
@@ -5034,8 +4994,8 @@ static INLINE void rflac_read_pcm_frames_s16__decode_mid_side__neon(
mid = (mid << 1) | (side & 0x01);
pOutputSamples[i*2+0] = (int16_t)(((mid + side) << shift) >> 16);
pOutputSamples[i*2+1] = (int16_t)(((mid - side) << shift) >> 16);
pOutputSamples[i*2+0] = rflac__s16_round((uint32_t)(((mid + side) << shift)));
pOutputSamples[i*2+1] = rflac__s16_round((uint32_t)(((mid - side) << shift)));
}
}
}
@@ -5084,29 +5044,19 @@ static INLINE void rflac_read_pcm_frames_s16__decode_independent_stereo__scalar(
uint32_t tempR2 = pInputSamples1U32[i*4+2] << shift1;
uint32_t tempR3 = pInputSamples1U32[i*4+3] << shift1;
tempL0 >>= 16;
tempL1 >>= 16;
tempL2 >>= 16;
tempL3 >>= 16;
tempR0 >>= 16;
tempR1 >>= 16;
tempR2 >>= 16;
tempR3 >>= 16;
pOutputSamples[i*8+0] = (int16_t)tempL0;
pOutputSamples[i*8+1] = (int16_t)tempR0;
pOutputSamples[i*8+2] = (int16_t)tempL1;
pOutputSamples[i*8+3] = (int16_t)tempR1;
pOutputSamples[i*8+4] = (int16_t)tempL2;
pOutputSamples[i*8+5] = (int16_t)tempR2;
pOutputSamples[i*8+6] = (int16_t)tempL3;
pOutputSamples[i*8+7] = (int16_t)tempR3;
pOutputSamples[i*8+0] = rflac__s16_round(tempL0);
pOutputSamples[i*8+1] = rflac__s16_round(tempR0);
pOutputSamples[i*8+2] = rflac__s16_round(tempL1);
pOutputSamples[i*8+3] = rflac__s16_round(tempR1);
pOutputSamples[i*8+4] = rflac__s16_round(tempL2);
pOutputSamples[i*8+5] = rflac__s16_round(tempR2);
pOutputSamples[i*8+6] = rflac__s16_round(tempL3);
pOutputSamples[i*8+7] = rflac__s16_round(tempR3);
}
for (i = (frameCount4 << 2); i < frameCount; ++i) {
pOutputSamples[i*2+0] = (int16_t)((pInputSamples0U32[i] << shift0) >> 16);
pOutputSamples[i*2+1] = (int16_t)((pInputSamples1U32[i] << shift1) >> 16);
pOutputSamples[i*2+0] = rflac__s16_round((uint32_t)((pInputSamples0U32[i] << shift0)));
pOutputSamples[i*2+1] = rflac__s16_round((uint32_t)((pInputSamples1U32[i] << shift1)));
}
}
@@ -5127,8 +5077,8 @@ static INLINE void rflac_read_pcm_frames_s16__decode_independent_stereo__sse2(
__m128i left = _mm_slli_epi32(_mm_loadu_si128((const __m128i*)pInputSamples0 + i), shift0);
__m128i right = _mm_slli_epi32(_mm_loadu_si128((const __m128i*)pInputSamples1 + i), shift1);
left = _mm_srai_epi32(left, 16);
right = _mm_srai_epi32(right, 16);
left = rflac__mm_round_s16_epi32(left);
right = rflac__mm_round_s16_epi32(right);
/* At this point we have results. We can now pack and interleave these
* into a single __m128i object and then store the in the output buffer. */
@@ -5136,8 +5086,8 @@ static INLINE void rflac_read_pcm_frames_s16__decode_independent_stereo__sse2(
}
for (i = (frameCount4 << 2); i < frameCount; ++i) {
pOutputSamples[i*2+0] = (int16_t)((pInputSamples0U32[i] << shift0) >> 16);
pOutputSamples[i*2+1] = (int16_t)((pInputSamples1U32[i] << shift1) >> 16);
pOutputSamples[i*2+0] = rflac__s16_round((uint32_t)((pInputSamples0U32[i] << shift0)));
pOutputSamples[i*2+1] = rflac__s16_round((uint32_t)((pInputSamples1U32[i] << shift1)));
}
}
#endif
@@ -5164,22 +5114,22 @@ static INLINE void rflac_read_pcm_frames_s16__decode_independent_stereo__neon(
left = vreinterpretq_s32_u32(vshlq_u32(vld1q_u32(pInputSamples0U32 + i*4), shift0_4));
right = vreinterpretq_s32_u32(vshlq_u32(vld1q_u32(pInputSamples1U32 + i*4), shift1_4));
left = vshrq_n_s32(left, 16);
right = vshrq_n_s32(right, 16);
left = vrshrq_n_s32(left, 16);
right = vrshrq_n_s32(right, 16);
{
/* interleaving store; replaces vzip + contiguous store */
int16x4x2_t v;
v.val[0] = vmovn_s32(left);
v.val[1] = vmovn_s32(right);
v.val[0] = vqmovn_s32(left);
v.val[1] = vqmovn_s32(right);
vst2_s16(pOutputSamples + i*8, v);
}
}
for (i = (frameCount4 << 2); i < frameCount; ++i)
{
pOutputSamples[i*2+0] = (int16_t)((pInputSamples0U32[i] << shift0) >> 16);
pOutputSamples[i*2+1] = (int16_t)((pInputSamples1U32[i] << shift1) >> 16);
pOutputSamples[i*2+0] = rflac__s16_round((uint32_t)((pInputSamples0U32[i] << shift0)));
pOutputSamples[i*2+1] = rflac__s16_round((uint32_t)((pInputSamples1U32[i] << shift1)));
}
}
#endif
@@ -5219,12 +5169,12 @@ static void rflac_read_pcm_frames_s16__decode_left_side__wide(rflac* pFlac,
for (i = 0; i < frameCount; ++i)
{
int64_t left = (int64_t)pInLeft[i] << shiftL;
int64_t side = pInSide[i] << shiftS;
int64_t left = (int64_t)((uint64_t)(int64_t)pInLeft[i] << shiftL);
int64_t side = (int64_t)((uint64_t)pInSide[i] << shiftS);
int64_t right = left - side;
pOutputSamples[i*2+0] = (int16_t)(left >> 16);
pOutputSamples[i*2+1] = (int16_t)(right >> 16);
pOutputSamples[i*2+0] = rflac__s16_round((uint32_t)(left));
pOutputSamples[i*2+1] = rflac__s16_round((uint32_t)(right));
}
}
@@ -5238,12 +5188,12 @@ static void rflac_read_pcm_frames_s16__decode_right_side__wide(rflac* pFlac,
for (i = 0; i < frameCount; ++i)
{
int64_t side = pInSide[i] << shiftS;
int64_t right = (int64_t)pInRight[i] << shiftR;
int64_t side = (int64_t)((uint64_t)pInSide[i] << shiftS);
int64_t right = (int64_t)((uint64_t)(int64_t)pInRight[i] << shiftR);
int64_t left = right + side;
pOutputSamples[i*2+0] = (int16_t)(left >> 16);
pOutputSamples[i*2+1] = (int16_t)(right >> 16);
pOutputSamples[i*2+0] = rflac__s16_round((uint32_t)(left));
pOutputSamples[i*2+1] = rflac__s16_round((uint32_t)(right));
}
}
@@ -5257,17 +5207,17 @@ static void rflac_read_pcm_frames_s16__decode_mid_side__wide(rflac* pFlac,
for (i = 0; i < frameCount; ++i)
{
int64_t mid = (int64_t)pInMid[i] << shiftM;
int64_t side = pInSide[i] << shiftS;
int64_t mid = (int64_t)((uint64_t)(int64_t)pInMid[i] << shiftM);
int64_t side = (int64_t)((uint64_t)pInSide[i] << shiftS);
int64_t left;
int64_t right;
mid = (mid << 1) | (side & 0x01);
mid = (int64_t)((uint64_t)mid << 1) | (side & 0x01);
left = (mid + side) >> 1;
right = (mid - side) >> 1;
pOutputSamples[i*2+0] = (int16_t)(left >> 16);
pOutputSamples[i*2+1] = (int16_t)(right >> 16);
pOutputSamples[i*2+0] = rflac__s16_round((uint32_t)(left));
pOutputSamples[i*2+1] = rflac__s16_round((uint32_t)(right));
}
}
@@ -5282,8 +5232,8 @@ static void rflac_read_pcm_frames_f32__decode_left_side__wide(rflac* pFlac,
for (i = 0; i < frameCount; ++i)
{
int64_t left = (int64_t)pInLeft[i] << shiftL;
int64_t side = pInSide[i] << shiftS;
int64_t left = (int64_t)((uint64_t)(int64_t)pInLeft[i] << shiftL);
int64_t side = (int64_t)((uint64_t)pInSide[i] << shiftS);
int64_t right = left - side;
pOutputSamples[i*2+0] = (int32_t)left * factor;
@@ -5302,8 +5252,8 @@ static void rflac_read_pcm_frames_f32__decode_right_side__wide(rflac* pFlac,
for (i = 0; i < frameCount; ++i)
{
int64_t side = pInSide[i] << shiftS;
int64_t right = (int64_t)pInRight[i] << shiftR;
int64_t side = (int64_t)((uint64_t)pInSide[i] << shiftS);
int64_t right = (int64_t)((uint64_t)(int64_t)pInRight[i] << shiftR);
int64_t left = right + side;
pOutputSamples[i*2+0] = (int32_t)left * factor;
@@ -5322,12 +5272,12 @@ static void rflac_read_pcm_frames_f32__decode_mid_side__wide(rflac* pFlac,
for (i = 0; i < frameCount; ++i)
{
int64_t mid = (int64_t)pInMid[i] << shiftM;
int64_t side = pInSide[i] << shiftS;
int64_t mid = (int64_t)((uint64_t)(int64_t)pInMid[i] << shiftM);
int64_t side = (int64_t)((uint64_t)pInSide[i] << shiftS);
int64_t left;
int64_t right;
mid = (mid << 1) | (side & 0x01);
mid = (int64_t)((uint64_t)mid << 1) | (side & 0x01);
left = (mid + side) >> 1;
right = (mid - side) >> 1;
@@ -5413,7 +5363,7 @@ static uint64_t rflac_read_pcm_frames_s16(rflac* pFlac, uint64_t framesToRead,
unsigned int j;
for (j = 0; j < channelCount; ++j) {
int32_t sampleS32 = (int32_t)((uint32_t)(pFlac->currentFLACFrame.subframes[j].pSamplesS32[iFirstPCMFrame + i]) << (unusedBitsPerSample + pFlac->currentFLACFrame.subframes[j].wastedBitsPerSample));
pBufferOut[(i*channelCount)+j] = (int16_t)(sampleS32 >> 16);
pBufferOut[(i*channelCount)+j] = rflac__s16_round((uint32_t)(sampleS32));
}
}
}
@@ -6875,12 +6825,19 @@ int rflac_process(rflac_t *f, size_t *read, size_t *wrote)
}
int rflac_seek(rflac_t *f, uint64_t frame, uint64_t *byte_offset)
{
uint64_t point_frame;
return rflac_seek_point(f, frame, byte_offset, &point_frame);
}
int rflac_seek_point(rflac_t *f, uint64_t frame, uint64_t *byte_offset,
uint64_t *point_frame)
{
uint32_t i;
uint32_t best = 0;
int found = 0;
if (!f || !f->dec || !byte_offset)
if (!f || !f->dec || !byte_offset || !point_frame)
return RFLAC_PROCESS_ERROR;
if (!f->dec->pSeekpoints || !f->dec->seekpointCount)
return RFLAC_PROCESS_ERROR;
@@ -6905,6 +6862,7 @@ int rflac_seek(rflac_t *f, uint64_t frame, uint64_t *byte_offset)
*byte_offset = f->dec->firstFLACFramePosInBytes
+ f->dec->pSeekpoints[best].flacFrameOffset;
*point_frame = f->dec->pSeekpoints[best].firstPCMFrame;
return RFLAC_PROCESS_NEXT;
}
+1 -63
View File
@@ -494,7 +494,7 @@ static void image_texture_clear(struct texture_image *img)
img->fp16 = false;
}
static bool image_texture_load_buffer_probe(data_transfer_t *keep,
bool image_texture_load_buffer_probe(data_transfer_t *keep,
bool *kept, struct texture_image *out_img,
enum image_type_enum type, const void *ptr, size_t len,
const image_texture_request_t *req,
@@ -543,56 +543,6 @@ bool image_texture_load_buffer_request(struct texture_image *out_img,
should_abort, abort_ud, NULL);
}
bool image_texture_load_request(struct texture_image *out_img,
const char *path, const image_texture_request_t *req,
bool (*should_abort)(void *ud), void *ud)
{
return image_texture_load_request_ex(out_img, path, req,
should_abort, ud, NULL);
}
bool image_texture_load_request_ex(struct texture_image *out_img,
const char *path, const image_texture_request_t *req,
bool (*should_abort)(void *ud), void *ud, int *png_probe)
{
enum image_type_enum type = image_texture_get_type(path);
if (png_probe)
*png_probe = -1;
if (type != IMAGE_TYPE_NONE)
{
/* The synchronous read rides the data_transfer prefix spine
* like every other loader: filestream/VFS routing (overlays
* and driver assets from archive members or content://
* documents), 64-bit lengths, the hardware guard behind the
* read, and honest short-read detection. A zero budget fills
* to completion in one blocking call, which is this API's
* contract. */
struct data_transfer *dt = data_transfer_open_prefix(path, 0);
if (dt)
{
size_t file_len = 0;
const uint8_t *ptr = NULL;
bool kept = false;
bool loaded;
data_transfer_iterate(dt, 0);
ptr = data_transfer_ptr(dt, &file_len);
loaded = data_transfer_complete(dt) && ptr && file_len
&& image_texture_load_buffer_probe(dt, &kept, out_img, type,
ptr, file_len, req, should_abort, ud, png_probe);
if (!kept)
data_transfer_free(dt);
if (loaded)
return true;
}
}
if (out_img)
image_texture_clear(out_img);
return false;
}
/* The request the plain loads carry: the caller's image, whose
* ->pix10 asks for 10-bit on the way in */
static void image_texture_request_of(const struct texture_image *img,
@@ -604,10 +554,6 @@ static void image_texture_request_of(const struct texture_image *img,
req->want_compressed = true;
}
bool image_texture_load(struct texture_image *out_img, const char *path)
{
return image_texture_load_ex(out_img, path, NULL, NULL);
}
void image_texture_free(struct texture_image *img)
{
@@ -827,11 +773,3 @@ bool image_texture_load_buffer(struct texture_image *out_img,
buffer_len, &req, NULL, NULL);
}
bool image_texture_load_ex(struct texture_image *out_img,
const char *path, bool (*should_abort)(void *ud), void *ud)
{
image_texture_request_t req;
image_texture_request_of(out_img, &req);
return image_texture_load_request(out_img, path, &req,
should_abort, ud);
}
@@ -25,6 +25,7 @@
#include <boolean.h>
#include <formats/image.h>
#include <formats/data_transfer.h>
#include <streams/image_texture_file.h>
#ifdef HAVE_GCD
#include <dispatch/dispatch.h>
#elif defined(HAVE_THREADS)
+9 -32
View File
@@ -100,8 +100,8 @@ uint32_t cdfs_get_first_sector(cdfs_file_t* file);
* sector 16, assuming raw 2352-byte sectors. If the probe fails
* (e.g. a cooked 2048-byte image), stream_sector_size is left 0 and
* the caller must fill in stream_sector_size / stream_sector_header_size
* itself (the path adapters below derive them from the file size or
* the cue sheet). */
* itself (the path adapters in streams/cdfs_file.c derive them from the
* file size or the cue sheet). */
cdfs_track_t* cdfs_track_from_stream(
intfstream_t* stream,
unsigned first_sector_offset,
@@ -132,37 +132,14 @@ typedef struct cdfs_cue_info
bool cdfs_parse_cue(const char *cue_text, size_t len,
unsigned track_index, cdfs_cue_info_t *out);
/* --- Path adapters: open by filename, then wrap ------------------------ */
/* Sets @track's sector size and header size from its stream's length
* - 2352, 2048 or 2336 bytes a sector, whichever divides it - and
* leaves them 0 when none does. For a raw track whose ISO-9660 probe in
* cdfs_track_from_stream found nothing. */
void cdfs_track_sector_size_from_length(cdfs_track_t* track);
/* opens the specified track in a CD or virtual CD file - the resulting stream should be passed to
* cdfs_open_file to get access to a file within the CD.
*
* supported files:
* real CD - path will be in the form "cdrom://drive1.cue" or "cdrom://d:/drive.cue"
* bin/cue - path will point to the cue file
* chd - path will point to the chd file
*
* for bin/cue files, the following storage modes are supported:
* MODE2/2352
* MODE1/2352
* MODE1/2048 - untested
* MODE2/2336 - untested
*/
cdfs_track_t* cdfs_open_track(const char* path, unsigned int track_index);
/* opens the first data track in a CD or virtual CD file. see cdfs_open_track for supported file formats
*/
cdfs_track_t* cdfs_open_data_track(const char* path);
/* opens a raw track file for a CD or virtual CD.
*
* supported files:
* real CD - path will be in the form "cdrom://drive1-track01.bin" or "cdrom://d:/drive-track01.bin"
* BIN handles obtain their own validated geometry.
* bin - path will point to the bin file
* iso - path will point to the iso file
*/
cdfs_track_t* cdfs_open_raw_track(const char* path);
/* cdfs_open_track, cdfs_open_data_track and cdfs_open_raw_track, which
* open a track by filename, are in <streams/cdfs_file.h>. */
/* closes the CD or virtual CD track and frees the associated memory */
void cdfs_close_track(cdfs_track_t* track);
@@ -158,16 +158,22 @@ data_transfer_t *data_transfer_open_source(size_t len,
* NULL unless the transfer completed successfully. */
uint8_t *data_transfer_source_detach(data_transfer_t *dt, size_t *len);
data_transfer_t *data_transfer_open_window(const char *path, size_t keep);
/* Borrow a seekable source for the window's lifetime. The callback writes
* directly into final window storage, returning bytes read or -1 on error.
* A short read fails the operation. Closing the window does not close ud. */
* A short read fails the operation. Closing the window does not close ud
* unless data_transfer_set_release() said how.
* data_transfer_open_window(path), which opens a file for it, is in
* <streams/data_transfer_file.h>. */
typedef int64_t (*data_transfer_read_at_t)(void *ud, uint64_t offset,
uint8_t *dst, size_t bytes);
data_transfer_t *data_transfer_open_window_reader(size_t length, size_t keep,
data_transfer_read_at_t read_at, void *ud);
/* What frees a reader's ud when the transfer is freed */
typedef void (*data_transfer_release_t)(void *ud);
void data_transfer_set_release(data_transfer_t *dt,
data_transfer_release_t release);
const uint8_t *data_transfer_window_base(data_transfer_t *dt, size_t *len);
/* True when the transfer is genuinely windowed - address space was
* reserved and only the window is committed. False when the platform
@@ -296,8 +302,10 @@ void data_transfer_arena_release(data_transfer_arena_t *a);
* when commit_cap is 0. There is no built-in ceiling: a caller that
* asks for no cap gets no cap, and a file too large for memory is
* refused at open rather than presented as a capped prefix. */
data_transfer_t *data_transfer_open_prefix(const char *path,
size_t commit_cap);
data_transfer_t *data_transfer_open_prefix_reader(size_t length,
size_t commit_cap, data_transfer_read_at_t read_at, void *ud);
/* data_transfer_open_prefix(path, commit_cap), which opens a file for
* it, is in <streams/data_transfer_file.h>. */
/* The fill stopped at commit_cap with the file continuing beyond it.
* Terminal: iterate() will not advance further. */
+19 -29
View File
@@ -33,6 +33,8 @@
RETRO_BEGIN_DECLS
struct data_transfer;
enum image_process_code
{
IMAGE_PROCESS_ERROR = -2,
@@ -344,24 +346,25 @@ bool image_texture_scale(struct texture_image *img,
bool image_texture_load_buffer(struct texture_image *img,
enum image_type_enum type, void *s, size_t len);
/* The loader run to completion over a whole buffer or a file, with
* @req naming what the caller takes (NULL: the ordinary image). The
* abort hook is asked between steps; it may be NULL. False leaves
* @img empty. */
/* The loader run to completion over a whole buffer, with @req naming
* what the caller takes (NULL: the ordinary image). The abort hook is
* asked between steps; it may be NULL. False leaves @img empty. Loading
* by path - image_texture_load and the rest - is in
* <streams/image_texture_file.h>. */
bool image_texture_load_buffer_request(struct texture_image *img,
enum image_type_enum type, const void *buf, size_t len,
const image_texture_request_t *req,
bool (*should_abort)(void *ud), void *ud);
bool image_texture_load_request(struct texture_image *img,
const char *path, const image_texture_request_t *req,
bool (*should_abort)(void *ud), void *ud);
/* image_texture_load_request answering, from the bytes it read, whether
* the file is an animated PNG: 1 yes, 0 a still, -1 not a PNG or no
* decode. Spares a caller that keeps an animation's bytes a second
* read of every still to ask. @png_probe may be NULL. */
bool image_texture_load_request_ex(struct texture_image *img,
const char *path, const image_texture_request_t *req,
/* image_texture_load_buffer_request over bytes @keep, a data_transfer,
* holds: an image that can go on reading them (a compressed texture)
* takes @keep, frees it with itself and sets *@kept; *@png_probe, where
* not NULL, says whether the bytes are an animated PNG - 1 yes, 0 a
* still, -1 not a PNG or no decode. @keep and @kept may be NULL. */
bool image_texture_load_buffer_probe(struct data_transfer *keep,
bool *kept, struct texture_image *img,
enum image_type_enum type, const void *buf, size_t len,
const image_texture_request_t *req,
bool (*should_abort)(void *ud), void *ud, int *png_probe);
/* Run @fn for every index below @n, across the cores at once where
@@ -382,27 +385,14 @@ void image_texture_set_run_ex(unsigned n,
void (*thread_done)(void));
/* Decode the @n files @paths name into @imgs together, each as
* image_texture_load_request does with @req; a path that is NULL,
* empty or unreadable leaves its image empty. Returns how many
* decoded. In formats/image_texture_set.c. */
* image_texture_load_request (<streams/image_texture_file.h>) does with
* @req; a path that is NULL, empty or unreadable leaves its image
* empty. Returns how many decoded. In formats/image_texture_set.c. */
unsigned image_texture_load_set(const char *const *paths,
struct texture_image *imgs, unsigned n,
const image_texture_request_t *req);
/* ->pix10 is an ask on the way in and an answer on the way out: set
* it before the call to have a decoder that can emit XRGB2101010 do
* so, and read it after to find out whether it did. Clear on entry
* means the ordinary 8-bit image, as before. */
bool image_texture_load(struct texture_image *img, const char *path);
/* image_texture_load with an abort hook: both decode stages are
* incremental (one chunk / one pass per step) and @should_abort is
* asked between steps; returning true abandons the decode, which then
* fails cleanly. For decodes on worker threads that must stop promptly
* at shutdown or when their result is no longer wanted. @should_abort
* may be NULL. */
bool image_texture_load_ex(struct texture_image *img, const char *path,
bool (*should_abort)(void *ud), void *ud);
void image_texture_free(struct texture_image *img);
/* Force a CPU decode of a compressed texture_image into ->pixels (RGBA8).
+18
View File
@@ -261,9 +261,27 @@ uint64_t rflac_total_frames(const rflac_t *f);
* is the common case: a table is optional, and a stream carried in a
* container never has one. That is not a failure of the seek, only of
* this shortcut, and the caller's fallback is the same as above.
*
* The frame the offset is at is not reported, and rflac_seek_resumed()
* needs it: rflac_seek_point() gives both.
*/
int rflac_seek(rflac_t *f, uint64_t frame, uint64_t *byte_offset);
/**
* rflac_seek_point:
* @f : decoder
* @frame : PCM frame to reach
* @byte_offset: receives where the caller should resume input
* @point_frame: receives the PCM frame that input starts at
*
* As rflac_seek(), and also the seek point's own frame (at or before
* @frame), which is what rflac_seek_resumed() is to be told and where
* output resumes: decoding forward @frame - @point_frame frames reaches
* the target.
*/
int rflac_seek_point(rflac_t *f, uint64_t frame, uint64_t *byte_offset,
uint64_t *point_frame);
/**
* rflac_seek_resumed:
* @f : decoder
@@ -0,0 +1,293 @@
/* Copyright (C) 2010-2026 The RetroArch team
*
* ---------------------------------------------------------------------------------------
* The following license statement only applies to this file (retro_deque.h).
* ---------------------------------------------------------------------------------------
*
* Permission is hereby granted, free of charge,
* to any person obtaining a copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
* and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#ifndef __LIBRETRO_SDK_RETRO_DEQUE_H
#define __LIBRETRO_SDK_RETRO_DEQUE_H
/*
* retro_deque.h - a double-ended queue of one type, in one block
*
* What std::deque is used for - push at either end, pop at either end,
* the n-th element, insert and erase in the middle, in order - without
* what it costs: std::deque keeps its elements in blocks of a few dozen
* and takes a block from the heap, or gives one back, each time its
* contents cross a block's end. A queue that is pushed at one end and
* popped at the other does that for ever, however few elements it holds.
*
* Here the elements are a ring in one block, a power of two long, set
* aside when the queue is made for as many elements as the caller says it
* holds. Pushing, popping, inserting and erasing never allocate and never
* free. A push or insert into a queue that holds its limit is refused,
* with one compare (the caller decides: drop, wait, or reserve more - the
* one call besides init that allocates). Where the old code relied on a
* queue with no limit, push_back_growing (and push_front_growing) reserves twice as much when the
* limit is reached, and so allocates only then.
*
* The functions are made for the element's type with
* RETRO_DEQUE_DEFINE(name, type), so an element is copied as what it is
* (a compare-and-mask and a store for a word-sized one) and not through
* memcpy with a size known only when the program runs:
*
* struct deadline { uint64_t when; void *who; };
* RETRO_DEQUE_DEFINE(deadlines, struct deadline)
*
* deadlines_t q;
* deadlines_init(&q, 60); (holds 60, in a block of 64)
* deadlines_push_back(&q, &d); (false when full)
* deadlines_insert(&q, 3, &d); (before the 4th oldest)
* d = *deadlines_front(&q);
* deadlines_pop_front(&q);
* deadlines_erase(&q, i);
* deadlines_free(&q);
*
* Insert and erase move whichever side of the place is shorter, so the
* ends cost one element and the middle half the queue.
*
* The type has to be one C copies by assignment (anything C89 has; in
* C++, a trivially copyable type). Not thread safe: one thread, or the
* caller's own lock. C89; MSVC compiles it.
*
* The allocator is malloc and free unless RETRO_DEQUE_MALLOC and
* RETRO_DEQUE_FREE are defined before this header is included.
*/
#include <stddef.h>
#include <stdlib.h>
#include <boolean.h>
#include <retro_inline.h>
#ifndef RETRO_DEQUE_MALLOC
#define RETRO_DEQUE_MALLOC(bytes) malloc(bytes)
#endif
#ifndef RETRO_DEQUE_FREE
#define RETRO_DEQUE_FREE(ptr) free(ptr)
#endif
/* C's casts in C, C++'s in C++: a C++ unit that warns of old-style casts
* (or refuses them) includes this as it is. */
#ifdef __cplusplus
#define RETRO_DEQUE_CAST(to, value) static_cast<to>(value)
#else
#define RETRO_DEQUE_CAST(to, value) ((to)(value))
#endif
#define RETRO_DEQUE_DEFINE(name, type) \
/* The element's type by one name, so that "const" in front of it means \
* the element whatever the type is spelled as: with a pointer type \
* written out, "const type *" would be a pointer to a pointer to const. */ \
typedef type name##_value_t; \
typedef struct name \
{ \
name##_value_t *data; /* mask + 1 elements */ \
size_t mask; /* the block's length, a power of two, less one */ \
size_t first; /* where the oldest is */ \
size_t count; \
size_t limit; /* how many it holds: what init or reserve was asked for; \
* 0 while it has no block */ \
} name##_t; \
\
/* A queue that holds @limit elements, no more (its block is the next \
* power of two). False when there is no memory; the queue then holds \
* nothing and refuses every push. */ \
static INLINE bool name##_init(name##_t *q, size_t limit) \
{ \
size_t capacity = 1; \
while (capacity < limit && capacity < RETRO_DEQUE_CAST(size_t, -1) / 2 / sizeof(name##_value_t)) \
capacity <<= 1; \
q->first = 0; \
q->count = 0; \
q->mask = 0; \
q->limit = 0; \
q->data = capacity >= limit ? RETRO_DEQUE_CAST(name##_value_t*, RETRO_DEQUE_MALLOC(capacity * sizeof(name##_value_t))) : NULL; \
if (!q->data) \
return false; \
q->mask = capacity - 1; \
q->limit = limit; \
return true; \
} \
\
/* Safe on a queue whose init failed, and on one already freed */ \
static INLINE void name##_free(name##_t *q) \
{ \
if (q->data) \
RETRO_DEQUE_FREE(q->data); \
q->data = NULL; \
q->mask = 0; \
q->first = 0; \
q->count = 0; \
q->limit = 0; \
} \
\
static INLINE size_t name##_count(const name##_t *q) { return q->count; } \
static INLINE bool name##_empty(const name##_t *q) { return q->count == 0; } \
static INLINE size_t name##_limit(const name##_t *q) { return q->limit; } \
static INLINE bool name##_full(const name##_t *q) { return q->count >= q->limit; } \
\
/* The @i-th oldest; @i below the count */ \
static INLINE name##_value_t *name##_at(const name##_t *q, size_t i) \
{ \
return &q->data[(q->first + i) & q->mask]; \
} \
\
static INLINE name##_value_t *name##_front(const name##_t *q) { return &q->data[q->first]; } \
static INLINE name##_value_t *name##_back(const name##_t *q) \
{ \
return &q->data[(q->first + q->count - 1) & q->mask]; \
} \
\
/* Holds @limit from now on (never fewer than it holds already): the \
* elements move to a larger block when that takes one, in order. False \
* when there is no memory, the queue then as it was. Pointers into the \
* queue are invalid afterwards. */ \
static INLINE bool name##_reserve(name##_t *q, size_t limit) \
{ \
name##_t larger; \
size_t i; \
if (limit <= q->limit) \
return true; \
if (q->data && limit <= q->mask + 1) \
{ \
q->limit = limit; \
return true; \
} \
if (!name##_init(&larger, limit)) \
return false; \
for (i = 0; i < q->count; i++) \
larger.data[i] = *name##_at(q, i); \
larger.count = q->count; \
if (q->data) \
RETRO_DEQUE_FREE(q->data); \
*q = larger; \
return true; \
} \
\
/* False when the queue is full: nothing changed */ \
static INLINE bool name##_push_back(name##_t *q, const name##_value_t *value) \
{ \
if (name##_full(q)) \
return false; \
q->data[(q->first + q->count) & q->mask] = *value; \
q->count++; \
return true; \
} \
\
/* For a queue with no limit of its own (what a std::deque was used as): \
* when it holds its limit, it is first made to hold twice as many - \
* that push allocates, the others do not. Give it at init the most it \
* is expected to hold, and it never will. False only when there is no \
* memory: nothing changed. */ \
static INLINE bool name##_push_back_growing(name##_t *q, const name##_value_t *value) \
{ \
if (name##_full(q) && !name##_reserve(q, q->limit ? q->limit * 2 : 8)) \
return false; \
q->data[(q->first + q->count) & q->mask] = *value; \
q->count++; \
return true; \
} \
\
static INLINE bool name##_push_front(name##_t *q, const name##_value_t *value) \
{ \
if (name##_full(q)) \
return false; \
q->first = (q->first - 1) & q->mask; \
q->data[q->first] = *value; \
q->count++; \
return true; \
} \
\
/* push_front for a queue with no limit of its own, as push_back_growing */ \
static INLINE bool name##_push_front_growing(name##_t *q, const name##_value_t *value) \
{ \
if (name##_full(q) && !name##_reserve(q, q->limit ? q->limit * 2 : 8)) \
return false; \
q->first = (q->first - 1) & q->mask; \
q->data[q->first] = *value; \
q->count++; \
return true; \
} \
\
/* The queue is not empty */ \
static INLINE void name##_pop_front(name##_t *q) \
{ \
q->first = (q->first + 1) & q->mask; \
q->count--; \
} \
\
static INLINE void name##_pop_back(name##_t *q) \
{ \
q->count--; \
} \
\
/* @value before the @i-th oldest (@i up to the count: at the end). \
* False when the queue is full: nothing changed */ \
static INLINE bool name##_insert(name##_t *q, size_t i, const name##_value_t *value) \
{ \
size_t k; \
if (name##_full(q) || i > q->count) \
return false; \
if (i < q->count - i) \
{ \
/* the older side is the shorter: it moves one place down */ \
q->first = (q->first - 1) & q->mask; \
for (k = 0; k < i; k++) \
q->data[(q->first + k) & q->mask] = q->data[(q->first + k + 1) & q->mask]; \
} \
else \
{ \
for (k = q->count; k > i; k--) \
q->data[(q->first + k) & q->mask] = q->data[(q->first + k - 1) & q->mask]; \
} \
q->data[(q->first + i) & q->mask] = *value; \
q->count++; \
return true; \
} \
\
/* The @i-th oldest goes; @i below the count */ \
static INLINE void name##_erase(name##_t *q, size_t i) \
{ \
size_t k; \
if (i < q->count - 1 - i) \
{ \
for (k = i; k > 0; k--) \
q->data[(q->first + k) & q->mask] = q->data[(q->first + k - 1) & q->mask]; \
q->first = (q->first + 1) & q->mask; \
} \
else \
{ \
for (k = i; k + 1 < q->count; k++) \
q->data[(q->first + k) & q->mask] = q->data[(q->first + k + 1) & q->mask]; \
} \
q->count--; \
} \
\
static INLINE void name##_clear(name##_t *q) \
{ \
q->first = 0; \
q->count = 0; \
} \
\
/* So that a use of the macro ends in a semicolon, or not, as the caller \
* likes */ \
typedef name##_t name##_deque_defined_t
#endif
@@ -0,0 +1,68 @@
/* Copyright (C) 2010-2026 The RetroArch team
*
* ---------------------------------------------------------------------------------------
* The following license statement only applies to this file (cdfs_file.h).
* ---------------------------------------------------------------------------------------
*
* Permission is hereby granted, free of charge,
* to any person obtaining a copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
* and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#ifndef __LIBRETRO_SDK_STREAMS_CDFS_FILE_H__
#define __LIBRETRO_SDK_STREAMS_CDFS_FILE_H__
#include <retro_common_api.h>
#include <formats/cdfs.h>
RETRO_BEGIN_DECLS
/* cdfs tracks opened by filename: the path is resolved and the file
* opened through intfstream, then handed to cdfs_track_from_stream.
* Close the track with cdfs_close_track. */
/* opens the specified track in a CD or virtual CD file - the resulting stream should be passed to
* cdfs_open_file to get access to a file within the CD.
*
* supported files:
* real CD - path will be in the form "cdrom://drive1.cue" or "cdrom://d:/drive.cue"
* bin/cue - path will point to the cue file
* chd - path will point to the chd file
*
* for bin/cue files, the following storage modes are supported:
* MODE2/2352
* MODE1/2352
* MODE1/2048 - untested
* MODE2/2336 - untested
*/
cdfs_track_t* cdfs_open_track(const char* path, unsigned int track_index);
/* opens the first data track in a CD or virtual CD file. see cdfs_open_track for supported file formats
*/
cdfs_track_t* cdfs_open_data_track(const char* path);
/* opens a raw track file for a CD or virtual CD.
*
* supported files:
* real CD - path will be in the form "cdrom://drive1-track01.bin" or "cdrom://d:/drive-track01.bin"
* BIN handles obtain their own validated geometry.
* bin - path will point to the bin file
* iso - path will point to the iso file
*/
cdfs_track_t* cdfs_open_raw_track(const char* path);
RETRO_END_DECLS
#endif
@@ -0,0 +1,48 @@
/* Copyright (C) 2010-2026 The RetroArch team
*
* ---------------------------------------------------------------------------------------
* The following license statement only applies to this file (data_transfer_file.h).
* ---------------------------------------------------------------------------------------
*
* Permission is hereby granted, free of charge,
* to any person obtaining a copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
* and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#ifndef __LIBRETRO_SDK_STREAMS_DATA_TRANSFER_FILE_H__
#define __LIBRETRO_SDK_STREAMS_DATA_TRANSFER_FILE_H__
#include <stddef.h>
#include <retro_common_api.h>
#include <formats/data_transfer.h>
RETRO_BEGIN_DECLS
/* data_transfer over a file opened by path through filestream, so
* 64-bit offsets and VFS-only paths (archive members, Android
* content:// documents, frontend overrides) work like any other.
* The transfer owns the file and closes it when freed. */
/* data_transfer_open_prefix_reader over the file at @path */
data_transfer_t *data_transfer_open_prefix(const char *path,
size_t commit_cap);
/* data_transfer_open_window_reader over the file at @path */
data_transfer_t *data_transfer_open_window(const char *path, size_t keep);
RETRO_END_DECLS
#endif
@@ -0,0 +1,68 @@
/* Copyright (C) 2010-2026 The RetroArch team
*
* ---------------------------------------------------------------------------------------
* The following license statement only applies to this file (image_texture_file.h).
* ---------------------------------------------------------------------------------------
*
* Permission is hereby granted, free of charge,
* to any person obtaining a copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
* and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#ifndef __LIBRETRO_SDK_STREAMS_IMAGE_TEXTURE_FILE_H__
#define __LIBRETRO_SDK_STREAMS_IMAGE_TEXTURE_FILE_H__
#include <retro_common_api.h>
#include <boolean.h>
#include <formats/image.h>
RETRO_BEGIN_DECLS
/* Images loaded by path: the file read whole through
* data_transfer_open_prefix - filestream/VFS routing, so overlays and
* driver assets from archive members or content:// documents load like
* any other, 64-bit lengths, honest short reads - and decoded by
* image_texture_load_buffer_probe. */
/* ->pix10 is an ask on the way in and an answer on the way out: set
* it before the call to have a decoder that can emit XRGB2101010 do
* so, and read it after to find out whether it did. Clear on entry
* means the ordinary 8-bit image, as before. */
bool image_texture_load(struct texture_image *img, const char *path);
/* image_texture_load with an abort hook: both decode stages are
* incremental (one chunk / one pass per step) and @should_abort is
* asked between steps; returning true abandons the decode, which then
* fails cleanly. For decodes on worker threads that must stop promptly
* at shutdown or when their result is no longer wanted. @should_abort
* may be NULL. */
bool image_texture_load_ex(struct texture_image *img, const char *path,
bool (*should_abort)(void *ud), void *ud);
bool image_texture_load_request(struct texture_image *img,
const char *path, const image_texture_request_t *req,
bool (*should_abort)(void *ud), void *ud);
/* image_texture_load_request answering, from the bytes it read, whether
* the file is an animated PNG: 1 yes, 0 a still, -1 not a PNG or no
* decode. Spares a caller that keeps an animation's bytes a second
* read of every still to ask. @png_probe may be NULL. */
bool image_texture_load_request_ex(struct texture_image *img,
const char *path, const image_texture_request_t *req,
bool (*should_abort)(void *ud), void *ud, int *png_probe);
RETRO_END_DECLS
#endif
@@ -2,7 +2,7 @@
#ifndef LIBRETRO_RDVD_STREAM_H
#define LIBRETRO_RDVD_STREAM_H
#include <formats/rdvd_demux.h>
#include <formats/rdvd_vfs.h>
#include <streams/rdvd_vfs.h>
RETRO_BEGIN_DECLS
enum rdvd_stream_status
{
+22
View File
@@ -187,6 +187,9 @@ struct libretro_vfs_implementation_file
#endif
{
int64_t size;
/* where a stdio stream stands, kept here so a write need not ask
* stdio to keep size; -1 when not known, and stdio is asked */
int64_t stdio_pos;
uint64_t mappos;
uint64_t mapsize;
#if defined(_WIN32) && !defined(_XBOX) && !defined(HAVE_MMAP)
@@ -199,6 +202,14 @@ struct libretro_vfs_implementation_file
HANDLE fh;
/* CreateFileMapping handle backing `mapped`; NULL when unmapped */
HANDLE map_handle;
/* A stream on a handle of its own (not VFS_WIN32_NO_NATIVE): its
* position, the part of the file `buf` holds - read from it, or, with
* ndirty set, written to it and not yet to the file - and an error */
uint64_t npos;
uint64_t nbuf_at;
size_t nbuf_len;
int nerr;
int ndirty;
#if !defined(_XBOX) && !defined(HAVE_MMAP)
void *warm; /* mapped read-ahead thread, NULL until used */
#endif
@@ -247,6 +258,17 @@ struct libretro_vfs_implementation_file
retro_atomic_size_t append_end;
#ifdef VFS_HAVE_AT_HANDLE
retro_atomic_ptr_t at_handle;
/* at_handle is synchronous - Windows' cached-read fast path - and
* held by one call at a time, at_sync_busy saying so; a call that
* finds it held takes at_ovl, the same file opened for overlapped
* I/O, whose calls Windows runs side by side. Each overlapped call
* waits on an event of its own: one of at_ev, claimed by its bit in
* at_ev_busy and made at first use, or a passing one when all are in
* use. A native stream's own handle serves as its at_handle. */
retro_atomic_ptr_t at_ovl;
HANDLE at_ev[8];
retro_atomic_int_t at_ev_busy;
retro_atomic_int_t at_sync_busy;
#endif
};
#endif
+6
View File
@@ -2822,8 +2822,14 @@ static void retro_task_threaded_gather(void)
* running or parked, or one finished and not yet retired. */
static bool task_threaded_busy(void)
{
/* A finishing task counts itself finished before it stops counting
* as running, and goes on the finished stack only after both; the
* gather moves it to the retire list before it stops counting as
* finished. The count, not the stack, so a finish between the
* running count and the stack is never seen as nothing at all. */
return retro_atomic_load_acquire_int(&tasks_running_count)
> retro_atomic_load_acquire_int(&tasks_scheduled_count)
|| retro_atomic_load_acquire_int(&tasks_finished_count)
|| retro_atomic_load_acquire_ptr(&task_finished_stack)
|| task_retire.front;
}
@@ -58,6 +58,8 @@ static uint8_t *data;
static uint8_t *frame;
static size_t frame_len;
static uint64_t read_octets;
static unsigned read_at_calls;
static bool positional; /* the VFS offers v6 reads at offsets */
static void fail(const char *what, const char *detail)
{
@@ -81,6 +83,17 @@ static int64_t count_read(struct retro_vfs_file_handle *h, void *s,
return r;
}
static int64_t count_read_at(struct retro_vfs_file_handle *h, void *s,
uint64_t len, int64_t offset)
{
int64_t r = retro_vfs_file_read_at_impl(
(libretro_vfs_implementation_file*)h, s, len, offset);
read_at_calls++;
if (r > 0)
read_octets += (uint64_t)r;
return r;
}
static void install_vfs(bool lend)
{
static struct retro_vfs_interface iface;
@@ -99,10 +112,16 @@ static void install_vfs(bool lend)
iface.remove = retro_vfs_file_remove_impl;
iface.rename = retro_vfs_file_rename_impl;
info.required_interface_version = FILESTREAM_REQUIRED_VFS_VERSION;
if (positional)
{
iface.read_at = count_read_at;
info.required_interface_version = 6;
}
info.iface = &iface;
filestream_vfs_init(&info);
filestream_set_mapped_ptr_cb(lend ? lend_mapped_ptr : NULL);
read_octets = 0;
read_octets = 0;
read_at_calls = 0;
}
static int extract_cb(const char *name, const char *valid_exts,
@@ -280,11 +299,20 @@ int main(void)
return 2;
fclose(f);
/* without v6 an extract's blocks are sought and read; with it, read
* at their offsets */
check_read("read, plain file", false);
check_read("read, lent", true);
check_extract("extract, plain file", false);
check_extract("extract, lent", true);
check_window();
positional = true;
check_read("read at offsets, plain file", false);
check_extract("extract at offsets, plain file", false);
if (!read_at_calls)
fail("extract at offsets, plain file", "no read at an offset");
check_window();
positional = false;
install_vfs(false);
if ( file_archive_get_file_crc32(ZST_PATH "#archive_zst_test.bin")
!= encoding_crc32(0, data, DATA_LEN))
+58 -2
View File
@@ -19,6 +19,11 @@ TARGET_TEST15 := vfs_positional_nommap_test
TARGET_TEST16 := vfs_positional_off32_test
TARGET_TEST17 := vfs_positional_pathreopen_test
TARGET_TEST18 := vfs_positional_ntverify_test
TARGET_TEST19 := vfs_positional_wide_test
TARGET_TEST20 := vfs_seek_read_mix_test
TARGET_TEST21 := vfs_write_mix_test
BENCH_CRT := vfs_native_bench
BENCH_NATIVE := vfs_native_bench_native
LIBRETRO_COMM_DIR := ../../..
@@ -122,6 +127,11 @@ endif
ifeq ($(MMAP),1)
CFLAGS += -DHAVE_MMAP
endif
# As RetroArch's Makefile builds Linux: what glibc declares only under it
# (fallocate's hole punching) is what a frontend runs
ifeq ($(UNAME_S),Linux)
CFLAGS += -D_GNU_SOURCE
endif
# vfs_bulk_read_test asserts which I/O path an open lands on, and the
# interesting configuration - descriptor I/O with no file mapping - is
@@ -134,6 +144,14 @@ ifeq ($(DESCRIPTOR_IO),1)
CFLAGS += -DVFS_HAVE_DESCRIPTOR_IO=1
endif
# 'make VFS_NATIVE=0' builds the suite with Windows streams on the C
# runtime (VFS_WIN32_NO_NATIVE) rather than a handle of their own;
# elsewhere it changes nothing.
VFS_NATIVE ?=
ifeq ($(VFS_NATIVE),0)
CFLAGS += -DVFS_WIN32_NO_NATIVE
endif
# FAKE_CFR=1 stands in a copy_file_range that copies to EOF whatever
# len it was given (what gVisor was observed doing); FAKE_CFR=2 one
# that copies three times len. Both exercise vfs_v5_metadata_test's
@@ -166,7 +184,8 @@ all: $(TARGET) $(TARGET_TEST) $(TARGET_TEST2) $(TARGET_TEST3) $(TARGET_TEST4) \
$(TARGET_TEST5) $(TARGET_TEST6) $(TARGET_TEST8) $(TARGET_TEST9) \
$(TARGET_TEST10) $(TARGET_TEST11) $(TARGET_TEST12) $(TARGET_TEST13) \
$(TARGET_TEST14) $(TARGET_TEST15) $(TARGET_TEST16) $(TARGET_TEST17) \
$(TARGET_TEST18) $(RENAME_TARGET)
$(TARGET_TEST18) $(TARGET_TEST19) $(TARGET_TEST20) $(TARGET_TEST21) $(BENCH_CRT) $(BENCH_NATIVE) \
$(RENAME_TARGET)
%.o: %.c
$(CC) -c -o $@ $< $(CFLAGS)
@@ -248,6 +267,39 @@ $(NTVERIFY_VFS_OBJ): $(LIBRETRO_COMM_DIR)/vfs/vfs_implementation.c
$(TARGET_TEST18): vfs_positional_required.o $(NTVERIFY_OBJS)
$(CC) -o $@ $^ $(LDFLAGS) -lpthread
# vfs_positional_wide_test: the positional test from twelve threads
vfs_positional_wide.o: vfs_positional_test.c
$(CC) -c -o $@ $< $(CFLAGS) -DVFS_POSITIONAL_TEST_REQUIRED -DTHREADS=12
$(TARGET_TEST19): vfs_positional_wide.o $(COMMON_OBJS)
$(CC) -o $@ $^ $(LDFLAGS) -lpthread
$(TARGET_TEST20): vfs_seek_read_mix_test.o $(COMMON_OBJS)
$(CC) -o $@ $^ $(LDFLAGS)
$(TARGET_TEST21): vfs_write_mix_test.o $(COMMON_OBJS)
$(CC) -o $@ $^ $(LDFLAGS)
# vfs_native_bench / vfs_native_bench_native: read-only stream timings
# through the C runtime (VFS_WIN32_NO_NATIVE), and through the stream's
# own handle - run both on Windows and compare. Elsewhere the two are
# the same build.
CRT_VFS_OBJ := vfs_implementation_crt.o
BENCH_OBJS := $(filter-out %/vfs_implementation.o,$(COMMON_OBJS)) \
rthreads_ra.o retro_eventcount_ra.o
$(CRT_VFS_OBJ): $(LIBRETRO_COMM_DIR)/vfs/vfs_implementation.c
$(CC) -c -o $@ $< $(CFLAGS) -DVFS_WIN32_NO_NATIVE
vfs_native_bench_crt.o: vfs_native_bench.c
$(CC) -c -o $@ $< $(CFLAGS) -DVFS_WIN32_NO_NATIVE
$(BENCH_CRT): vfs_native_bench_crt.o $(BENCH_OBJS) $(CRT_VFS_OBJ)
$(CC) -o $@ $^ $(LDFLAGS) $(RA_LIBS) -lpthread
$(BENCH_NATIVE): vfs_native_bench.o $(COMMON_OBJS) rthreads_ra.o retro_eventcount_ra.o
$(CC) -o $@ $^ $(LDFLAGS) $(RA_LIBS) -lpthread
$(TARGET_TEST10): vfs_view_test.o $(VIEW_OBJS)
$(CC) -o $@ $^ $(LDFLAGS)
@@ -326,7 +378,11 @@ clean:
rm -f $(TARGET) $(TARGET_TEST) $(TARGET_TEST2) $(TARGET_TEST3) \
$(TARGET_TEST4) $(TARGET_TEST5) $(TARGET_TEST6) \
$(TARGET_TEST7) $(TARGET_TEST8) $(TARGET_TEST9) $(TARGET_TEST10) \
$(TARGET_TEST11) $(TARGET_TEST12) $(TARGET_TEST13) $(TARGET_TEST14) $(TARGET_TEST15) $(TARGET_TEST16) $(TARGET_TEST17) $(TARGET_TEST18) $(CLOSE_VFS_OBJ) \
$(TARGET_TEST11) $(TARGET_TEST12) $(TARGET_TEST13) $(TARGET_TEST14) $(TARGET_TEST15) $(TARGET_TEST16) $(TARGET_TEST17) $(TARGET_TEST18) $(TARGET_TEST19) \
$(TARGET_TEST20) vfs_seek_read_mix_test.o \
$(TARGET_TEST21) vfs_write_mix_test.o \
$(BENCH_CRT) $(BENCH_NATIVE) vfs_positional_wide.o \
vfs_native_bench.o vfs_native_bench_crt.o $(CRT_VFS_OBJ) $(CLOSE_VFS_OBJ) \
$(OBJS) $(FAULT_FS_OBJ) $(RENAME_VFS_OBJ) \
$(RA_VFS_OBJ) $(HOOK_VFS_OBJ) rthreads_ra.o retro_eventcount_ra.o \
$(NOMMAP_VFS_OBJ) vfs_positional_required.o $(OFF32_VFS_OBJ) \
@@ -271,16 +271,38 @@ static void test_path_selection(void)
retro_vfs_file_close_impl(s);
}
/* No hint: unchanged, buffered. */
/* No hint: unchanged, buffered - on a handle of its own where
* Windows takes read-only streams off the C runtime. */
s = retro_vfs_file_open_impl(path, RETRO_VFS_FILE_ACCESS_READ,
RETRO_VFS_FILE_ACCESS_HINT_NONE);
CHECK(s != NULL, "plain open failed");
if (s)
{
#if !defined(VFS_WIN32_NO_NATIVE) && defined(_WIN32) && !defined(_XBOX)
CHECK(s->fp == NULL && s->fh != NULL,
"plain read-only open is not on its own handle");
#else
CHECK(s->fp != NULL, "plain open left stdio");
#endif
retro_vfs_file_close_impl(s);
}
/* ... and so do write streams */
s = retro_vfs_file_open_impl("bulk_select_pw.bin",
RETRO_VFS_FILE_ACCESS_WRITE, RETRO_VFS_FILE_ACCESS_HINT_NONE);
CHECK(s != NULL, "plain write open failed");
if (s)
{
#if !defined(VFS_WIN32_NO_NATIVE) && defined(_WIN32) && !defined(_XBOX)
CHECK(s->fp == NULL && s->fh != NULL,
"plain write open is not on its own handle");
#else
CHECK(s->fp != NULL, "plain write open left stdio");
#endif
retro_vfs_file_close_impl(s);
remove("bulk_select_pw.bin");
}
/* Write modes take the descriptor path where the platform lists
* descriptor writes, and stay buffered where it does not. */
s = retro_vfs_file_open_impl("bulk_select_w.bin",
@@ -0,0 +1,408 @@
/* Copyright (C) 2010-2026 The RetroArch team
*
* ---------------------------------------------------------------------------------------
* The following license statement only applies to this file (vfs_native_bench.c).
* ---------------------------------------------------------------------------------------
*
* Permission is hereby granted, free of charge,
* to any person obtaining a copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
* and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
/* Stream throughput through filestream: the C runtime path
* (vfs_native_bench, VFS_WIN32_NO_NATIVE) against the stream's own
* handle (vfs_native_bench_native), the same source built twice. On
* anything but Windows the two are the same build.
*
* sequential reads of 1, 16, 256, 4096 and 65536 bytes, and 1 MiB
* 4 KiB at random offsets by seek and read, and by filestream_read_at
* 4 KiB at random offsets by filestream_read_at from 4 threads
* open, read 4 KiB, close, over 1000 small files
* sequential writes of 16, 256, 4096 and 65536 bytes, and 1 MiB, to a
* new file, its close included
* 64 bytes written at random offsets of a 4 MiB file by seek and write
* create, write 4 KiB, close, over 1000 small files
*
* Each case runs five times and reports the best, in ns per call and
* MB/s; the file is read once first so every case runs from the cache.
* Usage: vfs_native_bench [file] (default: makes a 64 MiB one here) */
#include <retro_posix_source.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <streams/file_stream.h>
#include <rthreads/rthreads.h>
#ifdef _WIN32
#include <windows.h>
#else
#include <time.h>
#endif
#define FILE_BYTES (64u << 20)
#define RUNS 5
#define RANDOM_OPS 20000
#define SMALL_FILES 1000
static double now_ns(void)
{
#ifdef _WIN32
static LARGE_INTEGER f;
LARGE_INTEGER c;
if (!f.QuadPart)
QueryPerformanceFrequency(&f);
QueryPerformanceCounter(&c);
return (double)c.QuadPart * 1e9 / (double)f.QuadPart;
#else
struct timespec t;
clock_gettime(CLOCK_MONOTONIC, &t);
return (double)t.tv_sec * 1e9 + (double)t.tv_nsec;
#endif
}
static uint8_t *buf;
static int64_t size;
static void report(const char *what, double ns, double calls, double bytes)
{
printf("%-34s %10.1f ns/call %9.1f MB/s\n", what, ns / calls,
bytes / (ns / 1e9) / 1e6);
}
static void sequential(const char *path, int64_t chunk)
{
double best = 0, bytes = 0, calls = 0;
int r;
char what[64];
int64_t limit = chunk < 256 ? (int64_t)(4u << 20) : size;
for (r = 0; r < RUNS; r++)
{
RFILE *f = filestream_open(path, RETRO_VFS_FILE_ACCESS_READ,
RETRO_VFS_FILE_ACCESS_HINT_NONE);
int64_t done = 0, got;
double t0, t;
unsigned n = 0;
if (!f)
return;
t0 = now_ns();
while (done < limit && (got = filestream_read(f, buf, chunk)) > 0)
{
done += got;
n++;
}
t = now_ns() - t0;
filestream_close(f);
if (!r || t < best)
{
best = t;
bytes = (double)done;
calls = (double)n;
}
}
snprintf(what, sizeof(what), "sequential %lld B", (long long)chunk);
report(what, best, calls, bytes);
}
static void random_reads(const char *path, int at)
{
double best = 0;
int r;
for (r = 0; r < RUNS; r++)
{
RFILE *f = filestream_open(path, RETRO_VFS_FILE_ACCESS_READ,
RETRO_VFS_FILE_ACCESS_HINT_NONE);
unsigned x = 12345, k;
double t0, t;
if (!f)
return;
t0 = now_ns();
for (k = 0; k < RANDOM_OPS; k++)
{
int64_t off;
x = x * 1103515245u + 12345u;
off = (int64_t)((x >> 4) % (uint32_t)(size - 4096));
if (at)
{
if (filestream_read_at(f, buf, 4096, off) != 4096)
break;
}
else
{
filestream_seek(f, off, RETRO_VFS_SEEK_POSITION_START);
if (filestream_read(f, buf, 4096) != 4096)
break;
}
}
t = now_ns() - t0;
filestream_close(f);
if (!r || t < best)
best = t;
}
report(at ? "random 4 KiB, read_at" : "random 4 KiB, seek+read", best,
RANDOM_OPS, (double)RANDOM_OPS * 4096);
}
struct worker
{
RFILE *f;
unsigned seed;
};
static void work(void *ud)
{
struct worker *w = (struct worker*)ud;
uint8_t b[4096];
unsigned x = w->seed, k;
for (k = 0; k < RANDOM_OPS / 4; k++)
{
x = x * 1103515245u + 12345u;
filestream_read_at(w->f, b, sizeof(b),
(int64_t)((x >> 4) % (uint32_t)(size - 4096)));
}
}
static void threaded_reads(const char *path)
{
double best = 0;
int r, i;
RFILE *f = filestream_open(path, RETRO_VFS_FILE_ACCESS_READ,
RETRO_VFS_FILE_ACCESS_HINT_NONE);
if (!f || filestream_read_at(f, buf, 1, 0) != 1)
{
printf("%-34s no reads at offsets here\n", "random 4 KiB, read_at x4");
filestream_close(f);
return;
}
for (r = 0; r < RUNS; r++)
{
sthread_t *t[4];
struct worker w[4];
double t0 = now_ns(), dt;
for (i = 0; i < 4; i++)
{
w[i].f = f;
w[i].seed = 7u + 977u * (unsigned)i;
t[i] = sthread_create(work, &w[i]);
}
for (i = 0; i < 4; i++)
if (t[i])
sthread_join(t[i]);
dt = now_ns() - t0;
if (!r || dt < best)
best = dt;
}
filestream_close(f);
report("random 4 KiB, read_at x4 threads", best, RANDOM_OPS,
(double)RANDOM_OPS * 4096);
}
static void small_files(void)
{
char name[64];
unsigned i;
double best = 0;
int r;
for (i = 0; i < SMALL_FILES; i++)
{
snprintf(name, sizeof(name), "vfs_bench_small_%04u.bin", i);
filestream_write_file(name, buf, 4096);
}
for (r = 0; r < RUNS; r++)
{
double t0 = now_ns(), t;
for (i = 0; i < SMALL_FILES; i++)
{
RFILE *f;
snprintf(name, sizeof(name), "vfs_bench_small_%04u.bin", i);
if ((f = filestream_open(name, RETRO_VFS_FILE_ACCESS_READ,
RETRO_VFS_FILE_ACCESS_HINT_NONE)))
{
filestream_read(f, buf, 4096);
filestream_close(f);
}
}
t = now_ns() - t0;
if (!r || t < best)
best = t;
}
for (i = 0; i < SMALL_FILES; i++)
{
snprintf(name, sizeof(name), "vfs_bench_small_%04u.bin", i);
remove(name);
}
report("open + read 4 KiB + close", best, SMALL_FILES,
(double)SMALL_FILES * 4096);
}
static void sequential_writes(int64_t chunk)
{
double best = 0;
int r;
char what[64];
int64_t limit = chunk < 4096 ? (int64_t)(4u << 20) : (int64_t)(32u << 20);
for (r = 0; r < RUNS; r++)
{
RFILE *f = filestream_open("vfs_bench_w.bin",
RETRO_VFS_FILE_ACCESS_WRITE, RETRO_VFS_FILE_ACCESS_HINT_NONE);
int64_t done = 0;
double t0, t;
if (!f)
return;
t0 = now_ns();
while (done < limit && filestream_write(f, buf, chunk) == chunk)
done += chunk;
filestream_close(f);
t = now_ns() - t0;
if (!r || t < best)
best = t;
}
remove("vfs_bench_w.bin");
snprintf(what, sizeof(what), "write sequential %lld B", (long long)chunk);
report(what, best, (double)(limit / chunk), (double)limit);
}
static void random_writes(void)
{
double best = 0;
int r;
RFILE *f = filestream_open("vfs_bench_w.bin",
RETRO_VFS_FILE_ACCESS_WRITE, RETRO_VFS_FILE_ACCESS_HINT_NONE);
if (!f)
return;
filestream_write(f, buf, 1u << 20);
filestream_write(f, buf, 1u << 20);
filestream_write(f, buf, 1u << 20);
filestream_write(f, buf, 1u << 20);
filestream_close(f);
for (r = 0; r < RUNS; r++)
{
unsigned x = 4321, k;
double t0, t;
if (!(f = filestream_open("vfs_bench_w.bin",
RETRO_VFS_FILE_ACCESS_WRITE
| RETRO_VFS_FILE_ACCESS_UPDATE_EXISTING,
RETRO_VFS_FILE_ACCESS_HINT_NONE)))
break;
t0 = now_ns();
for (k = 0; k < RANDOM_OPS; k++)
{
x = x * 1103515245u + 12345u;
filestream_seek(f, (int64_t)((x >> 4) % ((4u << 20) - 64)),
RETRO_VFS_SEEK_POSITION_START);
if (filestream_write(f, buf, 64) != 64)
break;
}
filestream_close(f);
t = now_ns() - t0;
if (!r || t < best)
best = t;
}
remove("vfs_bench_w.bin");
report("write random 64 B, seek+write", best, RANDOM_OPS,
(double)RANDOM_OPS * 64);
}
static void small_writes(void)
{
char name[64];
unsigned i;
double best = 0;
int r;
for (r = 0; r < RUNS; r++)
{
double t0 = now_ns(), t;
for (i = 0; i < SMALL_FILES; i++)
{
RFILE *f;
snprintf(name, sizeof(name), "vfs_bench_small_%04u.bin", i);
if ((f = filestream_open(name, RETRO_VFS_FILE_ACCESS_WRITE,
RETRO_VFS_FILE_ACCESS_HINT_NONE)))
{
filestream_write(f, buf, 4096);
filestream_close(f);
}
}
t = now_ns() - t0;
if (!r || t < best)
best = t;
}
for (i = 0; i < SMALL_FILES; i++)
{
snprintf(name, sizeof(name), "vfs_bench_small_%04u.bin", i);
remove(name);
}
report("create + write 4 KiB + close", best, SMALL_FILES,
(double)SMALL_FILES * 4096);
}
int main(int argc, char **argv)
{
const char *path = argc > 1 ? argv[1] : "vfs_bench_64m.bin";
int made = 0;
RFILE *f;
if (!(buf = (uint8_t*)malloc(1u << 20)))
return 1;
memset(buf, 0x5A, 1u << 20);
if (argc < 2)
{
unsigned i;
if (!(f = filestream_open(path, RETRO_VFS_FILE_ACCESS_WRITE,
RETRO_VFS_FILE_ACCESS_HINT_NONE)))
return 1;
for (i = 0; i < FILE_BYTES >> 20; i++)
filestream_write(f, buf, 1u << 20);
filestream_close(f);
made = 1;
}
if (!(f = filestream_open(path, RETRO_VFS_FILE_ACCESS_READ,
RETRO_VFS_FILE_ACCESS_HINT_NONE)))
return 1;
size = filestream_get_size(f);
while (filestream_read(f, buf, 1u << 20) > 0) { }
filestream_close(f);
if (size < 8192)
return 1;
#ifdef VFS_WIN32_NO_NATIVE
printf("C runtime streams, %lld bytes\n", (long long)size);
#else
printf("native streams, %lld bytes\n", (long long)size);
#endif
sequential(path, 1);
sequential(path, 16);
sequential(path, 256);
sequential(path, 4096);
sequential(path, 65536);
sequential(path, 1 << 20);
random_reads(path, 0);
random_reads(path, 1);
threaded_reads(path);
small_files();
sequential_writes(16);
sequential_writes(256);
sequential_writes(4096);
sequential_writes(65536);
sequential_writes(1 << 20);
random_writes();
small_writes();
if (made)
remove(path);
free(buf);
return 0;
}
@@ -23,9 +23,11 @@
* Unix host, which always has pread(), or on Windows NT, which always
* has a second handle, a -1 there fails instead - the build without
* HAVE_MMAP (vfs_positional_nommap_test) holds that as Android builds,
* vfs_positional_pathreopen_test the path reopen, and
* vfs_positional_pathreopen_test the path reopen,
* vfs_positional_ntverify_test the handle opened relative to the
* stream's, checked against it as NT before Vista has it checked. */
* stream's, checked against it as NT before Vista has it checked, and
* vfs_positional_wide_test twelve threads at once - more than a
* Windows stream keeps events for, so some calls make their own. */
#include <stdio.h>
#include <stdlib.h>
@@ -67,7 +69,9 @@ static void thread_join(test_thread_t t)
#endif
#define FILE_SIZE (4u << 20)
#ifndef THREADS
#define THREADS 4
#endif
#define READS 20000
#define RECORDS 5000
#define RECORD_LEN 64
@@ -0,0 +1,149 @@
/* Copyright (C) 2010-2026 The RetroArch team
*
* ---------------------------------------------------------------------------------------
* The following license statement only applies to this file (vfs_seek_read_mix_test.c).
* ---------------------------------------------------------------------------------------
*
* Permission is hereby granted, free of charge,
* to any person obtaining a copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
* and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
/* A read-only stream read the way parsers read it: seeks to random
* places, reads of every size from 1 byte to past 64 KiB - some
* across where a refill ends - runs of sequential reads after a seek,
* seeks back into what was just read, seeks past the end, and the
* position after each. Every byte is checked against the fixture, whose
* bytes depend on where they lie. On Windows this is the stream's own
* buffer sizing its refills by how it is being read. */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <streams/file_stream.h>
#define FILE_BYTES ((3u << 20) + 1234)
#define ROUNDS 20000
static int failures;
#define CHECK(c) do { if (!(c)) { fprintf(stderr, "%s:%d: check failed: %s\n", \
__FILE__, __LINE__, #c); failures++; } } while (0)
static uint8_t at(uint64_t i)
{
return (uint8_t)((i * 2654435761u) >> 11);
}
static int same(const uint8_t *p, uint64_t off, uint64_t n)
{
uint64_t i;
for (i = 0; i < n; i++)
if (p[i] != at(off + i))
return 0;
return 1;
}
int main(void)
{
static const char path[] = "vfs_seek_read_mix.bin";
static const unsigned sizes[] = { 1, 3, 16, 100, 512, 4095, 4096, 4097,
30000, 65535, 65536, 65537, 200000 };
uint8_t *data = (uint8_t*)malloc(FILE_BYTES);
uint8_t *buf = (uint8_t*)malloc(200000);
RFILE *f;
unsigned x = 4242, k;
uint64_t i;
if (!data || !buf)
return 1;
for (i = 0; i < FILE_BYTES; i++)
data[i] = at(i);
CHECK(filestream_write_file(path, data, FILE_BYTES));
free(data);
f = filestream_open(path, RETRO_VFS_FILE_ACCESS_READ,
RETRO_VFS_FILE_ACCESS_HINT_NONE);
CHECK(f != NULL);
if (!f)
return 1;
CHECK(filestream_get_size(f) == FILE_BYTES);
for (k = 0; k < ROUNDS; k++)
{
int64_t pos, got, want, left;
unsigned run, r;
x = x * 1103515245u + 12345u;
switch ((x >> 28) & 3)
{
case 0: /* anywhere */
pos = (int64_t)((x >> 3) % FILE_BYTES);
break;
case 1: /* back a little, into what was read */
pos = filestream_tell(f) - (int64_t)((x >> 8) % 70000);
if (pos < 0)
pos = 0;
break;
case 2: /* near the end, or past it */
pos = FILE_BYTES - 70000 + (int64_t)((x >> 8) % 80000);
break;
default: /* on from where it is */
pos = filestream_tell(f);
break;
}
CHECK(filestream_seek(f, pos, RETRO_VFS_SEEK_POSITION_START) >= 0);
CHECK(filestream_tell(f) == pos);
run = 1 + ((x >> 12) & 7);
for (r = 0; r < run; r++)
{
x = x * 1103515245u + 12345u;
want = (int64_t)sizes[(x >> 20) % (sizeof(sizes) / sizeof(sizes[0]))];
left = pos < (int64_t)FILE_BYTES ? (int64_t)FILE_BYTES - pos : 0;
got = filestream_read(f, buf, want);
CHECK(got == (want < left ? want : left));
if (got > 0)
{
CHECK(same(buf, (uint64_t)pos, (uint64_t)got));
pos += got;
}
CHECK(filestream_tell(f) == pos);
if (failures > 20)
break;
}
if (failures > 20)
break;
}
/* and straight through, after all that */
CHECK(filestream_seek(f, 0, RETRO_VFS_SEEK_POSITION_START) >= 0);
for (i = 0; i < FILE_BYTES; )
{
int64_t got = filestream_read(f, buf, 7777);
if (got <= 0 || !same(buf, i, (uint64_t)got))
{
failures++;
break;
}
i += (uint64_t)got;
}
CHECK(i == FILE_BYTES);
CHECK(filestream_read(f, buf, 10) == 0);
filestream_close(f);
remove(path);
free(buf);
printf("vfs seek/read mix: %s\n", failures ? "FAIL" : "ok");
return failures ? 1 : 0;
}
@@ -0,0 +1,341 @@
/* Copyright (C) 2010-2026 The RetroArch team
*
* ---------------------------------------------------------------------------------------
* The following license statement only applies to this file (vfs_write_mix_test.c).
* ---------------------------------------------------------------------------------------
*
* Permission is hereby granted, free of charge,
* to any person obtaining a copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
* and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
/* A stream written the way savers, config writers and patchers write
* it, in every mode a stream is opened to write in: runs of small
* writes, writes of every size up to past 64 KiB, writes past the end
* that leave a gap, seeks of every kind, reads back, positions and
* sizes after each, flushes, truncates shorter and longer, holes
* punched, reads at offsets, and closing and opening it again to update it. A copy of
* the file kept here says what every read, position and size must be,
* and what the file holds when it is closed. On Windows this is the
* stream's own buffer holding the writes (VFS_WIN32_NO_NATIVE: the C
* runtime's). */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <boolean.h>
#include <libretro.h>
#include <streams/file_stream.h>
#define PATH "vfs_write_mix_test.bin"
#define MODEL_MAX (2u << 20)
#define POS_MAX (1u << 20)
#define ROUNDS 12000
static int failures;
static uint8_t *model;
static uint8_t *scratch;
static uint64_t msize;
static uint64_t mpos;
static uint32_t seed = 0x2545f491u;
#define CHECK(c) do { if (!(c)) { fprintf(stderr, \
"%s:%d: round %u, mode %u: check failed: %s\n", __FILE__, __LINE__, \
round_no, mode_no, #c); failures++; } } while (0)
static unsigned round_no;
static unsigned mode_no;
static uint32_t rnd(uint32_t n)
{
seed ^= seed << 13;
seed ^= seed >> 17;
seed ^= seed << 5;
return n ? seed % n : 0;
}
/* @len bytes written at @at of the copy, zeroes in any gap before them */
static void model_write(uint64_t at, const uint8_t *s, uint64_t len)
{
if (at > msize)
memset(model + msize, 0, (size_t)(at - msize));
memcpy(model + at, s, (size_t)len);
if (at + len > msize)
msize = at + len;
}
static void model_truncate(uint64_t len)
{
if (len > msize)
memset(model + msize, 0, (size_t)(len - msize));
msize = len;
}
static void fill(uint8_t *p, uint64_t len)
{
uint64_t i;
uint8_t b = (uint8_t)rnd(256);
for (i = 0; i < len; i++)
p[i] = (uint8_t)(b + i * 7 + (i >> 9));
}
/* The file as it stands on disk, read without the stream */
static void check_disk(void)
{
FILE *f = fopen(PATH, "rb");
uint64_t got;
if (!f)
{
CHECK(f != NULL);
return;
}
got = (uint64_t)fread(scratch, 1, MODEL_MAX, f);
fclose(f);
CHECK(got == msize);
CHECK(got != msize || memcmp(scratch, model, (size_t)got) == 0);
}
static void run_mode(unsigned mode, unsigned reopen_mode, bool readable,
bool creates)
{
RFILE *f;
unsigned r;
if (!creates)
{
/* an update opens what is there */
FILE *seedf = fopen(PATH, "wb");
msize = 4096 + rnd(70000);
fill(model, msize);
if (seedf)
{
fwrite(model, 1, (size_t)msize, seedf);
fclose(seedf);
}
}
else
msize = 0;
mpos = 0;
if (!(f = filestream_open(PATH, mode, RETRO_VFS_FILE_ACCESS_HINT_NONE)))
{
CHECK(f != NULL);
return;
}
CHECK(filestream_get_size(f) == (int64_t)msize);
for (r = 0; r < ROUNDS; r++)
{
uint32_t op = rnd(100);
round_no = r;
if (op < 35 || (op < 48 && mpos >= POS_MAX))
{
/* a run of small writes */
uint64_t len = 1 + rnd(300);
if (mpos + len > MODEL_MAX)
continue;
fill(scratch, len);
CHECK(filestream_write(f, scratch, (int64_t)len) == (int64_t)len);
model_write(mpos, scratch, len);
mpos += len;
}
else if (op < 48)
{
/* up to past the buffer; one in four at least as large */
uint64_t len = (op < 45) ? 300 + rnd(70000)
: 65536 + rnd(140000);
if (mpos + len > MODEL_MAX)
continue;
fill(scratch, len);
CHECK(filestream_write(f, scratch, (int64_t)len) == (int64_t)len);
model_write(mpos, scratch, len);
mpos += len;
}
else if (op < 60)
{
uint64_t to = rnd((uint32_t)(msize + 100000));
if (to > POS_MAX)
to = rnd(POS_MAX);
CHECK(filestream_seek(f, (int64_t)to,
RETRO_VFS_SEEK_POSITION_START) >= 0);
mpos = to;
}
else if (op < 65)
{
int64_t d = (int64_t)rnd(140000) - 70000;
if ((int64_t)mpos + d < 0 || mpos + (uint64_t)(d < 0 ? 0 : d) > POS_MAX)
continue;
CHECK(filestream_seek(f, d, RETRO_VFS_SEEK_POSITION_CURRENT) >= 0);
mpos = (uint64_t)((int64_t)mpos + d);
}
else if (op < 68)
{
int64_t d = -(int64_t)rnd((uint32_t)msize + 1) + (int64_t)rnd(5000);
if ((int64_t)msize + d < 0 || msize + (uint64_t)(d < 0 ? 0 : d) > POS_MAX)
continue;
CHECK(filestream_seek(f, d, RETRO_VFS_SEEK_POSITION_END) >= 0);
mpos = (uint64_t)((int64_t)msize + d);
}
else if (op < 80)
{
uint64_t len = 1 + ((op & 1) ? rnd(300) : rnd(90000));
uint64_t want = mpos >= msize ? 0
: (msize - mpos < len ? msize - mpos : len);
int64_t got = filestream_read(f, scratch, (int64_t)len);
if (!readable)
CHECK(got <= 0);
else
{
CHECK(got == (int64_t)want);
CHECK(got != (int64_t)want
|| memcmp(scratch, model + mpos, (size_t)want) == 0);
if (got > 0)
mpos += (uint64_t)got;
}
}
else if (op < 84)
CHECK(filestream_tell(f) == (int64_t)mpos);
else if (op < 87)
CHECK(filestream_get_size(f) == (int64_t)msize);
else if (op < 89)
{
CHECK(filestream_flush(f) == 0);
CHECK(filestream_tell(f) == (int64_t)mpos);
}
else if (op < 92)
{
uint64_t to = rnd((uint32_t)(msize + 50000));
if (to > POS_MAX)
continue;
CHECK(filestream_truncate(f, (int64_t)to) == 0);
model_truncate(to);
CHECK(filestream_get_size(f) == (int64_t)msize);
CHECK(filestream_tell(f) == (int64_t)mpos);
}
else if (op < 93)
{
/* zeroes where the filesystem can free a range, the size kept;
* -1 where it cannot */
uint64_t at = rnd((uint32_t)(msize + 1000));
uint64_t len = 1 + rnd(150000);
if (filestream_punch_hole(f, (int64_t)at, (int64_t)len) == 0
&& at < msize)
memset(model + at, 0, (size_t)(msize - at < len
? msize - at : len));
CHECK(filestream_get_size(f) == (int64_t)msize);
CHECK(filestream_tell(f) == (int64_t)mpos);
}
else if (op < 96)
{
/* at an offset, from the file itself: what was written goes
* there first */
uint64_t at = rnd((uint32_t)(msize + 1000));
uint64_t len = 1 + rnd(80000);
uint64_t want = at >= msize ? 0
: (msize - at < len ? msize - at : len);
int64_t got;
CHECK(filestream_flush(f) == 0);
got = filestream_read_at(f, scratch, (int64_t)len, (int64_t)at);
/* -1 is a stream with no reads at offsets */
CHECK(got == -1 || got == (int64_t)want);
CHECK(got != (int64_t)want
|| memcmp(scratch, model + at, (size_t)want) == 0);
CHECK(filestream_tell(f) == (int64_t)mpos);
}
else
{
CHECK(filestream_close(f) == 0);
check_disk();
if (!(f = filestream_open(PATH, reopen_mode,
RETRO_VFS_FILE_ACCESS_HINT_NONE)))
{
CHECK(f != NULL);
return;
}
readable = true; /* every update stream reads */
mpos = 0;
CHECK(filestream_get_size(f) == (int64_t)msize);
}
}
CHECK(filestream_close(f) == 0);
check_disk();
}
/* Writes the stream still holds, then a hole punched and a truncate
* over them: they go into the file first, so what is left of them is
* the zeroes and the cut */
static void check_held_writes(void)
{
RFILE *f;
uint8_t b[4000];
int punched;
round_no = 0;
mode_no = 4;
memset(b, 0xaa, sizeof(b));
if (!(f = filestream_open(PATH, RETRO_VFS_FILE_ACCESS_WRITE,
RETRO_VFS_FILE_ACCESS_HINT_NONE)))
{
CHECK(f != NULL);
return;
}
CHECK(filestream_write(f, b, sizeof(b)) == (int64_t)sizeof(b));
punched = filestream_punch_hole(f, 0, 2000) == 0;
CHECK(filestream_write(f, b, sizeof(b)) == (int64_t)sizeof(b));
CHECK(filestream_truncate(f, 6000) == 0);
CHECK(filestream_close(f) == 0);
memset(model, 0xaa, 6000);
if (punched)
memset(model, 0, 2000);
msize = 6000;
check_disk();
}
int main(void)
{
if ( !(model = (uint8_t*)malloc(MODEL_MAX))
|| !(scratch = (uint8_t*)malloc(MODEL_MAX)))
return 2;
check_held_writes();
mode_no = 0;
run_mode(RETRO_VFS_FILE_ACCESS_WRITE,
RETRO_VFS_FILE_ACCESS_WRITE | RETRO_VFS_FILE_ACCESS_UPDATE_EXISTING,
false, true);
mode_no = 1;
run_mode(RETRO_VFS_FILE_ACCESS_READ_WRITE,
RETRO_VFS_FILE_ACCESS_READ_WRITE
| RETRO_VFS_FILE_ACCESS_UPDATE_EXISTING, true, true);
mode_no = 2;
run_mode(RETRO_VFS_FILE_ACCESS_WRITE
| RETRO_VFS_FILE_ACCESS_UPDATE_EXISTING,
RETRO_VFS_FILE_ACCESS_WRITE | RETRO_VFS_FILE_ACCESS_UPDATE_EXISTING,
true, false);
mode_no = 3;
run_mode(RETRO_VFS_FILE_ACCESS_READ_WRITE
| RETRO_VFS_FILE_ACCESS_UPDATE_EXISTING,
RETRO_VFS_FILE_ACCESS_READ_WRITE
| RETRO_VFS_FILE_ACCESS_UPDATE_EXISTING, true, false);
remove(PATH);
free(model);
free(scratch);
if (failures)
{
printf("vfs_write_mix_test: %d check(s) failed\n", failures);
return 1;
}
printf("vfs_write_mix_test: every read, position, size and file matched\n");
return 0;
}
@@ -22,11 +22,26 @@ static int injected_close(int fd)
descriptor_closes++;
return fail_descriptor ? -1 : result;
}
#ifdef _WIN32
#include <windows.h>
/* Streams on Windows NT close their own handle: that is the descriptor
* close there. Nothing else in these checks closes a handle. */
static BOOL injected_close_handle(HANDLE h)
{
BOOL result = CloseHandle(h);
descriptor_closes++;
return fail_descriptor ? FALSE : result;
}
#define CloseHandle injected_close_handle
#endif
#define fclose injected_fclose
#define close injected_close
#include "../../../vfs/vfs_implementation.c"
#undef fclose
#undef close
#ifdef _WIN32
#undef CloseHandle
#endif
static int RETRO_CALLCONV callback_close(struct retro_vfs_file_handle *file)
{
@@ -49,11 +64,20 @@ int main(void)
if (file)
{
CHECK(filestream_write(file, "data", 4) == 4);
#ifdef VFS_NATIVE
/* written through its own handle, closed with it */
before = descriptor_closes;
fail_descriptor = 1;
CHECK(filestream_close(file) == -1);
CHECK(descriptor_closes == before + 1);
fail_descriptor = 0;
#else
before = stdio_closes;
fail_stdio = 1;
CHECK(filestream_close(file) == -1);
CHECK(stdio_closes == before + 1);
fail_stdio = 0;
#endif
}
native = retro_vfs_file_open_impl("close_result.bin", RETRO_VFS_FILE_ACCESS_READ, 0);
CHECK(native != NULL);
@@ -20,7 +20,7 @@ COMMON_SOURCES := \
$(LIBRETRO_COMM_DIR)/file/file_path_io.c \
$(LIBRETRO_COMM_DIR)/formats/audio_transfer.c \
$(LIBRETRO_COMM_DIR)/formats/mp3/rmp3.c \
$(LIBRETRO_COMM_DIR)/formats/data_transfer.c \
$(LIBRETRO_COMM_DIR)/formats/data_transfer.c $(LIBRETRO_COMM_DIR)/streams/data_transfer_file.c \
$(LIBRETRO_COMM_DIR)/formats/aac/raac.c \
$(LIBRETRO_COMM_DIR)/formats/opus/ropus.c \
$(LIBRETRO_COMM_DIR)/formats/vorbis/rvorbis.c \
@@ -56,6 +56,7 @@
#include <formats/audio.h>
#include <formats/data_transfer.h>
#include <streams/data_transfer_file.h>
#include <formats/rwebm.h>
#if defined(__linux__) || defined(__unix__) || defined(__APPLE__)
@@ -15,7 +15,7 @@ SOURCES := audio_transfer_flac_test.c \
$(LIBRETRO_COMM_DIR)/file/file_path.c \
$(LIBRETRO_COMM_DIR)/file/file_path_io.c \
$(LIBRETRO_COMM_DIR)/formats/audio_transfer.c \
$(LIBRETRO_COMM_DIR)/formats/data_transfer.c \
$(LIBRETRO_COMM_DIR)/formats/data_transfer.c $(LIBRETRO_COMM_DIR)/streams/data_transfer_file.c \
$(LIBRETRO_COMM_DIR)/formats/flac/rflac.c \
$(LIBRETRO_COMM_DIR)/formats/webm/rwebm.c \
$(LIBRETRO_COMM_DIR)/memmap/memmap.c \
@@ -9,7 +9,10 @@
* the frames as delimited packets, all given up front;
* - demuxed input grown a packet at a time, each growth only after
* the decoder has run dry - the progressive source the growth
* contract in <formats/audio.h> is written for.
* contract in <formats/audio.h> is written for;
* - seeks in a native .flac buffer that carries a seek table, which
* take the table's shortcut, each landing on the frame asked for
* (rflac_seek_point gives the seek point's own frame as well).
*
* FLAC is lossless, so every lane must give back exactly the PCM that
* went in: every sample, and not one frame more or less. The fixture
@@ -27,6 +30,7 @@
#include <math.h>
#include <formats/audio.h>
#include <formats/rflac.h>
#include <formats/rwebm.h>
#include <streams/file_stream.h>
@@ -263,6 +267,64 @@ static void lane_demuxed(const struct demuxed *d, int grow)
free(pcm);
}
/* Seeks in a buffer with a seek table: points at frames 0, 4096, 8192
* and 12288. Each lands on the frame asked for, between the points and
* on them, forward and back; resumed from a point at its own frame, not
* at frame 0 (which landed a seek the point's frames past its target). */
static void lane_seek(const char *path)
{
static const uint64_t targets[] = { 10000, 4096, 15000, 100, 12288,
19999, 6000 };
const char *lane = "native .flac buffer, seek table";
size_t len, i;
void *buf = load(path, &len);
void *ctx = audio_transfer_new(AUDIO_TYPE_FLAC);
int16_t pcm[1000 * 2];
rflac_t *f = rflac_new();
uint64_t at = 0, point = 0;
/* The point itself, straight from rflac */
CHECK(f != NULL, "%s: rflac_new", lane);
if (f)
{
rflac_set_in(f, (const uint8_t*)buf, len);
rflac_set_eof(f);
while (!rflac_format(f))
if (rflac_process(f, NULL, NULL) != RFLAC_PROCESS_NEXT)
break;
CHECK(rflac_seek_point(f, 10000, &at, &point) == RFLAC_PROCESS_NEXT
&& point == 8192 && at > 0 && at < len,
"%s: the point for frame 10000 is frame %lu at byte %lu", lane,
(unsigned long)point, (unsigned long)at);
rflac_free(f);
}
audio_transfer_set_buffer_ptr(ctx, AUDIO_TYPE_FLAC, buf, len);
CHECK(audio_transfer_start(ctx, AUDIO_TYPE_FLAC), "%s: start failed", lane);
for (i = 0; i < sizeof(targets) / sizeof(targets[0]); i++)
{
const size_t want = SRC_FRAMES - targets[i] < 1000
? (size_t)(SRC_FRAMES - targets[i]) : 1000;
size_t got, k;
CHECK(audio_transfer_seek(ctx, AUDIO_TYPE_FLAC, targets[i]),
"%s: seek to %lu refused", lane, (unsigned long)targets[i]);
got = drain(ctx, pcm, 0, want, 1000);
CHECK(got == want, "%s: %lu frames after a seek to %lu, %lu wanted",
lane, (unsigned long)got, (unsigned long)targets[i],
(unsigned long)want);
for (k = 0; k < got; k++)
if ( pcm[k * 2] != src_pcm[(targets[i] + k) * 2]
|| pcm[k * 2 + 1] != src_pcm[(targets[i] + k) * 2 + 1])
{
CHECK(0, "%s: after a seek to %lu, frame %lu differs", lane,
(unsigned long)targets[i], (unsigned long)k);
break;
}
}
audio_transfer_free(ctx, AUDIO_TYPE_FLAC);
free(buf);
}
int main(void)
{
struct demuxed d;
@@ -273,6 +335,7 @@ int main(void)
lane_buffer("native .flac buffer", "fixtures/tone.flac");
lane_buffer("Matroska buffer", "fixtures/tone.mka");
lane_windowed("fixtures/tone.mka", 4096, 2048);
lane_seek("fixtures/tone_seektable.flac");
mka = load("fixtures/tone.mka", &mka_len);
demux_mka((const uint8_t*)mka, mka_len, &d);
@@ -1,12 +1,13 @@
#!/usr/bin/env python3
"""Regenerates fixtures/tone.flac and fixtures/tone.mka.
"""Regenerates fixtures/tone.flac, fixtures/tone.mka and
fixtures/tone_seektable.flac.
20000 stereo frames at 44100 Hz - not a multiple of the encoder's block
size, so each stream ends on a short frame - of two sines with a little
low-order noise so the predictor has residual to code. The test
regenerates the same PCM from the same formula and compares every
sample, so keep the two in step. Needs ffmpeg; the committed fixtures
are what CI uses."""
sample, so keep the two in step. Needs ffmpeg, and flac and metaflac
for the seek table; the committed fixtures are what CI uses."""
import math
import os
@@ -34,4 +35,13 @@ for out in ("tone.flac", "tone.mka"):
"-f", "s16le", "-ar", str(RATE), "-ac", "2",
"-i", raw, "-c:a", "flac",
os.path.join(fix, out)])
# The same PCM through the reference encoder, with a seek table: a point
# at each of the first frames (ffmpeg writes no table). The seek lane
# lands between them.
seek = os.path.join(fix, "tone_seektable.flac")
subprocess.check_call(["flac", "--silent", "--force", "--force-raw-format",
"--endian=little", "--sign=signed", "--channels=2",
"--bps=16", "--sample-rate=" + str(RATE), "-S-",
"-o", seek, raw])
subprocess.check_call(["metaflac", "--add-seekpoint=5x", seek])
os.remove(raw)
+11 -2
View File
@@ -3,6 +3,7 @@ include ../../sample_objdir.mk
TARGET := cdfs_dir_record_test$(EXE)
TARGET_TEST2 := cdfs_pure_api_test$(EXE)
TARGET_TEST3 := cdfs_io_error_test$(EXE)
TARGET_TEST4 := cdfs_file_test$(EXE)
.DEFAULT_GOAL := all
LIBRETRO_COMM_DIR := ../../..
@@ -53,6 +54,10 @@ endif
TEST3_OBJS := $(SAMPLE_OBJDIR)/cdfs_io_error_test.o $(call sample_objs,$(filter-out cdfs_pure_api_test.o $(LIBRETRO_COMM_DIR)/formats/cdfs/cdfs.o,$(TEST2_SOURCES:.c=.o)))
# cdfs_file_test: the same stack and the path openers over it
TEST4_OBJS := $(SAMPLE_OBJDIR)/cdfs_file_test.o $(call sample_objs,$(filter-out cdfs_pure_api_test.o,$(TEST2_SOURCES:.c=.o))) \
$(call sample_objs,$(LIBRETRO_COMM_DIR)/streams/cdfs_file.o)
ifeq ($(OS),Windows_NT)
LDLIBS += -lwinmm
endif
@@ -64,7 +69,10 @@ $(SAMPLE_OBJDIR)/cdfs_io_error_test.o: cdfs_io_error_test.c $(LIBRETRO_COMM_DIR)
$(TARGET_TEST3): $(TEST3_OBJS)
$(CC) -o $@ $^ $(LDFLAGS) $(LDLIBS)
all: $(TARGET) $(TARGET_TEST2) $(TARGET_TEST3)
$(TARGET_TEST4): $(TEST4_OBJS)
$(CC) -o $@ $^ $(LDFLAGS) $(LDLIBS)
all: $(TARGET) $(TARGET_TEST2) $(TARGET_TEST3) $(TARGET_TEST4)
%.o: %.c
$(CC) -c -o $@ $< $(CFLAGS)
@@ -81,6 +89,7 @@ $(TARGET_TEST2): $(TEST2_OBJS)
clean:
rm -rf $(SAMPLE_OBJDIR)
rm -f $(TARGET) $(TARGET_TEST2) $(TARGET_TEST3) $(OBJS) $(TEST2_OBJS) $(TEST3_OBJS)
rm -f $(TARGET) $(TARGET_TEST2) $(TARGET_TEST3) $(TARGET_TEST4) $(OBJS) \
$(TEST2_OBJS) $(TEST3_OBJS) $(TEST4_OBJS)
.PHONY: clean
@@ -0,0 +1,193 @@
/* Copyright (C) 2010-2026 The RetroArch team
*
* ---------------------------------------------------------------------------------------
* The following license statement only applies to this file (cdfs_file_test.c).
* ---------------------------------------------------------------------------------------
*
* Permission is hereby granted, free of charge,
* to any person obtaining a copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
* and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
/* streams/cdfs_file.c: tracks opened by filename.
*
* - a cooked .iso and a raw .bin with no volume descriptor take their
* sector layout from their length (2048 and 2352 bytes a sector);
* - a cue sheet in a directory of its own names its bin relatively, is
* resolved against its own directory, and gives the MODE1/2352 layout
* and the INDEX 01 offset of the track it selects;
* - cdfs_open_data_track takes a cue's first data track, an .iso as raw;
* - each opened track reads back the marker written in its sector 3;
* - an unknown extension opens only as track 1, and then only if it is
* a bin or an iso.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <formats/cdfs.h>
#include <file/file_path.h>
#include <streams/cdfs_file.h>
#include <streams/file_stream.h>
#define SECTORS 24
static int failures;
#define CHECK(c) do { if (!(c)) { fprintf(stderr, "%s:%d: check failed: %s\n", \
__FILE__, __LINE__, #c); failures++; } } while (0)
static const char dir[] = "cdfs_file_tmp";
static const char iso[] = "cdfs_file_tmp/cooked.iso";
static const char bin[] = "cdfs_file_tmp/raw.bin";
static const char cue[] = "cdfs_file_tmp/disc.cue";
static const char pregap[] = "cdfs_file_tmp/pregap.bin";
static const char pre_cue[] = "cdfs_file_tmp/pregap.cue";
static const char odd[] = "cdfs_file_tmp/disc.img";
/* @sectors of @size bytes, user data at @header; sector s's data
* starts with "S<s>" and the rest of every byte is s */
static int write_image(const char *path, unsigned sectors, unsigned size,
unsigned header, unsigned lead)
{
uint8_t buf[2352];
unsigned s;
RFILE *f = filestream_open(path, RETRO_VFS_FILE_ACCESS_WRITE,
RETRO_VFS_FILE_ACCESS_HINT_NONE);
if (!f)
return 0;
memset(buf, 0xEE, sizeof(buf));
for (s = 0; s < lead; s++)
filestream_write(f, buf, size);
for (s = 0; s < sectors; s++)
{
memset(buf, (int)s, size);
sprintf((char*)buf + header, "S%u", s);
if (filestream_write(f, buf, size) != (int64_t)size)
{
filestream_close(f);
return 0;
}
}
filestream_close(f);
return 1;
}
static int write_text(const char *path, const char *text)
{
return filestream_write_file(path, text, (int64_t)strlen(text));
}
/* Sector 3 of the track, through the cdfs raw view */
static void reads_marker(cdfs_track_t *track, const char *what)
{
cdfs_file_t file;
char got[8];
(void)what;
memset(got, 0, sizeof(got));
CHECK(cdfs_open_file(&file, track, NULL) == 1);
cdfs_seek_sector(&file, 3);
CHECK(cdfs_read_file(&file, got, 2) == 2);
CHECK(memcmp(got, "S3", 2) == 0);
cdfs_close_file(&file);
}
int main(void)
{
cdfs_track_t *t;
path_mkdir(dir);
CHECK(write_image(iso, SECTORS, 2048, 0, 0));
CHECK(write_image(bin, SECTORS, 2352, 16, 0));
/* two sectors ahead of INDEX 01 */
CHECK(write_image(pregap, SECTORS, 2352, 16, 2));
CHECK(write_image(odd, SECTORS, 2048, 0, 0));
CHECK(write_text(cue,
"FILE \"raw.bin\" BINARY\n"
" TRACK 01 MODE1/2352\n"
" INDEX 01 00:00:00\n"));
CHECK(write_text(pre_cue,
"FILE \"pregap.bin\" BINARY\n"
" TRACK 01 MODE1/2352\n"
" INDEX 00 00:00:00\n"
" INDEX 01 00:00:02\n"));
/* a cooked iso: 2048 from the length */
if ((t = cdfs_open_raw_track(iso)))
{
CHECK(t->stream_sector_size == 2048 && t->stream_sector_header_size == 0);
reads_marker(t, "iso");
cdfs_close_track(t);
}
else
failures++;
/* a raw bin: 2352 from the length */
if ((t = cdfs_open_raw_track(bin)))
{
CHECK(t->stream_sector_size == 2352);
cdfs_close_track(t);
}
else
failures++;
/* the cue, its bin found beside it */
if ((t = cdfs_open_track(cue, 1)))
{
CHECK(t->stream_sector_size == 2352 && t->stream_sector_header_size == 16);
CHECK(t->first_sector_offset == 0);
reads_marker(t, "cue");
cdfs_close_track(t);
}
else
failures++;
/* INDEX 01 two sectors in */
if ((t = cdfs_open_track(pre_cue, 1)))
{
CHECK(t->first_sector_offset == 2 * 2352);
reads_marker(t, "cue pregap");
cdfs_close_track(t);
}
else
failures++;
/* first data track: of the cue, and an iso as raw */
if ((t = cdfs_open_data_track(cue)))
{
CHECK(t->stream_sector_size == 2352);
cdfs_close_track(t);
}
else
failures++;
if ((t = cdfs_open_data_track(iso)))
{
CHECK(t->stream_sector_size == 2048);
cdfs_close_track(t);
}
else
failures++;
/* not a bin, iso, cue or chd */
CHECK(cdfs_open_track(odd, 2) == NULL);
CHECK(cdfs_open_track(odd, 1) == NULL);
CHECK(cdfs_open_track("cdfs_file_tmp/missing.cue", 1) == NULL);
remove(iso); remove(bin); remove(pregap); remove(odd);
remove(cue); remove(pre_cue);
remove(dir);
printf("cdfs file: %s\n", failures ? "FAIL" : "ok");
return failures ? 1 : 0;
}
@@ -161,7 +161,7 @@ int main(void)
memset(&layout, 0, sizeof(layout));
layout.stream = track.stream;
sparse_size = UINT64_C(4194307) * sizes[mode];
cdfs_determine_sector_size_from_file_size(&layout);
cdfs_track_sector_size_from_length(&layout);
CHECK(layout.stream_sector_size == sizes[mode]);
sparse_size = UINT64_C(4194304) * sizes[mode] + 12345;
}
@@ -13,7 +13,7 @@ COMMON_SOURCES := \
$(LIBRETRO_COMM_DIR)/encodings/encoding_utf.c \
$(LIBRETRO_COMM_DIR)/file/file_path.c \
$(LIBRETRO_COMM_DIR)/file/file_path_io.c \
$(LIBRETRO_COMM_DIR)/formats/data_transfer.c \
$(LIBRETRO_COMM_DIR)/formats/data_transfer.c $(LIBRETRO_COMM_DIR)/streams/data_transfer_file.c \
$(LIBRETRO_COMM_DIR)/memmap/memmap.c \
$(LIBRETRO_COMM_DIR)/streams/file_stream.c \
$(LIBRETRO_COMM_DIR)/string/rstrtod.c \
@@ -63,6 +63,7 @@
#include <boolean.h>
#include <formats/data_transfer.h>
#include <streams/data_transfer_file.h>
static int test_fails;
@@ -49,6 +49,7 @@
#include <string.h>
#include <stdint.h>
#include <formats/data_transfer.h>
#include <streams/data_transfer_file.h>
#include <memmap.h>
#ifdef DT_COUNT_IO
#include <streams/file_stream.h>
@@ -66,6 +66,7 @@
#include <string.h>
#include <stdint.h>
#include <formats/data_transfer.h>
#include <streams/data_transfer_file.h>
#include <memmap.h>
#ifdef DT_COUNT_IO
#include <streams/file_stream.h>
+6 -6
View File
@@ -34,11 +34,11 @@ $(TARGET_TEST3): rdvd_ifo_test.c ../../../formats/dvd/rdvd_ifo.c ../../../includ
$(TARGET_TEST4): rdvd_source_test.c ../../../formats/dvd/rdvd_source.c ../../../formats/dvd/rdvd_ifo.c ../../../include/formats/rdvd_source.h ../../../include/formats/rdvd_ifo.h
$(CC) $(CFLAGS) rdvd_source_test.c ../../../formats/dvd/rdvd_source.c ../../../formats/dvd/rdvd_ifo.c -o $@ $(LDFLAGS)
DVD_STREAM_HEADERS := ../../../include/formats/rdvd_stream.h ../../../include/formats/rdvd_demux.h
DVD_STREAM_SOURCES := ../../../formats/dvd/rdvd_stream.c ../../../formats/dvd/rdvd_demux.c ../../../formats/dvd/rdvd_packet.c ../../../formats/mpeg1/rmpeg1_ps.c
DVD_STREAM_HEADERS := ../../../include/streams/rdvd_stream.h ../../../include/formats/rdvd_demux.h
DVD_STREAM_SOURCES := ../../../streams/rdvd_stream.c ../../../formats/dvd/rdvd_demux.c ../../../formats/dvd/rdvd_packet.c ../../../formats/mpeg1/rmpeg1_ps.c
$(TARGET_TEST5): $(DVD_STREAM_HEADERS) $(DVD_STREAM_SOURCES) rdvd_vfs_test.c ../../../formats/dvd/rdvd_vfs.c ../../../formats/dvd/rdvd_source.c ../../../formats/dvd/rdvd_ifo.c ../../../queues/retro_spsc.c ../../../include/formats/rdvd_vfs.h
$(CC) $(CFLAGS) -DHAVE_THREADS $(DVD_STREAM_SOURCES) rdvd_vfs_test.c ../../../formats/dvd/rdvd_vfs.c ../../../formats/dvd/rdvd_source.c ../../../formats/dvd/rdvd_ifo.c ../../../queues/retro_spsc.c -o $@ $(LDFLAGS)
$(TARGET_TEST5): $(DVD_STREAM_HEADERS) $(DVD_STREAM_SOURCES) rdvd_vfs_test.c ../../../streams/rdvd_vfs.c ../../../formats/dvd/rdvd_source.c ../../../formats/dvd/rdvd_ifo.c ../../../queues/retro_spsc.c ../../../include/streams/rdvd_vfs.h
$(CC) $(CFLAGS) -DHAVE_THREADS $(DVD_STREAM_SOURCES) rdvd_vfs_test.c ../../../streams/rdvd_vfs.c ../../../formats/dvd/rdvd_source.c ../../../formats/dvd/rdvd_ifo.c ../../../queues/retro_spsc.c -o $@ $(LDFLAGS)
ifneq ($(findstring mingw,$(shell $(CC) -dumpmachine)),)
VFS_REAL_LIBS :=
@@ -46,8 +46,8 @@ else
VFS_REAL_LIBS := -lpthread -lrt
endif
VFS_REAL_SOURCES := ../../../formats/dvd/rdvd_lpcm.c ../../../formats/lpcm/rlpcm.c ../../../formats/dvd/rdvd_ac3.c ../../../formats/ac3/rac3_frame.c ../../../formats/ac3/rac3_decode.c ../../../rthreads/tpool.c ../../../rthreads/retro_eventcount.c ../../../rthreads/rthreads.c ../../../features/features_cpu.c ../../../compat/fopen_utf8.c ../../../compat/compat_strl.c ../../../encodings/encoding_utf.c ../../../file/file_path.c ../../../file/file_path_io.c ../../../file/retro_dirent.c ../../../streams/file_stream.c ../../../string/rstrtod.c ../../../time/rtime.c ../../../vfs/vfs_implementation.c
$(TARGET_TEST6): $(DVD_STREAM_HEADERS) $(DVD_STREAM_SOURCES) rdvd_vfs_real_test.c ../../../formats/dvd/rdvd_vfs.c ../../../formats/dvd/rdvd_source.c ../../../formats/dvd/rdvd_ifo.c ../../../queues/retro_spsc.c $(VFS_REAL_SOURCES)
$(CC) $(filter-out -std=c89 -Werror,$(CFLAGS)) -std=gnu99 -DHAVE_THREADS $(DVD_STREAM_SOURCES) rdvd_vfs_real_test.c ../../../formats/dvd/rdvd_vfs.c ../../../formats/dvd/rdvd_source.c ../../../formats/dvd/rdvd_ifo.c ../../../queues/retro_spsc.c $(VFS_REAL_SOURCES) -o $@ $(LDFLAGS) $(VFS_REAL_LIBS) -lm
$(TARGET_TEST6): $(DVD_STREAM_HEADERS) $(DVD_STREAM_SOURCES) rdvd_vfs_real_test.c ../../../streams/rdvd_vfs.c ../../../formats/dvd/rdvd_source.c ../../../formats/dvd/rdvd_ifo.c ../../../queues/retro_spsc.c $(VFS_REAL_SOURCES)
$(CC) $(filter-out -std=c89 -Werror,$(CFLAGS)) -std=gnu99 -DHAVE_THREADS $(DVD_STREAM_SOURCES) rdvd_vfs_real_test.c ../../../streams/rdvd_vfs.c ../../../formats/dvd/rdvd_source.c ../../../formats/dvd/rdvd_ifo.c ../../../queues/retro_spsc.c $(VFS_REAL_SOURCES) -o $@ $(LDFLAGS) $(VFS_REAL_LIBS) -lm
AC3_SOURCES := ../../../formats/dvd/rdvd_ac3.c ../../../formats/ac3/rac3_frame.c
$(TARGET_TEST7): rdvd_ac3_test.c $(AC3_SOURCES) ../../../include/formats/rdvd_ac3.h
@@ -9,8 +9,8 @@
#else
#include <unistd.h>
#endif
#include <formats/rdvd_vfs.h>
#include <formats/rdvd_stream.h>
#include <streams/rdvd_vfs.h>
#include <streams/rdvd_stream.h>
#include <rthreads/rthreads.h>
#include <retro_atomic.h>
#include <features/features_cpu.h>
@@ -1,8 +1,8 @@
/* Copyright (C) 2026 The RetroArch team. SPDX-License-Identifier: MIT */
#include <stdio.h>
#include <string.h>
#include <formats/rdvd_vfs.h>
#include <formats/rdvd_stream.h>
#include <streams/rdvd_vfs.h>
#include <streams/rdvd_stream.h>
struct RFILE
{
uint64_t size, position;
@@ -18,9 +18,9 @@ SOURCES := image_loader_test.c \
$(LIBRETRO_COMM_DIR)/features/features_cpu.c \
$(LIBRETRO_COMM_DIR)/file/file_path.c \
$(LIBRETRO_COMM_DIR)/file/file_path_io.c \
$(LIBRETRO_COMM_DIR)/formats/data_transfer.c \
$(LIBRETRO_COMM_DIR)/formats/data_transfer.c $(LIBRETRO_COMM_DIR)/streams/data_transfer_file.c \
$(LIBRETRO_COMM_DIR)/formats/dds/rdds.c \
$(LIBRETRO_COMM_DIR)/formats/image_texture.c \
$(LIBRETRO_COMM_DIR)/formats/image_texture.c $(LIBRETRO_COMM_DIR)/streams/image_texture_file.c \
$(LIBRETRO_COMM_DIR)/formats/image_texture_scale.c \
$(LIBRETRO_COMM_DIR)/formats/image_texture_set.c \
$(LIBRETRO_COMM_DIR)/formats/image_transfer.c \
@@ -27,6 +27,7 @@
#include <boolean.h>
#include <encodings/crc32.h>
#include <formats/image.h>
#include <streams/image_texture_file.h>
#define W 24
#define H 16
@@ -0,0 +1,63 @@
include ../../sample_objdir.mk
TARGET := rflac_s16_test
TARGET_TEST := rflac_s16_nosimd_test
LIBRETRO_COMM_DIR := ../../..
SOURCES := \
$(LIBRETRO_COMM_DIR)/compat/fopen_utf8.c \
$(LIBRETRO_COMM_DIR)/compat/compat_strl.c \
$(LIBRETRO_COMM_DIR)/encodings/encoding_utf.c \
$(LIBRETRO_COMM_DIR)/features/features_cpu.c \
$(LIBRETRO_COMM_DIR)/file/file_path.c \
$(LIBRETRO_COMM_DIR)/file/file_path_io.c \
$(LIBRETRO_COMM_DIR)/streams/file_stream.c \
$(LIBRETRO_COMM_DIR)/string/rstrtod.c \
$(LIBRETRO_COMM_DIR)/string/stdstring.c \
$(LIBRETRO_COMM_DIR)/time/rtime.c \
$(LIBRETRO_COMM_DIR)/vfs/vfs_implementation.c
OBJS := $(call sample_objs,$(SOURCES:.c=.o))
CFLAGS += -Wall -pedantic -std=gnu99 -g1 -O1 -I$(LIBRETRO_COMM_DIR)/include
LDFLAGS += -lm
ifneq ($(SANITIZER),)
CFLAGS := -fsanitize=$(SANITIZER) -fno-omit-frame-pointer $(CFLAGS)
LDFLAGS := -fsanitize=$(SANITIZER) $(LDFLAGS)
endif
all: $(TARGET) $(TARGET_TEST)
$(SAMPLE_OBJDIR)/%.o: $$(call sample_src,$$*).c
@mkdir -p $(@D)
$(CC) -c -o $@ $< $(CFLAGS)
# rflac itself twice: with the SIMD loops the target has, and without
$(SAMPLE_OBJDIR)/rflac_simd.o: $(LIBRETRO_COMM_DIR)/formats/flac/rflac.c
@mkdir -p $(@D)
$(CC) -c -o $@ $< $(CFLAGS)
$(SAMPLE_OBJDIR)/rflac_nosimd.o: $(LIBRETRO_COMM_DIR)/formats/flac/rflac.c
@mkdir -p $(@D)
$(CC) -c -o $@ $< $(CFLAGS) -DRFLAC_NO_SIMD
$(TARGET): rflac_s16_test.o $(SAMPLE_OBJDIR)/rflac_simd.o $(OBJS)
$(CC) -o $@ $^ $(LDFLAGS)
$(TARGET_TEST): rflac_s16_test.o $(SAMPLE_OBJDIR)/rflac_nosimd.o $(OBJS)
$(CC) -o $@ $^ $(LDFLAGS)
rflac_s16_test.o: rflac_s16_test.c
$(CC) -c -o $@ $< $(CFLAGS)
check: all
./$(TARGET)
./$(TARGET_TEST)
clean:
rm -rf $(SAMPLE_OBJDIR)
rm -f $(TARGET) $(TARGET_TEST) rflac_s16_test.o
.PHONY: all check clean
@@ -0,0 +1,104 @@
#!/usr/bin/env python3
"""Regenerates the fixtures of rflac_s16_test: FLAC streams of 16, 24 and
32 bits, mono, stereo and six channels, made by the reference encoder
from PCM built with integers only, which the test builds again the same
way and so knows every sample of. The stereo streams are made so that the
encoder picks each of the four channel assignments somewhere (checked
here with flac --analyze), and the 32-bit one so that its side channel
needs 33 bits. Needs flac; the committed fixtures are what CI uses."""
import os
import subprocess
import struct
FRAMES = 5003 # not a multiple of 4: the SIMD loops' scalar tails run too
here = os.path.dirname(os.path.abspath(__file__))
fix = os.path.join(here, "fixtures")
os.makedirs(fix, exist_ok=True)
def lcg(seed):
while True:
seed = (seed * 1103515245 + 12345) & 0xffffffff
yield seed
def tri(i, period, amp):
"""A triangle wave, integer only: rises then falls over period frames."""
p = i % period
half = period // 2
v = p if p < half else period - p
return (v * 2 * amp) // half - amp
def sample(kind, bps, ch, i, c, noise):
top = (1 << (bps - 1)) - 1
n = next(noise) >> 20 # 12 bits of noise
if kind == "stereo":
# Frames 0-1023 the two noisy about one wave (mid/side), 1024-2047
# left clean and right the same with noise (left/side), 2048-3071
# the other way round (side/right), the rest unrelated
# (independent)
base = tri(i, 397, top // 2)
seg = i // 1024
if seg == 0:
v = base + (n if c else -n)
elif seg == 1:
v = base if c == 0 else base + n
elif seg == 2:
v = base + n if c == 0 else base
else:
v = tri(i, 397 + 113 * c, top // 3) + n * (c + 1)
elif kind == "wide":
# Full scale and opposite: left - right needs 33 bits
v = top - (n & 255) if c == 0 else -top + (n & 255)
if i % 7 == 0:
v = -v
else:
v = tri(i, 211 + 37 * c, top // 2) + n
v = max(-top - 1, min(top, v))
return v
def pcm(kind, bps, ch):
noise = lcg(bps * 131 + ch)
out = bytearray()
width = (bps + 7) // 8
for i in range(FRAMES):
for c in range(ch):
v = sample(kind, bps, ch, i, c, noise)
out += (v & ((1 << (width * 8)) - 1)).to_bytes(width, "little")
return out
FIXTURES = [
("s16_stereo.flac", "stereo", 16, 2),
("s24_stereo.flac", "stereo", 24, 2),
("s24_mono.flac", "mono", 24, 1),
("s24_6ch.flac", "multi", 24, 6),
("s32_wide.flac", "wide", 32, 2),
]
seen = set()
for name, kind, bps, ch in FIXTURES:
raw = os.path.join(fix, "src.raw")
out = os.path.join(fix, name)
with open(raw, "wb") as f:
f.write(pcm(kind, bps, ch))
subprocess.check_call(["flac", "--silent", "--force", "--force-raw-format",
"--endian=little", "--sign=signed",
"--channels=" + str(ch), "--bps=" + str(bps),
"--sample-rate=48000", "-8", "-b", "1024",
"-o", out, raw])
os.remove(raw)
if ch == 2:
a = subprocess.run(["flac", "--analyze", "--stdout", out],
capture_output=True, text=True).stdout
for line in a.splitlines():
for tag in ("INDEPENDENT", "LEFT_SIDE", "RIGHT_SIDE", "MID_SIDE"):
if "channel_assignment=" + tag in line:
seen.add(tag)
missing = {"INDEPENDENT", "LEFT_SIDE", "RIGHT_SIDE", "MID_SIDE"} - seen
if missing:
raise SystemExit("channel assignments not made: " + ", ".join(sorted(missing)))
print("assignments:", ", ".join(sorted(seen)))
@@ -0,0 +1,157 @@
/* rflac's 16-bit output, against the PCM each fixture was encoded from:
*
* - a stream of 16 bits comes out exactly as it went in;
* - one of 24 or 32 bits comes out rounded to the nearest 16-bit step
* (halves up) and clamped at the top - not cut down, which left every
* sample half a step low on average;
* - both, through every way a stereo frame's channels are coded
* (independent, left/side, side/right, mid/side, which the stereo
* fixtures hold each of), a 32-bit stream's 33-bit side channel, mono
* and six channels, and the SIMD loops and their scalar tails (the
* fixtures are 5003 frames).
*
* Built twice: as the target builds (SSE2 on x86-64, NEON on ARM) and with
* RFLAC_NO_SIMD, the scalar loops. The PCM is rebuilt here exactly as
* mkfixtures.py builds it, with integers only. */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <formats/rflac.h>
#include <streams/file_stream.h>
#define FRAMES 5003
static unsigned failures;
#define CHECK(cond, ...) do { if (!(cond)) { failures++; \
fprintf(stderr, "FAIL %s:%d: ", __FILE__, __LINE__); \
fprintf(stderr, __VA_ARGS__); fputc('\n', stderr); } } while (0)
static int64_t tri(int64_t i, int64_t period, int64_t amp)
{
int64_t p = i % period;
int64_t half = period / 2;
int64_t v = p < half ? p : period - p;
return (v * 2 * amp) / half - amp;
}
/* mkfixtures.py's sample(), step for step */
static int64_t sample(const char *kind, unsigned bps, unsigned c, int64_t i,
uint32_t *seed)
{
int64_t top = ((int64_t)1 << (bps - 1)) - 1;
int64_t n, v;
*seed = *seed * 1103515245u + 12345u;
n = (int64_t)(*seed >> 20);
if (!strcmp(kind, "stereo"))
{
int64_t base = tri(i, 397, top / 2);
int64_t seg = i / 1024;
if (seg == 0)
v = base + (c ? n : -n);
else if (seg == 1)
v = c == 0 ? base : base + n;
else if (seg == 2)
v = c == 0 ? base + n : base;
else
v = tri(i, 397 + 113 * c, top / 3) + n * (c + 1);
}
else if (!strcmp(kind, "wide"))
{
v = c == 0 ? top - (n & 255) : -top + (n & 255);
if (i % 7 == 0)
v = -v;
}
else
v = tri(i, 211 + 37 * c, top / 2) + n;
if (v > top)
v = top;
if (v < -top - 1)
v = -top - 1;
return v;
}
/* The 16-bit value a sample of @bps bits should give: its value at 16
* bits, rounded to the nearest step, halves up, the top clamped */
static int16_t expect(int64_t v, unsigned bps)
{
int64_t r;
if (bps <= 16)
return (int16_t)(v * ((int64_t)1 << (16 - bps)));
r = (v + ((int64_t)1 << (bps - 17))) >> (bps - 16);
return (int16_t)(r > 32767 ? 32767 : r);
}
static void run(const char *name, const char *kind, unsigned bps, unsigned ch)
{
char path[256];
void *buf = NULL;
int64_t len = 0;
rflac_t *f = rflac_new();
int16_t *out = (int16_t*)calloc((size_t)(FRAMES + 64) * ch, sizeof(int16_t));
size_t got = 0, i;
uint32_t seed = bps * 131 + ch;
unsigned bad = 0, idle = 0;
long sum_err = 0;
int result;
snprintf(path, sizeof(path), "fixtures/%s", name);
if (!filestream_read_file(path, &buf, &len) || len <= 0)
{
fprintf(stderr, "cannot read %s (run from the sample directory)\n", path);
exit(2);
}
CHECK(f != NULL, "%s: rflac_new", name);
rflac_set_in(f, (const uint8_t*)buf, (size_t)len);
rflac_set_eof(f);
do
{
size_t wrote = 0;
rflac_set_out_s16(f, out + got * ch, FRAMES + 64 - got);
result = rflac_process(f, NULL, &wrote);
got += wrote;
idle = wrote ? 0 : idle + 1;
} while (result == RFLAC_PROCESS_NEXT && idle < 4);
CHECK(got == FRAMES, "%s: %lu frames, %d wanted", name, (unsigned long)got,
FRAMES);
for (i = 0; i < got * ch && i < (size_t)FRAMES * ch; i++)
{
int64_t v = sample(kind, bps, (unsigned)(i % ch), (int64_t)(i / ch), &seed);
int16_t want = expect(v, bps);
if (out[i] != want)
{
if (!bad)
fprintf(stderr, " %s: sample %lu is %d, %d wanted (from %lld)\n",
name, (unsigned long)i, out[i], want, (long long)v);
bad++;
}
sum_err += out[i] - want;
}
CHECK(bad == 0, "%s: %u of %lu samples off", name, bad,
(unsigned long)(got * ch));
(void)sum_err;
rflac_free(f);
free(out);
free(buf);
}
int main(void)
{
run("s16_stereo.flac", "stereo", 16, 2);
run("s24_stereo.flac", "stereo", 24, 2);
run("s24_mono.flac", "mono", 24, 1);
run("s24_6ch.flac", "multi", 24, 6);
run("s32_wide.flac", "wide", 32, 2);
if (failures)
{
fprintf(stderr, "%u failure(s)\n", failures);
return 1;
}
printf("[pass] rflac_s16_test\n");
return 0;
}
@@ -0,0 +1,24 @@
# queues/retro_deque.h: a double-ended queue of one type in one block.
# Built as C89, pedantic, as the header promises.
TARGET := retro_deque_test
CORE_DIR := ../../..
SOURCES := retro_deque_test.c
CFLAGS += -Wall -Wextra -std=c89 -pedantic -Wdeclaration-after-statement -g1 -O2 -I$(CORE_DIR)/include
OBJS := $(SOURCES:.c=.o)
ifneq ($(SANITIZER),)
CFLAGS := -fsanitize=$(SANITIZER) -fno-omit-frame-pointer $(CFLAGS)
LDFLAGS := -fsanitize=$(SANITIZER) $(LDFLAGS)
endif
all: $(TARGET)
%.o: %.c
$(CC) -c -o $@ $< $(CFLAGS)
$(TARGET): $(OBJS)
$(CC) -o $@ $^ $(LDFLAGS)
check: $(TARGET)
./$(TARGET)
clean:
rm -f $(TARGET) $(OBJS)
.PHONY: all check clean
@@ -0,0 +1,309 @@
/* queues/retro_deque.h against a model that cannot be wrong (an array,
* shifted for every change):
*
* - two million pushes, pops, inserts and erases at either end and in the
* middle, the queue the same as the model after each, for a word-sized
* element and for a struct;
* - a queue holds as many as it was made for, refuses the next and is
* left as it was; reserve makes room and keeps the order;
* - none of it allocates after init, but reserve;
* - a queue marched along (pushed at one end, popped at the other) for a
* million elements never leaves its block. */
static unsigned long allocations;
#define RETRO_DEQUE_MALLOC(bytes) (allocations++, malloc(bytes))
#include <queues/retro_deque.h>
#include <stdio.h>
#include <string.h>
struct deadline
{
unsigned long when;
void *who;
unsigned char tag[5];
};
RETRO_DEQUE_DEFINE(words, unsigned long);
RETRO_DEQUE_DEFINE(deadlines, struct deadline);
RETRO_DEQUE_DEFINE(pointers, struct deadline*);
static int failures;
#define CHECK(cond) do { if (!(cond)) { fprintf(stderr, "%s:%d: check failed: %s\n", __FILE__, __LINE__, #cond); failures++; } } while (0)
/* 32 bits of it, wherever unsigned long is longer */
static unsigned long rng_state = 2463534242UL;
static unsigned long rng(void)
{
rng_state ^= (rng_state << 13) & 0xffffffffUL;
rng_state ^= rng_state >> 17;
rng_state ^= (rng_state << 5) & 0xffffffffUL;
return rng_state;
}
#define MODEL_MAX 64
static void against_model(void)
{
words_t q;
unsigned long model[MODEL_MAX];
size_t n = 0, i;
unsigned long step, wrong = 0, refused = 0, before;
CHECK(words_init(&q, MODEL_MAX));
CHECK(words_limit(&q) == MODEL_MAX);
before = allocations;
for (step = 0; step < 2000000; step++)
{
unsigned long r = rng();
unsigned long v = r ^ step;
size_t at = n ? (size_t)((r >> 12) % (n + 1)) : 0;
switch (r % 8)
{
case 0:
case 1:
if (words_push_back(&q, &v))
{
if (n == MODEL_MAX) wrong++;
else model[n++] = v;
}
else
{
refused++;
if (n != MODEL_MAX) wrong++;
}
break;
case 2:
if (words_push_front(&q, &v))
{
if (n == MODEL_MAX) wrong++;
else
{
memmove(model + 1, model, n * sizeof(model[0]));
model[0] = v;
n++;
}
}
else if (n != MODEL_MAX) wrong++;
break;
case 3:
if (n)
{
if (*words_front(&q) != model[0]) wrong++;
words_pop_front(&q);
memmove(model, model + 1, (n - 1) * sizeof(model[0]));
n--;
}
break;
case 4:
if (n)
{
if (*words_back(&q) != model[n - 1]) wrong++;
words_pop_back(&q);
n--;
}
break;
case 5:
if (words_insert(&q, at, &v))
{
if (n == MODEL_MAX) wrong++;
else
{
memmove(model + at + 1, model + at, (n - at) * sizeof(model[0]));
model[at] = v;
n++;
}
}
else if (n != MODEL_MAX) wrong++;
break;
case 6:
if (n)
{
size_t e = at % n;
words_erase(&q, e);
memmove(model + e, model + e + 1, (n - e - 1) * sizeof(model[0]));
n--;
}
break;
default:
if ((r >> 16) % 4096 == 0)
{
words_clear(&q);
n = 0;
}
break;
}
if (words_count(&q) != n || words_empty(&q) != (n == 0) || words_full(&q) != (n == MODEL_MAX))
wrong++;
for (i = 0; i < n; i++)
if (*words_at(&q, i) != model[i])
{
wrong++;
break;
}
}
CHECK(wrong == 0);
CHECK(refused > 0);
CHECK(allocations == before);
printf("retro_deque: 2000000 changes against the model, %lu wrong, %lu refused while full, %lu allocations\n",
wrong, refused, allocations - before);
words_free(&q);
words_free(&q);
}
int main(void)
{
against_model();
/* A struct; a full queue left as it was; reserve */
{
deadlines_t q;
struct deadline d, extra;
size_t i;
unsigned long before;
memset(&d, 0, sizeof(d));
CHECK(deadlines_init(&q, 4));
for (i = 0; i < 4; i++)
{
d.when = 100 + i;
d.who = &q;
d.tag[4] = (unsigned char)i;
CHECK(deadlines_push_back(&q, &d));
}
extra = d;
extra.when = 999;
CHECK(!deadlines_push_back(&q, &extra));
CHECK(!deadlines_push_front(&q, &extra));
CHECK(!deadlines_insert(&q, 2, &extra));
CHECK(deadlines_count(&q) == 4);
for (i = 0; i < 4; i++)
CHECK(deadlines_at(&q, i)->when == 100 + i && deadlines_at(&q, i)->tag[4] == i);
/* round the ring first, so that reserve has a wrapped queue to move */
deadlines_pop_front(&q);
d.when = 104;
CHECK(deadlines_push_back(&q, &d));
before = allocations;
CHECK(deadlines_reserve(&q, 4)); /* has it already */
CHECK(deadlines_reserve(&q, 2)); /* never fewer */
CHECK(allocations == before && deadlines_limit(&q) == 4);
CHECK(deadlines_reserve(&q, 9));
CHECK(allocations == before + 1);
CHECK(deadlines_limit(&q) == 9 && deadlines_count(&q) == 4);
for (i = 0; i < 4; i++)
CHECK(deadlines_at(&q, i)->when == 101 + i);
CHECK(deadlines_insert(&q, 4, &extra)); /* at the end */
CHECK(deadlines_back(&q)->when == 999);
CHECK(!deadlines_insert(&q, 7, &extra)); /* past the end */
/* It holds nine, in a block of sixteen: the tenth is refused, and
* a reserve within the block takes no memory */
while (deadlines_push_back(&q, &extra)) {}
CHECK(deadlines_count(&q) == 9);
before = allocations;
CHECK(deadlines_reserve(&q, 16) && allocations == before);
CHECK(deadlines_push_back(&q, &extra) && deadlines_count(&q) == 10);
deadlines_free(&q);
CHECK(deadlines_limit(&q) == 0 && deadlines_full(&q));
CHECK(!deadlines_push_back(&q, &extra)); /* freed: holds nothing */
}
/* Pointers as elements: what is pushed is the pointer, through a
* pointer to it that may be to a const one */
{
pointers_t q;
struct deadline a, b;
struct deadline *pa = &a;
struct deadline *const pb = &b;
CHECK(pointers_init(&q, 3));
CHECK(pointers_push_back(&q, &pa));
CHECK(pointers_push_back(&q, &pb));
CHECK(pointers_push_front(&q, &pb));
CHECK(!pointers_insert(&q, 1, &pa) || pointers_count(&q) == 4);
CHECK(*pointers_front(&q) == &b && *pointers_at(&q, 1) == &a && *pointers_back(&q) == &b);
(*pointers_at(&q, 1))->when = 5; /* the element is not const */
CHECK(a.when == 5);
pointers_erase(&q, 0);
CHECK(*pointers_front(&q) == &a);
pointers_free(&q);
}
/* A queue with no limit of its own: it grows when it has to, in order,
* and only then; an empty one that was never made grows too */
{
words_t q;
unsigned long i, before, wrong = 0;
CHECK(words_init(&q, 4));
before = allocations;
for (i = 0; i < 4; i++)
CHECK(words_push_back_growing(&q, &i));
CHECK(allocations == before);
words_pop_front(&q); /* wrapped when it grows */
for (i = 4; i < 100; i++)
CHECK(words_push_back_growing(&q, &i));
CHECK(allocations == before + 5); /* 8, 16, 32, 64, 128 */
CHECK(words_count(&q) == 99 && words_limit(&q) == 128);
for (i = 0; i < 99; i++)
wrong += *words_at(&q, i) != i + 1;
CHECK(wrong == 0);
words_free(&q);
i = 7;
CHECK(words_push_back_growing(&q, &i) && *words_front(&q) == 7 && words_limit(&q) == 8);
words_free(&q);
}
/* The same from the front: newest first, grown in order */
{
words_t q;
unsigned long i, before, wrong = 0;
CHECK(words_init(&q, 4));
before = allocations;
for (i = 0; i < 100; i++)
CHECK(words_push_front_growing(&q, &i));
CHECK(allocations == before + 5);
CHECK(words_count(&q) == 100 && words_limit(&q) == 128);
for (i = 0; i < 100; i++)
wrong += *words_at(&q, i) != 99 - i;
CHECK(wrong == 0);
words_free(&q);
i = 3;
CHECK(words_push_front_growing(&q, &i) && *words_back(&q) == 3 && words_limit(&q) == 8);
words_free(&q);
}
/* Marched along: a few elements, pushed at one end and popped at the
* other, a million times. One block, the one it was given. */
{
words_t q;
unsigned long i, before, wrong = 0;
unsigned long *block;
CHECK(words_init(&q, 16));
block = q.data;
before = allocations;
for (i = 0; i < 5; i++)
words_push_back(&q, &i);
for (i = 5; i < 1000005; i++)
{
if (*words_front(&q) != i - 5)
wrong++;
words_pop_front(&q);
if (!words_push_back(&q, &i))
wrong++;
}
CHECK(wrong == 0);
CHECK(q.data == block && allocations == before);
words_free(&q);
}
printf("retro_deque: %s\n", failures ? "FAIL" : "ok");
return failures ? 1 : 0;
}
+194
View File
@@ -0,0 +1,194 @@
/* Copyright (C) 2010-2026 The RetroArch team
*
* ---------------------------------------------------------------------------------------
* The following license statement only applies to this file (cdfs_file.c).
* ---------------------------------------------------------------------------------------
*
* Permission is hereby granted, free of charge,
* to any person obtaining a copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
* and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
/* The path side of cdfs: formats/cdfs/cdfs.c reads only through the
* stream a track holds, and this resolves the filename, opens cue, bin,
* iso and chd files and hands the stream in. */
#include <stdlib.h>
#include <string.h>
#include <retro_miscellaneous.h>
#include <compat/strl.h>
#include <file/file_path.h>
#include <string/stdstring.h>
#include <streams/interface_stream.h>
#ifdef HAVE_CHD
#include <streams/chd_stream.h>
#endif
#include <streams/cdfs_file.h>
static cdfs_track_t* cdfs_open_cue_track(
const char* path, unsigned int track_index)
{
cdfs_cue_info_t info;
char track_path[PATH_MAX_LENGTH];
int64_t _len;
bool parsed = false;
char *cue_contents = NULL;
cdfs_track_t *track = NULL;
intfstream_t *cue_stream = intfstream_open_file(path,
RETRO_VFS_FILE_ACCESS_READ, RETRO_VFS_FILE_ACCESS_HINT_NONE);
if (!cue_stream)
return NULL;
/* A cue sheet is a small text file: read it with one bulk read and
* release the handle before the (potentially slow) bin open below,
* instead of interleaving parsing with per-line stream reads. */
_len = intfstream_get_size(cue_stream);
if (_len > 0 && (cue_contents = (char*)malloc((size_t)_len)))
parsed = (intfstream_read(cue_stream, cue_contents, _len) == _len);
intfstream_close(cue_stream);
free(cue_stream);
if (parsed)
parsed = cdfs_parse_cue(cue_contents, (size_t)_len,
track_index, &info);
if (cue_contents)
free(cue_contents);
if (!parsed)
return NULL;
/* Resolve the FILE name from the cue against the cue's own
* directory unless it already carries a path. */
if ( strchr(info.bin_name, '/')
|| strchr(info.bin_name, '\\'))
strlcpy(track_path, info.bin_name, sizeof(track_path));
else
{
fill_pathname_basedir(track_path, path, sizeof(track_path));
strlcat(track_path, info.bin_name, sizeof(track_path));
}
track = cdfs_track_from_stream(intfstream_open_file(
track_path, RETRO_VFS_FILE_ACCESS_READ,
RETRO_VFS_FILE_ACCESS_HINT_NONE),
info.first_sector_offset, info.first_sector_index);
if (track && track->stream_sector_size == 0)
{
track->stream_sector_size = info.sector_size;
if (info.sector_size == 2352)
track->stream_sector_header_size = 16;
else if (info.sector_size == 2336)
track->stream_sector_header_size = 8;
}
return track;
}
#ifdef HAVE_CHD
static cdfs_track_t* cdfs_open_chd_track(const char* path, int32_t track_index)
{
cdfs_track_t *track;
intfstream_t *intf_stream = intfstream_open_chd_track(path,
RETRO_VFS_FILE_ACCESS_READ, RETRO_VFS_FILE_ACCESS_HINT_NONE,
track_index);
if (!intf_stream)
return NULL;
track = cdfs_track_from_stream(intf_stream,
intfstream_get_offset_to_start(intf_stream),
intfstream_get_first_sector(intf_stream));
if (track && track->stream_sector_header_size == 0)
{
track->stream_sector_size = intfstream_get_frame_size(intf_stream);
if (track->stream_sector_size == 2352)
track->stream_sector_header_size = 16;
else if (track->stream_sector_size == 2336)
track->stream_sector_header_size = 8;
}
return track;
}
#endif
struct cdfs_track_t* cdfs_open_track(const char* path,
unsigned int track_index)
{
const char* ext = path_get_extension(path);
if (string_is_equal_noncase(ext, "cue"))
return cdfs_open_cue_track(path, track_index);
#ifdef HAVE_CHD
if (string_is_equal_noncase(ext, "chd"))
return cdfs_open_chd_track(path, track_index);
#endif
/* if opening track 1, try opening as a raw track */
if (track_index == 1)
return cdfs_open_raw_track(path);
/* unsupported file type */
return NULL;
}
struct cdfs_track_t* cdfs_open_data_track(const char* path)
{
const char* ext = path_get_extension(path);
if (string_is_equal_noncase(ext, "cue"))
return cdfs_open_cue_track(path, 0);
#ifdef HAVE_CHD
if (string_is_equal_noncase(ext, "chd"))
return cdfs_open_chd_track(path, CHDSTREAM_TRACK_PRIMARY);
#endif
/* unsupported file type - try opening as a raw track */
return cdfs_open_raw_track(path);
}
cdfs_track_t* cdfs_open_raw_track(const char* path)
{
const char *ext = path_get_extension(path);
cdfs_track_t *track = NULL;
if ( string_is_equal_noncase(ext, "bin")
|| string_is_equal_noncase(ext, "iso"))
{
intfstream_t* file = intfstream_open_file(path,
RETRO_VFS_FILE_ACCESS_READ, RETRO_VFS_FILE_ACCESS_HINT_NONE);
track = cdfs_track_from_stream(file, 0, 0);
if (track && track->stream_sector_size == 0)
{
cdfs_track_sector_size_from_length(track);
if (track->stream_sector_size == 0)
{
cdfs_close_track(track);
track = NULL;
}
}
}
return track;
}
@@ -0,0 +1,107 @@
/* Copyright (C) 2010-2026 The RetroArch team
*
* ---------------------------------------------------------------------------------------
* The following license statement only applies to this file (data_transfer_file.c).
* ---------------------------------------------------------------------------------------
*
* Permission is hereby granted, free of charge,
* to any person obtaining a copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
* and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
/* The file side of data_transfer: formats/data_transfer.c reads only
* through its reader callback, and this opens the file, supplies the
* reader and closes the file with the transfer. */
#include <stdint.h>
#include <streams/file_stream.h>
#include <streams/data_transfer_file.h>
/* @bytes at @offset: one read at the offset, or a seek and a read
* where the file has none */
static int64_t data_transfer_file_read_at(void *ud, uint64_t offset,
uint8_t *dst, size_t bytes)
{
RFILE *f = (RFILE*)ud;
int64_t got;
if (offset > (uint64_t)INT64_MAX)
return -1;
if ((got = filestream_read_at(f, dst, (int64_t)bytes,
(int64_t)offset)) >= 0)
return got;
if (filestream_seek(f, (int64_t)offset,
RETRO_VFS_SEEK_POSITION_START) < 0)
return -1;
return filestream_read(f, dst, (int64_t)bytes);
}
static void data_transfer_file_release(void *ud)
{
filestream_close((RFILE*)ud);
}
/* The file at @path and its length, or NULL */
static RFILE *data_transfer_file_open(const char *path, size_t *len)
{
int64_t l;
RFILE *f;
if (!path || !(f = filestream_open(path, RETRO_VFS_FILE_ACCESS_READ,
RETRO_VFS_FILE_ACCESS_HINT_NONE)))
return NULL;
l = filestream_get_size(f);
if (l <= 0 || (uint64_t)l > SIZE_MAX)
{
filestream_close(f);
return NULL;
}
*len = (size_t)l;
return f;
}
data_transfer_t *data_transfer_open_prefix(const char *path,
size_t commit_cap)
{
data_transfer_t *dt;
size_t len = 0;
RFILE *f = data_transfer_file_open(path, &len);
if (!f)
return NULL;
if (!(dt = data_transfer_open_prefix_reader(len, commit_cap,
data_transfer_file_read_at, f)))
{
filestream_close(f);
return NULL;
}
data_transfer_set_release(dt, data_transfer_file_release);
return dt;
}
data_transfer_t *data_transfer_open_window(const char *path, size_t keep)
{
data_transfer_t *dt;
size_t len = 0;
RFILE *f = data_transfer_file_open(path, &len);
if (!f)
return NULL;
if (!(dt = data_transfer_open_window_reader(len, keep,
data_transfer_file_read_at, f)))
{
filestream_close(f);
return NULL;
}
data_transfer_set_release(dt, data_transfer_file_release);
return dt;
}
@@ -0,0 +1,98 @@
/* Copyright (C) 2010-2026 The RetroArch team
*
* ---------------------------------------------------------------------------------------
* The following license statement only applies to this file (image_texture_file.c).
* ---------------------------------------------------------------------------------------
*
* Permission is hereby granted, free of charge,
* to any person obtaining a copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
* and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
/* The path side of image loading: formats/image_texture.c decodes the
* bytes it is given, and this reads them from the file. */
#include <stdlib.h>
#include <string.h>
#include <formats/image.h>
#include <formats/data_transfer.h>
#include <streams/data_transfer_file.h>
#include <streams/image_texture_file.h>
bool image_texture_load_request(struct texture_image *out_img,
const char *path, const image_texture_request_t *req,
bool (*should_abort)(void *ud), void *ud)
{
return image_texture_load_request_ex(out_img, path, req,
should_abort, ud, NULL);
}
bool image_texture_load_request_ex(struct texture_image *out_img,
const char *path, const image_texture_request_t *req,
bool (*should_abort)(void *ud), void *ud, int *png_probe)
{
enum image_type_enum type = image_texture_get_type(path);
if (png_probe)
*png_probe = -1;
if (type != IMAGE_TYPE_NONE)
{
/* A zero budget fills to completion in one blocking call, which
* is this API's contract */
struct data_transfer *dt = data_transfer_open_prefix(path, 0);
if (dt)
{
size_t file_len = 0;
const uint8_t *ptr = NULL;
bool kept = false;
bool loaded;
data_transfer_iterate(dt, 0);
ptr = data_transfer_ptr(dt, &file_len);
loaded = data_transfer_complete(dt) && ptr && file_len
&& image_texture_load_buffer_probe(dt, &kept, out_img, type,
ptr, file_len, req, should_abort, ud, png_probe);
if (!kept)
data_transfer_free(dt);
if (loaded)
return true;
}
}
/* nothing to decode leaves the image empty */
if (out_img)
image_texture_load_buffer_probe(NULL, NULL, out_img, type, NULL, 0,
req, NULL, NULL, NULL);
return false;
}
bool image_texture_load_ex(struct texture_image *out_img,
const char *path, bool (*should_abort)(void *ud), void *ud)
{
image_texture_request_t req;
req.rgba = out_img->supports_rgba;
req.want_10bit = out_img->pix10;
req.want_fp16 = false;
req.want_compressed = true;
return image_texture_load_request(out_img, path, &req,
should_abort, ud);
}
bool image_texture_load(struct texture_image *out_img, const char *path)
{
return image_texture_load_ex(out_img, path, NULL, NULL);
}
@@ -1,7 +1,7 @@
/* Copyright (C) 2026 The RetroArch team. SPDX-License-Identifier: MIT */
#include <string.h>
#include <retro_atomic.h>
#include <formats/rdvd_stream.h>
#include <streams/rdvd_stream.h>
bool rdvd_stream_init(rdvd_stream_t *stream, rdvd_source_t *source,
rdvd_vfs_t *vfs, tpool_t *pool, rmpeg1_ps_t *ps, uint8_t *buffer, size_t capacity)
{
@@ -2,7 +2,7 @@
#include <stdlib.h>
#include <string.h>
#include <retro_atomic.h>
#include <formats/rdvd_vfs.h>
#include <streams/rdvd_vfs.h>
#if defined(HAVE_THREADS) && defined(RETRO_ATOMIC_HAS_PTR)
#include <retro_spsc.h>
#define RDVD_VFS_ENABLED
@@ -11,7 +11,8 @@
*
* cc -DHAVE_CHD -DHAVE_RCHD -DHAVE_RCHD_DEFLATE -DHAVE_RCHD_LZMA \
* -DHAVE_RCHD_FLAC -DHAVE_RCHD_ZSTD -I libretro-common/include \
* -o cdfs_rchd <this> libretro-common/formats/cdfs/cdfs.c ...
* -o cdfs_rchd <this> libretro-common/formats/cdfs/cdfs.c \
* libretro-common/streams/cdfs_file.c ...
*/
/* Reads SYSTEM.CNF, follows it to the boot executable, and digests
* that -- megabytes through cdfs rather than a 68-byte stub. Also
@@ -21,6 +22,7 @@
#include <stdlib.h>
#include <string.h>
#include <formats/cdfs.h>
#include <streams/cdfs_file.h>
static unsigned long long h64(const uint8_t *p, size_t n, unsigned long long h)
{ size_t i; for (i = 0; i < n; i++) h = (h ^ p[i]) * 1099511628211ULL; return h; }
int main(int argc, char **argv)
@@ -17,6 +17,7 @@
#include <stdlib.h>
#include <string.h>
#include <formats/cdfs.h>
#include <streams/cdfs_file.h>
static unsigned long long h64(const uint8_t *p, size_t n, unsigned long long h)
{ size_t i; for (i = 0; i < n; i++) h = (h ^ p[i]) * 1099511628211ULL; return h; }
int main(int argc, char **argv)
@@ -19,6 +19,7 @@
#include <stdlib.h>
#include <string.h>
#include <formats/cdfs.h>
#include <streams/cdfs_file.h>
#include <streams/chd_stream.h>
#include <rc_hash.h>
+605 -106
View File
@@ -338,6 +338,36 @@ static void vfs_unix_to_filetime(int64_t unix_s, FILETIME *ft)
#endif
#define RFILE_HINT_UNBUFFERED (1 << 8)
/* opened both to read and to write ("w+b", "r+b") */
#define RFILE_HINT_UPDATE (1 << 12)
/* A stream on Windows NT opens a handle of its own rather than a C
* runtime FILE, keeps its own position and a 64 KiB buffer, and reads
* and writes at offsets - with no CRT lock or buffer on the way, and its
* reads and writes at offsets on the same handle. The buffer holds what
* was last read or a run of writes not yet in the file, never both.
* VFS_WIN32_NO_NATIVE keeps the C runtime;
* samples/file/vfs/vfs_native_bench.c times the two. */
#if !defined(VFS_WIN32_NO_NATIVE) && defined(_WIN32) && !defined(_XBOX) \
&& !defined(__WINRT__) && defined(VFS_HAVE_AT_HANDLE)
#define VFS_NATIVE 1
#define RFILE_HINT_WIN32_NATIVE (1 << 9)
#define RFILE_HINT_NATIVE_WRITE (1 << 10) /* opened to write */
#define RFILE_HINT_NATIVE_READ (1 << 11) /* opened to read */
#ifndef INVALID_FILE_SIZE
#define INVALID_FILE_SIZE ((DWORD)0xFFFFFFFF)
#endif
#define VFS_NATIVE_BUF 0x10000
static bool vfs_native_open(libretro_vfs_implementation_file *stream,
const char *path, unsigned mode);
static int vfs_native_flush(libretro_vfs_implementation_file *stream);
static int64_t vfs_native_write(libretro_vfs_implementation_file *stream,
const void *s, uint64_t len);
static int64_t vfs_native_seek(libretro_vfs_implementation_file *stream,
int64_t offset, int whence);
static int64_t vfs_native_read(libretro_vfs_implementation_file *stream,
void *s, uint64_t len);
#endif
/* Largest count handed to one fread/fwrite/read/write call. bionic's
* stdio routes counts through an int-typed callback (aborting via
@@ -530,6 +560,21 @@ static int64_t retro_vfs_fp_tell64(FILE *fp)
#endif
}
/* Before acting on a stdio stream's descriptor: the writes stdio holds
* into the file, the stream's position returned for the caller to seek
* back to after, which drops what was read ahead. ftell counts that
* read-ahead on every C runtime; fflush writes what is held, though on
* msvcrt it drops the read-ahead without seeking back, and glibc's fseek
* to where the stream already is can keep a buffer it then serves reads
* from - so neither alone. -1 when the stream cannot. */
static int64_t retro_vfs_fp_settle(FILE *fp)
{
int64_t pos = retro_vfs_fp_tell64(fp);
if (pos < 0 || fflush(fp) != 0)
return -1;
return pos;
}
static int64_t retro_vfs_fd_seek64(int fd, int64_t offset, int whence)
{
#if defined(_WIN32) && (!defined(WINAPI_FAMILY) || defined(_CRT_USE_WINAPI_FAMILY_DESKTOP_APP))
@@ -650,6 +695,10 @@ int64_t retro_vfs_file_seek_internal(
if (!stream)
return -1;
#ifdef VFS_NATIVE
if (stream->hints & RFILE_HINT_WIN32_NATIVE)
return vfs_native_seek(stream, offset, whence);
#endif
if ((stream->hints & RFILE_HINT_UNBUFFERED) == 0)
{
#ifdef HAVE_CDROM
@@ -664,7 +713,18 @@ int64_t retro_vfs_file_seek_internal(
if (stream->scheme == VFS_SCHEME_NFS)
return retro_vfs_file_seek_nfs(stream, offset, whence);
#endif
return retro_vfs_fp_seek64(stream->fp, offset, whence);
if (retro_vfs_fp_seek64(stream->fp, offset, whence) != 0)
{
stream->stdio_pos = -1;
return -1;
}
if (whence == SEEK_SET)
stream->stdio_pos = offset;
else if (whence == SEEK_CUR && stream->stdio_pos >= 0)
stream->stdio_pos += offset;
else
stream->stdio_pos = -1;
return 0;
}
#ifdef VFS_HAVE_FILE_MAPPING
/* Need to check stream->mapped because this function is
@@ -740,6 +800,291 @@ int64_t retro_vfs_file_seek_internal(
#define VFS_POSITIONAL_WIN32 1
#endif
#ifdef VFS_POSITIONAL_WIN32
/* @len bytes read into, or written from, @s at @offset of @h: with @ev
* NULL a synchronous handle, its file pointer left to whatever ReadFile
* makes of it; else an overlapped one, a call that goes pending waited
* out on @ev. The bytes moved, short at the end of the file, or -1
* when none could be. */
static int64_t vfs_win32_io(HANDLE h, HANDLE ev, void *s, uint64_t len,
uint64_t offset, bool write)
{
uint8_t *p = (uint8_t*)s;
uint64_t total = 0;
while (len != 0)
{
OVERLAPPED ov;
DWORD _did = 0;
DWORD _chunk = (len > 0x40000000u) ? 0x40000000u : (DWORD)len;
uint64_t where = offset + total;
BOOL ok;
ov.Internal = 0;
ov.InternalHigh = 0;
ov.Offset = (DWORD)(where & 0xffffffffu);
ov.OffsetHigh = (DWORD)(where >> 32);
ov.hEvent = ev;
ok = write ? WriteFile(h, p, _chunk, &_did, &ov)
: ReadFile(h, p, _chunk, &_did, &ov);
if (!ok && ev && GetLastError() == ERROR_IO_PENDING)
ok = GetOverlappedResult(h, &ov, &_did, TRUE);
if (!ok)
{
if (!write && GetLastError() == ERROR_HANDLE_EOF)
break;
return (total != 0) ? (int64_t)total : -1;
}
if (_did == 0)
{
if (write)
return (total != 0) ? (int64_t)total : -1;
break;
}
total += _did;
p += _did;
len -= _did;
}
return (int64_t)total;
}
#endif
#ifdef VFS_NATIVE
/* Write streams as the C runtime opens them - "wb" and "w+b" create or
* truncate, "r+b" opens what is there - shared for reading and writing
* as it shares them; a read-only one also lets the file be renamed or
* deleted under it. Opened to read as well as write, so reads at offsets
* go through the handle itself. */
static bool vfs_native_open(libretro_vfs_implementation_file *stream,
const char *path, unsigned mode)
{
wchar_t *wide;
HANDLE h;
DWORD hi = 0, lo;
char *buf;
bool write = mode != RETRO_VFS_FILE_ACCESS_READ;
DWORD access = write ? (GENERIC_READ | GENERIC_WRITE) : GENERIC_READ;
DWORD share = write ? (FILE_SHARE_READ | FILE_SHARE_WRITE)
: (FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE);
DWORD create = ((mode & RETRO_VFS_FILE_ACCESS_UPDATE_EXISTING)
|| !write) ? OPEN_EXISTING : CREATE_ALWAYS;
/* NT only: 9x's ReadFile takes no offset on a file */
if ((GetVersion() & 0x80000000u) || !(wide = utf8_to_utf16_string_alloc(path)))
return false;
h = CreateFileW(wide, access, share, NULL, create, FILE_ATTRIBUTE_NORMAL,
NULL);
free(wide);
if (h == INVALID_HANDLE_VALUE)
return false;
lo = GetFileSize(h, &hi);
if ( (lo == INVALID_FILE_SIZE && GetLastError() != NO_ERROR)
|| !(buf = (char*)malloc(VFS_NATIVE_BUF)))
{
CloseHandle(h);
return false;
}
stream->fh = h;
stream->buf = buf;
stream->size = (int64_t)(((uint64_t)hi << 32) | lo);
stream->npos = 0;
stream->nbuf_at = 0;
stream->nbuf_len = 0;
stream->nerr = 0;
stream->ndirty = 0;
stream->hints |= RFILE_HINT_WIN32_NATIVE;
/* "wb" alone is the one the C runtime will not read from */
if (mode != RETRO_VFS_FILE_ACCESS_WRITE)
stream->hints |= RFILE_HINT_NATIVE_READ;
if (write)
stream->hints |= RFILE_HINT_NATIVE_WRITE;
return true;
}
/* The run of writes the buffer holds, into the file in one call. The
* buffer then holds those bytes as read; a run that does not all go in
* is dropped, and the stream's error set. */
static int vfs_native_flush(libretro_vfs_implementation_file *stream)
{
int64_t put;
if (!stream->ndirty)
return 0;
stream->ndirty = 0;
if (!stream->nbuf_len)
return 0;
put = vfs_win32_io(stream->fh, NULL, stream->buf, stream->nbuf_len,
stream->nbuf_at, true);
if (put == (int64_t)stream->nbuf_len)
return 0;
stream->nbuf_len = 0;
stream->nerr = 1;
return -1;
}
/* Into the buffer where the write carries on from the run it holds,
* else the run goes to the file and a new one starts here; a write as
* large as the buffer goes straight to the file. */
static int64_t vfs_native_write(libretro_vfs_implementation_file *stream,
const void *s, uint64_t len)
{
const uint8_t *p = (const uint8_t*)s;
uint64_t total = 0;
if (!(stream->hints & RFILE_HINT_NATIVE_WRITE))
{
stream->nerr = 1;
return -1;
}
while (len != 0)
{
uint64_t at = stream->npos;
if ( stream->ndirty
&& at == stream->nbuf_at + stream->nbuf_len
&& stream->nbuf_len < VFS_NATIVE_BUF)
{
size_t n = VFS_NATIVE_BUF - stream->nbuf_len;
if ((uint64_t)n > len)
n = (size_t)len;
memcpy(stream->buf + stream->nbuf_len, p, n);
stream->nbuf_len += n;
stream->npos += n;
p += n;
total += n;
len -= n;
continue;
}
if (vfs_native_flush(stream) != 0)
break;
/* what was read from the buffer may be where this lands */
stream->nbuf_len = 0;
if (len >= VFS_NATIVE_BUF)
{
int64_t put = vfs_win32_io(stream->fh, NULL, (void*)p, len, at,
true);
if (put > 0)
{
stream->npos += (uint64_t)put;
total += (uint64_t)put;
len -= (uint64_t)put;
}
break;
}
stream->ndirty = 1;
stream->nbuf_at = at;
}
if ((int64_t)stream->npos > stream->size)
stream->size = (int64_t)stream->npos;
if (len != 0)
{
stream->nerr = 1;
if (total == 0)
return -1;
}
return (int64_t)total;
}
/* As fseek: anywhere from 0, past the end included. The end counts the
* writes the buffer holds. */
static int64_t vfs_native_seek(libretro_vfs_implementation_file *stream,
int64_t offset, int whence)
{
int64_t base;
switch (whence)
{
case SEEK_SET:
base = 0;
break;
case SEEK_CUR:
base = (int64_t)stream->npos;
break;
case SEEK_END:
{
DWORD hi = 0, lo = GetFileSize(stream->fh, &hi);
if (lo == INVALID_FILE_SIZE && GetLastError() != NO_ERROR)
return -1;
base = (int64_t)(((uint64_t)hi << 32) | lo);
if ( stream->ndirty
&& (int64_t)(stream->nbuf_at + stream->nbuf_len) > base)
base = (int64_t)(stream->nbuf_at + stream->nbuf_len);
}
break;
default:
return -1;
}
if ( (offset < 0 && base + offset < 0)
|| (offset > 0 && base > INT64_MAX - offset))
return -1;
stream->npos = (uint64_t)(base + offset);
return 0;
}
/* From the buffer where it holds the position, else a read at the
* position: straight into @s for a request as large as the buffer;
* into the buffer for a smaller one - all of it where the read carries
* on from the buffer's end or starts the file, and only the request,
* rounded up to 4 KiB, after a seek elsewhere, so a reader jumping about
* reads what it asks for and one that then reads on is sequential
* again from the next refill */
static int64_t vfs_native_read(libretro_vfs_implementation_file *stream,
void *s, uint64_t len)
{
uint8_t *p = (uint8_t*)s;
uint64_t total = 0;
if (!(stream->hints & RFILE_HINT_NATIVE_READ))
{
stream->nerr = 1;
return -1;
}
/* what was written goes to the file before anything is read back */
if (stream->ndirty && vfs_native_flush(stream) != 0)
return -1;
while (len != 0)
{
uint64_t at = stream->npos;
int64_t got;
if (at >= stream->nbuf_at && at - stream->nbuf_at < stream->nbuf_len)
{
size_t off = (size_t)(at - stream->nbuf_at);
size_t n = stream->nbuf_len - off;
if ((uint64_t)n > len)
n = (size_t)len;
memcpy(p, stream->buf + off, n);
p += n;
total += n;
len -= n;
stream->npos += n;
continue;
}
if (len >= VFS_NATIVE_BUF)
{
if ((got = vfs_win32_io(stream->fh, NULL, p, len, at, false)) < 0)
break;
stream->npos += (uint64_t)got;
total += (uint64_t)got;
return (int64_t)total;
}
{
uint64_t fill = VFS_NATIVE_BUF;
if ( at != stream->nbuf_at + stream->nbuf_len
&& (at != 0 || stream->nbuf_len))
fill = (len + 4095) & ~(uint64_t)4095;
got = vfs_win32_io(stream->fh, NULL, stream->buf, fill, at, false);
}
if (got <= 0)
{
if (got == 0)
return (int64_t)total;
break;
}
stream->nbuf_at = at;
stream->nbuf_len = (size_t)got;
}
if (len != 0)
{
stream->nerr = 1;
if (total == 0)
return -1;
}
return (int64_t)total;
}
#endif
#ifdef VFS_POSITIONAL_POSIX
/* pread/pwrite at a 64-bit offset, as retro_vfs_fd_seek64 seeks:
* pread64 on 32-bit Android (off_t stays 32 bits there before API 24,
@@ -924,6 +1269,9 @@ libretro_vfs_implementation_file *retro_vfs_file_open_impl(
default:
goto error;
}
if ( mode != RETRO_VFS_FILE_ACCESS_READ
&& mode != RETRO_VFS_FILE_ACCESS_WRITE)
stream->hints |= RFILE_HINT_UPDATE;
if ((stream->hints & RFILE_HINT_UNBUFFERED) == 0)
{
@@ -982,6 +1330,12 @@ libretro_vfs_implementation_file *retro_vfs_file_open_impl(
#endif
default:
#ifdef VFS_NATIVE
if ( !(stream->hints
& RETRO_VFS_FILE_ACCESS_HINT_FREQUENT_ACCESS)
&& vfs_native_open(stream, path, mode))
break;
#endif
if (!(fp = (FILE*)fopen_utf8(path, mode_str)))
{
#if TARGET_OS_IPHONE
@@ -1281,6 +1635,9 @@ libretro_vfs_implementation_file *retro_vfs_file_open_impl(
stream->size > 0 ? (size_t)stream->size : 0);
#ifdef VFS_HAVE_AT_HANDLE
retro_atomic_ptr_init(&stream->at_handle, NULL);
retro_atomic_ptr_init(&stream->at_ovl, NULL);
retro_atomic_int_init(&stream->at_ev_busy, 0);
retro_atomic_int_init(&stream->at_sync_busy, 0);
#endif
return stream;
@@ -1320,12 +1677,31 @@ int retro_vfs_file_close_impl(libretro_vfs_implementation_file *stream)
#ifdef VFS_POSITIONAL_WIN32
{
HANDLE at = (HANDLE)retro_atomic_load_acquire_ptr(&stream->at_handle);
if (at)
HANDLE at = (HANDLE)retro_atomic_load_acquire_ptr(&stream->at_handle);
HANDLE ovl = (HANDLE)retro_atomic_load_acquire_ptr(&stream->at_ovl);
int i;
/* a native stream's at_handle is its own, closed with it */
if (at && at != stream->fh)
CloseHandle(at);
if (ovl)
CloseHandle(ovl);
for (i = 0; i < 8; i++)
if (stream->at_ev[i])
CloseHandle(stream->at_ev[i]);
}
#endif
#ifdef VFS_NATIVE
if (stream->hints & RFILE_HINT_WIN32_NATIVE)
{
if (vfs_native_flush(stream) != 0)
result = -1;
if (!CloseHandle(stream->fh))
result = -1;
stream->fh = NULL;
}
else
#endif
if ((stream->hints & RFILE_HINT_UNBUFFERED) == 0)
{
if (stream->fp)
@@ -1411,6 +1787,10 @@ int retro_vfs_file_error_impl(libretro_vfs_implementation_file *stream)
#ifdef HAVE_NFSCLIENT
if (stream->scheme == VFS_SCHEME_NFS)
return retro_vfs_file_error_nfs(stream);
#endif
#ifdef VFS_NATIVE
if (stream->hints & RFILE_HINT_WIN32_NATIVE)
return stream->nerr;
#endif
if (!stream->fp)
return -1;
@@ -1641,14 +2021,30 @@ int retro_vfs_file_punch_hole_impl(libretro_vfs_implementation_file *stream,
RETRO_FILE_ZERO_DATA_INFORMATION zero_info;
DWORD returned = 0;
HANDLE handle = stream->fh;
int64_t fp_pos = -1;
BOOL ok;
#ifdef VFS_NATIVE
/* the range zeroed in the file, not in the buffer */
if (stream->hints & RFILE_HINT_WIN32_NATIVE)
{
if (vfs_native_flush(stream) != 0)
return -1;
stream->nbuf_len = 0;
}
#endif
if (!handle || handle == INVALID_HANDLE_VALUE)
{
if (!stream->fp)
/* writes stdio still holds go in first, so the range is zero */
if ( !stream->fp
|| (fp_pos = retro_vfs_fp_settle(stream->fp)) < 0)
return -1;
handle = (HANDLE)_get_osfhandle(_fileno(stream->fp));
if (handle == INVALID_HANDLE_VALUE)
{
retro_vfs_fp_seek64(stream->fp, fp_pos, SEEK_SET);
return -1;
}
}
/* The file has to be marked sparse before a zero-data range does
@@ -1662,10 +2058,11 @@ int retro_vfs_file_punch_hole_impl(libretro_vfs_implementation_file *stream,
zero_info.FileOffset.QuadPart = offset;
zero_info.BeyondFinalZero.QuadPart = offset + len;
if (!DeviceIoControl(handle, RA_FSCTL_SET_ZERO_DATA, &zero_info,
sizeof(zero_info), NULL, 0, &returned, NULL))
return -1;
return 0;
ok = DeviceIoControl(handle, RA_FSCTL_SET_ZERO_DATA, &zero_info,
sizeof(zero_info), NULL, 0, &returned, NULL);
if (fp_pos >= 0)
retro_vfs_fp_seek64(stream->fp, fp_pos, SEEK_SET);
return ok ? 0 : -1;
}
/* fallocate() sits behind _GNU_SOURCE in glibc (it is gated on __USE_GNU),
* so whether it is *declared* depends on the consumer's feature-test
@@ -1682,21 +2079,25 @@ int retro_vfs_file_punch_hole_impl(libretro_vfs_implementation_file *stream,
&& (!defined(__UCLIBC__) || (defined(__UCLIBC_LINUX_SPECIFIC__) \
&& defined(__UCLIBC_MAJOR__) && __UCLIBC_MAJOR__ >= 1))
{
int fd = stream->fd;
int fd = stream->fd;
int64_t fp_pos = -1;
int ok;
if (fd < 0)
{
if (!stream->fp)
/* writes stdio holds go in first, so the range is zero */
if ( !stream->fp
|| (fp_pos = retro_vfs_fp_settle(stream->fp)) < 0)
return -1;
fd = fileno(stream->fp);
if (fd < 0)
return -1;
}
if (fallocate(fd, FALLOC_FL_PUNCH_HOLE | FALLOC_FL_KEEP_SIZE,
(off_t)offset, (off_t)len) != 0)
return -1;
return 0;
ok = fd >= 0
&& fallocate(fd, FALLOC_FL_PUNCH_HOLE | FALLOC_FL_KEEP_SIZE,
(off_t)offset, (off_t)len) == 0;
if (fp_pos >= 0)
retro_vfs_fp_seek64(stream->fp, fp_pos, SEEK_SET);
return ok ? 0 : -1;
}
#else
/* No hole punching on this platform. */
@@ -1709,7 +2110,30 @@ int retro_vfs_file_punch_hole_impl(libretro_vfs_implementation_file *stream,
int64_t retro_vfs_file_truncate_impl(libretro_vfs_implementation_file *stream, int64_t len)
{
/* The descriptor itself on the descriptor path, the stream's below */
int fd = -1;
int fd = -1;
int done = 0;
int64_t fp_pos = -1; /* the stdio stream's, to go back to */
#ifdef VFS_NATIVE
/* The handle's end moved to @len, after the writes the buffer holds;
* what it held as read may lie past the new end */
if (stream && (stream->hints & RFILE_HINT_WIN32_NATIVE))
{
LONG hi = (LONG)((uint64_t)len >> 32);
if ( len < 0
|| !(stream->hints & RFILE_HINT_NATIVE_WRITE)
|| vfs_native_flush(stream) != 0)
return -1;
stream->nbuf_len = 0;
SetLastError(NO_ERROR);
if ( (SetFilePointer(stream->fh, (LONG)(DWORD)(len & 0xffffffff), &hi,
FILE_BEGIN) == INVALID_SET_FILE_POINTER
&& GetLastError() != NO_ERROR)
|| !SetEndOfFile(stream->fh))
return -1;
stream->size = len;
return 0;
}
#endif
if (stream)
{
if (stream->hints & RFILE_HINT_UNBUFFERED)
@@ -1721,7 +2145,9 @@ int64_t retro_vfs_file_truncate_impl(libretro_vfs_implementation_file *stream, i
#endif
fd = stream->fd;
}
else if (stream->fp)
/* writes stdio still holds go in first, and are cut with the rest */
else if (stream->fp
&& (fp_pos = retro_vfs_fp_settle(stream->fp)) >= 0)
#ifdef _WIN32
fd = _fileno(stream->fp);
#elif !defined(VITA) && !defined(PSP) && !defined(PS2) && !defined(ORBIS)
@@ -1740,29 +2166,22 @@ int64_t retro_vfs_file_truncate_impl(libretro_vfs_implementation_file *stream, i
* an error for larger lengths rather than silently truncating
* the file. */
#if defined(_MSC_VER) && _MSC_VER >= 1400
if (fd >= 0 && _chsize_s(fd, len) == 0)
{
stream->size = len;
return 0;
}
done = fd >= 0 && _chsize_s(fd, len) == 0;
#else
if (fd >= 0 && len >= 0 && len <= (int64_t)LONG_MAX
&& _chsize(fd, (long)len) == 0)
{
stream->size = len;
return 0;
}
done = fd >= 0 && len >= 0 && len <= (int64_t)LONG_MAX
&& _chsize(fd, (long)len) == 0;
#endif
#elif !defined(VITA) && !defined(PSP) && !defined(PS2) && !defined(ORBIS)
if (fd >= 0 && ftruncate(fd, (off_t)len) == 0)
{
stream->size = len;
return 0;
}
done = fd >= 0 && ftruncate(fd, (off_t)len) == 0;
#else
(void)fd;
#endif
return -1;
if (fp_pos >= 0)
retro_vfs_fp_seek64(stream->fp, fp_pos, SEEK_SET);
if (!done)
return -1;
stream->size = len;
return 0;
}
int64_t retro_vfs_file_tell_impl(libretro_vfs_implementation_file *stream)
@@ -1770,6 +2189,10 @@ int64_t retro_vfs_file_tell_impl(libretro_vfs_implementation_file *stream)
if (!stream)
return -1;
#ifdef VFS_NATIVE
if (stream->hints & RFILE_HINT_WIN32_NATIVE)
return (int64_t)stream->npos;
#endif
if ((stream->hints & RFILE_HINT_UNBUFFERED) == 0)
{
#ifdef HAVE_CDROM
@@ -1784,7 +2207,9 @@ int64_t retro_vfs_file_tell_impl(libretro_vfs_implementation_file *stream)
if (stream->scheme == VFS_SCHEME_NFS)
return retro_vfs_file_tell_nfs(stream);
#endif
return retro_vfs_fp_tell64(stream->fp);
if (stream->stdio_pos < 0)
stream->stdio_pos = retro_vfs_fp_tell64(stream->fp);
return stream->stdio_pos;
}
#ifdef VFS_HAVE_FILE_MAPPING
/* Need to check stream->mapped because this function
@@ -1854,6 +2279,10 @@ int64_t retro_vfs_file_read_impl(libretro_vfs_implementation_file *stream,
if (len > (uint64_t)INT64_MAX)
return -1;
#ifdef VFS_NATIVE
if (stream->hints & RFILE_HINT_WIN32_NATIVE)
return vfs_native_read(stream, s, len);
#endif
if ((stream->hints & RFILE_HINT_UNBUFFERED) == 0)
{
#ifdef HAVE_CDROM
@@ -1884,10 +2313,17 @@ int64_t retro_vfs_file_read_impl(libretro_vfs_implementation_file *stream,
size_t _got = fread(p, 1, _chunk, stream->fp);
total += _got;
if (_got < _chunk)
{
/* past an error, where it stands is stdio's to say */
if (ferror(stream->fp))
stream->stdio_pos = -1;
break;
}
p += _got;
len -= _got;
}
if (stream->stdio_pos >= 0)
stream->stdio_pos += (int64_t)total;
return (int64_t)total;
}
}
@@ -1960,6 +2396,10 @@ int64_t retro_vfs_file_write_impl(libretro_vfs_implementation_file *stream, cons
if (!stream)
return -1;
#ifdef VFS_NATIVE
if (stream->hints & RFILE_HINT_WIN32_NATIVE)
return vfs_native_write(stream, s, len);
#endif
if ((stream->hints & RFILE_HINT_UNBUFFERED) == 0)
{
#ifdef HAVE_NFSCLIENT
@@ -1982,6 +2422,8 @@ int64_t retro_vfs_file_write_impl(libretro_vfs_implementation_file *stream, cons
return ret;
}
#endif
/* the position kept, so no tell per write; stdio asked once
* where it is not known */
pos = retro_vfs_file_tell_impl(stream);
/* Same int-typed stdio callback contract as the read side:
* chunk so no single fwrite count exceeds what bionic takes. */
@@ -2000,6 +2442,9 @@ int64_t retro_vfs_file_write_impl(libretro_vfs_implementation_file *stream, cons
len -= _put;
}
ret = (ssize_t)total;
/* a short write leaves stdio's position its own to say */
stream->stdio_pos = (len != 0 || pos < 0) ? -1
: pos + (int64_t)total;
}
if (ret > 0 && pos + ret > stream->size)
@@ -2181,7 +2626,7 @@ static bool vfs_positional_same_file(HANDLE base, HANDLE made)
#ifndef VFS_WIN32_FORCE_PATH_REOPEN
/* The file @base is open on, opened again relative to @base itself with
* an empty name: read-write where it may be, else read-only */
static HANDLE vfs_positional_reopen_handle(HANDLE base)
static HANDLE vfs_positional_reopen_handle(HANDLE base, bool overlapped)
{
static wchar_t empty[1];
vfs_nt_create_file_t create;
@@ -2190,6 +2635,7 @@ static HANDLE vfs_positional_reopen_handle(HANDLE base)
vfs_nt_iosb_t iosb;
HMODULE nt = GetModuleHandleA("ntdll.dll");
HANDLE made;
ULONG options;
if (!nt || !(create = (vfs_nt_create_file_t)(void (*)(void))
GetProcAddress(nt, "NtCreateFile")))
return INVALID_HANDLE_VALUE;
@@ -2202,16 +2648,18 @@ static HANDLE vfs_positional_reopen_handle(HANDLE base)
attr.attributes = 0x40; /* OBJ_CASE_INSENSITIVE */
attr.security = NULL;
attr.qos = NULL;
/* FILE_OPEN, FILE_NON_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT */
/* FILE_OPEN, FILE_NON_DIRECTORY_FILE, and FILE_SYNCHRONOUS_IO_NONALERT
* unless overlapped */
options = overlapped ? 0x40 : (0x40 | 0x20);
if (create(&made, GENERIC_READ | GENERIC_WRITE | SYNCHRONIZE
| FILE_READ_ATTRIBUTES, &attr, &iosb, NULL, 0,
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
1, 0x40 | 0x20, NULL, 0) >= 0)
1, options, NULL, 0) >= 0)
return made;
if (create(&made, GENERIC_READ | SYNCHRONIZE | FILE_READ_ATTRIBUTES,
&attr, &iosb, NULL, 0,
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
1, 0x40 | 0x20, NULL, 0) >= 0)
1, options, NULL, 0) >= 0)
return made;
return INVALID_HANDLE_VALUE;
}
@@ -2220,19 +2668,22 @@ static HANDLE vfs_positional_reopen_handle(HANDLE base)
/* The file @base is open on, opened again by @path: refused unless the
* volume and file index say it is the same file, so a rename or a
* replacement since the stream opened is never read instead. */
static HANDLE vfs_positional_reopen_path(HANDLE base, const char *path)
static HANDLE vfs_positional_reopen_path(HANDLE base, const char *path,
bool overlapped)
{
DWORD flags = FILE_ATTRIBUTE_NORMAL
| (overlapped ? FILE_FLAG_OVERLAPPED : 0);
HANDLE made;
wchar_t *wide;
if (!path || !(wide = utf8_to_utf16_string_alloc(path)))
return INVALID_HANDLE_VALUE;
made = CreateFileW(wide, GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL, NULL);
flags, NULL);
if (made == INVALID_HANDLE_VALUE)
made = CreateFileW(wide, GENERIC_READ,
FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL, NULL);
flags, NULL);
free(wide);
if (made != INVALID_HANDLE_VALUE && !vfs_positional_same_file(base, made))
{
@@ -2247,25 +2698,38 @@ static HANDLE vfs_positional_reopen_path(HANDLE base, const char *path)
* from Vista on is what ReOpenFile does, and before it is kept only
* when it is the same file; else the stream's path, checked the same
* way. None on 9x/ME, whose ReadFile takes no offset on a file. */
static HANDLE vfs_positional_handle(libretro_vfs_implementation_file *stream)
static HANDLE vfs_positional_handle(libretro_vfs_implementation_file *stream,
bool overlapped)
{
retro_atomic_ptr_t *slot = overlapped ? &stream->at_ovl
: &stream->at_handle;
HANDLE base, made = INVALID_HANDLE_VALUE;
HANDLE at = (HANDLE)retro_atomic_load_acquire_ptr(&stream->at_handle);
HANDLE at = (HANDLE)retro_atomic_load_acquire_ptr(slot);
DWORD version = GetVersion();
int fd;
if (at)
return at;
if (version & 0x80000000u)
return NULL;
fd = (stream->hints & RFILE_HINT_UNBUFFERED) ? stream->fd
: (stream->fp ? _fileno(stream->fp) : -1);
if (fd < 0)
return NULL;
base = (HANDLE)_get_osfhandle(fd);
if (base == INVALID_HANDLE_VALUE)
#ifdef VFS_NATIVE
/* its own handle is synchronous and reads at offsets already */
if ((stream->hints & RFILE_HINT_WIN32_NATIVE) && !overlapped)
return stream->fh;
if (stream->hints & RFILE_HINT_WIN32_NATIVE)
base = stream->fh;
else
#endif
{
fd = (stream->hints & RFILE_HINT_UNBUFFERED) ? stream->fd
: (stream->fp ? _fileno(stream->fp) : -1);
if (fd < 0)
return NULL;
base = (HANDLE)_get_osfhandle(fd);
}
if (!base || base == INVALID_HANDLE_VALUE)
return NULL;
#ifndef VFS_WIN32_FORCE_PATH_REOPEN
made = vfs_positional_reopen_handle(base);
made = vfs_positional_reopen_handle(base, overlapped);
/* Vista's I/O manager is the one ReOpenFile was made for; an older
* one's answer is taken only for the same file */
#ifndef VFS_WIN32_ALWAYS_VERIFY
@@ -2281,14 +2745,81 @@ static HANDLE vfs_positional_handle(libretro_vfs_implementation_file *stream)
}
#endif
if (made == INVALID_HANDLE_VALUE)
made = vfs_positional_reopen_path(base, stream->orig_path);
made = vfs_positional_reopen_path(base, stream->orig_path,
overlapped);
if (made == INVALID_HANDLE_VALUE)
return NULL;
if (retro_atomic_cas_ptr(&stream->at_handle, NULL, made))
if (retro_atomic_cas_ptr(slot, NULL, made))
return made;
/* Another thread's is in: this one is not needed */
CloseHandle(made);
return (HANDLE)retro_atomic_load_acquire_ptr(&stream->at_handle);
return (HANDLE)retro_atomic_load_acquire_ptr(slot);
}
/* An event for one call on @stream's at_handle: a slot's, claimed by
* its bit, or a passing one when all eight are in use (*slot -1) */
static HANDLE vfs_at_event_take(libretro_vfs_implementation_file *stream,
int *slot)
{
int busy = retro_atomic_load_acquire_int(&stream->at_ev_busy);
for (;;)
{
int i;
for (i = 0; i < 8 && (busy & (1 << i)); i++) { }
if (i == 8)
break;
if (retro_atomic_cas_int(&stream->at_ev_busy, busy, busy | (1 << i)))
{
if (!stream->at_ev[i])
stream->at_ev[i] = CreateEventA(NULL, TRUE, FALSE, NULL);
if (stream->at_ev[i])
{
*slot = i;
return stream->at_ev[i];
}
retro_atomic_fetch_and_int(&stream->at_ev_busy, ~(1 << i));
break;
}
busy = retro_atomic_load_acquire_int(&stream->at_ev_busy);
}
*slot = -1;
return CreateEventA(NULL, TRUE, FALSE, NULL);
}
static void vfs_at_event_give(libretro_vfs_implementation_file *stream,
HANDLE ev, int slot)
{
if (slot >= 0)
retro_atomic_fetch_and_int(&stream->at_ev_busy, ~(1 << slot));
else if (ev)
CloseHandle(ev);
}
/* @len bytes moved at @offset through the second handle: the
* synchronous one where no other call holds it, else the overlapped
* one, on an event of the call's own */
static int64_t vfs_positional_io(libretro_vfs_implementation_file *stream,
void *s, uint64_t len, uint64_t offset, bool write)
{
HANDLE h, ev;
int slot;
int64_t did;
if (retro_atomic_cas_int(&stream->at_sync_busy, 0, 1))
{
h = vfs_positional_handle(stream, false);
if (h)
did = vfs_win32_io(h, NULL, s, len, offset, write);
retro_atomic_store_release_int(&stream->at_sync_busy, 0);
if (h)
return did;
}
if ( !(h = vfs_positional_handle(stream, true))
|| !(ev = vfs_at_event_take(stream, &slot)))
return -1;
did = vfs_win32_io(h, ev, s, len, offset, write);
vfs_at_event_give(stream, ev, slot);
return did;
}
#endif
@@ -2337,35 +2868,7 @@ int64_t retro_vfs_file_read_at_impl(libretro_vfs_implementation_file *stream,
return (int64_t)total;
}
#elif defined(VFS_POSITIONAL_WIN32)
{
uint8_t *p = (uint8_t*)s;
uint64_t total = 0;
HANDLE at = vfs_positional_handle(stream);
if (!at)
return -1;
while (len != 0)
{
OVERLAPPED ov;
DWORD _got = 0;
DWORD _chunk = (len > 0x40000000u) ? 0x40000000u : (DWORD)len;
uint64_t where = (uint64_t)offset + total;
memset(&ov, 0, sizeof(ov));
ov.Offset = (DWORD)(where & 0xffffffffu);
ov.OffsetHigh = (DWORD)(where >> 32);
if (!ReadFile(at, p, _chunk, &_got, &ov))
{
if (GetLastError() == ERROR_HANDLE_EOF)
break;
return (total != 0) ? (int64_t)total : -1;
}
if (_got == 0)
break;
total += _got;
p += _got;
len -= _got;
}
return (int64_t)total;
}
return vfs_positional_io(stream, s, len, (uint64_t)offset, false);
#else
return -1;
#endif
@@ -2420,27 +2923,8 @@ int64_t retro_vfs_file_write_at_impl(libretro_vfs_implementation_file *stream,
}
#elif defined(VFS_POSITIONAL_WIN32)
{
const uint8_t *p = (const uint8_t*)s;
uint64_t total = 0;
HANDLE at = vfs_positional_handle(stream);
if (!at)
return -1;
while (len != 0)
{
OVERLAPPED ov;
DWORD _put = 0;
DWORD _chunk = (len > 0x40000000u) ? 0x40000000u : (DWORD)len;
uint64_t where = (uint64_t)offset + total;
memset(&ov, 0, sizeof(ov));
ov.Offset = (DWORD)(where & 0xffffffffu);
ov.OffsetHigh = (DWORD)(where >> 32);
if (!WriteFile(at, p, _chunk, &_put, &ov) || _put == 0)
return (total != 0) ? (int64_t)total : -1;
total += _put;
p += _put;
len -= _put;
}
return (int64_t)total;
return vfs_positional_io(stream, (void*)s, len, (uint64_t)offset,
true);
}
#else
(void)offset;
@@ -2466,9 +2950,24 @@ int retro_vfs_file_flush_impl(libretro_vfs_implementation_file *stream)
#endif
/* A descriptor holds no bytes of its own to flush: what was
* written is in the file already */
#ifdef VFS_NATIVE
if (stream->hints & RFILE_HINT_WIN32_NATIVE)
return vfs_native_flush(stream);
#endif
if (stream->hints & RFILE_HINT_UNBUFFERED)
return stream->fd >= 0 ? 0 : -1;
if (stream->fp && fflush(stream->fp) == 0)
if (!stream->fp)
return -1;
/* A stream that reads too keeps its position: msvcrt's fflush drops
* what was read ahead without seeking back */
if (stream->hints & RFILE_HINT_UPDATE)
{
int64_t pos = retro_vfs_fp_settle(stream->fp);
if (pos < 0 || retro_vfs_fp_seek64(stream->fp, pos, SEEK_SET) != 0)
return -1;
return 0;
}
if (fflush(stream->fp) == 0)
return 0;
return -1;
}
+3
View File
@@ -257,6 +257,9 @@ static int action_left_scroll(unsigned type, const char *label,
unsigned scroll_speed = (unsigned)((MAX(scroll_accel, 2) - 2) / 4 + 1);
unsigned fast_scroll_speed = 10 * scroll_speed;
if (!config_get_ptr()->bools.menu_scroll_quick)
return 0;
if (selection > fast_scroll_speed)
{
size_t idx = selection - fast_scroll_speed;
+3
View File
@@ -254,6 +254,9 @@ static int action_right_scroll(unsigned type, const char *label,
unsigned fast_scroll_speed = 10 * scroll_speed;
size_t entries_end = MENU_LIST_GET_SELECTION(menu_list, 0)->size;
if (!config_get_ptr()->bools.menu_scroll_quick)
return 0;
if (selection + fast_scroll_speed < entries_end)
{
size_t idx = selection + fast_scroll_speed;
+2
View File
@@ -2181,6 +2181,7 @@ int menu_cbs_init_bind_sublabel(menu_file_list_cbs_t *cbs,
{ MENU_ENUM_LABEL_VIDEO_THREADED_PRESENT_REPEAT, MENU_ENUM_SUBLABEL_VIDEO_THREADED_PRESENT_REPEAT },
{ MENU_ENUM_LABEL_VIDEO_THREADED_DISPLAY_PACING, MENU_ENUM_SUBLABEL_VIDEO_THREADED_DISPLAY_PACING },
{ MENU_ENUM_LABEL_VIDEO_PRESENT_TIMING_FROM_DISPLAY, MENU_ENUM_SUBLABEL_VIDEO_PRESENT_TIMING_FROM_DISPLAY },
{ MENU_ENUM_LABEL_VIDEO_SKIP_DUPE_FRAMES, MENU_ENUM_SUBLABEL_VIDEO_SKIP_DUPE_FRAMES },
{ MENU_ENUM_LABEL_VIDEO_MAX_FRAME_LATENCY, MENU_ENUM_SUBLABEL_VIDEO_MAX_FRAME_LATENCY },
{ MENU_ENUM_LABEL_VIDEO_HARD_SYNC, MENU_ENUM_SUBLABEL_VIDEO_HARD_SYNC },
{ MENU_ENUM_LABEL_VIDEO_HARD_SYNC_FRAMES, MENU_ENUM_SUBLABEL_VIDEO_HARD_SYNC_FRAMES },
@@ -2318,6 +2319,7 @@ int menu_cbs_init_bind_sublabel(menu_file_list_cbs_t *cbs,
{ MENU_ENUM_LABEL_MENU_SHOW_SHUTDOWN, MENU_ENUM_SUBLABEL_MENU_SHOW_SHUTDOWN },
{ MENU_ENUM_LABEL_MENU_SHOW_ONLINE_UPDATER, MENU_ENUM_SUBLABEL_MENU_SHOW_ONLINE_UPDATER },
{ MENU_ENUM_LABEL_MENU_SHOW_CORE_UPDATER, MENU_ENUM_SUBLABEL_MENU_SHOW_CORE_UPDATER },
{ MENU_ENUM_LABEL_MENU_SCROLL_QUICK, MENU_ENUM_SUBLABEL_MENU_SCROLL_QUICK },
{ MENU_ENUM_LABEL_MENU_SCROLL_FAST, MENU_ENUM_SUBLABEL_MENU_SCROLL_FAST },
{ MENU_ENUM_LABEL_MENU_SCROLL_DELAY, MENU_ENUM_SUBLABEL_MENU_SCROLL_DELAY },
{ MENU_ENUM_LABEL_MENU_KIOSK_MODE_PASSWORD, MENU_ENUM_SUBLABEL_MENU_KIOSK_MODE_PASSWORD },
+2 -2
View File
@@ -10779,6 +10779,7 @@ unsigned menu_displaylist_build_list(
{MENU_ENUM_LABEL_INPUT_ALL_USERS_CONTROL_MENU, PARSE_ONLY_BOOL, true},
{MENU_ENUM_LABEL_MENU_SINGLECLICK_PLAYLISTS, PARSE_ONLY_BOOL, true},
{MENU_ENUM_LABEL_MENU_ALLOW_TABS_BACK, PARSE_ONLY_BOOL, true},
{MENU_ENUM_LABEL_MENU_SCROLL_QUICK, PARSE_ONLY_BOOL, true},
{MENU_ENUM_LABEL_MENU_SCROLL_FAST, PARSE_ONLY_BOOL, true},
{MENU_ENUM_LABEL_MENU_SCROLL_DELAY, PARSE_ONLY_UINT, true},
{MENU_ENUM_LABEL_INPUT_DISABLE_INFO_BUTTON, PARSE_ONLY_BOOL, true},
@@ -12907,6 +12908,7 @@ unsigned menu_displaylist_build_list(
{MENU_ENUM_LABEL_VIDEO_THREADED_PRESENT_REPEAT, PARSE_ONLY_BOOL, false},
{MENU_ENUM_LABEL_VIDEO_THREADED_DISPLAY_PACING, PARSE_ONLY_BOOL, false},
{MENU_ENUM_LABEL_VIDEO_PRESENT_TIMING_FROM_DISPLAY, PARSE_ONLY_BOOL, false},
{MENU_ENUM_LABEL_VIDEO_SKIP_DUPE_FRAMES, PARSE_ONLY_BOOL, true},
{MENU_ENUM_LABEL_VIDEO_MAX_FRAME_LATENCY, PARSE_ONLY_INT, false},
{MENU_ENUM_LABEL_VIDEO_MAX_SWAPCHAIN_IMAGES, PARSE_ONLY_UINT, false},
#if defined(HAVE_D3DKMT) || defined(HAVE_KMS)
@@ -12964,8 +12966,6 @@ unsigned menu_displaylist_build_list(
&& !video_driver_is_hw_context()
&& config_get_ptr()->bools.video_threaded_present_repeat;
break;
build_list[i].checked = *video_driver_get_threaded();
break;
case MENU_ENUM_LABEL_VIDEO_THREADED_DISPLAY_PACING:
build_list[i].checked = *video_driver_get_threaded()
&& !video_driver_is_hw_context();
+1
View File
@@ -10587,6 +10587,7 @@ enum msg_hash_enums
#undef S_ACTION_EX_NS_H
#undef SETTINGS_DEF_STRINGS_PASS
#undef SETTINGS_DEF_ENUM_PASS
MENU_LABEL(MENU_SCROLL_QUICK),
MENU_LABEL(MENU_SCROLL_FAST),
MENU_LABEL(MENU_SCROLL_DELAY),
/* GENERATED REGION: kiosk mode password setting enum rows (see settings/settings_def_kiosk_password.h). */
+4
View File
@@ -942,6 +942,7 @@ extern const struct msg_hash_us_lbl_blob_s msg_hash_us_lbl_blob;
#define MENU_ENUM_LABEL_VIDEO_THREADED_PRESENT_REPEAT_STR (msg_hash_us_lbl_blob.m_MENU_ENUM_LABEL_VIDEO_THREADED_PRESENT_REPEAT)
#define MENU_ENUM_LABEL_VIDEO_THREADED_DISPLAY_PACING_STR (msg_hash_us_lbl_blob.m_MENU_ENUM_LABEL_VIDEO_THREADED_DISPLAY_PACING)
#define MENU_ENUM_LABEL_VIDEO_PRESENT_TIMING_FROM_DISPLAY_STR (msg_hash_us_lbl_blob.m_MENU_ENUM_LABEL_VIDEO_PRESENT_TIMING_FROM_DISPLAY)
#define MENU_ENUM_LABEL_VIDEO_SKIP_DUPE_FRAMES_STR (msg_hash_us_lbl_blob.m_MENU_ENUM_LABEL_VIDEO_SKIP_DUPE_FRAMES)
#define MENU_ENUM_LABEL_VIDEO_MAX_FRAME_LATENCY_STR (msg_hash_us_lbl_blob.m_MENU_ENUM_LABEL_VIDEO_MAX_FRAME_LATENCY)
#define MENU_ENUM_LABEL_VIDEO_HARD_SYNC_STR (msg_hash_us_lbl_blob.m_MENU_ENUM_LABEL_VIDEO_HARD_SYNC)
#define MENU_ENUM_LABEL_VIDEO_HARD_SYNC_FRAMES_STR (msg_hash_us_lbl_blob.m_MENU_ENUM_LABEL_VIDEO_HARD_SYNC_FRAMES)
@@ -1220,6 +1221,7 @@ extern const struct msg_hash_us_lbl_blob_s msg_hash_us_lbl_blob;
#define MENU_ENUM_LABEL_INPUT_ANALOG_SENSITIVITY_STR (msg_hash_us_lbl_blob.m_MENU_ENUM_LABEL_INPUT_ANALOG_SENSITIVITY)
#define MENU_ENUM_LABEL_INPUT_SENSORS_ENABLE_STR (msg_hash_us_lbl_blob.m_MENU_ENUM_LABEL_INPUT_SENSORS_ENABLE)
#define MENU_ENUM_LABEL_MENU_SCROLL_DELAY_STR (msg_hash_us_lbl_blob.m_MENU_ENUM_LABEL_MENU_SCROLL_DELAY)
#define MENU_ENUM_LABEL_MENU_SCROLL_QUICK_STR (msg_hash_us_lbl_blob.m_MENU_ENUM_LABEL_MENU_SCROLL_QUICK)
#define MENU_ENUM_LABEL_UI_COMPANION_ENABLE_STR (msg_hash_us_lbl_blob.m_MENU_ENUM_LABEL_UI_COMPANION_ENABLE)
#define MENU_ENUM_LABEL_UI_COMPANION_START_ON_BOOT_STR (msg_hash_us_lbl_blob.m_MENU_ENUM_LABEL_UI_COMPANION_START_ON_BOOT)
#define MENU_ENUM_LABEL_DESKTOP_MENU_ENABLE_STR (msg_hash_us_lbl_blob.m_MENU_ENUM_LABEL_DESKTOP_MENU_ENABLE)
@@ -2727,6 +2729,7 @@ extern const struct msg_hash_us_lbl_blob_s msg_hash_us_lbl_blob;
#define MENU_ENUM_LABEL_VIDEO_THREADED_PRESENT_REPEAT_STR "video_threaded_present_repeat"
#define MENU_ENUM_LABEL_VIDEO_THREADED_DISPLAY_PACING_STR "video_threaded_display_pacing"
#define MENU_ENUM_LABEL_VIDEO_PRESENT_TIMING_FROM_DISPLAY_STR "video_present_timing_from_display"
#define MENU_ENUM_LABEL_VIDEO_SKIP_DUPE_FRAMES_STR "video_skip_dupe_frames"
#define MENU_ENUM_LABEL_VIDEO_MAX_FRAME_LATENCY_STR "video_max_frame_latency"
#define MENU_ENUM_LABEL_VIDEO_HARD_SYNC_STR "video_hard_sync"
#define MENU_ENUM_LABEL_VIDEO_HARD_SYNC_FRAMES_STR "video_hard_sync_frames"
@@ -3005,6 +3008,7 @@ extern const struct msg_hash_us_lbl_blob_s msg_hash_us_lbl_blob;
#define MENU_ENUM_LABEL_INPUT_ANALOG_SENSITIVITY_STR "input_analog_sensitivity"
#define MENU_ENUM_LABEL_INPUT_SENSORS_ENABLE_STR "input_sensors_enable"
#define MENU_ENUM_LABEL_MENU_SCROLL_DELAY_STR "menu_scroll_delay"
#define MENU_ENUM_LABEL_MENU_SCROLL_QUICK_STR "menu_scroll_quick"
#define MENU_ENUM_LABEL_UI_COMPANION_ENABLE_STR "ui_companion_enable"
#define MENU_ENUM_LABEL_UI_COMPANION_START_ON_BOOT_STR "ui_companion_start_on_boot"
#define MENU_ENUM_LABEL_DESKTOP_MENU_ENABLE_STR "desktop_menu_enable"
+5 -1
View File
@@ -6289,6 +6289,9 @@ static runloop_pace_facts_t runloop_pace_gather(settings_t *settings,
if (video_context_driver_presentable()) f |= PACE_FACT_PRESENTABLE;
if (runloop_st->frame_limit_minimum_time) f |= PACE_FACT_FRAME_LIMIT;
if (core_ran) f |= PACE_FACT_CORE_RAN;
if ( core_ran
&& video_st->dupe_skip_hold
&& settings->bools.video_skip_dupe_frames) f |= PACE_FACT_DUPE_SKIP;
return runloop_driver_pacing_gather(settings, runloop_st, video_st, f);
}
@@ -10380,7 +10383,8 @@ end:
? video_st->video_refresh_rate_original
: settings->floats.video_refresh_rate);
}
else if (runloop_pace_driver_fallback(pace_facts, runloop_st->pace))
else if ( runloop_pace_driver_fallback(pace_facts, runloop_st->pace)
|| runloop_pace_dupe_skip_engages(pace_facts, runloop_st->pace))
{
runloop_st->pace |= RUNLOOP_PACE_TIMER;
pace_limit_min = runloop_content_frame_time_us(
+24 -1
View File
@@ -786,9 +786,12 @@ enum runloop_pace_fact
PACE_FACT_DRIVER_PACING = (1 << 18), /* the driver's low-latency backend
is up and took the mode */
PACE_FACT_CORE_RAN = (1 << 19), /* this iteration ran the core */
PACE_FACT_DRIVER_PACING_MISSING = (1 << 20) /* Driver Pacing is on, but
PACE_FACT_DRIVER_PACING_MISSING = (1 << 20),/* Driver Pacing is on, but
the driver has no backend
that took it */
PACE_FACT_DUPE_SKIP = (1 << 21) /* Skip Duplicate Frames is
leaving the core's repeats
unpresented */
};
typedef unsigned runloop_pace_facts_t;
@@ -822,6 +825,7 @@ static INLINE bool runloop_pace_driver_owns(runloop_pace_facts_t f,
{
return (f & PACE_FACT_DRIVER_PACING)
&& (f & PACE_FACT_CORE_RAN)
&& !(f & PACE_FACT_DUPE_SKIP)
&& (f & PACE_FACT_FOCUSED)
&& !(f & PACE_FACT_NONBLOCKING)
&& !(f & PACE_FACT_FASTMOTION)
@@ -859,6 +863,23 @@ static INLINE bool runloop_pace_driver_fallback(runloop_pace_facts_t f,
&& runloop_pace_driver_owns(f | PACE_FACT_DRIVER_PACING, pace);
}
/* A repeat left unpresented by Skip Duplicate Frames reaches no present,
* so neither VSync nor the threaded hold can keep the loop on it, and the
* presents between repeats come at the content's rate, which a swapchain
* queues without blocking. Where audio, a scanline or the timer do not
* already hold the loop, the timer holds it to the content's period for
* as long as repeats are being skipped. The driver's release waits on a
* present, so it stands aside for that time (runloop_pace_driver_owns). */
static INLINE bool runloop_pace_dupe_skip_engages(runloop_pace_facts_t f,
unsigned pace)
{
return (f & PACE_FACT_DUPE_SKIP)
&& !(f & PACE_FACT_NONBLOCKING)
&& !(f & PACE_FACT_FASTMOTION)
&& !(pace & (RUNLOOP_PACE_AUDIO | RUNLOOP_PACE_SCANLINE
| RUNLOOP_PACE_TIMER | RUNLOOP_PACE_NOWINDOW));
}
static INLINE unsigned runloop_pace_sources(runloop_pace_facts_t f)
{
unsigned pace = RUNLOOP_PACE_NONE;
@@ -932,6 +953,8 @@ static INLINE unsigned runloop_pace_decide(runloop_pace_facts_t f)
pace |= RUNLOOP_PACE_TIMER;
else if (runloop_pace_driver_fallback(f, pace))
pace |= RUNLOOP_PACE_TIMER;
else if (runloop_pace_dupe_skip_engages(f, pace))
pace |= RUNLOOP_PACE_TIMER;
return pace;
}
Loaded 100 of 140 files, more files were not shown because too many files have changed in this diff. Show more