mirror of
https://github.com/libretro/RetroArch.git
synced 2026-11-12 13:08:24 -10:00
Compare commits
23
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
54e6ef1869 | ||
|
|
a26d6b552b | ||
|
|
c4174413a1 | ||
|
|
990384fdb7 | ||
|
|
c7a5f6edb8 | ||
|
|
68d25aa5bf | ||
|
|
434b5525b6 | ||
|
|
6bda23daad | ||
|
|
d4fa1c6401 | ||
|
|
75b43c75a9 | ||
|
|
8714cee2fe | ||
|
|
fd17192acb | ||
|
|
1656558977 | ||
|
|
13677e89f8 | ||
|
|
a1b7f2204a | ||
|
|
d928778305 | ||
|
|
fbc6073586 | ||
|
|
d5eedad5ff | ||
|
|
92e1e205ce | ||
|
|
b42806c02c | ||
|
|
9ce7ef2311 | ||
|
|
cf816b8940 | ||
|
|
68b07f6a1a |
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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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 \
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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 */
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -30,6 +30,7 @@
|
||||
#include "stb_image.h"
|
||||
#else
|
||||
#include <formats/image.h>
|
||||
#include <streams/image_texture_file.h>
|
||||
#endif
|
||||
|
||||
#include <libretro.h>
|
||||
|
||||
@@ -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
@@ -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,
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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"
|
||||
|
||||
|
||||
@@ -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"
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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. */
|
||||
|
||||
@@ -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).
|
||||
|
||||
@@ -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
|
||||
+1
-1
@@ -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
|
||||
{
|
||||
File renamed without changes.
@@ -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
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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);
|
||||
|
||||
Binary file not shown.
@@ -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)
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -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;
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 },
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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). */
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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
Reference in new issue
Block a user