Compare commits

...
Author SHA1 Message Date
LibretroAdmin 65a62c10e0 vfs_v5_metadata_test: skip the read-only open check where the system allows the write
Wine keeps the read-only attribute in Unix mode bits, which root
ignores, so under Wine run as root a read-only file opens for writing
and the check failed though the VFS was right. As the POSIX build
skips the check as root, the Windows build now skips it when a plain
CreateFileA for writing succeeds on the read-only file - which on
Windows it does not, so the check still runs there and under Wine as
an ordinary user.
2026-11-13 03:12:51 +01:00
LibretroAdmin 855002e6d6 data_transfer: a window feed never reads in a gap the consumer has passed
A feed whose frontier was further behind the consumer than its margin
filled everything from the frontier to the consumer in one extend. A
consumer far into a large file - its media past a large leading
section, or a small step back there that the feed takes for a lap and
rewinds to the head - made most of the file resident: the preview of
a 7.8 GB MP4 with its media at the end committed 6 GB and was killed.
The frontier now moves up to the margin behind the consumer before the
extend, which reads only from there; the bytes it skips are behind the
consumer and outside its margin, never read.

data_transfer_window_test starts a consumer 1 MiB from the end of a
256 MiB source, moves it on and steps it back, and checks that each
feed reads and keeps resident no more than the margin and lookahead
around it; without the change the first feed reads all 256 MiB.
2026-11-13 03:12:48 +01:00
LibretroAdmin 5c5c158392 rvorbis: a sparse codebook with no entry in use frees its lookup scratch
A sparse codebook whose entries are all unused skips the lookup
expansion; the multiplicands it read are freed on that path too.
rvorbis_codebook_test opens such a setup header under LeakSanitizer.
2026-11-13 03:06:17 +01:00
LibretroAdmin a2b9c2aa2d vfs_hybrid: VFS v6, positional reads and writes
The hybrid asks the frontend for v6 first and offers v6 itself: a
local handle reads and writes at an offset through the local
implementation, a frontend handle through the frontend's read_at and
write_at when it gave v6, and answers -1 otherwise so the caller seeks
and reads. vfs_hybrid_test checks each side, and builds on Windows
with MMAP=0 as the vfs samples do.
2026-11-13 03:06:17 +01:00
LibretroAdmin 9f3c78217f rtga: rtga_decode_rows, rows where the caller wants them
As rjpeg_decode_rows: one call writes each row, BGRA or RGBA in byte
order, into memory the caller names per row, or into rtga's own row,
which is kept for the next image. The decode loops write through the
row being filled, so rtga_process_image's surface is one more place
rows go. rtga_test checks the rows against rtga_process_image over
every type, depth and origin, raw and RLE, files cut short and both
byte orders.
2026-11-13 03:06:17 +01:00
LibretroAdmin ae02a3dd2a rtga: a header refused after the surface is made frees it
An indexed TGA whose colour map is cut short is refused once the
surface exists; rtga_proc_reset frees whatever surface nobody received,
whatever the phase. rtga_test covers it under LeakSanitizer.
2026-11-13 03:06:17 +01:00
LibretroAdmin 8a8f7307b2 rslc: length() of an unsized array asks the buffer
RSLC_EXTENDED, SPIR-V only. length() on a buffer block's unsized last
member compiled to the constant 0: the declared length, which an
unsized array does not have. It is OpArrayLength of the block and the
member now, whether the member is named bare or through the block's
instance.

Found by running the parallel-ps3 Vulkan renderer's compute stages from
glslang's modules and from rslc's: its aggregator adds up src[i] for
i < src.length(), and from rslc's module added nothing.

rslc_ext_test has a compute stage that takes both lengths and checks
for the two OpArrayLength; the module validates.
2026-11-13 02:45:57 +01:00
LibretroAdmin 8756cdc136 core updater: a download's handle outlives its handler until it retires
The push's duplicate finder reads remote_filename through task->state,
from other threads than the worker, for as long as the task is
findable. The handler leaves task->state set; the last reference still
tears the download down where it is dropped, and the struct with
remote_filename is freed once that teardown and the task's cleanup
have both let go of it. A finished download does not count as a
duplicate, as before.

download_backup_wait_test gains a lane that runs the finder on the
main thread against forty downloads finishing on the worker; under
TSan it reports the race on task->state.
2026-11-13 02:30:07 +01:00
davey 93cc8b8fd2 tasks: a playlist thumbnail download keeps its handle until it retires
A finished task stays findable until it retires, and the push's
duplicate finder reads the handle's playlist_config, so the handle is
freed in cleanup, shared with the entry download. The playlist itself
is freed by the handler when the download finishes, on the worker, so
retirement on the main thread frees only the small handle.

The pl_thumbnail_wait_test "finished" lane pushes a second download of
a playlist whose first one has finished but not retired.

Closes #20075
2026-11-13 02:30:03 +01:00
LibretroAdmin 9926d191c0 rslc: leaner SPIR-V for block paths, const in parameters and constants
Over the 387 shaders of the parallel-ps3 Vulkan renderer's test corpus
rslc's modules were 13.8% larger than glslang's; they are 0.6% larger
now. Four things in the SPIR-V emitter:

- A path into a variable is one access chain. a[i].b.c went out as
  three chains, each from the last one's result and each with a pointer
  type of its own; the steps are now kept while the expression is
  walked and go out as one instruction.
- A const in parameter is handed over as its value, as glslang does.
  Every parameter was a pointer: a variable made and stored at each
  call and a load at each use in the function. A const in parameter
  indexed by a variable is put in a variable there, once. The front end
  now refuses a write to one, which it let through.
- One pointer type for each type and storage class. Pointers to block
  structures and their arrays were made anew at every use.
- A constant vector made before is used again.

rslc_ext_test compiles a stage that reads through a block, calls with
const in and plain in parameters and repeats a constant, and checks
that no access chain starts from another's result, no pointer type or
constant vector is made twice and only the plain in parameter is a
pointer; it fails on the emitter as it was. The module validates.
2026-11-13 02:11:53 +01:00
d270b4030f QT fixes. (#20081)
* QT fixes.

* Qt: keep the wheel on popups, scroll bars and lone controls; restore only rowed docks

The wheel goes to the surrounding scroll area only for a spin box,
combo box or slider that sits in one; neither walk crosses a window,
so an open combo popup scrolls its own list, a scroll bar keeps its
own axis, and a control with no scroll area around it (grid zoom
slider, core combo) takes the wheel as before.

restoreDockLayout() detaches only the docks it re-adds to a side, so
a dock without a saved row keeps its default place and a floating one
stays in the layout. The thumbnail dock refresh is a queued slot
instead of a lambda, and the dock names come from one table.

---------

Co-authored-by: Tatsuya79 <jeanpascalmeunier@hotmailcom>
Co-authored-by: LibretroAdmin <[email protected]>
2026-11-13 02:10:45 +01:00
LibretroAdmin 1e66f8e705 rslc: barycentric inputs, multisampled subpass inputs, textureSamples RSLC_EXTENDED, SPIR-V only. What the parallel-ps3 fragment program decompiler writes for a game's own programs and the fixed shaders did not use; with it rslc compiles the 308 fragment and vertex programs that core's test builds from the console's microcode, to modules with glslang's interface and layouts.
- pervertexEXT on a fragment input (GL_EXT_fragment_shader_barycentric):
  the input is each vertex's value, an array of three, decorated
  PerVertexKHR.
- gl_BaryCoordEXT and gl_BaryCoordNoPerspEXT. Either, or a pervertexEXT
  input, declares FragmentBarycentricKHR and
  SPV_KHR_fragment_shader_barycentric.
- subpassInputMS: subpassLoad takes the sample, which goes out as the
  Sample image operand of the read.
- textureSamples on sampler2DMS and sampler2DMSArray
  (OpImageQuerySamples).

rslc_ext_test has a stage that interpolates a pervertexEXT input itself
and one that reads a multisampled attachment; the modules validate.
2026-11-13 02:05:07 +01:00
LibretroAdmin 76e903b229 r7z: progress takes the buffer's address, not data to read
r7z_progress_set only publishes where the folder is being decoded to,
so its buffer parameter is not const: GCC's -Wmaybe-uninitialized
takes a const pointer argument as a read of the freshly allocated
pend_out.
2026-11-13 02:04:10 +01:00
LibretroAdmin ac0b850300 companion: adding files pushes what the playlist lacks without searching it
Adding files and folders to a playlist pushed each file with
playlist_push(), which searches every entry first, so an add grew with
the square of the playlist. The add now indexes the playlist's paths
once (playlist_dedup_t, as the scanner does): a file whose path is not
there is pushed unchecked, the push's search having nothing to find,
and one whose path is there takes the full push, which moves an entry
for the same core to the top or adds one for another. The playlist
comes out the same; 5000 files added to a 20000-entry playlist take
9 ms of playlist work rather than 1.6 s.

companion_core_test adds new files, files already there for the same
core and for another, a file twice and a folder twice, and checks the
playlist and the count against a full push of each file.
2026-11-13 01:37:00 +01:00
LibretroAdmin 75869ea349 rslc: one multisampled image, as a sampler and as a storage image
RSLC_EXTENDED, SPIR-V only. What the parallel-ps3 Vulkan renderer's
multisample resolve tasks and passes use; with it rslc compiles all 79
of that renderer's fixed shaders, to modules with glslang's interface
and layouts.

- sampler2DMS, isampler2DMS, usampler2DMS: texelFetch with the sample,
  textureSize without a level. (sampler2DMSArray was there; the plain
  one was not.)
- image2DMS, iimage2DMS, uimage2DMS: imageLoad and imageStore take the
  sample, which goes out as the Sample image operand;
  StorageImageMultisample is declared.
- Image formats rg32f, rg16f, r16f, r11f_g11f_b10f, rgba16, rgb10_a2,
  rg16, r16 and rgba8_snorm. StorageImageExtendedFormats is declared
  for every format outside the base set, not only r8 and rg8.
- An image that is only written may leave its format out; the module
  declares StorageImageWriteWithoutFormat. One that is read still needs
  it.

rslc_ext_test has the resolve, the unresolve and the fragment fetches;
the modules validate.
2026-11-13 01:33:55 +01:00
LibretroAdmin d7f44c5e94 rslc: a step through a swizzle, a member's offset, the stencil reference
From the parallel-ps3 Vulkan renderer's deswizzle tasks, overlay pass
and depth-stencil resolve, which did not compile.

All builds:
- v.xy-- and ++v.zx. A step went through one pointer to its operand,
  and several components of a vector have none: the statement was
  refused ("cannot emit statement"). It is read as a value, stepped and
  written back the way an assignment to the same place is.

RSLC_EXTENDED, SPIR-V only (a printed destination refuses the stage):
- layout(offset = n) on a block's member places it there, and the
  members after it follow: a fragment stage's part of a push block that
  starts where the vertex stage's ends. The qualifier was parsed and
  dropped, so the members sat at 0.
- gl_FragStencilRefARB (GL_ARB_shader_stencil_export): the built-in,
  StencilExportEXT with its extension, and StencilRefReplacingEXT on
  the entry point, as glslang emits it.

rslc_ext_test has a stage for each. What the stepped swizzle computes
is not run by it; the module validates.
2026-11-13 01:29:13 +01:00
LibretroAdmin 6879826551 rslc: what an emulator's generated stages say, and a macro that names itself
Found compiling every shader source the parallel-ps3 Vulkan renderer's
interpreter and compute tasks make (29 stages, 639 KB of GLSL), none of
which compiled before and all of which do now, with the interface and
block layouts glslang gives them.

All builds:
- A macro's own name inside its expansion stays painted. It was left
  alone while the macro was being expanded but became a macro again
  when those tokens were scanned a second time - an argument prescanned
  and then rescanned in the body it was substituted into - so
  "#define offset params().offset" used inside another macro's argument
  expanded twice and read a member that does not exist.

RSLC_EXTENDED:
- #version 420, 430 and 440 are taken as the dialect 450 is.
- gl_WorkGroupSize: the constant the local size layout made.
- A uniform block that says std430 is laid out std430, the first block
  too (it was std140 whatever it said: a float[3] after three scalars
  went to offset 80, glslang puts it at 76).
- A uniform block's last member may be an array of unstated length
  (GL_EXT_uniform_buffer_unsized_array). SPIR-V only: the printed
  destinations refuse it.

rslc_ext_test has a stage for each, and 410 still refused.
2026-11-13 01:12:21 +01:00
LibretroAdmin cc87d6cc22 playlist: a push to the front takes room kept before the first entry
A playlist keeps its entries with room before the first, so a push
takes the slot there instead of moving every entry up one; when there
is none, the entries move into an allocation with as much room again
before them. A full playlist still drops its last entry, and cannot
fail for want of memory: with no room to be had its entries move up
over the last as before. Order, eviction and what is written are
unchanged. A scan adding 50000 entries spends about 0.17 s on its
pushes rather than 5.4 s.

playlist_parity_test checks pushes, unchecked pushes, pushes of entries
already there, deletes and clears against a list kept beside them, on
a loaded playlist at a capacity that evicts and on one grown to 4095
entries.
2026-11-13 01:11:39 +01:00
LibretroAdmin 5750097fbe vfs: an open reads the file's size in one call or none
An open that does not truncate takes the size the native Windows stream
read opening, or one fstat() of the descriptor on Linux, Android, the
Apple platforms, the BSDs and Haiku, for a regular file. Elsewhere, and
for anything fstat() does not answer, it seeks to the end and back as
before. On Linux an open, a 4 KiB read and a close make five system
calls rather than seven.
2026-11-13 01:11:35 +01:00
libretroadmin 155c1d8f59 retro_posix_source.h: ask old glibc for BSD and SVID names too
glibc before 2.19 ignores _DEFAULT_SOURCE, and the _POSIX_C_SOURCE set
here turns off its implicit _BSD_SOURCE, so strcasecmp(), M_PI,
syscall(), MAP_ANONYMOUS and SA_ONSTACK go undeclared. webOS ships
glibc 2.12, where a core's net_http.c no longer compiled. _BSD_SOURCE
and _SVID_SOURCE are what such a glibc enables by default; newer ones
accept them silently beside _DEFAULT_SOURCE.

The webOS job compiles every file including the header with the SDK's
compiler and no feature macros of its own.
2026-11-13 00:32:01 +01:00
LibretroAdmin 54e6ef1869 task_queue: a wait sees a task between finishing and the finished stack
A finishing task's settle counts it finished, stops counting it as
running and then pushes it onto the finished stack. The threaded
task_queue_wait() asked whether anything was left by the running count
against the scheduled count, the finished stack and the retire list -
so in the moment between the running count dropping and the push it
saw nothing, and returned before the task was retired or its callback
run. Where a handler's last pass ends just as the waiter looks, the
wait gave up on a task it was waiting for.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

No files matched your search

@@ -235,6 +235,27 @@ jobs:
./vfs_positional_nommap_test.exe
./vfs_positional_ntverify_test.exe
./vfs_positional_pathreopen_test.exe
# twelve threads: more than a stream keeps events for
./vfs_positional_wide_test.exe
# seeks and reads of every size, every byte checked, through
# the stream's own buffer
./vfs_seek_read_mix_test.exe
# writes, seeks, reads, truncates and holes in every write
# mode through the stream's own buffer, checked against a copy
./vfs_write_mix_test.exe
# The same with streams on the C runtime (VFS_WIN32_NO_NATIVE):
# the stream contract, the path an open takes, large files,
# offsets from threads, writes.
make clean MMAP=0
make MMAP=0 VFS_NATIVE=0
./vfs_bulk_read_test.exe
./vfs_seek_contract_test.exe
./vfs_large_file_test.exe
./vfs_readahead_test.exe
./vfs_descriptor_rw_test.exe
./vfs_positional_test.exe
./vfs_positional_nommap_test.exe
./vfs_write_mix_test.exe
# The Win64 fastjmp_set stores xmm6-xmm15 with movaps and faults
# on a misaligned buffer. Linux has no such stores, so the Linux
+2
View File
@@ -60,6 +60,7 @@ jobs:
./cdfs_dir_record_test
./cdfs_pure_api_test
./cdfs_io_error_test
./cdfs_file_test
env:
ASAN_OPTIONS: detect_leaks=1
UBSAN_OPTIONS: print_stacktrace=1:halt_on_error=1
@@ -72,3 +73,4 @@ jobs:
WINEDEBUG=-all wine ./cdfs_dir_record_test.exe
WINEDEBUG=-all wine ./cdfs_pure_api_test.exe
WINEDEBUG=-all wine ./cdfs_io_error_test.exe
WINEDEBUG=-all wine ./cdfs_file_test.exe
@@ -304,6 +304,12 @@ jobs:
# the same run as vfs_positional_test here.
vfs_positional_pathreopen_test
vfs_positional_ntverify_test
vfs_positional_wide_test
# seeks and reads of every size, every byte checked
vfs_seek_read_mix_test
# writes, seeks, reads, truncates and holes in every write
# mode, checked against a copy of the file
vfs_write_mix_test
cdrom_cuesheet_overflow_test
rdvd_h262_test
rdvd_lpcm_test
@@ -318,6 +324,8 @@ jobs:
cdfs_dir_record_test
cdfs_pure_api_test
cdfs_io_error_test
# cdfs tracks opened by filename: iso, bin, cue beside its bin
cdfs_file_test
chd_meta_overflow_test
chd_stream_threads_test
chd_stream_ahead_test
@@ -462,6 +470,9 @@ jobs:
# Twenty threads change one record two million times in all
# while three copy it: no change lost, no copy torn.
retro_cas_record_test
# A double-ended queue of one type in one block, against a
# model: two million changes, none wrong, none allocating.
retro_deque_test
sthread_ex_test
fast_cores_test
memshm_test
@@ -491,6 +502,11 @@ jobs:
# The FLAC arm, sample-exact in every input mode, a grown
# demuxed packet set and a raised WebM wall both resuming.
audio_transfer_flac_test
# rflac's 16-bit output against the PCM its fixtures were
# made from: 16 bits exact, 24 and 32 rounded to the nearest
# step, through every stereo coding, with SIMD and without.
rflac_s16_test
rflac_s16_nosimd_test
# The HDR video blits: PQ and HLG to linear scRGB against
# double-precision references, and HLG tone-mapped as HDR.
image_hdr_blit_test
@@ -545,6 +561,10 @@ jobs:
# self-contained regression tests came to be compiled every
# run and never executed.
declare -a NORUN_TARGETS=(
# Benchmarks: C runtime streams (VFS_WIN32_NO_NATIVE)
# against the stream's own handle, for timing on Windows.
vfs_native_bench
vfs_native_bench_native
# Fixture-driven checks run in Linux-samples-record.yml.
flv_remux_test
rtmp_socket_native_test
+33
View File
@@ -0,0 +1,33 @@
#!/bin/sh
# Compile every libretro-common file that includes retro_posix_source.h
# with $CC and no feature macros of its own, as a core's Makefile does.
# webOS.yml runs it with the webOS SDK, whose glibc 2.12 predates
# _DEFAULT_SOURCE: a name glibc hides behind __USE_MISC fails here
# instead of in a core's webOS build.
#
# CC=arm-webos-linux-gnueabi-gcc sh posix-source-check.sh
set -e
cd "$(dirname "$0")/../../.."
CC=${CC:-cc}
fail=0
n=0
for tu in $(grep -rl --include='*.c' '#include <retro_posix_source.h>' \
libretro-common | grep -v '/samples/' | sort); do
n=$((n + 1))
if ! out=$($CC -I. -Ilibretro-common/include -Wall \
-Werror=implicit-function-declaration -Werror=implicit-int \
-DHAVE_THREADS -DHAVE_NETWORKING -fsyntax-only "$tu" 2>&1); then
echo "FAIL $tu"
echo "$out" | grep ": error: " | sed 's/^/ /' | head -8
fail=1
fi
done
if [ "$n" -eq 0 ]; then
echo "no file includes retro_posix_source.h"
exit 1
fi
[ "$fail" = 0 ] && echo "ok $n files include retro_posix_source.h"
exit $fail
+6
View File
@@ -101,6 +101,12 @@ jobs:
tar xzf /tmp/webos-deps/cscd98_buildroot-nc4/aarch64-webos-linux-gnu_sdk-buildroot-x86_64.tar.gz
./aarch64-webos-linux-gnu_sdk-buildroot/relocate-sdk.sh
- name: libretro-common feature macros on the SDK's glibc 2.12
shell: bash
run: |
. /tmp/arm-webos-linux-gnueabi_sdk-buildroot/environment-setup
sh .github/workflows/scripts/posix-source-check.sh
- name: Load RARCH_VERSION from version.all
id: version
shell: bash
+2
View File
@@ -1,4 +1,6 @@
# Future
- LIBRETRO-COMMON/FLAC: 24- and 32-bit FLAC decoded to 16 bits is rounded to the nearest step instead of cut down (every sample was half a step low on average); 16-bit FLAC is unchanged
- LIBRETRO-COMMON/FLAC: A seek in a FLAC that has a seek table lands on the frame asked for (it landed as far past it as the seek point was from the start); rflac_seek_point gives the seek point's frame
- D3D11: Software-rendering cores draw straight into lent staging textures, copied to the frame texture on the GPU instead of by the CPU
- VULKAN: Keep the pipeline cache on disk between runs, one file per GPU, so stock and slang shader pipelines are not recompiled by the driver on every launch, preset load or driver reinit
- LINUX: Game Mode implemented natively (I/O priority plus a power-profiles-daemon performance hold); Feral libgamemode is no longer loaded and deps/feralgamemode is removed
+5 -5
View File
@@ -528,7 +528,7 @@ ifneq ($(HAVE_STRL), 1)
OBJ += $(LIBRETRO_COMM_DIR)/compat/compat_strl.o
endif
OBJ += $(LIBRETRO_COMM_DIR)/formats/image_texture.o \
OBJ += $(LIBRETRO_COMM_DIR)/formats/image_texture.o $(LIBRETRO_COMM_DIR)/streams/image_texture_file.o \
$(LIBRETRO_COMM_DIR)/formats/image_texture_scale.o \
$(LIBRETRO_COMM_DIR)/formats/image_texture_set.o \
$(LIBRETRO_COMM_DIR)/formats/image/image_yuv_blit.o
@@ -2655,9 +2655,9 @@ ifeq ($(HAVE_RMPEG1), 1)
$(LIBRETRO_COMM_DIR)/formats/dvd/rdvd_packet.o \
$(LIBRETRO_COMM_DIR)/formats/dvd/rdvd_spu.o \
$(LIBRETRO_COMM_DIR)/formats/dvd/rdvd_ifo.o \
$(LIBRETRO_COMM_DIR)/formats/dvd/rdvd_stream.o \
$(LIBRETRO_COMM_DIR)/formats/dvd/rdvd_source.o \
$(LIBRETRO_COMM_DIR)/formats/dvd/rdvd_vfs.o
$(LIBRETRO_COMM_DIR)/streams/rdvd_stream.o \
$(LIBRETRO_COMM_DIR)/streams/rdvd_vfs.o
# MPEG-1 audio is layer II, which rmp3 already decodes; a consumer that
# wants sound from an MPEG-1 stream needs HAVE_RMP3 as well.
endif
@@ -2673,7 +2673,7 @@ OBJ += $(LIBRETRO_COMM_DIR)/formats/bmp/rbmp_encode.o \
$(LIBRETRO_COMM_DIR)/formats/image_transfer.o \
$(LIBRETRO_COMM_DIR)/formats/video_player.o \
$(LIBRETRO_COMM_DIR)/formats/audio_transfer.o \
$(LIBRETRO_COMM_DIR)/formats/data_transfer.o \
$(LIBRETRO_COMM_DIR)/formats/data_transfer.o $(LIBRETRO_COMM_DIR)/streams/data_transfer_file.o \
$(LIBRETRO_COMM_DIR)/memmap/memmap.o \
$(LIBRETRO_COMM_DIR)/formats/m3u/rm3u.o
@@ -2889,7 +2889,7 @@ ifeq ($(HAVE_NETWORKING), 1)
cheevos/cheevos_client.o \
cheevos/cheevos_menu.o \
cheevos/cheevos_badge.o \
$(LIBRETRO_COMM_DIR)/formats/cdfs/cdfs.o \
$(LIBRETRO_COMM_DIR)/formats/cdfs/cdfs.o $(LIBRETRO_COMM_DIR)/streams/cdfs_file.o \
deps/rcheevos/src/rc_client.o \
deps/rcheevos/src/rc_compat.o \
deps/rcheevos/src/rc_libretro.o \
+1
View File
@@ -23,6 +23,7 @@
#include <streams/file_stream.h>
#include <features/features_cpu.h>
#include <formats/cdfs.h>
#include <streams/cdfs_file.h>
#include <compat/strl.h>
#include <retro_miscellaneous.h>
#include <retro_math.h>
+4 -6
View File
@@ -33,7 +33,6 @@
typedef struct
{
int64_t pos; /* in the disc image */
uint64_t file_pos; /* where the file stands; seeks to it are skipped */
ciso_t *ciso;
RFILE *file;
} rcheevos_ciso_t;
@@ -51,12 +50,12 @@ static int rcheevos_ciso_pump(rcheevos_ciso_t *handle, bool opening)
if (err != CISO_PENDING)
return err;
if ( handle->file_pos == req.offset
|| filestream_seek(handle->file, (int64_t)req.offset,
/* At the offset in one call; seek and read where there is none */
if ((got = filestream_read_at(handle->file, req.dst,
(int64_t)req.length, (int64_t)req.offset)) < 0
&& filestream_seek(handle->file, (int64_t)req.offset,
RETRO_VFS_SEEK_POSITION_START) == 0)
got = filestream_read(handle->file, req.dst, (int64_t)req.length);
/* unknown after a failed read: seek next time */
handle->file_pos = got > 0 ? req.offset + (uint64_t)got : ~(uint64_t)0;
ciso_feed(handle->ciso, got > 0 ? (size_t)got : 0);
}
}
@@ -81,7 +80,6 @@ static void* RC_CCONV rcheevos_ciso_open(const char *path)
if (!handle)
return NULL;
handle->pos = 0;
handle->file_pos = 0;
handle->ciso = NULL;
/* the descriptor path: no stdio buffer between the file and the
* buffers rc_hash reads into */
+6
View File
@@ -571,6 +571,10 @@
* times. Off: the fixed-timer pacing threaded video always had. */
#define DEFAULT_VIDEO_THREADED_DISPLAY_PACING false
/* Leave a frame the core marks as a repeat unpresented, so the display
* sees the content's own frame rate. Off: every repeat is presented. */
#define DEFAULT_VIDEO_SKIP_DUPE_FRAMES false
/* The Direct3D 9 drivers on a Direct3D9Ex device: a window presented
* through the flip model, and the frame latency settings honoured.
* Off: the discard swap effect and the runtime's own queue, as before. */
@@ -971,6 +975,8 @@
#define DEFAULT_MENU_SHOW_SUBLABELS_CURRENT_SELECTION_ONLY false
#define DEFAULT_MENU_SHOW_CONFIRM true
#define DEFAULT_MENU_DYNAMIC_WALLPAPER_ENABLE true
#define DEFAULT_MENU_SCROLL_QUICK false
#define DEFAULT_MENU_SCROLL_FAST false
#define DEFAULT_MENU_SCROLL_DELAY 256
+2
View File
@@ -754,6 +754,7 @@ typedef struct settings
bool video_threaded_display_pacing;
bool video_d3d9ex_present;
bool video_present_timing_from_display;
bool video_skip_dupe_frames;
bool video_font_enable;
bool video_disable_composition;
bool video_post_filter_record;
@@ -924,6 +925,7 @@ typedef struct settings
bool menu_linear_filter;
bool menu_texture_mipmapping;
bool menu_horizontal_animation;
bool menu_scroll_quick;
bool menu_scroll_fast;
bool menu_show_online_updater;
bool menu_show_full_paths;
+1
View File
@@ -30,6 +30,7 @@
#include "stb_image.h"
#else
#include <formats/image.h>
#include <streams/image_texture_file.h>
#endif
#include <libretro.h>
+1
View File
@@ -27,6 +27,7 @@
#include <retro_miscellaneous.h>
#include <streams/file_stream.h>
#include <formats/data_transfer.h>
#include <streams/data_transfer_file.h>
#include <streams/file_stream.h>
#include <formats/rwebm.h>
#include <formats/rwebm_video.h>
+45 -34
View File
@@ -677,11 +677,9 @@ static void win32_save_position(void)
{
bool ui_menubar_enable = settings->bools.ui_menubar_enable;
bool window_show_decor = settings->bools.video_window_show_decorations;
unsigned win_w =
VIDEO_SCALE_W(g_win32->pos_dims);
unsigned win_h =
VIDEO_SCALE_H(g_win32->pos_dims);
settings->uints.window_position_pos = g_win32->pos;
unsigned win_w = VIDEO_SCALE_W(g_win32->pos_dims);
unsigned win_h = VIDEO_SCALE_H(g_win32->pos_dims);
/* The frame the window reports includes whatever chrome it
* is wearing; the setting holds the client area, so take the
* chrome off both axes before the pair is stored. */
@@ -695,11 +693,12 @@ static void win32_save_position(void)
}
if (ui_menubar_enable)
{
int menu_bar_height = GetSystemMetrics(SM_CYMENU);
int menu_bar_height = GetSystemMetrics(SM_CYMENU);
win_h -= menu_bar_height;
}
settings->uints.window_position_dims =
VIDEO_SCALE_PACK(win_w, win_h);
settings->uints.window_position_pos = g_win32->pos;
settings->uints.window_position_dims = VIDEO_SCALE_PACK(win_w, win_h);
}
}
}
@@ -2154,8 +2153,10 @@ void win32_check_window(void *data,
if (g_win32_flags & WIN32_CMN_FLAG_RESIZED)
{
*resize = true;
*dims = VIDEO_SCALE_PACK(g_win32_resize_width, g_win32_resize_height);
*dims = VIDEO_SCALE_PACK(g_win32_resize_width, g_win32_resize_height);
g_win32_flags &= ~WIN32_CMN_FLAG_RESIZED;
/* Save the current position and size if set so. */
win32_save_position();
}
}
#endif
@@ -2235,37 +2236,50 @@ bool win32_has_focus(void *data)
{
settings_t *settings = config_get_ptr();
/* Ensure window size is big enough for core geometry. */
/* Apply/enforce windowed mode size. */
if ( settings
&& !settings->bools.video_fullscreen
&& !settings->bools.video_window_save_positions)
&& !settings->bools.video_fullscreen)
{
unsigned extra_width = 0;
unsigned extra_height = 0;
unsigned min_width = 0;
unsigned min_height = 0;
bool window_save_positions = settings->bools.video_window_save_positions;
unsigned extra_w = 0;
unsigned extra_h = 0;
unsigned win_w = 0;
unsigned win_h = 0;
win32_get_av_info_geometry(&min_width, &min_height);
/* Follow windowed mode size options in realtime when remembering position. */
if (window_save_positions)
{
win_w = VIDEO_SCALE_W(settings->uints.window_position_dims);
win_h = VIDEO_SCALE_H(settings->uints.window_position_dims);
}
/* Ensure window size is big enough for core geometry. */
else
win32_get_av_info_geometry(&win_w, &win_h);
if (settings->bools.video_window_show_decorations)
{
int border_thickness = GetSystemMetrics(SM_CXSIZEFRAME);
int title_bar_height = GetSystemMetrics(SM_CYCAPTION);
extra_width += border_thickness * 2;
extra_height += border_thickness * 2 + title_bar_height;
extra_w += border_thickness * 2;
extra_h += border_thickness * 2 + title_bar_height;
}
if (settings->bools.ui_menubar_enable)
extra_height += GetSystemMetrics(SM_CYMENU);
extra_h += GetSystemMetrics(SM_CYMENU);
if ( ( g_win32_resize_width < min_width
|| g_win32_resize_height < min_height)
&& min_width - g_win32_resize_width < MIN_WIDTH / 1.5f
&& min_height - g_win32_resize_height < MIN_HEIGHT / 1.5f)
if ( ( window_save_positions
&& ( win_w != g_win32_resize_width
|| win_h != g_win32_resize_height))
|| ( !window_save_positions
&& ( win_w > g_win32_resize_width
|| win_h > g_win32_resize_height)
&& win_w - g_win32_resize_width < MIN_WIDTH / 1.5f
&& win_h - g_win32_resize_height < MIN_HEIGHT / 1.5f)
)
SetWindowPos(main_window.hwnd, NULL, 0, 0,
min_width + extra_width,
min_height + extra_height,
win_w + extra_w,
win_h + extra_h,
SWP_NOMOVE);
}
@@ -2439,7 +2453,7 @@ bool win32_set_style(MONITORINFOEX *current_mon, HMONITOR *hm_to_use,
{
win32_common_state_t *g_win32 = (win32_common_state_t*)&win32_st;
bool position_set_from_config = false;
bool video_window_save_positions = settings->bools.video_window_save_positions;
bool window_save_positions = settings->bools.video_window_save_positions;
bool window_show_decor = settings->bools.video_window_show_decorations;
*style = WS_OVERLAPPEDWINDOW | WS_CLIPSIBLINGS | WS_CLIPCHILDREN;
@@ -2454,18 +2468,15 @@ bool win32_set_style(MONITORINFOEX *current_mon, HMONITOR *hm_to_use,
AdjustWindowRect(rect, *style, FALSE);
if (video_window_save_positions)
if (window_save_positions)
{
/* Set position from config */
int border_thickness = window_show_decor ? GetSystemMetrics(SM_CXSIZEFRAME) : 0;
int title_bar_height = window_show_decor ? GetSystemMetrics(SM_CYCAPTION) : 0;
unsigned window_position_width =
VIDEO_SCALE_W(settings->uints.window_position_dims);
unsigned window_position_height =
VIDEO_SCALE_H(settings->uints.window_position_dims);
unsigned window_position_width = VIDEO_SCALE_W(settings->uints.window_position_dims);
unsigned window_position_height = VIDEO_SCALE_H(settings->uints.window_position_dims);
g_win32->pos =
settings->uints.window_position_pos;
g_win32->pos = settings->uints.window_position_pos;
g_win32->pos_set = true;
g_win32->pos_dims = VIDEO_SCALE_PACK(
window_position_width + border_thickness * 2,
+1
View File
@@ -27,6 +27,7 @@
#include <retro_math.h>
#include <compat/strl.h>
#include <formats/image.h>
#include <streams/image_texture_file.h>
#ifdef HAVE_CONFIG_H
#include "../../config.h"
+1
View File
@@ -37,6 +37,7 @@
#include <encodings/utf.h>
#include <lists/string_list.h>
#include <formats/image.h>
#include <streams/image_texture_file.h>
#include <dxgi.h>
+1
View File
@@ -42,6 +42,7 @@
#include <encodings/utf.h>
#include <lists/string_list.h>
#include <formats/image.h>
#include <streams/image_texture_file.h>
#include <formats/image_yuv_blit.h>
#include "../common/video_import_color.h"
+1
View File
@@ -40,6 +40,7 @@
#include <encodings/utf.h>
#include <lists/string_list.h>
#include <formats/image.h>
#include <streams/image_texture_file.h>
#include <formats/image_yuv_blit.h>
#include "../common/video_import_color.h"
+1
View File
@@ -26,6 +26,7 @@
#define CINTERFACE
#include <formats/image.h>
#include <streams/image_texture_file.h>
#include <compat/strl.h>
#include <compat/posix_string.h>
#include <file/file_path.h>
+1
View File
@@ -31,6 +31,7 @@
#endif
#include <formats/image.h>
#include <streams/image_texture_file.h>
#include <compat/strl.h>
#include <compat/posix_string.h>
#include <file/file_path.h>
+1
View File
@@ -45,6 +45,7 @@
#include <gfx/scaler/scaler.h>
#include <gfx/math/matrix_4x4.h>
#include <formats/image.h>
#include <streams/image_texture_file.h>
#include <retro_inline.h>
#include <retro_miscellaneous.h>
#include <retro_math.h>
+1
View File
@@ -24,6 +24,7 @@
#include <encodings/utf.h>
#include <formats/image.h>
#include <streams/image_texture_file.h>
#include <file/file_path.h>
#include <string/stdstring.h>
+1
View File
@@ -34,6 +34,7 @@
#include <gfx/scaler/scaler.h>
#include <gfx/video_frame.h>
#include <formats/image.h>
#include <streams/image_texture_file.h>
#include <formats/image_yuv_blit.h>
#include <retro_inline.h>
#include <retro_miscellaneous.h>
+1
View File
@@ -25,6 +25,7 @@
#include <compat/strl.h>
#include <string/stdstring.h>
#include <formats/image.h>
#include <streams/image_texture_file.h>
#include <retro_miscellaneous.h>
#include "slang_process.h"
+1
View File
@@ -22,6 +22,7 @@
#include <compat/strl.h>
#include <formats/image.h>
#include <streams/image_texture_file.h>
#include <string/stdstring.h>
#include <gfx/math/matrix_4x4.h>
#include <retro_miscellaneous.h>
+1
View File
@@ -28,6 +28,7 @@
#include <streams/file_stream.h>
#include <formats/image.h>
#include <formats/data_transfer.h>
#include <streams/data_transfer_file.h>
#include <formats/video_player.h>
#include <retro_atomic.h>
#include <features/features_cpu.h>
+1
View File
@@ -19,6 +19,7 @@
#include <retro_atomic.h>
#include <retro_miscellaneous.h>
#include <formats/image_yuv_blit.h>
#include <streams/image_texture_file.h>
#include "gfx_surface.h"
#include "gfx_instrument.h"
+28
View File
@@ -9749,6 +9749,34 @@ void video_driver_frame(const void *data, unsigned width,
if (data)
video_st->main_flags &= ~VIDEO_FLAG_FF_FRAME_OWED;
/* Skip Duplicate Frames: a repeat from the core is left unpresented,
* so the display gets the content's own rate. Only a repeat the core
* sent, not the cached frame sent again for pause or the menu, and
* not where the presents themselves are the effect: Black Frame
* Insertion, shader sub-frames, the threaded presenter's repeats or
* a headset's interval. The hold tells the runloop to pace these
* iterations itself (runloop_pace_dupe_skip_engages). */
if ( !data
&& render_frame
&& settings->bools.video_skip_dupe_frames
&& !video_st->frame_repeat
&& !menu_is_alive
&& !video_info.input_driver_nonblock_state
&& !video_info.black_frame_insertion
&& video_info.shader_subframes <= 1
&& !video_st->headset_interval
#ifdef HAVE_THREADS
&& !( video_st->thread_wrapper_active
&& video_info.threaded_present_repeat)
#endif
)
{
render_frame = false;
video_st->dupe_skip_hold = VIDEO_DUPE_SKIP_HOLD;
}
else if (video_st->dupe_skip_hold && !video_st->frame_repeat)
video_st->dupe_skip_hold--;
/* What the shader chains read as FrameTimeDelta, from the reading
* taken at the top of this function. Measured between the frames
* the driver is handed, so a frame dropped by fast-forward
+9
View File
@@ -2054,6 +2054,10 @@ typedef struct
unsigned driver_pacing_interval_us;
uint8_t driver_pacing_mode;
uint8_t driver_pacing_reason;
/* Skip Duplicate Frames: the core frames left before the runloop
* stops pacing for skipped repeats, rearmed by each one skipped.
* Main thread. */
uint8_t dupe_skip_hold;
bool threaded;
@@ -2606,6 +2610,11 @@ float video_driver_get_aspect_ratio(void);
/* The aspect ratio, as the bits of the float in an atomic word. A
* union, not a cast through a pointer, so it stays inside what the
* standard allows and MSVC's aliasing rules. */
/* Core frames a skipped repeat keeps the runloop's dupe-skip pacing on
* for: long enough to span the gaps between repeats down to 1 in 8. */
#define VIDEO_DUPE_SKIP_HOLD 8
#ifdef HAVE_THREADS
#define VIDEO_CACHE_KEEPER ((size_t)sthread_get_current_thread_id())
#else
+3
View File
@@ -229,6 +229,7 @@ ACHIEVEMENTS
#define RC_CLIENT_SUPPORTS_HASH 1
#include "../libretro-common/formats/cdfs/cdfs.c"
#include "../libretro-common/streams/cdfs_file.c"
#include "../cheevos/cheevos.c"
#include "../cheevos/cheevos_client.c"
@@ -575,6 +576,7 @@ VIDEO IMAGE
============================================================ */
#include "../libretro-common/formats/image_texture.c"
#include "../libretro-common/streams/image_texture_file.c"
#include "../libretro-common/formats/image/image_yuv_blit.c"
#include "../libretro-common/formats/image_texture_scale.c"
#include "../libretro-common/formats/image_texture_set.c"
@@ -613,6 +615,7 @@ VIDEO IMAGE
#include "../libretro-common/formats/image_transfer.c"
#include "../libretro-common/formats/video_player.c"
#include "../libretro-common/formats/data_transfer.c"
#include "../libretro-common/streams/data_transfer_file.c"
#ifdef HAVE_RPNG
#include "../libretro-common/formats/png/rpng.c"
#include "../libretro-common/formats/png/rpng_apng.c"
+8 -2
View File
@@ -110,6 +110,7 @@ static int zstd_src_next(struct zstd_file_context *ctx,
return rzstd_blocks_step(ctx->zs, data, len);
for (;;)
{
int64_t got;
size_t want = rzstd_blocks_want(ctx->zs);
/* all of it out, and nothing after it */
if (!want)
@@ -117,8 +118,13 @@ static int zstd_src_next(struct zstd_file_context *ctx,
? RZSTD_PROCESS_END : RZSTD_PROCESS_ERROR;
if (want > RZSTD_WANT_MAX)
return RZSTD_PROCESS_ERROR;
filestream_seek(f, ctx->in_at, RETRO_VFS_SEEK_POSITION_START);
if (filestream_read(f, ctx->in, (int64_t)want) != (int64_t)want)
/* At the offset in one call; seek and read where there is none */
if ((got = filestream_read_at(f, ctx->in, (int64_t)want,
ctx->in_at)) < 0
&& filestream_seek(f, ctx->in_at,
RETRO_VFS_SEEK_POSITION_START) == 0)
got = filestream_read(f, ctx->in, (int64_t)want);
if (got != (int64_t)want)
return RZSTD_PROCESS_ERROR;
ctx->in_at += (int64_t)want;
if (rzstd_blocks_give(ctx->zs, ctx->in, want, data, len)
+1 -1
View File
@@ -2343,7 +2343,7 @@ static void r7z_progress_state(r7z_archive_t *a, int state)
}
/* What a reader is to see from now on: nothing while it is written */
static void r7z_progress_set(r7z_archive_t *a, const uint8_t *buf,
static void r7z_progress_set(r7z_archive_t *a, uint8_t *buf,
uint32_t folder, size_t pos, int state)
{
r7z_progress_state(a, 0);
+9 -3
View File
@@ -3635,12 +3635,18 @@ static uint32_t audio_transfer_flac_seek_to(struct audio_transfer_flac *fl,
#endif
&& fl->data)
{
uint64_t at = 0;
if (rflac_seek(fl->handle, frame, &at) == RFLAC_PROCESS_NEXT
uint64_t at = 0;
uint64_t point = 0;
if (rflac_seek_point(fl->handle, frame, &at, &point)
== RFLAC_PROCESS_NEXT
&& at < fl->size)
{
uint64_t landed;
rflac_seek_resumed(fl->handle, 0);
/* Resumed at the seek point's own frame. Told 0 instead, the
* decoder took the point for the start, and the remainder below
* was decoded on from the point as if from frame 0: the seek
* landed point frames past its target. */
rflac_seek_resumed(fl->handle, point);
rflac_set_in(fl->handle, (const uint8_t*)fl->data + at,
fl->size - (size_t)at);
rflac_set_eof(fl->handle);
+12 -179
View File
@@ -1,31 +1,21 @@
#include <retro_posix_source.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <formats/cdfs.h>
#include <retro_miscellaneous.h>
#include <compat/strl.h>
#include <file/file_path.h>
#include <string/stdstring.h>
#ifdef HAVE_CHD
#include <streams/chd_stream.h>
#endif
/* This TU is deliberately kept as a single translation unit but is
* organised in two layers:
*
* - the pure layer (everything through cdfs_parse_cue): ISO-9660
* sector/directory/file logic plus cue-sheet parsing. It only ever
* reads through an already-open track->stream or a caller-supplied
* memory buffer, and never opens a path. Entry points:
* cdfs_track_from_stream(), cdfs_parse_cue(), cdfs_open_file() and
* the read/seek family.
*
* - the path adapter layer (cdfs_open_cue_track onward): resolves
* filenames, opens cue/bin/iso/chd files via intfstream, then hands
* the resulting stream to the pure layer. file_path.h and the VFS
* are only used here. Callers that already hold a stream (or an
* in-memory image) can skip this layer entirely. */
/* ISO-9660 sector/directory/file logic plus cue-sheet parsing. It only
* ever reads through an already-open track->stream or a caller-supplied
* memory buffer, and never opens a path: streams/cdfs_file.c resolves
* filenames, opens cue/bin/iso/chd files and hands the stream in here.
* Entry points: cdfs_track_from_stream(), cdfs_parse_cue(),
* cdfs_open_file() and the read/seek family. */
static void cdfs_determine_sector_size(cdfs_track_t* track)
{
@@ -97,9 +87,9 @@ static void cdfs_determine_sector_size(cdfs_track_t* track)
}
}
static void cdfs_determine_sector_size_from_file_size(cdfs_track_t* track)
void cdfs_track_sector_size_from_length(cdfs_track_t* track)
{
/* attempt to determine stream_sector_size from file size */
/* attempt to determine stream_sector_size from the stream's length */
int64_t _len = intfstream_get_size(track->stream);
if (_len <= 0)
return;
@@ -686,163 +676,6 @@ bool cdfs_parse_cue(const char *cue_text, size_t len,
return false;
}
/* --- Path adapter layer ------------------------------------------------ */
static cdfs_track_t* cdfs_open_cue_track(
const char* path, unsigned int track_index)
{
cdfs_cue_info_t info;
char track_path[PATH_MAX_LENGTH];
int64_t _len;
bool parsed = false;
char *cue_contents = NULL;
cdfs_track_t *track = NULL;
intfstream_t *cue_stream = intfstream_open_file(path,
RETRO_VFS_FILE_ACCESS_READ, RETRO_VFS_FILE_ACCESS_HINT_NONE);
if (!cue_stream)
return NULL;
/* A cue sheet is a small text file: read it with one bulk read and
* release the handle before the (potentially slow) bin open below,
* instead of interleaving parsing with per-line stream reads. */
_len = intfstream_get_size(cue_stream);
if (_len > 0 && (cue_contents = (char*)malloc((size_t)_len)))
parsed = (intfstream_read(cue_stream, cue_contents, _len) == _len);
intfstream_close(cue_stream);
free(cue_stream);
if (parsed)
parsed = cdfs_parse_cue(cue_contents, (size_t)_len,
track_index, &info);
if (cue_contents)
free(cue_contents);
if (!parsed)
return NULL;
/* Resolve the FILE name from the cue against the cue's own
* directory unless it already carries a path. */
if ( strchr(info.bin_name, '/')
|| strchr(info.bin_name, '\\'))
strlcpy(track_path, info.bin_name, sizeof(track_path));
else
{
fill_pathname_basedir(track_path, path, sizeof(track_path));
strlcat(track_path, info.bin_name, sizeof(track_path));
}
track = cdfs_track_from_stream(intfstream_open_file(
track_path, RETRO_VFS_FILE_ACCESS_READ,
RETRO_VFS_FILE_ACCESS_HINT_NONE),
info.first_sector_offset, info.first_sector_index);
if (track && track->stream_sector_size == 0)
{
track->stream_sector_size = info.sector_size;
if (info.sector_size == 2352)
track->stream_sector_header_size = 16;
else if (info.sector_size == 2336)
track->stream_sector_header_size = 8;
}
return track;
}
#ifdef HAVE_CHD
static cdfs_track_t* cdfs_open_chd_track(const char* path, int32_t track_index)
{
cdfs_track_t *track;
intfstream_t *intf_stream = intfstream_open_chd_track(path,
RETRO_VFS_FILE_ACCESS_READ, RETRO_VFS_FILE_ACCESS_HINT_NONE,
track_index);
if (!intf_stream)
return NULL;
track = cdfs_track_from_stream(intf_stream,
intfstream_get_offset_to_start(intf_stream),
intfstream_get_first_sector(intf_stream));
if (track && track->stream_sector_header_size == 0)
{
track->stream_sector_size = intfstream_get_frame_size(intf_stream);
if (track->stream_sector_size == 2352)
track->stream_sector_header_size = 16;
else if (track->stream_sector_size == 2336)
track->stream_sector_header_size = 8;
}
return track;
}
#endif
struct cdfs_track_t* cdfs_open_track(const char* path,
unsigned int track_index)
{
const char* ext = path_get_extension(path);
if (string_is_equal_noncase(ext, "cue"))
return cdfs_open_cue_track(path, track_index);
#ifdef HAVE_CHD
if (string_is_equal_noncase(ext, "chd"))
return cdfs_open_chd_track(path, track_index);
#endif
/* if opening track 1, try opening as a raw track */
if (track_index == 1)
return cdfs_open_raw_track(path);
/* unsupported file type */
return NULL;
}
struct cdfs_track_t* cdfs_open_data_track(const char* path)
{
const char* ext = path_get_extension(path);
if (string_is_equal_noncase(ext, "cue"))
return cdfs_open_cue_track(path, 0);
#ifdef HAVE_CHD
if (string_is_equal_noncase(ext, "chd"))
return cdfs_open_chd_track(path, CHDSTREAM_TRACK_PRIMARY);
#endif
/* unsupported file type - try opening as a raw track */
return cdfs_open_raw_track(path);
}
cdfs_track_t* cdfs_open_raw_track(const char* path)
{
const char *ext = path_get_extension(path);
cdfs_track_t *track = NULL;
if ( string_is_equal_noncase(ext, "bin")
|| string_is_equal_noncase(ext, "iso"))
{
intfstream_t* file = intfstream_open_file(path,
RETRO_VFS_FILE_ACCESS_READ, RETRO_VFS_FILE_ACCESS_HINT_NONE);
track = cdfs_track_from_stream(file, 0, 0);
if (track && track->stream_sector_size == 0)
{
cdfs_determine_sector_size_from_file_size(track);
if (track->stream_sector_size == 0)
{
cdfs_close_track(track);
track = NULL;
}
}
}
return track;
}
void cdfs_close_track(cdfs_track_t* track)
{
if (track)
+54 -91
View File
@@ -29,8 +29,8 @@
* avail() for the buffer and how much of it is real, and the
* complete()/failed()/capped() terminals.
*
* - prefix (data_transfer_open_prefix): a file whose length is known
* at open. Address space for the whole length is reserved up
* - prefix (data_transfer_open_prefix_reader): a source whose length
* is known at open, read at offsets through a callback. Address space for the whole length is reserved up
* front so the base never moves, and physical pages are committed
* DT_COMMIT_STEP at a time as the fill advances, in DT_READ_CHUNK
* reads. commit_cap bounds the committed bytes and turns overrun
@@ -39,7 +39,7 @@
* sequential consumer holds a constant window over a file of any
* size.
*
* - window (data_transfer_open_window): the same reservation used
* - window (data_transfer_open_window_reader): the same reservation used
* cyclically, for sequential consumers that loop. A permanent
* head of 'keep' bytes plus a moving [wlo, whi) that feed()
* advances behind the consumer and extends ahead of it;
@@ -59,8 +59,11 @@
* growable buffer that shares this file and header but touches no
* data_transfer_t and no page machinery.
*
* File access goes through filestream/VFS throughout, so 64-bit
* offsets and VFS-only paths work. Where the platform cannot reserve
* No file I/O here: every read is the reader's. streams/
* data_transfer_file.c opens paths through filestream/VFS - 64-bit
* offsets, VFS-only paths - and hands the reader in, keeping the
* data_transfer_open_prefix/_window names. Where the platform cannot
* reserve
* address space, every mode degrades to a plain allocation of
* min(len, commit_cap) - the whole file when there is no cap - and a
* window simply holds the whole file; reserve_supported() and
@@ -68,8 +71,8 @@
*
* NOT IMPLEMENTED
*
* - Asynchrony. Every read is a synchronous filestream_read on the
* calling thread: iterate() blocks for the length of its budget,
* - Asynchrony. Every read is a synchronous call of the reader on
* the calling thread: iterate() blocks for the length of its budget,
* and there is nothing in flight for free() to cancel. Pacing is
* by byte budget and the caller does the time-slicing. The module
* began as a wrapper over nbio, which is where its vocabulary came
@@ -162,7 +165,6 @@
#include <stdlib.h>
#include <memmap.h>
#include <streams/file_stream.h>
#include <string.h>
#include <formats/data_transfer.h>
@@ -302,9 +304,10 @@ struct data_transfer_inert { size_t lo, hi; };
struct data_transfer
{
/* file-backed reader (open_prefix, and open_window over it) */
RFILE *f;
/* the reader every prefix and window fills from, and what frees
* its user data with the transfer, if anything */
data_transfer_read_at_t read_at;
data_transfer_release_t release;
void *read_ud;
uint8_t *map; /* reserved (or fallback-allocated) buffer */
size_t map_len; /* reserved bytes (page-rounded), 0 = fallback */
@@ -392,17 +395,12 @@ void data_transfer_arena_release(data_transfer_arena_t *a)
static int data_transfer_read_at(data_transfer_t *dt, size_t off,
uint8_t *dst, size_t n);
/* open_prefix's body. allow_pool is 0 for open_window, which builds
* on a prefix handle but reads map_len in punch/peek and so needs it
* to be the file's own rounded length rather than a pool slot's. */
static data_transfer_t *data_transfer_open_reader_ex(const char *path,
size_t commit_cap, int allow_pool, data_transfer_read_at_t read_at,
size_t length, void *ud);
static data_transfer_t *data_transfer_open_prefix_ex(const char *path,
size_t commit_cap, int allow_pool)
{
return data_transfer_open_reader_ex(path,commit_cap,allow_pool,NULL,0,NULL);
}
/* The prefix body. allow_pool is 0 for a window, which builds on a
* prefix handle but reads map_len in punch/peek and so needs it to be
* the source's own rounded length rather than a pool slot's. */
static data_transfer_t *data_transfer_open_reader_ex(size_t commit_cap,
int allow_pool, data_transfer_read_at_t read_at, size_t length,
void *ud);
/* The fill proper. data_transfer_iterate() refuses to run it on a
* window; open_window's no-reservation path genuinely needs it and
* calls it here directly. */
@@ -575,16 +573,19 @@ static data_transfer_t *data_transfer_start_window(data_transfer_t *dt, size_t k
return dt;
}
data_transfer_t *data_transfer_open_window(const char *path, size_t keep)
{
return data_transfer_start_window(data_transfer_open_prefix_ex(path,0,0),keep);
}
data_transfer_t *data_transfer_open_window_reader(size_t length,size_t keep,
data_transfer_read_at_t read_at,void *ud)
{
if (!read_at) return NULL;
return data_transfer_start_window(
data_transfer_open_reader_ex(NULL,0,0,read_at,length,ud),keep);
data_transfer_open_reader_ex(0,0,read_at,length,ud),keep);
}
void data_transfer_set_release(data_transfer_t *dt,
data_transfer_release_t release)
{
if (dt)
dt->release = release;
}
bool data_transfer_reserve_supported(void)
@@ -874,6 +875,13 @@ bool data_transfer_window_feed_budget(data_transfer_t *dt, size_t tell,
dt->wtell = tell;
if (tell > margin && tell - margin > dt->wlo)
data_transfer_window_advance(dt, tell - margin);
/* A frontier short of the margin behind the consumer moves up to it:
* the bytes between are behind the consumer and outside its margin,
* never read, and filling them in would make a gap of any size
* resident - a consumer that starts or jumps far into a large file,
* or one left there by the rewind above. */
if (dt->map_len && tell > margin && tell - margin > dt->whi)
data_transfer_window_rebase(dt, tell - margin);
hi = tell + lookahead;
/* The ceiling applies only while the frontier covers the consumer.
* A frontier behind tell means the consumer's next read lands on
@@ -901,44 +909,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 +1001,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 +1009,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 +1142,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 +1203,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 +1234,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 +1283,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 +1305,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 +1341,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 +1362,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 +1410,8 @@ void data_transfer_free(data_transfer_t *dt)
{
if (!dt)
return;
if (dt->f)
filestream_close(dt->f);
if (dt->release)
dt->release(dt->read_ud);
free(dt->inert);
if (dt->map)
{
+146 -188
View File
@@ -2154,6 +2154,13 @@ static uint32_t rflac__decode_samples_with_residual__rice__scalar(rflac_bs* bs,
}
#if defined(RFLAC_SUPPORT_SSE2)
/* rflac__s16_round for four lanes, but for the clamp, which the saturating
* pack after it does */
static INLINE __m128i rflac__mm_round_s16_epi32(__m128i x)
{
return _mm_srai_epi32(_mm_add_epi32(_mm_srai_epi32(x, 15), _mm_set1_epi32(1)), 1);
}
static INLINE __m128i rflac__mm_packs_interleaved_epi32(__m128i a, __m128i b)
{
__m128i r;
@@ -4387,6 +4394,17 @@ static rflac* rflac_open_with_metadata_private(rflac_read_proc onRead,
return pFlac;
}
/* A sample left-justified in 32 bits, to 16: rounded to the nearest step
* (halves up) and clamped, where a shift alone rounded down - every sample
* of a 24-bit stream half a step low on average. Shifting by 15 first keeps
* the rounding add from overflowing; only +32768 can come out, at the very
* top, and is clamped. A stream of 16 bits or fewer is unchanged by it. */
static INLINE int16_t rflac__s16_round(uint32_t x)
{
int32_t r = (((int32_t)x >> 15) + 1) >> 1;
return (int16_t)(r > 32767 ? 32767 : r);
}
static INLINE void rflac_read_pcm_frames_s16__decode_left_side__scalar(
rflac* pFlac, uint64_t frameCount, uint32_t unusedBitsPerSample,
const int32_t* pInputSamples0, const int32_t* pInputSamples1,
@@ -4416,24 +4434,14 @@ static INLINE void rflac_read_pcm_frames_s16__decode_left_side__scalar(
uint32_t right2 = left2 - side2;
uint32_t right3 = left3 - side3;
left0 >>= 16;
left1 >>= 16;
left2 >>= 16;
left3 >>= 16;
right0 >>= 16;
right1 >>= 16;
right2 >>= 16;
right3 >>= 16;
pOutputSamples[i*8+0] = (int16_t)left0;
pOutputSamples[i*8+1] = (int16_t)right0;
pOutputSamples[i*8+2] = (int16_t)left1;
pOutputSamples[i*8+3] = (int16_t)right1;
pOutputSamples[i*8+4] = (int16_t)left2;
pOutputSamples[i*8+5] = (int16_t)right2;
pOutputSamples[i*8+6] = (int16_t)left3;
pOutputSamples[i*8+7] = (int16_t)right3;
pOutputSamples[i*8+0] = rflac__s16_round(left0);
pOutputSamples[i*8+1] = rflac__s16_round(right0);
pOutputSamples[i*8+2] = rflac__s16_round(left1);
pOutputSamples[i*8+3] = rflac__s16_round(right1);
pOutputSamples[i*8+4] = rflac__s16_round(left2);
pOutputSamples[i*8+5] = rflac__s16_round(right2);
pOutputSamples[i*8+6] = rflac__s16_round(left3);
pOutputSamples[i*8+7] = rflac__s16_round(right3);
}
for (i = (frameCount4 << 2); i < frameCount; ++i)
@@ -4442,11 +4450,8 @@ static INLINE void rflac_read_pcm_frames_s16__decode_left_side__scalar(
uint32_t side = pInputSamples1U32[i] << shift1;
uint32_t right = left - side;
left >>= 16;
right >>= 16;
pOutputSamples[i*2+0] = (int16_t)left;
pOutputSamples[i*2+1] = (int16_t)right;
pOutputSamples[i*2+0] = rflac__s16_round(left);
pOutputSamples[i*2+1] = rflac__s16_round(right);
}
}
@@ -4468,8 +4473,8 @@ static INLINE void rflac_read_pcm_frames_s16__decode_left_side__sse2(
__m128i side = _mm_slli_epi32(_mm_loadu_si128((const __m128i*)pInputSamples1 + i), shift1);
__m128i right = _mm_sub_epi32(left, side);
left = _mm_srai_epi32(left, 16);
right = _mm_srai_epi32(right, 16);
left = rflac__mm_round_s16_epi32(left);
right = rflac__mm_round_s16_epi32(right);
_mm_storeu_si128((__m128i*)(pOutputSamples + i*8), rflac__mm_packs_interleaved_epi32(left, right));
}
@@ -4479,11 +4484,8 @@ static INLINE void rflac_read_pcm_frames_s16__decode_left_side__sse2(
uint32_t side = pInputSamples1U32[i] << shift1;
uint32_t right = left - side;
left >>= 16;
right >>= 16;
pOutputSamples[i*2+0] = (int16_t)left;
pOutputSamples[i*2+1] = (int16_t)right;
pOutputSamples[i*2+0] = rflac__s16_round(left);
pOutputSamples[i*2+1] = rflac__s16_round(right);
}
}
#endif
@@ -4509,14 +4511,14 @@ static INLINE void rflac_read_pcm_frames_s16__decode_left_side__neon(
uint32x4_t side = vshlq_u32(vld1q_u32(pInputSamples1U32 + i*4), shift1_4);
uint32x4_t right = vsubq_u32(left, side);
left = vshrq_n_u32(left, 16);
right = vshrq_n_u32(right, 16);
left = vreinterpretq_u32_s32(vrshrq_n_s32(vreinterpretq_s32_u32(left), 16));
right = vreinterpretq_u32_s32(vrshrq_n_s32(vreinterpretq_s32_u32(right), 16));
{
/* interleaving store; replaces vzip + contiguous store */
uint16x4x2_t v;
v.val[0] = vmovn_u32(left);
v.val[1] = vmovn_u32(right);
v.val[0] = vreinterpret_u16_s16(vqmovn_s32(vreinterpretq_s32_u32(left)));
v.val[1] = vreinterpret_u16_s16(vqmovn_s32(vreinterpretq_s32_u32(right)));
vst2_u16((uint16_t*)pOutputSamples + i*8, v);
}
}
@@ -4527,11 +4529,8 @@ static INLINE void rflac_read_pcm_frames_s16__decode_left_side__neon(
uint32_t side = pInputSamples1U32[i] << shift1;
uint32_t right = left - side;
left >>= 16;
right >>= 16;
pOutputSamples[i*2+0] = (int16_t)left;
pOutputSamples[i*2+1] = (int16_t)right;
pOutputSamples[i*2+0] = rflac__s16_round(left);
pOutputSamples[i*2+1] = rflac__s16_round(right);
}
}
#endif
@@ -4585,24 +4584,14 @@ static INLINE void rflac_read_pcm_frames_s16__decode_right_side__scalar(
uint32_t left2 = right2 + side2;
uint32_t left3 = right3 + side3;
left0 >>= 16;
left1 >>= 16;
left2 >>= 16;
left3 >>= 16;
right0 >>= 16;
right1 >>= 16;
right2 >>= 16;
right3 >>= 16;
pOutputSamples[i*8+0] = (int16_t)left0;
pOutputSamples[i*8+1] = (int16_t)right0;
pOutputSamples[i*8+2] = (int16_t)left1;
pOutputSamples[i*8+3] = (int16_t)right1;
pOutputSamples[i*8+4] = (int16_t)left2;
pOutputSamples[i*8+5] = (int16_t)right2;
pOutputSamples[i*8+6] = (int16_t)left3;
pOutputSamples[i*8+7] = (int16_t)right3;
pOutputSamples[i*8+0] = rflac__s16_round(left0);
pOutputSamples[i*8+1] = rflac__s16_round(right0);
pOutputSamples[i*8+2] = rflac__s16_round(left1);
pOutputSamples[i*8+3] = rflac__s16_round(right1);
pOutputSamples[i*8+4] = rflac__s16_round(left2);
pOutputSamples[i*8+5] = rflac__s16_round(right2);
pOutputSamples[i*8+6] = rflac__s16_round(left3);
pOutputSamples[i*8+7] = rflac__s16_round(right3);
}
for (i = (frameCount4 << 2); i < frameCount; ++i)
@@ -4611,11 +4600,8 @@ static INLINE void rflac_read_pcm_frames_s16__decode_right_side__scalar(
uint32_t right = pInputSamples1U32[i] << shift1;
uint32_t left = right + side;
left >>= 16;
right >>= 16;
pOutputSamples[i*2+0] = (int16_t)left;
pOutputSamples[i*2+1] = (int16_t)right;
pOutputSamples[i*2+0] = rflac__s16_round(left);
pOutputSamples[i*2+1] = rflac__s16_round(right);
}
}
@@ -4638,8 +4624,8 @@ static INLINE void rflac_read_pcm_frames_s16__decode_right_side__sse2(
__m128i right = _mm_slli_epi32(_mm_loadu_si128((const __m128i*)pInputSamples1 + i), shift1);
__m128i left = _mm_add_epi32(right, side);
left = _mm_srai_epi32(left, 16);
right = _mm_srai_epi32(right, 16);
left = rflac__mm_round_s16_epi32(left);
right = rflac__mm_round_s16_epi32(right);
_mm_storeu_si128((__m128i*)(pOutputSamples + i*8), rflac__mm_packs_interleaved_epi32(left, right));
}
@@ -4649,11 +4635,8 @@ static INLINE void rflac_read_pcm_frames_s16__decode_right_side__sse2(
uint32_t right = pInputSamples1U32[i] << shift1;
uint32_t left = right + side;
left >>= 16;
right >>= 16;
pOutputSamples[i*2+0] = (int16_t)left;
pOutputSamples[i*2+1] = (int16_t)right;
pOutputSamples[i*2+0] = rflac__s16_round(left);
pOutputSamples[i*2+1] = rflac__s16_round(right);
}
}
#endif
@@ -4679,14 +4662,14 @@ static INLINE void rflac_read_pcm_frames_s16__decode_right_side__neon(
uint32x4_t right = vshlq_u32(vld1q_u32(pInputSamples1U32 + i*4), shift1_4);
uint32x4_t left = vaddq_u32(right, side);
left = vshrq_n_u32(left, 16);
right = vshrq_n_u32(right, 16);
left = vreinterpretq_u32_s32(vrshrq_n_s32(vreinterpretq_s32_u32(left), 16));
right = vreinterpretq_u32_s32(vrshrq_n_s32(vreinterpretq_s32_u32(right), 16));
{
/* interleaving store; replaces vzip + contiguous store */
uint16x4x2_t v;
v.val[0] = vmovn_u32(left);
v.val[1] = vmovn_u32(right);
v.val[0] = vreinterpret_u16_s16(vqmovn_s32(vreinterpretq_s32_u32(left)));
v.val[1] = vreinterpret_u16_s16(vqmovn_s32(vreinterpretq_s32_u32(right)));
vst2_u16((uint16_t*)pOutputSamples + i*8, v);
}
}
@@ -4696,11 +4679,8 @@ static INLINE void rflac_read_pcm_frames_s16__decode_right_side__neon(
uint32_t right = pInputSamples1U32[i] << shift1;
uint32_t left = right + side;
left >>= 16;
right >>= 16;
pOutputSamples[i*2+0] = (int16_t)left;
pOutputSamples[i*2+1] = (int16_t)right;
pOutputSamples[i*2+0] = rflac__s16_round(left);
pOutputSamples[i*2+1] = rflac__s16_round(right);
}
}
#endif
@@ -4775,24 +4755,14 @@ static INLINE void rflac_read_pcm_frames_s16__decode_mid_side__scalar(
temp2R = (mid2 - side2) << shift;
temp3R = (mid3 - side3) << shift;
temp0L >>= 16;
temp1L >>= 16;
temp2L >>= 16;
temp3L >>= 16;
temp0R >>= 16;
temp1R >>= 16;
temp2R >>= 16;
temp3R >>= 16;
pOutputSamples[i*8+0] = (int16_t)temp0L;
pOutputSamples[i*8+1] = (int16_t)temp0R;
pOutputSamples[i*8+2] = (int16_t)temp1L;
pOutputSamples[i*8+3] = (int16_t)temp1R;
pOutputSamples[i*8+4] = (int16_t)temp2L;
pOutputSamples[i*8+5] = (int16_t)temp2R;
pOutputSamples[i*8+6] = (int16_t)temp3L;
pOutputSamples[i*8+7] = (int16_t)temp3R;
pOutputSamples[i*8+0] = rflac__s16_round(temp0L);
pOutputSamples[i*8+1] = rflac__s16_round(temp0R);
pOutputSamples[i*8+2] = rflac__s16_round(temp1L);
pOutputSamples[i*8+3] = rflac__s16_round(temp1R);
pOutputSamples[i*8+4] = rflac__s16_round(temp2L);
pOutputSamples[i*8+5] = rflac__s16_round(temp2R);
pOutputSamples[i*8+6] = rflac__s16_round(temp3L);
pOutputSamples[i*8+7] = rflac__s16_round(temp3R);
}
}
else
@@ -4833,24 +4803,14 @@ static INLINE void rflac_read_pcm_frames_s16__decode_mid_side__scalar(
temp2R = ((int32_t)(mid2 - side2) >> 1);
temp3R = ((int32_t)(mid3 - side3) >> 1);
temp0L >>= 16;
temp1L >>= 16;
temp2L >>= 16;
temp3L >>= 16;
temp0R >>= 16;
temp1R >>= 16;
temp2R >>= 16;
temp3R >>= 16;
pOutputSamples[i*8+0] = (int16_t)temp0L;
pOutputSamples[i*8+1] = (int16_t)temp0R;
pOutputSamples[i*8+2] = (int16_t)temp1L;
pOutputSamples[i*8+3] = (int16_t)temp1R;
pOutputSamples[i*8+4] = (int16_t)temp2L;
pOutputSamples[i*8+5] = (int16_t)temp2R;
pOutputSamples[i*8+6] = (int16_t)temp3L;
pOutputSamples[i*8+7] = (int16_t)temp3R;
pOutputSamples[i*8+0] = rflac__s16_round(temp0L);
pOutputSamples[i*8+1] = rflac__s16_round(temp0R);
pOutputSamples[i*8+2] = rflac__s16_round(temp1L);
pOutputSamples[i*8+3] = rflac__s16_round(temp1R);
pOutputSamples[i*8+4] = rflac__s16_round(temp2L);
pOutputSamples[i*8+5] = rflac__s16_round(temp2R);
pOutputSamples[i*8+6] = rflac__s16_round(temp3L);
pOutputSamples[i*8+7] = rflac__s16_round(temp3R);
}
}
@@ -4861,8 +4821,8 @@ static INLINE void rflac_read_pcm_frames_s16__decode_mid_side__scalar(
mid = (mid << 1) | (side & 0x01);
pOutputSamples[i*2+0] = (int16_t)(((uint32_t)((int32_t)(mid + side) >> 1) << unusedBitsPerSample) >> 16);
pOutputSamples[i*2+1] = (int16_t)(((uint32_t)((int32_t)(mid - side) >> 1) << unusedBitsPerSample) >> 16);
pOutputSamples[i*2+0] = rflac__s16_round((uint32_t)(((uint32_t)((int32_t)(mid + side) >> 1) << unusedBitsPerSample)));
pOutputSamples[i*2+1] = rflac__s16_round((uint32_t)(((uint32_t)((int32_t)(mid - side) >> 1) << unusedBitsPerSample)));
}
}
@@ -4892,8 +4852,8 @@ static INLINE void rflac_read_pcm_frames_s16__decode_mid_side__sse2(
left = _mm_srai_epi32(_mm_add_epi32(mid, side), 1);
right = _mm_srai_epi32(_mm_sub_epi32(mid, side), 1);
left = _mm_srai_epi32(left, 16);
right = _mm_srai_epi32(right, 16);
left = rflac__mm_round_s16_epi32(left);
right = rflac__mm_round_s16_epi32(right);
_mm_storeu_si128((__m128i*)(pOutputSamples + i*8), rflac__mm_packs_interleaved_epi32(left, right));
}
@@ -4904,8 +4864,8 @@ static INLINE void rflac_read_pcm_frames_s16__decode_mid_side__sse2(
mid = (mid << 1) | (side & 0x01);
pOutputSamples[i*2+0] = (int16_t)(((int32_t)(mid + side) >> 1) >> 16);
pOutputSamples[i*2+1] = (int16_t)(((int32_t)(mid - side) >> 1) >> 16);
pOutputSamples[i*2+0] = rflac__s16_round((uint32_t)(((int32_t)(mid + side) >> 1)));
pOutputSamples[i*2+1] = rflac__s16_round((uint32_t)(((int32_t)(mid - side) >> 1)));
}
}
else
@@ -4923,8 +4883,8 @@ static INLINE void rflac_read_pcm_frames_s16__decode_mid_side__sse2(
left = _mm_slli_epi32(_mm_add_epi32(mid, side), shift);
right = _mm_slli_epi32(_mm_sub_epi32(mid, side), shift);
left = _mm_srai_epi32(left, 16);
right = _mm_srai_epi32(right, 16);
left = rflac__mm_round_s16_epi32(left);
right = rflac__mm_round_s16_epi32(right);
_mm_storeu_si128((__m128i*)(pOutputSamples + i*8), rflac__mm_packs_interleaved_epi32(left, right));
}
@@ -4936,8 +4896,8 @@ static INLINE void rflac_read_pcm_frames_s16__decode_mid_side__sse2(
mid = (mid << 1) | (side & 0x01);
pOutputSamples[i*2+0] = (int16_t)(((mid + side) << shift) >> 16);
pOutputSamples[i*2+1] = (int16_t)(((mid - side) << shift) >> 16);
pOutputSamples[i*2+0] = rflac__s16_round((uint32_t)(((mid + side) << shift)));
pOutputSamples[i*2+1] = rflac__s16_round((uint32_t)(((mid - side) << shift)));
}
}
}
@@ -4972,14 +4932,14 @@ static INLINE void rflac_read_pcm_frames_s16__decode_mid_side__neon(
left = vshrq_n_s32(vreinterpretq_s32_u32(vaddq_u32(mid, side)), 1);
right = vshrq_n_s32(vreinterpretq_s32_u32(vsubq_u32(mid, side)), 1);
left = vshrq_n_s32(left, 16);
right = vshrq_n_s32(right, 16);
left = vrshrq_n_s32(left, 16);
right = vrshrq_n_s32(right, 16);
{
/* interleaving store; replaces vzip + contiguous store */
int16x4x2_t v;
v.val[0] = vmovn_s32(left);
v.val[1] = vmovn_s32(right);
v.val[0] = vqmovn_s32(left);
v.val[1] = vqmovn_s32(right);
vst2_s16(pOutputSamples + i*8, v);
}
}
@@ -4991,8 +4951,8 @@ static INLINE void rflac_read_pcm_frames_s16__decode_mid_side__neon(
mid = (mid << 1) | (side & 0x01);
pOutputSamples[i*2+0] = (int16_t)(((int32_t)(mid + side) >> 1) >> 16);
pOutputSamples[i*2+1] = (int16_t)(((int32_t)(mid - side) >> 1) >> 16);
pOutputSamples[i*2+0] = rflac__s16_round((uint32_t)(((int32_t)(mid + side) >> 1)));
pOutputSamples[i*2+1] = rflac__s16_round((uint32_t)(((int32_t)(mid - side) >> 1)));
}
}
else
@@ -5015,14 +4975,14 @@ static INLINE void rflac_read_pcm_frames_s16__decode_mid_side__neon(
left = vreinterpretq_s32_u32(vshlq_u32(vaddq_u32(mid, side), shift4));
right = vreinterpretq_s32_u32(vshlq_u32(vsubq_u32(mid, side), shift4));
left = vshrq_n_s32(left, 16);
right = vshrq_n_s32(right, 16);
left = vrshrq_n_s32(left, 16);
right = vrshrq_n_s32(right, 16);
{
/* interleaving store; replaces vzip + contiguous store */
int16x4x2_t v;
v.val[0] = vmovn_s32(left);
v.val[1] = vmovn_s32(right);
v.val[0] = vqmovn_s32(left);
v.val[1] = vqmovn_s32(right);
vst2_s16(pOutputSamples + i*8, v);
}
}
@@ -5034,8 +4994,8 @@ static INLINE void rflac_read_pcm_frames_s16__decode_mid_side__neon(
mid = (mid << 1) | (side & 0x01);
pOutputSamples[i*2+0] = (int16_t)(((mid + side) << shift) >> 16);
pOutputSamples[i*2+1] = (int16_t)(((mid - side) << shift) >> 16);
pOutputSamples[i*2+0] = rflac__s16_round((uint32_t)(((mid + side) << shift)));
pOutputSamples[i*2+1] = rflac__s16_round((uint32_t)(((mid - side) << shift)));
}
}
}
@@ -5084,29 +5044,19 @@ static INLINE void rflac_read_pcm_frames_s16__decode_independent_stereo__scalar(
uint32_t tempR2 = pInputSamples1U32[i*4+2] << shift1;
uint32_t tempR3 = pInputSamples1U32[i*4+3] << shift1;
tempL0 >>= 16;
tempL1 >>= 16;
tempL2 >>= 16;
tempL3 >>= 16;
tempR0 >>= 16;
tempR1 >>= 16;
tempR2 >>= 16;
tempR3 >>= 16;
pOutputSamples[i*8+0] = (int16_t)tempL0;
pOutputSamples[i*8+1] = (int16_t)tempR0;
pOutputSamples[i*8+2] = (int16_t)tempL1;
pOutputSamples[i*8+3] = (int16_t)tempR1;
pOutputSamples[i*8+4] = (int16_t)tempL2;
pOutputSamples[i*8+5] = (int16_t)tempR2;
pOutputSamples[i*8+6] = (int16_t)tempL3;
pOutputSamples[i*8+7] = (int16_t)tempR3;
pOutputSamples[i*8+0] = rflac__s16_round(tempL0);
pOutputSamples[i*8+1] = rflac__s16_round(tempR0);
pOutputSamples[i*8+2] = rflac__s16_round(tempL1);
pOutputSamples[i*8+3] = rflac__s16_round(tempR1);
pOutputSamples[i*8+4] = rflac__s16_round(tempL2);
pOutputSamples[i*8+5] = rflac__s16_round(tempR2);
pOutputSamples[i*8+6] = rflac__s16_round(tempL3);
pOutputSamples[i*8+7] = rflac__s16_round(tempR3);
}
for (i = (frameCount4 << 2); i < frameCount; ++i) {
pOutputSamples[i*2+0] = (int16_t)((pInputSamples0U32[i] << shift0) >> 16);
pOutputSamples[i*2+1] = (int16_t)((pInputSamples1U32[i] << shift1) >> 16);
pOutputSamples[i*2+0] = rflac__s16_round((uint32_t)((pInputSamples0U32[i] << shift0)));
pOutputSamples[i*2+1] = rflac__s16_round((uint32_t)((pInputSamples1U32[i] << shift1)));
}
}
@@ -5127,8 +5077,8 @@ static INLINE void rflac_read_pcm_frames_s16__decode_independent_stereo__sse2(
__m128i left = _mm_slli_epi32(_mm_loadu_si128((const __m128i*)pInputSamples0 + i), shift0);
__m128i right = _mm_slli_epi32(_mm_loadu_si128((const __m128i*)pInputSamples1 + i), shift1);
left = _mm_srai_epi32(left, 16);
right = _mm_srai_epi32(right, 16);
left = rflac__mm_round_s16_epi32(left);
right = rflac__mm_round_s16_epi32(right);
/* At this point we have results. We can now pack and interleave these
* into a single __m128i object and then store the in the output buffer. */
@@ -5136,8 +5086,8 @@ static INLINE void rflac_read_pcm_frames_s16__decode_independent_stereo__sse2(
}
for (i = (frameCount4 << 2); i < frameCount; ++i) {
pOutputSamples[i*2+0] = (int16_t)((pInputSamples0U32[i] << shift0) >> 16);
pOutputSamples[i*2+1] = (int16_t)((pInputSamples1U32[i] << shift1) >> 16);
pOutputSamples[i*2+0] = rflac__s16_round((uint32_t)((pInputSamples0U32[i] << shift0)));
pOutputSamples[i*2+1] = rflac__s16_round((uint32_t)((pInputSamples1U32[i] << shift1)));
}
}
#endif
@@ -5164,22 +5114,22 @@ static INLINE void rflac_read_pcm_frames_s16__decode_independent_stereo__neon(
left = vreinterpretq_s32_u32(vshlq_u32(vld1q_u32(pInputSamples0U32 + i*4), shift0_4));
right = vreinterpretq_s32_u32(vshlq_u32(vld1q_u32(pInputSamples1U32 + i*4), shift1_4));
left = vshrq_n_s32(left, 16);
right = vshrq_n_s32(right, 16);
left = vrshrq_n_s32(left, 16);
right = vrshrq_n_s32(right, 16);
{
/* interleaving store; replaces vzip + contiguous store */
int16x4x2_t v;
v.val[0] = vmovn_s32(left);
v.val[1] = vmovn_s32(right);
v.val[0] = vqmovn_s32(left);
v.val[1] = vqmovn_s32(right);
vst2_s16(pOutputSamples + i*8, v);
}
}
for (i = (frameCount4 << 2); i < frameCount; ++i)
{
pOutputSamples[i*2+0] = (int16_t)((pInputSamples0U32[i] << shift0) >> 16);
pOutputSamples[i*2+1] = (int16_t)((pInputSamples1U32[i] << shift1) >> 16);
pOutputSamples[i*2+0] = rflac__s16_round((uint32_t)((pInputSamples0U32[i] << shift0)));
pOutputSamples[i*2+1] = rflac__s16_round((uint32_t)((pInputSamples1U32[i] << shift1)));
}
}
#endif
@@ -5219,12 +5169,12 @@ static void rflac_read_pcm_frames_s16__decode_left_side__wide(rflac* pFlac,
for (i = 0; i < frameCount; ++i)
{
int64_t left = (int64_t)pInLeft[i] << shiftL;
int64_t side = pInSide[i] << shiftS;
int64_t left = (int64_t)((uint64_t)(int64_t)pInLeft[i] << shiftL);
int64_t side = (int64_t)((uint64_t)pInSide[i] << shiftS);
int64_t right = left - side;
pOutputSamples[i*2+0] = (int16_t)(left >> 16);
pOutputSamples[i*2+1] = (int16_t)(right >> 16);
pOutputSamples[i*2+0] = rflac__s16_round((uint32_t)(left));
pOutputSamples[i*2+1] = rflac__s16_round((uint32_t)(right));
}
}
@@ -5238,12 +5188,12 @@ static void rflac_read_pcm_frames_s16__decode_right_side__wide(rflac* pFlac,
for (i = 0; i < frameCount; ++i)
{
int64_t side = pInSide[i] << shiftS;
int64_t right = (int64_t)pInRight[i] << shiftR;
int64_t side = (int64_t)((uint64_t)pInSide[i] << shiftS);
int64_t right = (int64_t)((uint64_t)(int64_t)pInRight[i] << shiftR);
int64_t left = right + side;
pOutputSamples[i*2+0] = (int16_t)(left >> 16);
pOutputSamples[i*2+1] = (int16_t)(right >> 16);
pOutputSamples[i*2+0] = rflac__s16_round((uint32_t)(left));
pOutputSamples[i*2+1] = rflac__s16_round((uint32_t)(right));
}
}
@@ -5257,17 +5207,17 @@ static void rflac_read_pcm_frames_s16__decode_mid_side__wide(rflac* pFlac,
for (i = 0; i < frameCount; ++i)
{
int64_t mid = (int64_t)pInMid[i] << shiftM;
int64_t side = pInSide[i] << shiftS;
int64_t mid = (int64_t)((uint64_t)(int64_t)pInMid[i] << shiftM);
int64_t side = (int64_t)((uint64_t)pInSide[i] << shiftS);
int64_t left;
int64_t right;
mid = (mid << 1) | (side & 0x01);
mid = (int64_t)((uint64_t)mid << 1) | (side & 0x01);
left = (mid + side) >> 1;
right = (mid - side) >> 1;
pOutputSamples[i*2+0] = (int16_t)(left >> 16);
pOutputSamples[i*2+1] = (int16_t)(right >> 16);
pOutputSamples[i*2+0] = rflac__s16_round((uint32_t)(left));
pOutputSamples[i*2+1] = rflac__s16_round((uint32_t)(right));
}
}
@@ -5282,8 +5232,8 @@ static void rflac_read_pcm_frames_f32__decode_left_side__wide(rflac* pFlac,
for (i = 0; i < frameCount; ++i)
{
int64_t left = (int64_t)pInLeft[i] << shiftL;
int64_t side = pInSide[i] << shiftS;
int64_t left = (int64_t)((uint64_t)(int64_t)pInLeft[i] << shiftL);
int64_t side = (int64_t)((uint64_t)pInSide[i] << shiftS);
int64_t right = left - side;
pOutputSamples[i*2+0] = (int32_t)left * factor;
@@ -5302,8 +5252,8 @@ static void rflac_read_pcm_frames_f32__decode_right_side__wide(rflac* pFlac,
for (i = 0; i < frameCount; ++i)
{
int64_t side = pInSide[i] << shiftS;
int64_t right = (int64_t)pInRight[i] << shiftR;
int64_t side = (int64_t)((uint64_t)pInSide[i] << shiftS);
int64_t right = (int64_t)((uint64_t)(int64_t)pInRight[i] << shiftR);
int64_t left = right + side;
pOutputSamples[i*2+0] = (int32_t)left * factor;
@@ -5322,12 +5272,12 @@ static void rflac_read_pcm_frames_f32__decode_mid_side__wide(rflac* pFlac,
for (i = 0; i < frameCount; ++i)
{
int64_t mid = (int64_t)pInMid[i] << shiftM;
int64_t side = pInSide[i] << shiftS;
int64_t mid = (int64_t)((uint64_t)(int64_t)pInMid[i] << shiftM);
int64_t side = (int64_t)((uint64_t)pInSide[i] << shiftS);
int64_t left;
int64_t right;
mid = (mid << 1) | (side & 0x01);
mid = (int64_t)((uint64_t)mid << 1) | (side & 0x01);
left = (mid + side) >> 1;
right = (mid - side) >> 1;
@@ -5413,7 +5363,7 @@ static uint64_t rflac_read_pcm_frames_s16(rflac* pFlac, uint64_t framesToRead,
unsigned int j;
for (j = 0; j < channelCount; ++j) {
int32_t sampleS32 = (int32_t)((uint32_t)(pFlac->currentFLACFrame.subframes[j].pSamplesS32[iFirstPCMFrame + i]) << (unusedBitsPerSample + pFlac->currentFLACFrame.subframes[j].wastedBitsPerSample));
pBufferOut[(i*channelCount)+j] = (int16_t)(sampleS32 >> 16);
pBufferOut[(i*channelCount)+j] = rflac__s16_round((uint32_t)(sampleS32));
}
}
}
@@ -6875,12 +6825,19 @@ int rflac_process(rflac_t *f, size_t *read, size_t *wrote)
}
int rflac_seek(rflac_t *f, uint64_t frame, uint64_t *byte_offset)
{
uint64_t point_frame;
return rflac_seek_point(f, frame, byte_offset, &point_frame);
}
int rflac_seek_point(rflac_t *f, uint64_t frame, uint64_t *byte_offset,
uint64_t *point_frame)
{
uint32_t i;
uint32_t best = 0;
int found = 0;
if (!f || !f->dec || !byte_offset)
if (!f || !f->dec || !byte_offset || !point_frame)
return RFLAC_PROCESS_ERROR;
if (!f->dec->pSeekpoints || !f->dec->seekpointCount)
return RFLAC_PROCESS_ERROR;
@@ -6905,6 +6862,7 @@ int rflac_seek(rflac_t *f, uint64_t frame, uint64_t *byte_offset)
*byte_offset = f->dec->firstFLACFramePosInBytes
+ f->dec->pSeekpoints[best].flacFrameOffset;
*point_frame = f->dec->pSeekpoints[best].firstPCMFrame;
return RFLAC_PROCESS_NEXT;
}
+1 -63
View File
@@ -494,7 +494,7 @@ static void image_texture_clear(struct texture_image *img)
img->fp16 = false;
}
static bool image_texture_load_buffer_probe(data_transfer_t *keep,
bool image_texture_load_buffer_probe(data_transfer_t *keep,
bool *kept, struct texture_image *out_img,
enum image_type_enum type, const void *ptr, size_t len,
const image_texture_request_t *req,
@@ -543,56 +543,6 @@ bool image_texture_load_buffer_request(struct texture_image *out_img,
should_abort, abort_ud, NULL);
}
bool image_texture_load_request(struct texture_image *out_img,
const char *path, const image_texture_request_t *req,
bool (*should_abort)(void *ud), void *ud)
{
return image_texture_load_request_ex(out_img, path, req,
should_abort, ud, NULL);
}
bool image_texture_load_request_ex(struct texture_image *out_img,
const char *path, const image_texture_request_t *req,
bool (*should_abort)(void *ud), void *ud, int *png_probe)
{
enum image_type_enum type = image_texture_get_type(path);
if (png_probe)
*png_probe = -1;
if (type != IMAGE_TYPE_NONE)
{
/* The synchronous read rides the data_transfer prefix spine
* like every other loader: filestream/VFS routing (overlays
* and driver assets from archive members or content://
* documents), 64-bit lengths, the hardware guard behind the
* read, and honest short-read detection. A zero budget fills
* to completion in one blocking call, which is this API's
* contract. */
struct data_transfer *dt = data_transfer_open_prefix(path, 0);
if (dt)
{
size_t file_len = 0;
const uint8_t *ptr = NULL;
bool kept = false;
bool loaded;
data_transfer_iterate(dt, 0);
ptr = data_transfer_ptr(dt, &file_len);
loaded = data_transfer_complete(dt) && ptr && file_len
&& image_texture_load_buffer_probe(dt, &kept, out_img, type,
ptr, file_len, req, should_abort, ud, png_probe);
if (!kept)
data_transfer_free(dt);
if (loaded)
return true;
}
}
if (out_img)
image_texture_clear(out_img);
return false;
}
/* The request the plain loads carry: the caller's image, whose
* ->pix10 asks for 10-bit on the way in */
static void image_texture_request_of(const struct texture_image *img,
@@ -604,10 +554,6 @@ static void image_texture_request_of(const struct texture_image *img,
req->want_compressed = true;
}
bool image_texture_load(struct texture_image *out_img, const char *path)
{
return image_texture_load_ex(out_img, path, NULL, NULL);
}
void image_texture_free(struct texture_image *img)
{
@@ -827,11 +773,3 @@ bool image_texture_load_buffer(struct texture_image *out_img,
buffer_len, &req, NULL, NULL);
}
bool image_texture_load_ex(struct texture_image *out_img,
const char *path, bool (*should_abort)(void *ud), void *ud)
{
image_texture_request_t req;
image_texture_request_of(out_img, &req);
return image_texture_load_request(out_img, path, &req,
should_abort, ud);
}
@@ -25,6 +25,7 @@
#include <boolean.h>
#include <formats/image.h>
#include <formats/data_transfer.h>
#include <streams/image_texture_file.h>
#ifdef HAVE_GCD
#include <dispatch/dispatch.h>
#elif defined(HAVE_THREADS)
+153 -28
View File
@@ -88,10 +88,49 @@ struct rtga
* process() and nothing below changes behaviour. */
uint8_t *true_end;
size_t avail_req; /* frontier asked for, clamped at IDLE */
/* rtga_decode_rows: where the rows go, the row being written (NULL
* until the row's first pixel), and rtga's own row, kept for the
* next image */
const struct rtga_rows *rows;
uint32_t *dst;
uint32_t *own_row;
unsigned own_row_width;
bool avail_set;
bool need_more; /* last slice stopped at the wall */
};
/* Row y of the image: the surface's, or with rtga_decode_rows the
* caller's, or rtga's own when the caller names none. */
static uint32_t *rtga_row(rtga_t *tga, int y)
{
uint8_t *p;
if (!tga->rows)
return tga->output_image + (size_t)y * (size_t)tga->width;
p = tga->rows->row ? tga->rows->row(tga->rows->user, (unsigned)y) : NULL;
return p ? (uint32_t*)p : tga->own_row;
}
/* Row y is written. A row handed out by rtga_decode_rows holds bytes
* in order, which the pixel words are on a little-endian host. */
static void rtga_row_done(rtga_t *tga, int y, uint32_t *dst)
{
if (!tga->rows)
return;
#ifdef MSB_FIRST
{
int col;
for (col = 0; col < tga->width; col++)
{
uint32_t v = dst[col];
dst[col] = (v >> 24) | ((v >> 8) & 0xFF00u)
| ((v << 8) & 0xFF0000u) | (v << 24);
}
}
#endif
if (tga->rows->done)
tga->rows->done(tga->rows->user, (unsigned)y, (const uint8_t*)dst);
}
static INLINE uint8_t rtga_get8(rtga_context *s)
{
if (s->img_buffer < s->img_buffer_end)
@@ -209,7 +248,7 @@ static void rtga_indexed_pixels(rtga_t *tga, int npix)
{
rtga_context ctx;
rtga_context *s = &ctx;
uint32_t *output = tga->output_image;
uint32_t *dst = tga->dst;
const uint32_t *p32 = tga->pal32;
int width = tga->width;
int height = tga->height;
@@ -237,15 +276,19 @@ static void rtga_indexed_pixels(rtga_t *tga, int npix)
&& s->img_buffer >= s->img_buffer_end)
break;
dst_row = inverted ? (height - 1 - cur_row) : cur_row;
output[(size_t)dst_row * (size_t)width + (size_t)cur_col] =
p32[rtga_get8(s)];
if (!dst)
dst = rtga_row(tga, dst_row);
dst[cur_col] = p32[rtga_get8(s)];
if (++cur_col >= width)
{
rtga_row_done(tga, dst_row, dst);
dst = NULL;
cur_col = 0;
++cur_row;
}
}
tga->dst = dst;
tga->pixel_i = i;
tga->cur_col = cur_col;
tga->cur_row = cur_row;
@@ -343,11 +386,33 @@ static bool rtga_begin(rtga_t *tga, bool supports_rgba)
if (tga_width > 0x4000 || tga_height > 0x4000)
return false;
#endif
output = (uint32_t*)malloc(
(size_t)tga_width * (size_t)tga_height * sizeof(uint32_t));
if (!output)
return false;
tga->output_image = output;
tga->width = tga_width;
tga->height = tga_height;
if (tga->rows)
{
if ( tga->rows->size
&& !tga->rows->size(tga->rows->user,
(unsigned)tga_width, (unsigned)tga_height))
return false;
if ((unsigned)tga_width > tga->own_row_width)
{
uint32_t *row = (uint32_t*)realloc(tga->own_row,
(size_t)tga_width * sizeof(uint32_t));
if (!row)
return false;
tga->own_row = row;
tga->own_row_width = (unsigned)tga_width;
}
}
else
{
output = (uint32_t*)malloc(
(size_t)tga_width * (size_t)tga_height * sizeof(uint32_t));
if (!output)
return false;
tga->output_image = output;
}
tga->dst = NULL;
/* skip to the data's starting position (offset usually = 0) */
rtga_skip(s, tga_offset);
@@ -439,7 +504,6 @@ static void rtga_fast_rows(rtga_t *tga, int nrows)
* decoder. */
rtga_context ctx;
rtga_context *s = &ctx;
uint32_t *output = tga->output_image;
int tga_width = tga->width;
int tga_height = tga->height;
int tga_comp = tga->comp;
@@ -458,15 +522,13 @@ static void rtga_fast_rows(rtga_t *tga, int nrows)
for (; row < last; ++row)
{
int dst_row = tga_inverted ? (tga_height - 1 - row) : row;
/* size_t index so dst_row * tga_width does not overflow
* signed int for a legitimate 65535 x 65535 image - the
* same widening the indexed path below already does. */
uint32_t *dst = output + (size_t)dst_row * (size_t)tga_width;
uint32_t *dst;
int bytes_needed = tga_width * tga_comp;
int col;
if (s->img_buffer + bytes_needed > s->img_buffer_end)
break;
dst = rtga_row(tga, dst_row);
if (tga_comp == 4)
{
@@ -535,6 +597,7 @@ static void rtga_fast_rows(rtga_t *tga, int nrows)
}
s->img_buffer += bytes_needed;
rtga_row_done(tga, dst_row, dst);
}
/* The body's only early exit is the truncation check, so stopping
@@ -563,8 +626,22 @@ static void rtga_fast_rows(rtga_t *tga, int nrows)
return;
}
if (row < last)
if (row < last && tga->rows)
{
uint32_t fill = (tga_comp == 4) ? 0u : 0xFF000000u;
for (; row < tga_height; ++row)
{
int dst_row = tga_inverted ? (tga_height - 1 - row) : row;
uint32_t *dst = rtga_row(tga, dst_row);
int col;
for (col = 0; col < tga_width; col++)
dst[col] = fill;
rtga_row_done(tga, dst_row, dst);
}
}
else if (row < last)
{
uint32_t *output = tga->output_image;
size_t n = (size_t)(tga_height - row) * (size_t)tga_width;
uint32_t fill = (tga_comp == 4) ? 0u : 0xFF000000u;
uint32_t *p = tga_inverted
@@ -592,7 +669,7 @@ static void rtga_generic_pixels(rtga_t *tga, int npix)
* decoder. */
rtga_context ctx;
rtga_context *s = &ctx;
uint32_t *output = tga->output_image;
uint32_t *dst = tga->dst;
int tga_width = tga->width;
int tga_height = tga->height;
int tga_comp = tga->comp;
@@ -711,14 +788,16 @@ static void rtga_generic_pixels(rtga_t *tga, int npix)
| ((uint32_t)g << 8) | (uint32_t)b;
/* Write to correct position using tracked row/col
* (avoids per-pixel division and modulo). Use size_t for
* the index so dst_row * tga_width does not overflow
* signed int for a legitimate 65535 x 65535 image. */
* (avoids per-pixel division and modulo). */
dst_row = tga_inverted ? (tga_height - 1 - cur_row) : cur_row;
output[(size_t)dst_row * (size_t)tga_width + (size_t)cur_col] = pixel;
if (!dst)
dst = rtga_row(tga, dst_row);
dst[cur_col] = pixel;
if (++cur_col >= tga_width)
{
rtga_row_done(tga, dst_row, dst);
dst = NULL;
cur_col = 0;
++cur_row;
}
@@ -726,6 +805,7 @@ static void rtga_generic_pixels(rtga_t *tga, int npix)
--RLE_count;
}
tga->dst = dst;
tga->pixel_i = i;
tga->cur_col = cur_col;
tga->cur_row = cur_row;
@@ -737,17 +817,14 @@ static void rtga_generic_pixels(rtga_t *tga, int npix)
(void)tga_height;
}
/* Abandon an in-flight decode. The END path clears output_image
* first, transferring ownership, so this only frees what nobody
* received. */
/* Abandon an in-flight decode, or one rtga_begin() refused after
* making its surface. The END path clears output_image first,
* transferring ownership, so this only frees what nobody received. */
static void rtga_proc_reset(rtga_t *rtga)
{
if (rtga->phase != RTGA_PHASE_IDLE)
{
free(rtga->output_image);
rtga->output_image = NULL;
}
rtga->phase = RTGA_PHASE_IDLE;
free(rtga->output_image);
rtga->output_image = NULL;
rtga->phase = RTGA_PHASE_IDLE;
}
/* Smallest prefix that rtga_begin can parse: the 18-byte header plus
@@ -937,6 +1014,53 @@ int rtga_process_image(rtga_t *rtga, void **buf_data,
return IMAGE_PROCESS_END;
}
bool rtga_decode_rows(rtga_t *rtga, const uint8_t *data, size_t len,
bool rgba, const struct rtga_rows *rows,
unsigned *width, unsigned *height)
{
bool ok;
if (width)
*width = 0;
if (height)
*height = 0;
if (!rtga || !data || !rows || len < RTGA_MIN_HEADER
|| len > (size_t)INT_MAX)
return false;
rtga_proc_reset(rtga);
rtga->buff_data = (uint8_t*)data;
rtga->s.img_buffer = (uint8_t*)data;
rtga->s.img_buffer_original = (uint8_t*)data;
rtga->s.img_buffer_end = (uint8_t*)data + len;
rtga->true_end = rtga->s.img_buffer_end;
rtga->avail_set = false;
rtga->avail_req = 0;
rtga->need_more = false;
rtga->rows = rows;
rtga->width = 0;
rtga->height = 0;
ok = rtga_begin(rtga, rgba);
if (width)
*width = (unsigned)rtga->width;
if (height)
*height = (unsigned)rtga->height;
if (ok)
{
if (rtga->phase == RTGA_PHASE_FAST)
rtga_fast_rows(rtga, rtga->height);
else if (rtga->phase == RTGA_PHASE_INDEXED)
rtga_indexed_pixels(rtga, rtga->pixel_count);
else
rtga_generic_pixels(rtga, rtga->pixel_count);
}
rtga->rows = NULL;
rtga->dst = NULL;
rtga->phase = RTGA_PHASE_IDLE;
rtga->buff_data = NULL;
return ok;
}
bool rtga_set_buf_ptr(rtga_t *rtga, void *data)
{
if (!rtga)
@@ -957,6 +1081,7 @@ void rtga_free(rtga_t *rtga)
/* A finished decode handed its surface to the caller; an abandoned
* one still holds a partial surface. */
rtga_proc_reset(rtga);
free(rtga->own_row);
free(rtga);
}
+4
View File
@@ -4814,7 +4814,11 @@ static int start_decoder_setup(vorb *f)
if (sparse)
{
if (c->sorted_entries == 0)
{
setup_temp_free(f, mults,
sizeof(mults[0]) * c->lookup_values);
goto skip;
}
c->multiplicands = (rvorbis_codetype *) setup_malloc(f,
sizeof(c->multiplicands[0]) * len * c->dimensions);
} else
+154 -30
View File
@@ -853,7 +853,15 @@ static bool rslc_pp_expand_token(struct rslc_pp *pp, const struct rslc_tok *t,
if (!m)
return true;
if (sym->flags & RSLC_SYM_BUSY)
{
/* Its own name inside its own expansion: painted, so it is never
* a macro again however often those tokens are scanned from here
* on - as an argument prescanned and then rescanned in the body
* it was substituted into is. Both callers hold the token they
* go on to emit. */
((struct rslc_tok*)t)->flags |= RSLC_TF_NOEXPAND;
return true;
}
if (m->function_like)
{
@@ -1327,6 +1335,13 @@ static bool rslc_pp_version(struct rslc_pp *pp, uint32_t line)
}
}
if (ok && pp->c->version != 450 && pp->c->version != 460
#ifdef RSLC_EXTENDED
/* an emulator's own stages say 420 to 440 as well and mean
* the same dialect: the layout qualifiers they need came
* with 420 */
&& !(!pp->c->es && (pp->c->version == 420 || pp->c->version == 430
|| pp->c->version == 440))
#endif
&& !(pp->c->es && (pp->c->version == 310 || pp->c->version == 320)))
{
rslc_err(pp->c, "unsupported #version", NULL, line);
@@ -1767,6 +1782,7 @@ struct rslc_quals
bool has_storage;
bool push_constant;
bool std140;
bool std430; /* said so: RSLC_EXTENDED honours it on a uniform block */
bool is_uniform;
bool is_const;
bool is_in;
@@ -3051,11 +3067,12 @@ static bool rslc_p_eval(struct rslc_parser *p, uint32_t e, struct rslc_cval *out
* 2 3 4 vec2 vec3 vec4; i2.. ivec, u2.. uvec, b2.. bvec
* S sampler2D
* A N Z sampler2DArray, sampler2DMSArray, samplerBuffer
* J K image2D, image2DArray; g the gvec4 of the image's base,
* matched exactly
* D1 D3 DC DR sampler1D, sampler3D, samplerCube, samplerCubeArray
* J K L image2D, image2DArray, image2DMS; g the gvec4 of the
* image's base, matched exactly
* D1 D3 DC DR DM sampler1D, sampler3D, samplerCube, samplerCubeArray,
* sampler2DMS
* H1 H2 HA HC HR the shadow sampler1D, 2D, 2DArray, Cube, CubeArray
* P subpassInput
* P W subpassInput, subpassInputMS
* (RSLC_EXTENDED; their overloads are absent otherwise)
* M any matrix; Q square matrix; T transposed M result
* X any scalar or vector, same type as other X
@@ -3132,7 +3149,7 @@ static const struct rslc_bsig rslc_bsigs[] = {
{ RSLC_BFN_BITFIELDREVERSE, "bitfieldReverse", "I:I;U:U" },
{ RSLC_BFN_BITCOUNT, "bitCount", "I:I;U:I" },
{ RSLC_BFN_FINDLSB, "findLSB", "I:I;U:I" }, { RSLC_BFN_FINDMSB, "findMSB", "I:I;U:I" },
{ RSLC_BFN_TEXTURESIZE, "textureSize", "S i:i2" RSLC_X(";A i:i3;N:i3;Z:i"
{ RSLC_BFN_TEXTURESIZE, "textureSize", "S i:i2" RSLC_X(";A i:i3;N:i3;Z:i;DM:i2"
";D1 i:i;D3 i:i3;DC i:i2;DR i:i3;H1 i:i;H2 i:i2;HA i:i3;HC i:i2;HR i:i3") },
{ RSLC_BFN_TEXTUREQUERYLOD, "textureQueryLod", "S 2:2" RSLC_X(";A 2:2"
";D1 f:2;D3 3:2;DC 3:2;DR 3:2;H1 f:2;H2 2:2;HA 2:2;HC 3:2;HR 3:2") },
@@ -3147,7 +3164,7 @@ static const struct rslc_bsig rslc_bsigs[] = {
{ RSLC_BFN_TEXTUREOFFSET, "textureOffset", "S 2 i2:4;S 2 i2 f:4" RSLC_X(";A 3 i2:4;A 3 i2 f:4"
";D1 f i:4;D1 f i f:4;D3 3 i3:4;D3 3 i3 f:4"
";H1 3 i:f;H1 3 i f:f;H2 3 i2:f;H2 3 i2 f:f;HA 4 i2:f") },
{ RSLC_BFN_TEXELFETCH, "texelFetch", "S i2 i:4" RSLC_X(";A i3 i:4;N i3 i:4;Z i:4;D1 i i:4;D3 i3 i:4") },
{ RSLC_BFN_TEXELFETCH, "texelFetch", "S i2 i:4" RSLC_X(";A i3 i:4;N i3 i:4;Z i:4;D1 i i:4;D3 i3 i:4;DM i2 i:4") },
{ RSLC_BFN_TEXELFETCHOFFSET, "texelFetchOffset", "S i2 i i2:4" },
{ RSLC_BFN_TEXTUREPROJOFFSET, "textureProjOffset", "S 3 i2:4;S 4 i2:4;S 3 i2 f:4;S 4 i2 f:4" },
{ RSLC_BFN_TEXTURELODOFFSET, "textureLodOffset", "S 2 f i2:4" RSLC_X(";A 3 f i2:4"
@@ -3185,10 +3202,11 @@ static const struct rslc_bsig rslc_bsigs[] = {
{ RSLC_BFN_MEMORYBARRIERBUFFER, "memoryBarrierBuffer", ":v" },
{ RSLC_BFN_MEMORYBARRIERIMAGE, "memoryBarrierImage", ":v" },
{ RSLC_BFN_GROUPMEMORYBARRIER, "groupMemoryBarrier", ":v" },
{ RSLC_BFN_IMAGESTORE, "imageStore", "J i2 g:v;K i3 g:v" },
{ RSLC_BFN_IMAGELOAD, "imageLoad", "J i2:4;K i3:4" },
{ RSLC_BFN_IMAGESIZE, "imageSize", "J:i2;K:i3" },
{ RSLC_BFN_SUBPASSLOAD, "subpassLoad", "P:4" },
{ RSLC_BFN_IMAGESTORE, "imageStore", "J i2 g:v;K i3 g:v;L i2 i g:v" },
{ RSLC_BFN_IMAGELOAD, "imageLoad", "J i2:4;K i3:4;L i2 i:4" },
{ RSLC_BFN_IMAGESIZE, "imageSize", "J:i2;K:i3;L:i2" },
{ RSLC_BFN_SUBPASSLOAD, "subpassLoad", "P:4;W i:4" },
{ RSLC_BFN_TEXTURESAMPLES, "textureSamples", "DM:i;N:i" },
{ RSLC_BFN_SUBGROUPELECT, "subgroupElect", ":b" },
{ RSLC_BFN_SUBGROUPMIN, "subgroupMin", "F:F;I:I;U:U" },
{ RSLC_BFN_SUBGROUPMAX, "subgroupMax", "F:F;I:I;U:U" }
@@ -3284,20 +3302,22 @@ static bool rslc_bsig_match_tok(struct rslc_parser *p, const char *tok, unsigned
else if (len == 2 && (tok[0] == 'D' || tok[0] == 'H'))
{
unsigned dim = tok[1] == '1' ? RSLC_SDIM_1D
: (tok[1] == '2' || tok[1] == 'A') ? RSLC_SDIM_2D
: (tok[1] == '2' || tok[1] == 'A' || tok[1] == 'M') ? RSLC_SDIM_2D
: tok[1] == '3' ? RSLC_SDIM_3D : RSLC_SDIM_CUBE;
unsigned fl = ((tok[1] == 'A' || tok[1] == 'R') ? RSLC_SX_ARRAY : 0)
| (tok[1] == 'M' ? RSLC_SX_MS : 0)
| (tok[0] == 'H' ? RSLC_SX_SHADOW : 0);
if (t->kind != RSLC_TY_SAMPLERX || t->rows != dim || t->cols != fl)
return false;
*sbase = t->base;
return true;
}
else if (len == 1 && tok[0] == 'P')
return t->kind == RSLC_TY_SUBPASSINPUT;
else if (len == 1 && (tok[0] == 'J' || tok[0] == 'K'))
else if (len == 1 && (tok[0] == 'P' || tok[0] == 'W'))
return t->kind == RSLC_TY_SUBPASSINPUT && t->cols == (tok[0] == 'W' ? 2u : 1u);
else if (len == 1 && (tok[0] == 'J' || tok[0] == 'K' || tok[0] == 'L'))
{
if (t->kind != (tok[0] == 'J' ? RSLC_TY_IMAGE2D : RSLC_TY_IMAGE2DARRAY))
if (t->kind != (tok[0] == 'J' ? RSLC_TY_IMAGE2D
: tok[0] == 'K' ? RSLC_TY_IMAGE2DARRAY : RSLC_TY_IMAGE2DMS))
return false;
*sbase = t->base;
return true;
@@ -3391,7 +3411,7 @@ static const struct rslc_type *rslc_bsig_param_type(struct rslc_parser *p,
if (len == 1 && (tok[0] == 'S' || tok[0] == 'A' || tok[0] == 'N' || tok[0] == 'Z'))
return actual;
#ifdef RSLC_EXTENDED
if (len == 1 && (tok[0] == 'J' || tok[0] == 'K' || tok[0] == 'P'))
if (len == 1 && (tok[0] == 'J' || tok[0] == 'K' || tok[0] == 'L' || tok[0] == 'P' || tok[0] == 'W'))
return actual;
if (len == 2 && (tok[0] == 'D' || tok[0] == 'H'))
return actual;
@@ -3612,11 +3632,12 @@ static bool rslc_p_is_lvalue(struct rslc_parser *p, uint32_t e)
|| v->storage == RSLC_ST_MEMBER)
return false;
if (v->storage == RSLC_ST_PARAM && v->dir == RSLC_DIR_IN)
return true; /* in params are writable locals */
return !(v->quals & RSLC_VQ_PRECISE); /* in params are writable locals; const in ones are not */
if (v->storage == RSLC_ST_BUILTIN)
return v->builtin == RSLC_BI_POSITION || v->builtin == RSLC_BI_FRAGDEPTH
|| v->builtin == RSLC_BI_POINTSIZE || v->builtin == RSLC_BI_CLIPDISTANCE
|| v->builtin == RSLC_BI_CULLDISTANCE
|| v->builtin == RSLC_BI_FRAGSTENCILREF
|| (v->builtin == RSLC_BI_LAYER && v->dir == RSLC_DIR_OUT);
return true;
}
@@ -3975,7 +3996,8 @@ static uint32_t rslc_p_var_ref(struct rslc_parser *p, struct rslc_var *v, uint32
v->precision = rslc_p_default_prec(p, v->type, 0); /* as glslang: the default applies */
#ifdef RSLC_EXTENDED
if (v->storage == RSLC_ST_BUILTIN
&& (v->builtin == RSLC_BI_POINTCOORD || v->builtin == RSLC_BI_PRIMITIVEID))
&& (v->builtin == RSLC_BI_POINTCOORD || v->builtin == RSLC_BI_PRIMITIVEID
|| v->builtin == RSLC_BI_BARYCOORD || v->builtin == RSLC_BI_BARYCOORDNOPERSP))
p->c->prog.spirv_only = v->name->str;
#endif
e = rslc_p_mk(p, RSLC_OP_VAR, v->type, v->id, 0, 0, 0, line, v->precision);
@@ -4053,9 +4075,33 @@ static uint32_t rslc_p_primary(struct rslc_parser *p)
return rslc_p_mk(p, RSLC_OP_MEMBER, f->type, e, sym->decl->member, 0, 0, line,
rslc_prec_max(v->precision, f->precision));
}
#endif
#ifdef RSLC_EXTENDED
if ( sym->decl && sym->decl->kind == RSLC_DK_VAR
&& ((struct rslc_var*)sym->decl->ptr)->storage == RSLC_ST_BUILTIN
&& ((struct rslc_var*)sym->decl->ptr)->builtin == RSLC_BI_FRAGSTENCILREF
&& !p->c->prog.spirv_only)
p->c->prog.spirv_only = "gl_FragStencilRefARB";
#endif
if (sym->decl && sym->decl->kind == RSLC_DK_VAR)
return rslc_p_var_ref(p, (struct rslc_var*)sym->decl->ptr, line);
#ifdef RSLC_EXTENDED
/* gl_WorkGroupSize: the constant the local size layout before it
* made (1 where it named none) */
if ( p->c->stage == RSLC_STAGE_COMPUTE && !sym->decl
&& !strcmp(sym->str, "gl_WorkGroupSize"))
{
uint32_t args[3];
unsigned k;
for (k = 0; k < 3; k++)
{
uint32_t n = p->c->prog.cs_local[k];
if (!(args[k] = rslc_p_const_uint(p, n ? n : 1, line)))
return 0;
}
return rslc_p_construct(p, rslc_ty_vecn(p, RSLC_BASE_UINT, 3), args, 3, line);
}
#endif
rslc_err(p->c, "undeclared identifier ", sym->str, line);
return 0;
}
@@ -4185,6 +4231,17 @@ static uint32_t rslc_p_postfix_ops(struct rslc_parser *p, uint32_t e)
{
if (!rslc_p_expect(p, RSLC_P_LPAREN, "'('") || !rslc_p_expect(p, RSLC_P_RPAREN, "')'"))
return 0;
#ifdef RSLC_EXTENDED
if (!bt->array_len)
{
/* an unsized array: how long it is is the buffer's to say */
e = rslc_p_mk(p, RSLC_OP_UNARY, p->t_int, e, 0, 0, RSLC_UN_ARRAYLEN, line, 0);
if (!e)
return 0;
p->c->prog.spirv_only = "length() of an unsized array";
continue;
}
#endif
e = rslc_p_const_int(p, (int32_t)bt->array_len, line);
continue;
}
@@ -4621,6 +4678,11 @@ static bool rslc_p_image_format(const char *s, uint32_t len, uint8_t *fmt)
{ "rgba32f", RSLC_IMGFMT_RGBA32F }, { "rgba16f", RSLC_IMGFMT_RGBA16F },
{ "r32f", RSLC_IMGFMT_R32F }, { "rgba8", RSLC_IMGFMT_RGBA8 },
{ "rg8", RSLC_IMGFMT_RG8 }, { "r8", RSLC_IMGFMT_R8 },
{ "rgba8_snorm", RSLC_IMGFMT_RGBA8_SNORM }, { "rg32f", RSLC_IMGFMT_RG32F },
{ "rg16f", RSLC_IMGFMT_RG16F }, { "r11f_g11f_b10f", RSLC_IMGFMT_R11G11B10F },
{ "r16f", RSLC_IMGFMT_R16F }, { "rgba16", RSLC_IMGFMT_RGBA16 },
{ "rgb10_a2", RSLC_IMGFMT_RGB10_A2 }, { "rg16", RSLC_IMGFMT_RG16 },
{ "r16", RSLC_IMGFMT_R16 },
{ "rgba32i", RSLC_IMGFMT_RGBA32I }, { "rgba16i", RSLC_IMGFMT_RGBA16I },
{ "rgba8i", RSLC_IMGFMT_RGBA8I }, { "r32i", RSLC_IMGFMT_R32I },
{ "rgba32ui", RSLC_IMGFMT_RGBA32UI }, { "rgba16ui", RSLC_IMGFMT_RGBA16UI },
@@ -4658,7 +4720,7 @@ static bool rslc_p_layout(struct rslc_parser *p, struct rslc_quals *q)
else if (len == 6 && !memcmp(s, "offset", 6)) slot = &q->offset;
else if (len == 13 && !memcmp(s, "push_constant", 13)) q->push_constant = true;
else if (len == 6 && !memcmp(s, "std140", 6)) q->std140 = true;
else if (len == 6 && !memcmp(s, "std430", 6)) { }
else if (len == 6 && !memcmp(s, "std430", 6)) q->std430 = true;
else if (len == 12 && !memcmp(s, "column_major", 12)) { }
else if (len == 9 && !memcmp(s, "row_major", 9))
return rslc_p_fail(p, "row_major is not supported", NULL);
@@ -4751,6 +4813,12 @@ static bool rslc_p_quals(struct rslc_parser *p, struct rslc_quals *q)
case RSLC_KW_WRITEONLY: q->writeonly = true; break;
case RSLC_KW_RESTRICT:
case RSLC_KW_VOLATILE: break;
/* GL_EXT_fragment_shader_barycentric: the input is each
* vertex's value, an array of three */
case RSLC_KW_PERVERTEX:
q->interp = RSLC_INTERP_PERVERTEX;
p->c->prog.spirv_only = "pervertexEXT";
break;
#endif
default:
return true;
@@ -4819,13 +4887,32 @@ static const struct rslc_type *rslc_p_type_spec(struct rslc_parser *p,
ty = rslc_type_sampler2d(p->c, RSLC_BASE_UINT);
else if (k == RSLC_KW_SUBPASSINPUT)
ty = rslc_type_basic(p->c, RSLC_TY_SUBPASSINPUT, RSLC_BASE_FLOAT, 1, 1);
else if (k >= RSLC_KW_IMAGE2D && k <= RSLC_KW_UIMAGE2DARRAY)
else if (k == RSLC_KW_SUBPASSINPUTMS)
{
/* a multisampled attachment: cols 2 */
ty = rslc_type_basic(p->c, RSLC_TY_SUBPASSINPUT, RSLC_BASE_FLOAT, 1, 2);
#ifdef RSLC_EXTENDED
p->c->prog.spirv_only = "a multisampled subpass input";
#endif
}
else if (k >= RSLC_KW_IMAGE2D && k <= RSLC_KW_UIMAGE2DMS)
{
/* the format is the declaration's layout; it is applied there */
static const uint8_t bases[3] = { RSLC_BASE_FLOAT, RSLC_BASE_INT, RSLC_BASE_UINT };
unsigned i = k - RSLC_KW_IMAGE2D;
ty = rslc_type_basic(p->c, i < 3 ? RSLC_TY_IMAGE2D : RSLC_TY_IMAGE2DARRAY,
ty = rslc_type_basic(p->c, i < 3 ? RSLC_TY_IMAGE2D
: i < 6 ? RSLC_TY_IMAGE2DARRAY : RSLC_TY_IMAGE2DMS,
(enum rslc_base)bases[i % 3], 1, 0);
#ifdef RSLC_EXTENDED
if (i >= 6 && !p->c->prog.spirv_only)
p->c->prog.spirv_only = "a multisampled image";
#endif
}
else if (k >= RSLC_KW_SAMPLER2DMS && k <= RSLC_KW_USAMPLER2DMS)
{
static const uint8_t bases[3] = { RSLC_BASE_FLOAT, RSLC_BASE_INT, RSLC_BASE_UINT };
ty = rslc_type_basic(p->c, RSLC_TY_SAMPLERX,
(enum rslc_base)bases[k - RSLC_KW_SAMPLER2DMS], RSLC_SDIM_2D, RSLC_SX_MS);
}
else if (k >= RSLC_KW_SAMPLER2DARRAY && k <= RSLC_KW_USAMPLERBUFFER)
{
@@ -5013,7 +5100,7 @@ static bool rslc_p_starts_declaration(struct rslc_parser *p)
s = rslc_tok_sym(p->c, t);
if (s->kw >= RSLC_KW_CONST && s->kw <= RSLC_KW_STRUCT)
return true;
if (s->kw >= RSLC_KW_BUFFER && s->kw <= RSLC_KW_SHARED)
if (s->kw >= RSLC_KW_BUFFER && s->kw <= RSLC_KW_PERVERTEX)
return true; /* RSLC_EXTENDED: only seeded there */
if (s->kw >= RSLC_KW_VOID && s->kw <= RSLC_KW_LAST_TYPE)
{
@@ -5613,18 +5700,30 @@ static bool rslc_p_block_make(struct rslc_parser *p, const struct rslc_quals *q,
b->push = idx == 1;
b->std140 = idx == 0 || q->std140;
#ifdef RSLC_EXTENDED
/* a further uniform block is std140 as the first is; a storage
* buffer std430 unless it says otherwise, and only its last member
* may be an array of unstated length */
/* a further uniform block is std140 as the first is, unless it says
* std430 (GL_EXT_scalar_block_layout lets a uniform block say so, and
* its members are then placed as glslang places them); a storage
* buffer std430 unless it says otherwise. Only a block's last member
* may be an array of unstated length: a storage buffer's, or a
* uniform block's (GL_EXT_uniform_buffer_unsized_array), which only
* the SPIR-V destination takes. */
b->buffer = q->is_buffer;
b->readonly = q->readonly;
b->coherent = q->coherent;
if (idx >= 2 && !q->is_buffer)
b->std140 = true;
if (idx != 1 && !q->is_buffer && q->std430 && !q->std140)
b->std140 = false;
for (i = 0; i < nfields; i++)
if (fields[i].type->kind == RSLC_TY_ARRAY && !fields[i].type->array_len
&& (!q->is_buffer || i + 1 != nfields))
return rslc_p_fail(p, "only a storage buffer's last member may be unsized", NULL);
if (fields[i].type->kind == RSLC_TY_ARRAY && !fields[i].type->array_len)
{
if (i + 1 != nfields)
return rslc_p_fail(p, "only a block's last member may be unsized", NULL);
if (idx == 1)
return rslc_p_fail(p, "a push constant block's member may not be unsized", NULL);
if (!q->is_buffer && !p->c->prog.spirv_only)
p->c->prog.spirv_only = "an unsized array in a uniform block";
}
#endif
pr->has_block[idx] = true;
@@ -5706,6 +5805,18 @@ static bool rslc_p_block_decl(struct rslc_parser *p, struct rslc_quals *q,
fields[nfields].name = rslc_tok_sym(p->c, nt);
fields[nfields].line = nt->line;
fields[nfields].precision = rslc_p_default_prec(p, mt, fq.precision);
#ifdef RSLC_EXTENDED
/* layout(offset = n) on a member: it is placed there and the
* rest follow, as a fragment stage's half of a push block shared
* with the vertex stage starts past the vertex stage's. SPIR-V
* only: nothing printed says it. */
if (fq.offset != RSLC_UNSET)
{
fields[nfields].offset_p1 = fq.offset + 1;
if (!p->c->prog.spirv_only)
p->c->prog.spirv_only = "a block member's layout(offset)";
}
#endif
nfields++;
if (rslc_p_accept(p, RSLC_P_COMMA))
continue;
@@ -6189,11 +6300,15 @@ static struct rslc_var *rslc_p_var_make(struct rslc_parser *p, const struct rslc
rslc_p_fail(p, "an image must be a global uniform", NULL);
return NULL;
}
if (!q->img_fmt)
/* an image that is only written may leave its format out; the
* device then has to write without one */
if (!q->img_fmt && !q->writeonly)
{
rslc_p_fail(p, "an image needs its format in the layout", NULL);
rslc_p_fail(p, "an image that is read needs its format in the layout", NULL);
return NULL;
}
if (!q->img_fmt && !p->c->prog.spirv_only)
p->c->prog.spirv_only = "an image without a format";
/* rows carries the access: 1 read-write, 2 write only, 3 read only */
v->type = rslc_type_basic(p->c, (enum rslc_tkind)vt->kind, (enum rslc_base)vt->base,
q->writeonly ? RSLC_IMG_WRITEONLY : q->readonly ? RSLC_IMG_READONLY : RSLC_IMG_READWRITE,
@@ -6611,6 +6726,15 @@ static bool rslc_p_stage_builtins(struct rslc_parser *p)
|| !rslc_p_builtin_var(p, "gl_FrontFacing", p->t_bool, RSLC_BI_FRONTFACING, RSLC_DIR_IN))
return false;
#ifdef RSLC_EXTENDED
/* GL_ARB_shader_stencil_export: the stencil reference written by
* the stage */
if (!rslc_p_builtin_var(p, "gl_FragStencilRefARB", p->t_int, RSLC_BI_FRAGSTENCILREF, RSLC_DIR_OUT))
return false;
if ( !rslc_p_builtin_var(p, "gl_BaryCoordEXT", rslc_ty_vecn(p, RSLC_BASE_FLOAT, 3),
RSLC_BI_BARYCOORD, RSLC_DIR_IN)
|| !rslc_p_builtin_var(p, "gl_BaryCoordNoPerspEXT", rslc_ty_vecn(p, RSLC_BASE_FLOAT, 3),
RSLC_BI_BARYCOORDNOPERSP, RSLC_DIR_IN))
return false;
if (!rslc_p_builtin_var(p, "gl_SampleID", p->t_int, RSLC_BI_SAMPLEID, RSLC_DIR_IN)
|| !rslc_p_builtin_var(p, "gl_HelperInvocation", p->t_bool,
RSLC_BI_HELPERINVOCATION, RSLC_DIR_IN)
+299 -42
View File
@@ -427,9 +427,12 @@ static const char *const rslc_keywords[] = {
"samplerCubeArray", "isamplerCubeArray", "usamplerCubeArray",
"sampler1DShadow", "sampler2DShadow", "sampler2DArrayShadow",
"samplerCubeShadow", "samplerCubeArrayShadow",
"sampler2DMS", "isampler2DMS", "usampler2DMS",
"image2D", "iimage2D", "uimage2D", "image2DArray", "iimage2DArray", "uimage2DArray",
"subpassInput",
"buffer", "readonly", "writeonly", "coherent", "restrict", "volatile", "shared"
"image2DMS", "iimage2DMS", "uimage2DMS",
"subpassInput", "subpassInputMS",
"buffer", "readonly", "writeonly", "coherent", "restrict", "volatile", "shared",
"pervertexEXT"
#endif
};
@@ -984,6 +987,8 @@ uint32_t rslc_layout_field_offset(const struct rslc_type *t, uint32_t field,
uint32_t fa, fs, fst;
rslc_layout_of(t->fields[i].type, std140, &fa, &fs, &fst);
off = (off + fa - 1) / fa * fa;
if (t->fields[i].offset_p1)
off = t->fields[i].offset_p1 - 1;
if (i == field)
return off;
off += fs;
@@ -1209,6 +1214,12 @@ struct spv_const
uint32_t id;
};
struct spv_vconst
{
uint32_t type, id;
uint32_t c[4];
};
struct spv_var
{
uint32_t id; /* the OpVariable, or the constant for a folded const */
@@ -1216,6 +1227,7 @@ struct spv_var
uint32_t tid; /* type id of the variable's value */
uint8_t sc; /* storage class */
bool is_const; /* id is a constant value, not a pointer */
bool byval; /* a const in parameter: id is its value */
bool in_block; /* RSLC_ST_MEMBER / BLOCK: id is the block variable */
};
@@ -1235,6 +1247,10 @@ struct spv_emit
uint32_t *func_ids; /* by func id */
uint32_t *func_types;
struct spv_const *consts;
struct spv_vconst *vconsts; /* constant vectors made, by type and components */
uint32_t nvconsts, vconsts_cap;
struct spv_const *xptrs; /* pointer types to types outside tinfo: type, class, id */
uint32_t nxptrs, xptrs_cap;
struct rslc_wbuf entry_iface; /* interface ids for OpEntryPoint */
struct rslc_wbuf func_vars; /* OpVariable Function words, hoisted */
struct rslc_wbuf fntypes; /* interned OpTypeFunction: id, n, words[n] */
@@ -1252,6 +1268,8 @@ struct spv_emit
uint32_t entry_label;
bool need_imagequery;
bool need_storageimageextended;
bool need_storageimagemultisample;
bool need_storageimagewritewithoutformat;
bool need_inputattachment;
bool need_groupnonuniform;
bool need_groupnonuniformarith;
@@ -1262,6 +1280,8 @@ struct spv_emit
bool need_geometry; /* a pixel stage reading gl_Layer */
bool need_clipdistance;
bool writes_fragdepth; /* the entry point declares DepthReplacing */
bool need_barycentric; /* FragmentBarycentricKHR, with its extension */
bool writes_stencilref; /* and StencilRefReplacingEXT, with its extension */
bool need_culldistance;
bool in_main;
};
@@ -1625,7 +1645,7 @@ static uint32_t spv_type(struct spv_emit *e, const struct rslc_type *t)
w[0] = img; w[1] = s; w[2] = (uint32_t)t->rows - 1;
w[3] = (t->cols & RSLC_SX_SHADOW) ? 1 : 0;
w[4] = (t->cols & RSLC_SX_ARRAY) ? 1 : 0;
w[5] = 0; w[6] = 1; w[7] = 0;
w[5] = (t->cols & RSLC_SX_MS) ? 1 : 0; w[6] = 1; w[7] = 0;
if (t->rows == RSLC_SDIM_1D)
e->need_sampled1d = true;
if (t->rows == RSLC_SDIM_CUBE && (t->cols & RSLC_SX_ARRAY))
@@ -1639,6 +1659,7 @@ static uint32_t spv_type(struct spv_emit *e, const struct rslc_type *t)
}
case RSLC_TY_IMAGE2D:
case RSLC_TY_IMAGE2DARRAY:
case RSLC_TY_IMAGE2DMS:
{
/* a storage image: Sampled 2, its format from the layout (cols) */
uint32_t s = spv_type(e, rslc_type_basic(e->c, RSLC_TY_SCALAR, (enum rslc_base)t->base, 1, 1));
@@ -1646,9 +1667,14 @@ static uint32_t spv_type(struct spv_emit *e, const struct rslc_type *t)
return 0;
ti = &e->tinfo[t->id];
w[0] = id; w[1] = s; w[2] = 1 /* 2D */; w[3] = 0;
w[4] = t->kind == RSLC_TY_IMAGE2DARRAY; w[5] = 0; w[6] = 2; w[7] = t->cols;
if (t->cols == RSLC_IMGFMT_R8 || t->cols == RSLC_IMGFMT_RG8)
w[4] = t->kind == RSLC_TY_IMAGE2DARRAY; w[5] = t->kind == RSLC_TY_IMAGE2DMS;
w[6] = 2; w[7] = t->cols;
if (!t->cols)
e->need_storageimagewritewithoutformat = true;
else if (!RSLC_IMGFMT_IS_BASE(t->cols))
e->need_storageimageextended = true;
if (t->kind == RSLC_TY_IMAGE2DMS)
e->need_storageimagemultisample = true;
if (!spv_inst(e, SPV_SEC_TYPES, SpvOpTypeImage, w, 8))
return 0;
ti->img = id;
@@ -1661,7 +1687,7 @@ static uint32_t spv_type(struct spv_emit *e, const struct rslc_type *t)
if (!s)
return 0;
ti = &e->tinfo[t->id];
w[0] = id; w[1] = s; w[2] = 6; w[3] = 0; w[4] = 0; w[5] = 0; w[6] = 2; w[7] = 0;
w[0] = id; w[1] = s; w[2] = 6; w[3] = 0; w[4] = 0; w[5] = t->cols == 2; w[6] = 2; w[7] = 0;
if (!spv_inst(e, SPV_SEC_TYPES, SpvOpTypeImage, w, 8))
return 0;
ti->img = id;
@@ -1726,16 +1752,49 @@ static uint32_t spv_ptr(struct spv_emit *e, uint32_t tid, unsigned sc)
return 0;
return e->tinfo[i].ptr[sc];
}
/* A laid-out type or an ad-hoc one: make a fresh pointer type. */
/* A laid-out type or an ad-hoc one: one pointer type for each it is
* asked of, kept by the type's id and the storage class. */
for (i = 0; i < e->nxptrs; i++)
if (e->xptrs[i].type == tid && e->xptrs[i].bits == sc)
return e->xptrs[i].id;
{
uint32_t p = spv_id(e);
if (!spv_inst3(e, SPV_SEC_TYPES, SpvOpTypePointer, p, sc, tid))
return 0;
if (e->nxptrs == e->xptrs_cap)
{
uint32_t ncap = e->xptrs_cap ? e->xptrs_cap * 2 : 32;
struct spv_const *nv = (struct spv_const*)rslc_arena_alloc(e->c, ncap * sizeof(*nv));
if (!nv)
return 0;
if (e->nxptrs)
memcpy(nv, e->xptrs, e->nxptrs * sizeof(*nv));
e->xptrs = nv;
e->xptrs_cap = ncap;
}
e->xptrs[e->nxptrs].type = tid;
e->xptrs[e->nxptrs].bits = sc;
e->xptrs[e->nxptrs].id = p;
e->nxptrs++;
return p;
}
}
/* Function parameters are pointers; samplers point at the uniform. */
/* A const in parameter is never written: it is handed over as its
* value, with no variable made and stored for it at the call and no
* load in the function (as glslang does). Opaque types stay pointers. */
static bool spv_param_byval(const struct rslc_var *v)
{
if (v->storage != RSLC_ST_PARAM || v->dir != RSLC_DIR_IN || !(v->quals & RSLC_VQ_PRECISE))
return false;
#ifdef RSLC_EXTENDED
if (RSLC_TY_IS_IMAGE(v->type->kind) || v->type->kind == RSLC_TY_SUBPASSINPUT)
return false;
#endif
return !RSLC_TY_IS_SAMPLER(v->type->kind);
}
/* The other function parameters are pointers; samplers point at the uniform. */
static unsigned spv_param_sc(const struct rslc_type *t)
{
#ifdef RSLC_EXTENDED
@@ -1852,11 +1911,38 @@ static uint32_t spv_const_composite(struct spv_emit *e, const struct rslc_type *
}
else
return 0;
/* a vector made before is that one again */
if (t->kind == RSLC_TY_VEC)
{
uint32_t i;
for (i = 0; i < e->nvconsts; i++)
if (e->vconsts[i].type == tid && !memcmp(e->vconsts[i].c, words + 2, n * sizeof(uint32_t)))
return e->vconsts[i].id;
}
id = spv_id(e);
words[0] = tid;
words[1] = id;
if (!spv_inst(e, SPV_SEC_TYPES, SpvOpConstantComposite, words, 2 + n))
return 0;
if (t->kind == RSLC_TY_VEC)
{
if (e->nvconsts == e->vconsts_cap)
{
uint32_t ncap = e->vconsts_cap ? e->vconsts_cap * 2 : 32;
struct spv_vconst *nv = (struct spv_vconst*)rslc_arena_alloc(e->c, ncap * sizeof(*nv));
if (!nv)
return 0;
if (e->nvconsts)
memcpy(nv, e->vconsts, e->nvconsts * sizeof(*nv));
e->vconsts = nv;
e->vconsts_cap = ncap;
}
e->vconsts[e->nvconsts].type = tid;
e->vconsts[e->nvconsts].id = id;
memset(e->vconsts[e->nvconsts].c, 0, sizeof(e->vconsts[e->nvconsts].c));
memcpy(e->vconsts[e->nvconsts].c, words + 2, n * sizeof(uint32_t));
e->nvconsts++;
}
return id;
}
@@ -2345,10 +2431,52 @@ static uint32_t spv_temp_of(struct spv_emit *e, uint32_t val, const struct rslc_
return tmp;
}
/* The steps from a variable to the part of it an lvalue names, kept
* until the whole expression is walked so that they go out as one
* access chain: a[i].b.c is one instruction and one pointer type, not
* three of each. */
#define SPV_CHAIN_MAX 12
struct spv_chain
{
uint32_t idx[SPV_CHAIN_MAX];
unsigned n;
uint32_t tid; /* what the last step points at */
};
/* The chain so far, from root; nothing kept after it. */
static uint32_t spv_chain_emit(struct spv_emit *e, uint32_t root, struct spv_chain *ch, unsigned sc)
{
uint32_t w[3 + SPV_CHAIN_MAX];
uint32_t ptrt = spv_ptr(e, ch->tid, sc);
if (!ptrt)
return 0;
w[0] = ptrt; w[1] = spv_id(e); w[2] = root;
memcpy(w + 3, ch->idx, ch->n * sizeof(uint32_t));
if (!spv_fn_inst(e, SpvOpAccessChain, w, 3 + ch->n))
return 0;
ch->n = 0;
return w[1];
}
static bool spv_chain_push(struct spv_emit *e, uint32_t *root, struct spv_chain *ch, unsigned sc,
uint32_t idx, uint32_t tid)
{
if (ch->n == SPV_CHAIN_MAX)
{
*root = spv_chain_emit(e, *root, ch, sc);
if (!*root)
return false;
}
ch->idx[ch->n++] = idx;
ch->tid = tid;
return true;
}
/* The pointer for an lvalue expression: an access chain from a
* variable. sc receives its storage class, laid whether the pointee
* is a laid-out block type. */
static uint32_t spv_lvalue(struct spv_emit *e, uint32_t node, unsigned *sc, bool *laid, bool *std140)
static uint32_t spv_lvalue_root(struct spv_emit *e, uint32_t node, unsigned *sc, bool *laid,
bool *std140, struct spv_chain *ch)
{
const struct rslc_node *n = rslc_node_at(e->c, node);
switch (n->op)
@@ -2392,6 +2520,28 @@ static uint32_t spv_lvalue(struct spv_emit *e, uint32_t node, unsigned *sc, bool
}
return sv->ptr;
}
if (e->vars[v->id].byval)
{
/* a value indexed through a pointer: a variable holding it */
*sc = SPV_SC_FUNCTION; *laid = false; *std140 = false;
return spv_temp_of(e, e->vars[v->id].id, v->type);
}
if (v->storage == RSLC_ST_MEMBER)
{
/* a bare block member: the block, and the member's index */
const struct rslc_blockdecl *b = &e->c->prog.blocks[v->block];
uint32_t idx = spv_const_int(e, (int32_t)v->member);
uint32_t root = e->block_var[v->block];
uint32_t ftid;
*sc = b->push ? SPV_SC_PUSHCONSTANT : SPV_SC_UNIFORM;
*laid = true;
*std140 = b->std140;
e->member_active[v->block][v->member] = 1;
ftid = spv_type_laid(e, b->type->fields[v->member].type, b->std140);
if (!ftid || !idx || !spv_chain_push(e, &root, ch, *sc, idx, ftid))
return 0;
return root;
}
return spv_var_ptr(e, v, sc, laid, std140);
}
case RSLC_OP_MEMBER:
@@ -2400,8 +2550,7 @@ static uint32_t spv_lvalue(struct spv_emit *e, uint32_t node, unsigned *sc, bool
const struct rslc_node *bn = rslc_node_at(e->c, n->a);
uint32_t base;
uint32_t idx;
uint32_t tid, ptrt, id;
uint32_t w[4];
uint32_t tid;
if (n->op == RSLC_OP_MEMBER && bn->op == RSLC_OP_VAR
&& e->c->prog.vars[bn->a]->storage == RSLC_ST_BLOCK)
{
@@ -2415,7 +2564,7 @@ static uint32_t spv_lvalue(struct spv_emit *e, uint32_t node, unsigned *sc, bool
e->member_active[bv->block][n->b] = 1;
}
else
base = spv_lvalue(e, n->a, sc, laid, std140);
base = spv_lvalue_root(e, n->a, sc, laid, std140, ch);
if (!base)
return 0;
if (n->op == RSLC_OP_MEMBER)
@@ -2441,40 +2590,25 @@ static uint32_t spv_lvalue(struct spv_emit *e, uint32_t node, unsigned *sc, bool
#ifdef RSLC_EXTENDED
have_tid:
#endif
if (!tid)
if (!tid || !spv_chain_push(e, &base, ch, *sc, idx, tid))
return 0;
ptrt = spv_ptr(e, tid, *sc);
if (!ptrt)
return 0;
id = spv_id(e);
w[0] = ptrt; w[1] = id; w[2] = base; w[3] = idx;
if (!spv_fn_inst(e, SpvOpAccessChain, w, 4))
return 0;
return id;
return base;
}
case RSLC_OP_SWIZZLE:
{
/* single-component swizzle is an access chain */
unsigned cnt = n->aux & 3u;
uint32_t base, idx, tid, ptrt, id;
uint32_t w[4];
uint32_t base, idx, tid;
if (cnt != 1)
return 0;
base = spv_lvalue(e, n->a, sc, laid, std140);
base = spv_lvalue_root(e, n->a, sc, laid, std140, ch);
if (!base)
return 0;
idx = spv_const_int(e, (int32_t)((n->aux >> 2) & 3u));
tid = spv_type(e, n->type);
if (!idx || !tid)
if (!idx || !tid || !spv_chain_push(e, &base, ch, *sc, idx, tid))
return 0;
ptrt = spv_ptr(e, tid, *sc);
if (!ptrt)
return 0;
id = spv_id(e);
w[0] = ptrt; w[1] = id; w[2] = base; w[3] = idx;
if (!spv_fn_inst(e, SpvOpAccessChain, w, 4))
return 0;
return id;
return base;
}
default:
{
@@ -2488,6 +2622,18 @@ have_tid:
}
}
static uint32_t spv_lvalue(struct spv_emit *e, uint32_t node, unsigned *sc, bool *laid, bool *std140)
{
struct spv_chain ch;
uint32_t root;
ch.n = 0;
root = spv_lvalue_root(e, node, sc, laid, std140, &ch);
if (root && ch.n)
return spv_chain_emit(e, root, &ch, *sc);
return root;
}
/* Stores val into the lvalue node; multi-component swizzles become a
* load, insert, store. */
static bool spv_assign_to(struct spv_emit *e, uint32_t node, uint32_t val)
@@ -3147,7 +3293,8 @@ static uint32_t spv_builtin_node(struct spv_emit *e, const struct rslc_node *n)
return 0;
#ifdef RSLC_EXTENDED
/* multisampled and buffer images have no levels */
if (t0->kind == RSLC_TY_SAMPLER2DMSARRAY || t0->kind == RSLC_TY_SAMPLERBUFFER)
if ( t0->kind == RSLC_TY_SAMPLER2DMSARRAY || t0->kind == RSLC_TY_SAMPLERBUFFER
|| (t0->kind == RSLC_TY_SAMPLERX && (t0->cols & RSLC_SX_MS)))
return spv_r1(e, SpvOpImageQuerySize, tid, img, prec);
#endif
return spv_r2(e, SpvOpImageQuerySizeLod, tid, img, args[1], prec);
@@ -3173,7 +3320,8 @@ static uint32_t spv_builtin_node(struct spv_emit *e, const struct rslc_node *n)
/* a buffer has no level; a multisampled image takes the sample */
if (t0->kind == RSLC_TY_SAMPLERBUFFER)
return spv_image_op(e, SpvOpImageFetch, tid, img, args[1], 0, 0, NULL, 0, prec);
if (t0->kind == RSLC_TY_SAMPLER2DMSARRAY)
if ( t0->kind == RSLC_TY_SAMPLER2DMSARRAY
|| (t0->kind == RSLC_TY_SAMPLERX && (t0->cols & RSLC_SX_MS)))
{
ops[0] = args[2];
return spv_image_op(e, SpvOpImageFetch, tid, img, args[1], 0, SPV_IMG_SAMPLE, ops, 1, prec);
@@ -3376,13 +3524,28 @@ static uint32_t spv_builtin_node(struct spv_emit *e, const struct rslc_node *n)
}
case RSLC_BFN_IMAGESTORE:
{
uint32_t w[3];
uint32_t w[5];
w[0] = args[0]; w[1] = args[1]; w[2] = args[2];
if (nargs == 4)
{
/* a multisampled image: (image, coordinate, sample, texel) */
w[2] = args[3]; w[3] = SPV_IMG_SAMPLE; w[4] = args[2];
if (!spv_fn_inst(e, SpvOpImageWrite, w, 5))
return 0;
return tid;
}
if (!spv_fn_inst(e, SpvOpImageWrite, w, 3))
return 0;
return tid;
}
case RSLC_BFN_IMAGELOAD:
if (nargs == 3)
{
/* a multisampled image: the sample is an image operand */
uint32_t ops[1];
ops[0] = args[2];
return spv_image_op(e, SpvOpImageRead, tid, args[0], args[1], 0, SPV_IMG_SAMPLE, ops, 1, prec);
}
return spv_r2(e, SpvOpImageRead, tid, args[0], args[1], prec);
case RSLC_BFN_IMAGESIZE:
e->need_imagequery = true;
@@ -3395,8 +3558,23 @@ static uint32_t spv_builtin_node(struct spv_emit *e, const struct rslc_node *n)
rslc_type_basic(e->c, RSLC_TY_VEC, RSLC_BASE_INT, 2, 1), zero, &used);
if (!coord)
return 0;
if (nargs == 2)
{
/* a multisampled attachment: the sample is an image operand */
uint32_t ops[1];
ops[0] = args[1];
return spv_image_op(e, SpvOpImageRead, tid, args[0], coord, 0, SPV_IMG_SAMPLE, ops, 1, prec);
}
return spv_r2(e, SpvOpImageRead, tid, args[0], coord, prec);
}
case RSLC_BFN_TEXTURESAMPLES:
{
uint32_t img = spv_r1(e, SpvOpImage, e->tinfo[t0->id].img, args[0], 0);
if (!img)
return 0;
e->need_imagequery = true;
return spv_r1(e, 107 /* OpImageQuerySamples */, tid, img, prec);
}
case RSLC_BFN_SUBGROUPELECT:
{
/* subgroup scope; SPIR-V 1.3 */
@@ -3749,6 +3927,8 @@ static uint32_t spv_rvalue_impl(struct spv_emit *e, uint32_t node)
uint32_t ptr;
if (e->vars[v->id].is_const)
return spv_const_var(e, v);
if (e->vars[v->id].byval)
return e->vars[v->id].id;
ptr = spv_var_ptr(e, v, &sc, &laid, &std140);
if (!ptr)
return 0;
@@ -3763,7 +3943,8 @@ static uint32_t spv_rvalue_impl(struct spv_emit *e, uint32_t node)
|| rslc_node_at(e->c, n->b)->op == RSLC_OP_CONST;
bool base_is_ptr = base->op == RSLC_OP_VAR || base->op == RSLC_OP_MEMBER
|| base->op == RSLC_OP_INDEX || (base->op == RSLC_OP_SWIZZLE && (base->aux & 3u) == 1);
if (base->op == RSLC_OP_VAR && e->vars[e->c->prog.vars[base->a]->id].is_const)
if (base->op == RSLC_OP_VAR && (e->vars[e->c->prog.vars[base->a]->id].is_const
|| e->vars[e->c->prog.vars[base->a]->id].byval))
base_is_ptr = false;
if (!base_is_ptr && const_idx)
{
@@ -3841,6 +4022,13 @@ static uint32_t spv_rvalue_impl(struct spv_emit *e, uint32_t node)
return 0;
continue;
}
if (spv_param_byval(pv))
{
temps[i] = spv_rvalue(e, an);
if (!temps[i])
return 0;
continue;
}
temps[i] = spv_local(e, pv->type, NULL, pv->precision);
if (!temps[i])
return 0;
@@ -3887,6 +4075,37 @@ static uint32_t spv_rvalue_impl(struct spv_emit *e, uint32_t node)
return 0;
switch (n->aux)
{
#ifdef RSLC_EXTENDED
case RSLC_UN_ARRAYLEN:
{
/* OpArrayLength of the block and the member's index: the
* array is the last member of a buffer block, named bare
* or through the block's instance */
const struct rslc_var *v = NULL;
uint32_t member = 0, len, utid;
uint32_t w[4];
if (an->op == RSLC_OP_VAR && e->c->prog.vars[an->a]->storage == RSLC_ST_MEMBER)
{
v = e->c->prog.vars[an->a];
member = v->member;
}
else if (an->op == RSLC_OP_MEMBER && rslc_node_at(e->c, an->a)->op == RSLC_OP_VAR
&& e->c->prog.vars[rslc_node_at(e->c, an->a)->a]->storage == RSLC_ST_BLOCK)
{
v = e->c->prog.vars[rslc_node_at(e->c, an->a)->a];
member = an->b;
}
utid = spv_type(e, rslc_type_basic(e->c, RSLC_TY_SCALAR, RSLC_BASE_UINT, 1, 1));
if (!v || !utid)
return 0;
e->member_active[v->block][member] = 1;
len = spv_id(e);
w[0] = utid; w[1] = len; w[2] = e->block_var[v->block]; w[3] = member;
if (!spv_fn_inst(e, 68 /* OpArrayLength */, w, 4))
return 0;
return spv_r1(e, SpvOpBitcast, tid, len, 0);
}
#endif
case RSLC_UN_NEG:
{
uint32_t v = spv_rvalue(e, n->a);
@@ -3928,9 +4147,12 @@ static uint32_t spv_rvalue_impl(struct spv_emit *e, uint32_t node)
const struct rslc_type *t = an->type;
bool inc = n->aux == RSLC_UN_PREINC || n->aux == RSLC_UN_POSTINC;
bool post = n->aux == RSLC_UN_POSTINC || n->aux == RSLC_UN_POSTDEC;
if (!ptr)
/* no one pointer to it - several components of a vector
* (v.xy--): read as a value, written back as an assignment
* to the same place is */
if (!ptr && (e->c->failed || t->kind == RSLC_TY_MAT))
return 0;
old = spv_load(e, t, ptr, prec);
old = ptr ? spv_load(e, t, ptr, prec) : spv_rvalue(e, n->a);
if (!old)
return 0;
if (t->base == RSLC_BASE_FLOAT)
@@ -3951,7 +4173,7 @@ static uint32_t spv_rvalue_impl(struct spv_emit *e, uint32_t node)
}
else
nv = one ? spv_r2(e, spv_binop(t, inc ? RSLC_BIN_ADD : RSLC_BIN_SUB), tid, old, one, prec) : 0;
if (!nv || !spv_store(e, ptr, nv))
if (!nv || !(ptr ? spv_store(e, ptr, nv) : spv_assign_to(e, n->a, nv)))
return 0;
return post ? old : nv;
}
@@ -4413,7 +4635,8 @@ static bool spv_function(struct spv_emit *e, const struct rslc_func *f)
w[0] = 0; w[1] = rtid;
for (i = 0; i < f->nparams; i++)
{
w[2 + i] = spv_ptr_to(e, f->params[i]->type, spv_param_sc(f->params[i]->type));
w[2 + i] = spv_param_byval(f->params[i]) ? spv_type(e, f->params[i]->type)
: spv_ptr_to(e, f->params[i]->type, spv_param_sc(f->params[i]->type));
if (!w[2 + i])
return false;
}
@@ -4427,10 +4650,12 @@ static bool spv_function(struct spv_emit *e, const struct rslc_func *f)
for (i = 0; i < f->nparams; i++)
{
const struct rslc_var *pv = f->params[i];
uint32_t ptr = spv_ptr_to(e, pv->type, spv_param_sc(pv->type));
uint32_t ptr = spv_param_byval(pv) ? spv_type(e, pv->type)
: spv_ptr_to(e, pv->type, spv_param_sc(pv->type));
uint32_t pid = spv_id(e);
if (!ptr || !spv_inst2(e, SPV_SEC_FUNCS, SpvOpFunctionParameter, ptr, pid))
return false;
e->vars[pv->id].byval = spv_param_byval(pv);
e->vars[pv->id].id = pid;
e->vars[pv->id].sc = (uint8_t)spv_param_sc(pv->type);
if (pv->name && !spv_name(e, pid, pv->name->str))
@@ -4696,6 +4921,12 @@ static bool spv_global_var(struct spv_emit *e, const struct rslc_var *v)
return false;
if (v->interp == RSLC_INTERP_NOPERSPECTIVE && !spv_decorate(e, id, SPV_DEC_NOPERSPECTIVE))
return false;
if (v->interp == RSLC_INTERP_PERVERTEX)
{
if (!spv_decorate(e, id, 5285u /* PerVertexKHR */))
return false;
e->need_barycentric = true;
}
if ((v->quals & RSLC_VQ_CENTROID) && !spv_decorate(e, id, SPV_DEC_CENTROID))
return false;
if ((v->quals & RSLC_VQ_SAMPLE) && !spv_decorate(e, id, SPV_DEC_SAMPLE))
@@ -4776,6 +5007,9 @@ static bool spv_builtin_var(struct spv_emit *e, const struct rslc_var *v)
: v->builtin == RSLC_BI_LOCALINVOCATIONINDEX ? SPV_BI_LOCALINVOCATIONINDEX
: v->builtin == RSLC_BI_HELPERINVOCATION ? SPV_BI_HELPERINVOCATION
: v->builtin == RSLC_BI_POINTCOORD ? SPV_BI_POINTCOORD
: v->builtin == RSLC_BI_FRAGSTENCILREF ? 5014u /* FragStencilRefEXT */
: v->builtin == RSLC_BI_BARYCOORD ? 5286u /* BaryCoordKHR */
: v->builtin == RSLC_BI_BARYCOORDNOPERSP ? 5287u /* BaryCoordNoPerspKHR */
: v->builtin == RSLC_BI_PRIMITIVEID ? SPV_BI_PRIMITIVEID : SPV_BI_POSITION;
if (!ptr)
return false;
@@ -4784,6 +5018,10 @@ static bool spv_builtin_var(struct spv_emit *e, const struct rslc_var *v)
e->need_samplerateshading = true;
if (v->builtin == RSLC_BI_FRAGDEPTH)
e->writes_fragdepth = true;
if (v->builtin == RSLC_BI_FRAGSTENCILREF)
e->writes_stencilref = true;
if (v->builtin == RSLC_BI_BARYCOORD || v->builtin == RSLC_BI_BARYCOORDNOPERSP)
e->need_barycentric = true;
id = spv_id(e);
if (!spv_inst3(e, SPV_SEC_TYPES, SpvOpVariable, ptr, id, sc))
return false;
@@ -5183,6 +5421,10 @@ static bool rslc_emit_spirv(struct rslc_ctx *c, struct rslc_result *result)
return false;
if (e->need_clipdistance && !spv_inst1(e, SPV_SEC_CAPS, SpvOpCapability, SPV_CAP_CLIPDISTANCE))
return false;
if (e->writes_stencilref && !spv_inst1(e, SPV_SEC_CAPS, SpvOpCapability, 5013u /* StencilExportEXT */))
return false;
if (e->need_barycentric && !spv_inst1(e, SPV_SEC_CAPS, SpvOpCapability, 5284u /* FragmentBarycentricKHR */))
return false;
if (e->need_culldistance && !spv_inst1(e, SPV_SEC_CAPS, SpvOpCapability, SPV_CAP_CULLDISTANCE))
return false;
if (e->need_imagequery && !spv_inst1(e, SPV_SEC_CAPS, SpvOpCapability, SPV_CAP_IMAGEQUERY))
@@ -5190,6 +5432,12 @@ static bool rslc_emit_spirv(struct rslc_ctx *c, struct rslc_result *result)
if (e->need_storageimageextended &&
!spv_inst1(e, SPV_SEC_CAPS, SpvOpCapability, SPV_CAP_STORAGEIMAGEEXTENDED))
return false;
if (e->need_storageimagewritewithoutformat
&& !spv_inst1(e, SPV_SEC_CAPS, SpvOpCapability, 56u /* StorageImageWriteWithoutFormat */))
return false;
if (e->need_storageimagemultisample
&& !spv_inst1(e, SPV_SEC_CAPS, SpvOpCapability, 27u /* StorageImageMultisample */))
return false;
#ifdef RSLC_EXTENDED
if (e->need_inputattachment
&& !spv_inst1(e, SPV_SEC_CAPS, SpvOpCapability, SPV_CAP_INPUTATTACHMENT))
@@ -5237,6 +5485,12 @@ static bool rslc_emit_spirv(struct rslc_ctx *c, struct rslc_result *result)
return false;
}
#endif
if (e->writes_stencilref && !spv_inst_str(e, SPV_SEC_CAPS, SpvOpExtension, NULL, 0,
"SPV_EXT_shader_stencil_export", NULL, 0))
return false;
if (e->need_barycentric && !spv_inst_str(e, SPV_SEC_CAPS, SpvOpExtension, NULL, 0,
"SPV_KHR_fragment_shader_barycentric", NULL, 0))
return false;
if (!spv_inst_str(e, SPV_SEC_CAPS, SpvOpExtInstImport, &e->ext_id, 1, "GLSL.std.450", NULL, 0))
return false;
if (!spv_inst2(e, SPV_SEC_CAPS, SpvOpMemoryModel, 0 /* Logical */, 1 /* GLSL450 */))
@@ -5263,6 +5517,9 @@ static bool rslc_emit_spirv(struct rslc_ctx *c, struct rslc_result *result)
if (e->writes_fragdepth
&& !spv_inst2(e, SPV_SEC_ENTRY, SpvOpExecutionMode, main_id, 12u /* DepthReplacing */))
return false;
if (e->writes_stencilref
&& !spv_inst2(e, SPV_SEC_ENTRY, SpvOpExecutionMode, main_id, 5027u /* StencilRefReplacingEXT */))
return false;
#ifdef RSLC_EXTENDED
for (i = 0; i < c->prog.nxmodes; i += 1 + c->prog.xmodes[i])
{
+33 -4
View File
@@ -224,14 +224,17 @@ enum rslc_kw
/* RSLC_EXTENDED: the depth comparing ones */
RSLC_KW_SAMPLER1DSHADOW, RSLC_KW_SAMPLER2DSHADOW, RSLC_KW_SAMPLER2DARRAYSHADOW,
RSLC_KW_SAMPLERCUBESHADOW, RSLC_KW_SAMPLERCUBEARRAYSHADOW,
/* RSLC_EXTENDED: one multisampled image, not an array of them */
RSLC_KW_SAMPLER2DMS, RSLC_KW_ISAMPLER2DMS, RSLC_KW_USAMPLER2DMS,
/* RSLC_EXTENDED image types: float, int, uint of each */
RSLC_KW_IMAGE2D, RSLC_KW_IIMAGE2D, RSLC_KW_UIMAGE2D,
RSLC_KW_IMAGE2DARRAY, RSLC_KW_IIMAGE2DARRAY, RSLC_KW_UIMAGE2DARRAY,
RSLC_KW_IMAGE2DMS, RSLC_KW_IIMAGE2DMS, RSLC_KW_UIMAGE2DMS,
/* RSLC_EXTENDED: a fragment stage's input attachment */
RSLC_KW_SUBPASSINPUT,
RSLC_KW_SUBPASSINPUT, RSLC_KW_SUBPASSINPUTMS,
/* RSLC_EXTENDED qualifiers */
RSLC_KW_BUFFER, RSLC_KW_READONLY, RSLC_KW_WRITEONLY, RSLC_KW_COHERENT,
RSLC_KW_RESTRICT, RSLC_KW_VOLATILE, RSLC_KW_SHARED,
RSLC_KW_RESTRICT, RSLC_KW_VOLATILE, RSLC_KW_SHARED, RSLC_KW_PERVERTEX,
RSLC_KW_COUNT
};
@@ -260,6 +263,7 @@ enum rslc_tkind
/* RSLC_EXTENDED storage images; cols is the RSLC_IMGFMT_* */
RSLC_TY_IMAGE2D,
RSLC_TY_IMAGE2DARRAY,
RSLC_TY_IMAGE2DMS, /* multisampled: read and written a sample at a time; SPIR-V only */
/* RSLC_EXTENDED: a float input attachment */
RSLC_TY_SUBPASSINPUT,
/* RSLC_EXTENDED: any other sampler; rows is its RSLC_SDIM_*, cols its
@@ -273,8 +277,10 @@ enum rslc_tkind
#define RSLC_SDIM_CUBE 4
#define RSLC_SX_ARRAY 1u
#define RSLC_SX_SHADOW 2u
#define RSLC_SX_MS 4u /* sampler2DMS: 2D, fetched a sample at a time */
#define RSLC_TY_IS_IMAGE(k) ((k) == RSLC_TY_IMAGE2D || (k) == RSLC_TY_IMAGE2DARRAY)
#define RSLC_TY_IS_IMAGE(k) ((k) == RSLC_TY_IMAGE2D || (k) == RSLC_TY_IMAGE2DARRAY \
|| (k) == RSLC_TY_IMAGE2DMS)
/* A storage image type's rows: its access. */
#define RSLC_IMG_READWRITE 1
#define RSLC_IMG_WRITEONLY 2
@@ -286,6 +292,19 @@ enum rslc_tkind
#define RSLC_IMGFMT_RGBA16F 2
#define RSLC_IMGFMT_R32F 3
#define RSLC_IMGFMT_RGBA8 4
#define RSLC_IMGFMT_RGBA8_SNORM 5
#define RSLC_IMGFMT_RG32F 6
#define RSLC_IMGFMT_RG16F 7
#define RSLC_IMGFMT_R11G11B10F 8
#define RSLC_IMGFMT_R16F 9
#define RSLC_IMGFMT_RGBA16 10
#define RSLC_IMGFMT_RGB10_A2 11
#define RSLC_IMGFMT_RG16 12
#define RSLC_IMGFMT_R16 14
/* The formats every device with storage images has; the others need
* StorageImageExtendedFormats. */
#define RSLC_IMGFMT_IS_BASE(f) ((f) <= RSLC_IMGFMT_RGBA8_SNORM || ((f) >= RSLC_IMGFMT_RGBA32I \
&& (f) <= RSLC_IMGFMT_R32I) || ((f) >= RSLC_IMGFMT_RGBA32UI && (f) <= RSLC_IMGFMT_R32UI))
#define RSLC_IMGFMT_RG8 13
#define RSLC_IMGFMT_R8 15
#define RSLC_IMGFMT_RGBA32I 21
@@ -298,7 +317,7 @@ enum rslc_tkind
#define RSLC_IMGFMT_R32UI 33
/* The last keyword that names a type. */
#define RSLC_KW_LAST_TYPE RSLC_KW_SUBPASSINPUT
#define RSLC_KW_LAST_TYPE RSLC_KW_SUBPASSINPUTMS
/* Every sampler kind: sampler2D, and the RSLC_EXTENDED ones. */
#define RSLC_TY_IS_SAMPLER(k) ((k) == RSLC_TY_SAMPLER2D \
@@ -316,6 +335,10 @@ struct rslc_field
uint8_t interp; /* RSLC_EXTENDED: an in / out block member's */
uint8_t quals; /* RSLC_VQ_* of that member */
uint8_t pad;
uint32_t offset_p1; /* RSLC_EXTENDED, a block's member that
says layout(offset = n): n + 1, and
the members after it follow from
there; 0 when it does not */
};
struct rslc_type
@@ -364,6 +387,7 @@ enum rslc_storage
#define RSLC_INTERP_FLAT 1
#define RSLC_INTERP_NOPERSPECTIVE 2
#define RSLC_INTERP_SMOOTH 3
#define RSLC_INTERP_PERVERTEX 4 /* RSLC_EXTENDED: pervertexEXT, each vertex's value; SPIR-V only */
#define RSLC_VQ_CENTROID 1u
#define RSLC_VQ_SAMPLE 2u
@@ -425,6 +449,9 @@ struct rslc_var
#define RSLC_BI_HELPERINVOCATION 18 /* RSLC_EXTENDED, fragment */
#define RSLC_BI_POINTCOORD 19 /* RSLC_EXTENDED, fragment */
#define RSLC_BI_PRIMITIVEID 20 /* RSLC_EXTENDED, geometry gl_PrimitiveIDIn */
#define RSLC_BI_FRAGSTENCILREF 21 /* RSLC_EXTENDED, fragment out; SPIR-V only */
#define RSLC_BI_BARYCOORD 22 /* RSLC_EXTENDED, fragment in; SPIR-V only */
#define RSLC_BI_BARYCOORDNOPERSP 23
/* RSLC_EXTENDED: a geometry stage's primitives, in and out. */
#define RSLC_GS_POINTS 1
@@ -527,6 +554,7 @@ enum rslc_op
#define RSLC_UN_PREDEC 5
#define RSLC_UN_POSTINC 6
#define RSLC_UN_POSTDEC 7
#define RSLC_UN_ARRAYLEN 8 /* RSLC_EXTENDED: length() of a buffer block's unsized last member; SPIR-V only */
#define RSLC_BIN_NONE 0
#define RSLC_BIN_ADD 1
@@ -616,6 +644,7 @@ enum rslc_bfn
RSLC_BFN_MEMORYBARRIERBUFFER, RSLC_BFN_MEMORYBARRIERIMAGE, RSLC_BFN_GROUPMEMORYBARRIER,
RSLC_BFN_IMAGESTORE, RSLC_BFN_IMAGELOAD, RSLC_BFN_IMAGESIZE,
RSLC_BFN_SUBPASSLOAD, /* RSLC_EXTENDED, fragment */
RSLC_BFN_TEXTURESAMPLES, /* RSLC_EXTENDED: a multisampled sampler's count */
/* RSLC_EXTENDED: subgroup operations */
RSLC_BFN_SUBGROUPELECT, RSLC_BFN_SUBGROUPMIN, RSLC_BFN_SUBGROUPMAX,
RSLC_BFN_COUNT
+9 -32
View File
@@ -100,8 +100,8 @@ uint32_t cdfs_get_first_sector(cdfs_file_t* file);
* sector 16, assuming raw 2352-byte sectors. If the probe fails
* (e.g. a cooked 2048-byte image), stream_sector_size is left 0 and
* the caller must fill in stream_sector_size / stream_sector_header_size
* itself (the path adapters below derive them from the file size or
* the cue sheet). */
* itself (the path adapters in streams/cdfs_file.c derive them from the
* file size or the cue sheet). */
cdfs_track_t* cdfs_track_from_stream(
intfstream_t* stream,
unsigned first_sector_offset,
@@ -132,37 +132,14 @@ typedef struct cdfs_cue_info
bool cdfs_parse_cue(const char *cue_text, size_t len,
unsigned track_index, cdfs_cue_info_t *out);
/* --- Path adapters: open by filename, then wrap ------------------------ */
/* Sets @track's sector size and header size from its stream's length
* - 2352, 2048 or 2336 bytes a sector, whichever divides it - and
* leaves them 0 when none does. For a raw track whose ISO-9660 probe in
* cdfs_track_from_stream found nothing. */
void cdfs_track_sector_size_from_length(cdfs_track_t* track);
/* opens the specified track in a CD or virtual CD file - the resulting stream should be passed to
* cdfs_open_file to get access to a file within the CD.
*
* supported files:
* real CD - path will be in the form "cdrom://drive1.cue" or "cdrom://d:/drive.cue"
* bin/cue - path will point to the cue file
* chd - path will point to the chd file
*
* for bin/cue files, the following storage modes are supported:
* MODE2/2352
* MODE1/2352
* MODE1/2048 - untested
* MODE2/2336 - untested
*/
cdfs_track_t* cdfs_open_track(const char* path, unsigned int track_index);
/* opens the first data track in a CD or virtual CD file. see cdfs_open_track for supported file formats
*/
cdfs_track_t* cdfs_open_data_track(const char* path);
/* opens a raw track file for a CD or virtual CD.
*
* supported files:
* real CD - path will be in the form "cdrom://drive1-track01.bin" or "cdrom://d:/drive-track01.bin"
* BIN handles obtain their own validated geometry.
* bin - path will point to the bin file
* iso - path will point to the iso file
*/
cdfs_track_t* cdfs_open_raw_track(const char* path);
/* cdfs_open_track, cdfs_open_data_track and cdfs_open_raw_track, which
* open a track by filename, are in <streams/cdfs_file.h>. */
/* closes the CD or virtual CD track and frees the associated memory */
void cdfs_close_track(cdfs_track_t* track);
@@ -158,16 +158,22 @@ data_transfer_t *data_transfer_open_source(size_t len,
* NULL unless the transfer completed successfully. */
uint8_t *data_transfer_source_detach(data_transfer_t *dt, size_t *len);
data_transfer_t *data_transfer_open_window(const char *path, size_t keep);
/* Borrow a seekable source for the window's lifetime. The callback writes
* directly into final window storage, returning bytes read or -1 on error.
* A short read fails the operation. Closing the window does not close ud. */
* A short read fails the operation. Closing the window does not close ud
* unless data_transfer_set_release() said how.
* data_transfer_open_window(path), which opens a file for it, is in
* <streams/data_transfer_file.h>. */
typedef int64_t (*data_transfer_read_at_t)(void *ud, uint64_t offset,
uint8_t *dst, size_t bytes);
data_transfer_t *data_transfer_open_window_reader(size_t length, size_t keep,
data_transfer_read_at_t read_at, void *ud);
/* What frees a reader's ud when the transfer is freed */
typedef void (*data_transfer_release_t)(void *ud);
void data_transfer_set_release(data_transfer_t *dt,
data_transfer_release_t release);
const uint8_t *data_transfer_window_base(data_transfer_t *dt, size_t *len);
/* True when the transfer is genuinely windowed - address space was
* reserved and only the window is committed. False when the platform
@@ -253,8 +259,10 @@ bool data_transfer_window_feed(data_transfer_t *dt, size_t tell,
* behind the consumer. There the bytes at tell itself are what is
* missing - a lap that landed past the head, an open that rebased -
* and leaving them for a later tick leaves the consumer's very next
* read on unresident pages; that catch-up is one unbudgeted extend,
* exactly what window_feed has always done. budget == 0 disables
* read on unresident pages; that catch-up is one unbudgeted extend
* from margin bytes behind tell, never from further back: a frontier
* further behind than that moves up first, so a gap the consumer has
* passed is never read in. budget == 0 disables
* the ceiling entirely, making this window_feed by another name.
*
* *resident_hi (may be NULL) reports the sequential bound this call
@@ -296,8 +304,10 @@ void data_transfer_arena_release(data_transfer_arena_t *a);
* when commit_cap is 0. There is no built-in ceiling: a caller that
* asks for no cap gets no cap, and a file too large for memory is
* refused at open rather than presented as a capped prefix. */
data_transfer_t *data_transfer_open_prefix(const char *path,
size_t commit_cap);
data_transfer_t *data_transfer_open_prefix_reader(size_t length,
size_t commit_cap, data_transfer_read_at_t read_at, void *ud);
/* data_transfer_open_prefix(path, commit_cap), which opens a file for
* it, is in <streams/data_transfer_file.h>. */
/* The fill stopped at commit_cap with the file continuing beyond it.
* Terminal: iterate() will not advance further. */
+19 -29
View File
@@ -33,6 +33,8 @@
RETRO_BEGIN_DECLS
struct data_transfer;
enum image_process_code
{
IMAGE_PROCESS_ERROR = -2,
@@ -344,24 +346,25 @@ bool image_texture_scale(struct texture_image *img,
bool image_texture_load_buffer(struct texture_image *img,
enum image_type_enum type, void *s, size_t len);
/* The loader run to completion over a whole buffer or a file, with
* @req naming what the caller takes (NULL: the ordinary image). The
* abort hook is asked between steps; it may be NULL. False leaves
* @img empty. */
/* The loader run to completion over a whole buffer, with @req naming
* what the caller takes (NULL: the ordinary image). The abort hook is
* asked between steps; it may be NULL. False leaves @img empty. Loading
* by path - image_texture_load and the rest - is in
* <streams/image_texture_file.h>. */
bool image_texture_load_buffer_request(struct texture_image *img,
enum image_type_enum type, const void *buf, size_t len,
const image_texture_request_t *req,
bool (*should_abort)(void *ud), void *ud);
bool image_texture_load_request(struct texture_image *img,
const char *path, const image_texture_request_t *req,
bool (*should_abort)(void *ud), void *ud);
/* image_texture_load_request answering, from the bytes it read, whether
* the file is an animated PNG: 1 yes, 0 a still, -1 not a PNG or no
* decode. Spares a caller that keeps an animation's bytes a second
* read of every still to ask. @png_probe may be NULL. */
bool image_texture_load_request_ex(struct texture_image *img,
const char *path, const image_texture_request_t *req,
/* image_texture_load_buffer_request over bytes @keep, a data_transfer,
* holds: an image that can go on reading them (a compressed texture)
* takes @keep, frees it with itself and sets *@kept; *@png_probe, where
* not NULL, says whether the bytes are an animated PNG - 1 yes, 0 a
* still, -1 not a PNG or no decode. @keep and @kept may be NULL. */
bool image_texture_load_buffer_probe(struct data_transfer *keep,
bool *kept, struct texture_image *img,
enum image_type_enum type, const void *buf, size_t len,
const image_texture_request_t *req,
bool (*should_abort)(void *ud), void *ud, int *png_probe);
/* Run @fn for every index below @n, across the cores at once where
@@ -382,27 +385,14 @@ void image_texture_set_run_ex(unsigned n,
void (*thread_done)(void));
/* Decode the @n files @paths name into @imgs together, each as
* image_texture_load_request does with @req; a path that is NULL,
* empty or unreadable leaves its image empty. Returns how many
* decoded. In formats/image_texture_set.c. */
* image_texture_load_request (<streams/image_texture_file.h>) does with
* @req; a path that is NULL, empty or unreadable leaves its image
* empty. Returns how many decoded. In formats/image_texture_set.c. */
unsigned image_texture_load_set(const char *const *paths,
struct texture_image *imgs, unsigned n,
const image_texture_request_t *req);
/* ->pix10 is an ask on the way in and an answer on the way out: set
* it before the call to have a decoder that can emit XRGB2101010 do
* so, and read it after to find out whether it did. Clear on entry
* means the ordinary 8-bit image, as before. */
bool image_texture_load(struct texture_image *img, const char *path);
/* image_texture_load with an abort hook: both decode stages are
* incremental (one chunk / one pass per step) and @should_abort is
* asked between steps; returning true abandons the decode, which then
* fails cleanly. For decodes on worker threads that must stop promptly
* at shutdown or when their result is no longer wanted. @should_abort
* may be NULL. */
bool image_texture_load_ex(struct texture_image *img, const char *path,
bool (*should_abort)(void *ud), void *ud);
void image_texture_free(struct texture_image *img);
/* Force a CPU decode of a compressed texture_image into ->pixels (RGBA8).
+18
View File
@@ -261,9 +261,27 @@ uint64_t rflac_total_frames(const rflac_t *f);
* is the common case: a table is optional, and a stream carried in a
* container never has one. That is not a failure of the seek, only of
* this shortcut, and the caller's fallback is the same as above.
*
* The frame the offset is at is not reported, and rflac_seek_resumed()
* needs it: rflac_seek_point() gives both.
*/
int rflac_seek(rflac_t *f, uint64_t frame, uint64_t *byte_offset);
/**
* rflac_seek_point:
* @f : decoder
* @frame : PCM frame to reach
* @byte_offset: receives where the caller should resume input
* @point_frame: receives the PCM frame that input starts at
*
* As rflac_seek(), and also the seek point's own frame (at or before
* @frame), which is what rflac_seek_resumed() is to be told and where
* output resumes: decoding forward @frame - @point_frame frames reaches
* the target.
*/
int rflac_seek_point(rflac_t *f, uint64_t frame, uint64_t *byte_offset,
uint64_t *point_frame);
/**
* rflac_seek_resumed:
* @f : decoder
+39
View File
@@ -62,6 +62,45 @@ int rtga_process_image(rtga_t *rtga, void **buf,
bool rtga_set_buf_ptr(rtga_t *rtga, void *data);
/* An image decoded straight to where its rows go, in one call, with
* the decoder's memory kept for the next image.
*
* size the image's size, once the header is read, before any row;
* false stops the decode (NULL: any size)
* row where row y's width * 4 bytes are to be written: 4-byte
* aligned memory of the caller's, or NULL for rtga's own row
* (NULL: always rtga's)
* done row y is written, at px - where row() said, or rtga's own
* row, valid until the next row is begun (NULL: not told)
*
* Each row is asked for once and told done once, in the file's order:
* bottom to top for a TGA stored bottom-up. y counts from the top.
* Pixels are BGRA, or RGBA with @rgba, in byte order. A file that
* ends early is finished as rtga_process_image() finishes it. */
struct rtga_rows
{
bool (*size)(void *user, unsigned width, unsigned height);
uint8_t *(*row)(void *user, unsigned y);
void (*done)(void *user, unsigned y, const uint8_t *px);
void *user;
};
/**
* rtga_decode_rows:
* @data, @len : the whole file, read where it lies
* @rgba : RGBA rather than BGRA
* @rows : where the rows go
* @width, @height : receive the size (0 if the header was not read)
*
* Abandons any rtga_process_image() decode on @rtga.
*
* Returns: true when every row was written; false for a header this
* decoder does not take, or when @rows->size said no.
*/
bool rtga_decode_rows(rtga_t *rtga, const uint8_t *data, size_t len,
bool rgba, const struct rtga_rows *rows,
unsigned *width, unsigned *height);
void rtga_free(rtga_t *rtga);
rtga_t *rtga_alloc(void);
+12 -1
View File
@@ -48,7 +48,18 @@
* stage's gl_ClipDistance / gl_CullDistance (sized by the highest
* constant element written; HLSL and MSL refuse them yet), in / out
* interface blocks, RSLC_STAGE_GEOMETRY (SPIR-V and GLSL only), and
* storage buffers with the atomic builtins (SPIR-V and GLSL only).
* storage buffers with the atomic builtins (SPIR-V and GLSL only),
* and what a generated stage says: #version 420 to 440 for the same
* dialect, gl_WorkGroupSize, and a uniform block that says std430
* (placed as glslang places it under GL_EXT_scalar_block_layout) or
* ends in an array of unstated length
* (GL_EXT_uniform_buffer_unsized_array; SPIR-V only), a block member
* that says its layout(offset) (SPIR-V only),
* gl_FragStencilRefARB (SPIR-V only), and one multisampled image read
* and written a sample at a time: sampler2DMS and image2DMS with their
* i and u forms (SPIR-V only), the two-channel and 16-bit image
* formats, and a write-only image that leaves its format out (SPIR-V
* only).
* Without it
* none of that is accepted, nothing is compiled in for it, and every
* destination is word for word what it was. RSLC_NO_DXBC omits the
@@ -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
@@ -60,6 +60,15 @@
#define _DEFAULT_SOURCE 1
#endif
/* glibc before 2.19 knows only these spellings (webOS ships 2.12);
* later ones accept them silently next to _DEFAULT_SOURCE. */
#ifndef _BSD_SOURCE
#define _BSD_SOURCE 1
#endif
#ifndef _SVID_SOURCE
#define _SVID_SOURCE 1
#endif
#endif
#endif
@@ -0,0 +1,68 @@
/* Copyright (C) 2010-2026 The RetroArch team
*
* ---------------------------------------------------------------------------------------
* The following license statement only applies to this file (cdfs_file.h).
* ---------------------------------------------------------------------------------------
*
* Permission is hereby granted, free of charge,
* to any person obtaining a copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
* and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#ifndef __LIBRETRO_SDK_STREAMS_CDFS_FILE_H__
#define __LIBRETRO_SDK_STREAMS_CDFS_FILE_H__
#include <retro_common_api.h>
#include <formats/cdfs.h>
RETRO_BEGIN_DECLS
/* cdfs tracks opened by filename: the path is resolved and the file
* opened through intfstream, then handed to cdfs_track_from_stream.
* Close the track with cdfs_close_track. */
/* opens the specified track in a CD or virtual CD file - the resulting stream should be passed to
* cdfs_open_file to get access to a file within the CD.
*
* supported files:
* real CD - path will be in the form "cdrom://drive1.cue" or "cdrom://d:/drive.cue"
* bin/cue - path will point to the cue file
* chd - path will point to the chd file
*
* for bin/cue files, the following storage modes are supported:
* MODE2/2352
* MODE1/2352
* MODE1/2048 - untested
* MODE2/2336 - untested
*/
cdfs_track_t* cdfs_open_track(const char* path, unsigned int track_index);
/* opens the first data track in a CD or virtual CD file. see cdfs_open_track for supported file formats
*/
cdfs_track_t* cdfs_open_data_track(const char* path);
/* opens a raw track file for a CD or virtual CD.
*
* supported files:
* real CD - path will be in the form "cdrom://drive1-track01.bin" or "cdrom://d:/drive-track01.bin"
* BIN handles obtain their own validated geometry.
* bin - path will point to the bin file
* iso - path will point to the iso file
*/
cdfs_track_t* cdfs_open_raw_track(const char* path);
RETRO_END_DECLS
#endif
@@ -0,0 +1,48 @@
/* Copyright (C) 2010-2026 The RetroArch team
*
* ---------------------------------------------------------------------------------------
* The following license statement only applies to this file (data_transfer_file.h).
* ---------------------------------------------------------------------------------------
*
* Permission is hereby granted, free of charge,
* to any person obtaining a copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
* and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#ifndef __LIBRETRO_SDK_STREAMS_DATA_TRANSFER_FILE_H__
#define __LIBRETRO_SDK_STREAMS_DATA_TRANSFER_FILE_H__
#include <stddef.h>
#include <retro_common_api.h>
#include <formats/data_transfer.h>
RETRO_BEGIN_DECLS
/* data_transfer over a file opened by path through filestream, so
* 64-bit offsets and VFS-only paths (archive members, Android
* content:// documents, frontend overrides) work like any other.
* The transfer owns the file and closes it when freed. */
/* data_transfer_open_prefix_reader over the file at @path */
data_transfer_t *data_transfer_open_prefix(const char *path,
size_t commit_cap);
/* data_transfer_open_window_reader over the file at @path */
data_transfer_t *data_transfer_open_window(const char *path, size_t keep);
RETRO_END_DECLS
#endif
@@ -0,0 +1,68 @@
/* Copyright (C) 2010-2026 The RetroArch team
*
* ---------------------------------------------------------------------------------------
* The following license statement only applies to this file (image_texture_file.h).
* ---------------------------------------------------------------------------------------
*
* Permission is hereby granted, free of charge,
* to any person obtaining a copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
* and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#ifndef __LIBRETRO_SDK_STREAMS_IMAGE_TEXTURE_FILE_H__
#define __LIBRETRO_SDK_STREAMS_IMAGE_TEXTURE_FILE_H__
#include <retro_common_api.h>
#include <boolean.h>
#include <formats/image.h>
RETRO_BEGIN_DECLS
/* Images loaded by path: the file read whole through
* data_transfer_open_prefix - filestream/VFS routing, so overlays and
* driver assets from archive members or content:// documents load like
* any other, 64-bit lengths, honest short reads - and decoded by
* image_texture_load_buffer_probe. */
/* ->pix10 is an ask on the way in and an answer on the way out: set
* it before the call to have a decoder that can emit XRGB2101010 do
* so, and read it after to find out whether it did. Clear on entry
* means the ordinary 8-bit image, as before. */
bool image_texture_load(struct texture_image *img, const char *path);
/* image_texture_load with an abort hook: both decode stages are
* incremental (one chunk / one pass per step) and @should_abort is
* asked between steps; returning true abandons the decode, which then
* fails cleanly. For decodes on worker threads that must stop promptly
* at shutdown or when their result is no longer wanted. @should_abort
* may be NULL. */
bool image_texture_load_ex(struct texture_image *img, const char *path,
bool (*should_abort)(void *ud), void *ud);
bool image_texture_load_request(struct texture_image *img,
const char *path, const image_texture_request_t *req,
bool (*should_abort)(void *ud), void *ud);
/* image_texture_load_request answering, from the bytes it read, whether
* the file is an animated PNG: 1 yes, 0 a still, -1 not a PNG or no
* decode. Spares a caller that keeps an animation's bytes a second
* read of every still to ask. @png_probe may be NULL. */
bool image_texture_load_request_ex(struct texture_image *img,
const char *path, const image_texture_request_t *req,
bool (*should_abort)(void *ud), void *ud, int *png_probe);
RETRO_END_DECLS
#endif
@@ -2,7 +2,7 @@
#ifndef LIBRETRO_RDVD_STREAM_H
#define LIBRETRO_RDVD_STREAM_H
#include <formats/rdvd_demux.h>
#include <formats/rdvd_vfs.h>
#include <streams/rdvd_vfs.h>
RETRO_BEGIN_DECLS
enum rdvd_stream_status
{
+22
View File
@@ -187,6 +187,9 @@ struct libretro_vfs_implementation_file
#endif
{
int64_t size;
/* where a stdio stream stands, kept here so a write need not ask
* stdio to keep size; -1 when not known, and stdio is asked */
int64_t stdio_pos;
uint64_t mappos;
uint64_t mapsize;
#if defined(_WIN32) && !defined(_XBOX) && !defined(HAVE_MMAP)
@@ -199,6 +202,14 @@ struct libretro_vfs_implementation_file
HANDLE fh;
/* CreateFileMapping handle backing `mapped`; NULL when unmapped */
HANDLE map_handle;
/* A stream on a handle of its own (not VFS_WIN32_NO_NATIVE): its
* position, the part of the file `buf` holds - read from it, or, with
* ndirty set, written to it and not yet to the file - and an error */
uint64_t npos;
uint64_t nbuf_at;
size_t nbuf_len;
int nerr;
int ndirty;
#if !defined(_XBOX) && !defined(HAVE_MMAP)
void *warm; /* mapped read-ahead thread, NULL until used */
#endif
@@ -247,6 +258,17 @@ struct libretro_vfs_implementation_file
retro_atomic_size_t append_end;
#ifdef VFS_HAVE_AT_HANDLE
retro_atomic_ptr_t at_handle;
/* at_handle is synchronous - Windows' cached-read fast path - and
* held by one call at a time, at_sync_busy saying so; a call that
* finds it held takes at_ovl, the same file opened for overlapped
* I/O, whose calls Windows runs side by side. Each overlapped call
* waits on an event of its own: one of at_ev, claimed by its bit in
* at_ev_busy and made at first use, or a passing one when all are in
* use. A native stream's own handle serves as its at_handle. */
retro_atomic_ptr_t at_ovl;
HANDLE at_ev[8];
retro_atomic_int_t at_ev_busy;
retro_atomic_int_t at_sync_busy;
#endif
};
#endif
+6
View File
@@ -2822,8 +2822,14 @@ static void retro_task_threaded_gather(void)
* running or parked, or one finished and not yet retired. */
static bool task_threaded_busy(void)
{
/* A finishing task counts itself finished before it stops counting
* as running, and goes on the finished stack only after both; the
* gather moves it to the retire list before it stops counting as
* finished. The count, not the stack, so a finish between the
* running count and the stack is never seen as nothing at all. */
return retro_atomic_load_acquire_int(&tasks_running_count)
> retro_atomic_load_acquire_int(&tasks_scheduled_count)
|| retro_atomic_load_acquire_int(&tasks_finished_count)
|| retro_atomic_load_acquire_ptr(&task_finished_stack)
|| task_retire.front;
}
@@ -58,6 +58,8 @@ static uint8_t *data;
static uint8_t *frame;
static size_t frame_len;
static uint64_t read_octets;
static unsigned read_at_calls;
static bool positional; /* the VFS offers v6 reads at offsets */
static void fail(const char *what, const char *detail)
{
@@ -81,6 +83,17 @@ static int64_t count_read(struct retro_vfs_file_handle *h, void *s,
return r;
}
static int64_t count_read_at(struct retro_vfs_file_handle *h, void *s,
uint64_t len, int64_t offset)
{
int64_t r = retro_vfs_file_read_at_impl(
(libretro_vfs_implementation_file*)h, s, len, offset);
read_at_calls++;
if (r > 0)
read_octets += (uint64_t)r;
return r;
}
static void install_vfs(bool lend)
{
static struct retro_vfs_interface iface;
@@ -99,10 +112,16 @@ static void install_vfs(bool lend)
iface.remove = retro_vfs_file_remove_impl;
iface.rename = retro_vfs_file_rename_impl;
info.required_interface_version = FILESTREAM_REQUIRED_VFS_VERSION;
if (positional)
{
iface.read_at = count_read_at;
info.required_interface_version = 6;
}
info.iface = &iface;
filestream_vfs_init(&info);
filestream_set_mapped_ptr_cb(lend ? lend_mapped_ptr : NULL);
read_octets = 0;
read_octets = 0;
read_at_calls = 0;
}
static int extract_cb(const char *name, const char *valid_exts,
@@ -280,11 +299,20 @@ int main(void)
return 2;
fclose(f);
/* without v6 an extract's blocks are sought and read; with it, read
* at their offsets */
check_read("read, plain file", false);
check_read("read, lent", true);
check_extract("extract, plain file", false);
check_extract("extract, lent", true);
check_window();
positional = true;
check_read("read at offsets, plain file", false);
check_extract("extract at offsets, plain file", false);
if (!read_at_calls)
fail("extract at offsets, plain file", "no read at an offset");
check_window();
positional = false;
install_vfs(false);
if ( file_archive_get_file_crc32(ZST_PATH "#archive_zst_test.bin")
!= encoding_crc32(0, data, DATA_LEN))
+58 -2
View File
@@ -19,6 +19,11 @@ TARGET_TEST15 := vfs_positional_nommap_test
TARGET_TEST16 := vfs_positional_off32_test
TARGET_TEST17 := vfs_positional_pathreopen_test
TARGET_TEST18 := vfs_positional_ntverify_test
TARGET_TEST19 := vfs_positional_wide_test
TARGET_TEST20 := vfs_seek_read_mix_test
TARGET_TEST21 := vfs_write_mix_test
BENCH_CRT := vfs_native_bench
BENCH_NATIVE := vfs_native_bench_native
LIBRETRO_COMM_DIR := ../../..
@@ -122,6 +127,11 @@ endif
ifeq ($(MMAP),1)
CFLAGS += -DHAVE_MMAP
endif
# As RetroArch's Makefile builds Linux: what glibc declares only under it
# (fallocate's hole punching) is what a frontend runs
ifeq ($(UNAME_S),Linux)
CFLAGS += -D_GNU_SOURCE
endif
# vfs_bulk_read_test asserts which I/O path an open lands on, and the
# interesting configuration - descriptor I/O with no file mapping - is
@@ -134,6 +144,14 @@ ifeq ($(DESCRIPTOR_IO),1)
CFLAGS += -DVFS_HAVE_DESCRIPTOR_IO=1
endif
# 'make VFS_NATIVE=0' builds the suite with Windows streams on the C
# runtime (VFS_WIN32_NO_NATIVE) rather than a handle of their own;
# elsewhere it changes nothing.
VFS_NATIVE ?=
ifeq ($(VFS_NATIVE),0)
CFLAGS += -DVFS_WIN32_NO_NATIVE
endif
# FAKE_CFR=1 stands in a copy_file_range that copies to EOF whatever
# len it was given (what gVisor was observed doing); FAKE_CFR=2 one
# that copies three times len. Both exercise vfs_v5_metadata_test's
@@ -166,7 +184,8 @@ all: $(TARGET) $(TARGET_TEST) $(TARGET_TEST2) $(TARGET_TEST3) $(TARGET_TEST4) \
$(TARGET_TEST5) $(TARGET_TEST6) $(TARGET_TEST8) $(TARGET_TEST9) \
$(TARGET_TEST10) $(TARGET_TEST11) $(TARGET_TEST12) $(TARGET_TEST13) \
$(TARGET_TEST14) $(TARGET_TEST15) $(TARGET_TEST16) $(TARGET_TEST17) \
$(TARGET_TEST18) $(RENAME_TARGET)
$(TARGET_TEST18) $(TARGET_TEST19) $(TARGET_TEST20) $(TARGET_TEST21) $(BENCH_CRT) $(BENCH_NATIVE) \
$(RENAME_TARGET)
%.o: %.c
$(CC) -c -o $@ $< $(CFLAGS)
@@ -248,6 +267,39 @@ $(NTVERIFY_VFS_OBJ): $(LIBRETRO_COMM_DIR)/vfs/vfs_implementation.c
$(TARGET_TEST18): vfs_positional_required.o $(NTVERIFY_OBJS)
$(CC) -o $@ $^ $(LDFLAGS) -lpthread
# vfs_positional_wide_test: the positional test from twelve threads
vfs_positional_wide.o: vfs_positional_test.c
$(CC) -c -o $@ $< $(CFLAGS) -DVFS_POSITIONAL_TEST_REQUIRED -DTHREADS=12
$(TARGET_TEST19): vfs_positional_wide.o $(COMMON_OBJS)
$(CC) -o $@ $^ $(LDFLAGS) -lpthread
$(TARGET_TEST20): vfs_seek_read_mix_test.o $(COMMON_OBJS)
$(CC) -o $@ $^ $(LDFLAGS)
$(TARGET_TEST21): vfs_write_mix_test.o $(COMMON_OBJS)
$(CC) -o $@ $^ $(LDFLAGS)
# vfs_native_bench / vfs_native_bench_native: read-only stream timings
# through the C runtime (VFS_WIN32_NO_NATIVE), and through the stream's
# own handle - run both on Windows and compare. Elsewhere the two are
# the same build.
CRT_VFS_OBJ := vfs_implementation_crt.o
BENCH_OBJS := $(filter-out %/vfs_implementation.o,$(COMMON_OBJS)) \
rthreads_ra.o retro_eventcount_ra.o
$(CRT_VFS_OBJ): $(LIBRETRO_COMM_DIR)/vfs/vfs_implementation.c
$(CC) -c -o $@ $< $(CFLAGS) -DVFS_WIN32_NO_NATIVE
vfs_native_bench_crt.o: vfs_native_bench.c
$(CC) -c -o $@ $< $(CFLAGS) -DVFS_WIN32_NO_NATIVE
$(BENCH_CRT): vfs_native_bench_crt.o $(BENCH_OBJS) $(CRT_VFS_OBJ)
$(CC) -o $@ $^ $(LDFLAGS) $(RA_LIBS) -lpthread
$(BENCH_NATIVE): vfs_native_bench.o $(COMMON_OBJS) rthreads_ra.o retro_eventcount_ra.o
$(CC) -o $@ $^ $(LDFLAGS) $(RA_LIBS) -lpthread
$(TARGET_TEST10): vfs_view_test.o $(VIEW_OBJS)
$(CC) -o $@ $^ $(LDFLAGS)
@@ -326,7 +378,11 @@ clean:
rm -f $(TARGET) $(TARGET_TEST) $(TARGET_TEST2) $(TARGET_TEST3) \
$(TARGET_TEST4) $(TARGET_TEST5) $(TARGET_TEST6) \
$(TARGET_TEST7) $(TARGET_TEST8) $(TARGET_TEST9) $(TARGET_TEST10) \
$(TARGET_TEST11) $(TARGET_TEST12) $(TARGET_TEST13) $(TARGET_TEST14) $(TARGET_TEST15) $(TARGET_TEST16) $(TARGET_TEST17) $(TARGET_TEST18) $(CLOSE_VFS_OBJ) \
$(TARGET_TEST11) $(TARGET_TEST12) $(TARGET_TEST13) $(TARGET_TEST14) $(TARGET_TEST15) $(TARGET_TEST16) $(TARGET_TEST17) $(TARGET_TEST18) $(TARGET_TEST19) \
$(TARGET_TEST20) vfs_seek_read_mix_test.o \
$(TARGET_TEST21) vfs_write_mix_test.o \
$(BENCH_CRT) $(BENCH_NATIVE) vfs_positional_wide.o \
vfs_native_bench.o vfs_native_bench_crt.o $(CRT_VFS_OBJ) $(CLOSE_VFS_OBJ) \
$(OBJS) $(FAULT_FS_OBJ) $(RENAME_VFS_OBJ) \
$(RA_VFS_OBJ) $(HOOK_VFS_OBJ) rthreads_ra.o retro_eventcount_ra.o \
$(NOMMAP_VFS_OBJ) vfs_positional_required.o $(OFF32_VFS_OBJ) \
@@ -271,16 +271,38 @@ static void test_path_selection(void)
retro_vfs_file_close_impl(s);
}
/* No hint: unchanged, buffered. */
/* No hint: unchanged, buffered - on a handle of its own where
* Windows takes read-only streams off the C runtime. */
s = retro_vfs_file_open_impl(path, RETRO_VFS_FILE_ACCESS_READ,
RETRO_VFS_FILE_ACCESS_HINT_NONE);
CHECK(s != NULL, "plain open failed");
if (s)
{
#if !defined(VFS_WIN32_NO_NATIVE) && defined(_WIN32) && !defined(_XBOX)
CHECK(s->fp == NULL && s->fh != NULL,
"plain read-only open is not on its own handle");
#else
CHECK(s->fp != NULL, "plain open left stdio");
#endif
retro_vfs_file_close_impl(s);
}
/* ... and so do write streams */
s = retro_vfs_file_open_impl("bulk_select_pw.bin",
RETRO_VFS_FILE_ACCESS_WRITE, RETRO_VFS_FILE_ACCESS_HINT_NONE);
CHECK(s != NULL, "plain write open failed");
if (s)
{
#if !defined(VFS_WIN32_NO_NATIVE) && defined(_WIN32) && !defined(_XBOX)
CHECK(s->fp == NULL && s->fh != NULL,
"plain write open is not on its own handle");
#else
CHECK(s->fp != NULL, "plain write open left stdio");
#endif
retro_vfs_file_close_impl(s);
remove("bulk_select_pw.bin");
}
/* Write modes take the descriptor path where the platform lists
* descriptor writes, and stay buffered where it does not. */
s = retro_vfs_file_open_impl("bulk_select_w.bin",
@@ -0,0 +1,408 @@
/* Copyright (C) 2010-2026 The RetroArch team
*
* ---------------------------------------------------------------------------------------
* The following license statement only applies to this file (vfs_native_bench.c).
* ---------------------------------------------------------------------------------------
*
* Permission is hereby granted, free of charge,
* to any person obtaining a copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
* and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
/* Stream throughput through filestream: the C runtime path
* (vfs_native_bench, VFS_WIN32_NO_NATIVE) against the stream's own
* handle (vfs_native_bench_native), the same source built twice. On
* anything but Windows the two are the same build.
*
* sequential reads of 1, 16, 256, 4096 and 65536 bytes, and 1 MiB
* 4 KiB at random offsets by seek and read, and by filestream_read_at
* 4 KiB at random offsets by filestream_read_at from 4 threads
* open, read 4 KiB, close, over 1000 small files
* sequential writes of 16, 256, 4096 and 65536 bytes, and 1 MiB, to a
* new file, its close included
* 64 bytes written at random offsets of a 4 MiB file by seek and write
* create, write 4 KiB, close, over 1000 small files
*
* Each case runs five times and reports the best, in ns per call and
* MB/s; the file is read once first so every case runs from the cache.
* Usage: vfs_native_bench [file] (default: makes a 64 MiB one here) */
#include <retro_posix_source.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <streams/file_stream.h>
#include <rthreads/rthreads.h>
#ifdef _WIN32
#include <windows.h>
#else
#include <time.h>
#endif
#define FILE_BYTES (64u << 20)
#define RUNS 5
#define RANDOM_OPS 20000
#define SMALL_FILES 1000
static double now_ns(void)
{
#ifdef _WIN32
static LARGE_INTEGER f;
LARGE_INTEGER c;
if (!f.QuadPart)
QueryPerformanceFrequency(&f);
QueryPerformanceCounter(&c);
return (double)c.QuadPart * 1e9 / (double)f.QuadPart;
#else
struct timespec t;
clock_gettime(CLOCK_MONOTONIC, &t);
return (double)t.tv_sec * 1e9 + (double)t.tv_nsec;
#endif
}
static uint8_t *buf;
static int64_t size;
static void report(const char *what, double ns, double calls, double bytes)
{
printf("%-34s %10.1f ns/call %9.1f MB/s\n", what, ns / calls,
bytes / (ns / 1e9) / 1e6);
}
static void sequential(const char *path, int64_t chunk)
{
double best = 0, bytes = 0, calls = 0;
int r;
char what[64];
int64_t limit = chunk < 256 ? (int64_t)(4u << 20) : size;
for (r = 0; r < RUNS; r++)
{
RFILE *f = filestream_open(path, RETRO_VFS_FILE_ACCESS_READ,
RETRO_VFS_FILE_ACCESS_HINT_NONE);
int64_t done = 0, got;
double t0, t;
unsigned n = 0;
if (!f)
return;
t0 = now_ns();
while (done < limit && (got = filestream_read(f, buf, chunk)) > 0)
{
done += got;
n++;
}
t = now_ns() - t0;
filestream_close(f);
if (!r || t < best)
{
best = t;
bytes = (double)done;
calls = (double)n;
}
}
snprintf(what, sizeof(what), "sequential %lld B", (long long)chunk);
report(what, best, calls, bytes);
}
static void random_reads(const char *path, int at)
{
double best = 0;
int r;
for (r = 0; r < RUNS; r++)
{
RFILE *f = filestream_open(path, RETRO_VFS_FILE_ACCESS_READ,
RETRO_VFS_FILE_ACCESS_HINT_NONE);
unsigned x = 12345, k;
double t0, t;
if (!f)
return;
t0 = now_ns();
for (k = 0; k < RANDOM_OPS; k++)
{
int64_t off;
x = x * 1103515245u + 12345u;
off = (int64_t)((x >> 4) % (uint32_t)(size - 4096));
if (at)
{
if (filestream_read_at(f, buf, 4096, off) != 4096)
break;
}
else
{
filestream_seek(f, off, RETRO_VFS_SEEK_POSITION_START);
if (filestream_read(f, buf, 4096) != 4096)
break;
}
}
t = now_ns() - t0;
filestream_close(f);
if (!r || t < best)
best = t;
}
report(at ? "random 4 KiB, read_at" : "random 4 KiB, seek+read", best,
RANDOM_OPS, (double)RANDOM_OPS * 4096);
}
struct worker
{
RFILE *f;
unsigned seed;
};
static void work(void *ud)
{
struct worker *w = (struct worker*)ud;
uint8_t b[4096];
unsigned x = w->seed, k;
for (k = 0; k < RANDOM_OPS / 4; k++)
{
x = x * 1103515245u + 12345u;
filestream_read_at(w->f, b, sizeof(b),
(int64_t)((x >> 4) % (uint32_t)(size - 4096)));
}
}
static void threaded_reads(const char *path)
{
double best = 0;
int r, i;
RFILE *f = filestream_open(path, RETRO_VFS_FILE_ACCESS_READ,
RETRO_VFS_FILE_ACCESS_HINT_NONE);
if (!f || filestream_read_at(f, buf, 1, 0) != 1)
{
printf("%-34s no reads at offsets here\n", "random 4 KiB, read_at x4");
filestream_close(f);
return;
}
for (r = 0; r < RUNS; r++)
{
sthread_t *t[4];
struct worker w[4];
double t0 = now_ns(), dt;
for (i = 0; i < 4; i++)
{
w[i].f = f;
w[i].seed = 7u + 977u * (unsigned)i;
t[i] = sthread_create(work, &w[i]);
}
for (i = 0; i < 4; i++)
if (t[i])
sthread_join(t[i]);
dt = now_ns() - t0;
if (!r || dt < best)
best = dt;
}
filestream_close(f);
report("random 4 KiB, read_at x4 threads", best, RANDOM_OPS,
(double)RANDOM_OPS * 4096);
}
static void small_files(void)
{
char name[64];
unsigned i;
double best = 0;
int r;
for (i = 0; i < SMALL_FILES; i++)
{
snprintf(name, sizeof(name), "vfs_bench_small_%04u.bin", i);
filestream_write_file(name, buf, 4096);
}
for (r = 0; r < RUNS; r++)
{
double t0 = now_ns(), t;
for (i = 0; i < SMALL_FILES; i++)
{
RFILE *f;
snprintf(name, sizeof(name), "vfs_bench_small_%04u.bin", i);
if ((f = filestream_open(name, RETRO_VFS_FILE_ACCESS_READ,
RETRO_VFS_FILE_ACCESS_HINT_NONE)))
{
filestream_read(f, buf, 4096);
filestream_close(f);
}
}
t = now_ns() - t0;
if (!r || t < best)
best = t;
}
for (i = 0; i < SMALL_FILES; i++)
{
snprintf(name, sizeof(name), "vfs_bench_small_%04u.bin", i);
remove(name);
}
report("open + read 4 KiB + close", best, SMALL_FILES,
(double)SMALL_FILES * 4096);
}
static void sequential_writes(int64_t chunk)
{
double best = 0;
int r;
char what[64];
int64_t limit = chunk < 4096 ? (int64_t)(4u << 20) : (int64_t)(32u << 20);
for (r = 0; r < RUNS; r++)
{
RFILE *f = filestream_open("vfs_bench_w.bin",
RETRO_VFS_FILE_ACCESS_WRITE, RETRO_VFS_FILE_ACCESS_HINT_NONE);
int64_t done = 0;
double t0, t;
if (!f)
return;
t0 = now_ns();
while (done < limit && filestream_write(f, buf, chunk) == chunk)
done += chunk;
filestream_close(f);
t = now_ns() - t0;
if (!r || t < best)
best = t;
}
remove("vfs_bench_w.bin");
snprintf(what, sizeof(what), "write sequential %lld B", (long long)chunk);
report(what, best, (double)(limit / chunk), (double)limit);
}
static void random_writes(void)
{
double best = 0;
int r;
RFILE *f = filestream_open("vfs_bench_w.bin",
RETRO_VFS_FILE_ACCESS_WRITE, RETRO_VFS_FILE_ACCESS_HINT_NONE);
if (!f)
return;
filestream_write(f, buf, 1u << 20);
filestream_write(f, buf, 1u << 20);
filestream_write(f, buf, 1u << 20);
filestream_write(f, buf, 1u << 20);
filestream_close(f);
for (r = 0; r < RUNS; r++)
{
unsigned x = 4321, k;
double t0, t;
if (!(f = filestream_open("vfs_bench_w.bin",
RETRO_VFS_FILE_ACCESS_WRITE
| RETRO_VFS_FILE_ACCESS_UPDATE_EXISTING,
RETRO_VFS_FILE_ACCESS_HINT_NONE)))
break;
t0 = now_ns();
for (k = 0; k < RANDOM_OPS; k++)
{
x = x * 1103515245u + 12345u;
filestream_seek(f, (int64_t)((x >> 4) % ((4u << 20) - 64)),
RETRO_VFS_SEEK_POSITION_START);
if (filestream_write(f, buf, 64) != 64)
break;
}
filestream_close(f);
t = now_ns() - t0;
if (!r || t < best)
best = t;
}
remove("vfs_bench_w.bin");
report("write random 64 B, seek+write", best, RANDOM_OPS,
(double)RANDOM_OPS * 64);
}
static void small_writes(void)
{
char name[64];
unsigned i;
double best = 0;
int r;
for (r = 0; r < RUNS; r++)
{
double t0 = now_ns(), t;
for (i = 0; i < SMALL_FILES; i++)
{
RFILE *f;
snprintf(name, sizeof(name), "vfs_bench_small_%04u.bin", i);
if ((f = filestream_open(name, RETRO_VFS_FILE_ACCESS_WRITE,
RETRO_VFS_FILE_ACCESS_HINT_NONE)))
{
filestream_write(f, buf, 4096);
filestream_close(f);
}
}
t = now_ns() - t0;
if (!r || t < best)
best = t;
}
for (i = 0; i < SMALL_FILES; i++)
{
snprintf(name, sizeof(name), "vfs_bench_small_%04u.bin", i);
remove(name);
}
report("create + write 4 KiB + close", best, SMALL_FILES,
(double)SMALL_FILES * 4096);
}
int main(int argc, char **argv)
{
const char *path = argc > 1 ? argv[1] : "vfs_bench_64m.bin";
int made = 0;
RFILE *f;
if (!(buf = (uint8_t*)malloc(1u << 20)))
return 1;
memset(buf, 0x5A, 1u << 20);
if (argc < 2)
{
unsigned i;
if (!(f = filestream_open(path, RETRO_VFS_FILE_ACCESS_WRITE,
RETRO_VFS_FILE_ACCESS_HINT_NONE)))
return 1;
for (i = 0; i < FILE_BYTES >> 20; i++)
filestream_write(f, buf, 1u << 20);
filestream_close(f);
made = 1;
}
if (!(f = filestream_open(path, RETRO_VFS_FILE_ACCESS_READ,
RETRO_VFS_FILE_ACCESS_HINT_NONE)))
return 1;
size = filestream_get_size(f);
while (filestream_read(f, buf, 1u << 20) > 0) { }
filestream_close(f);
if (size < 8192)
return 1;
#ifdef VFS_WIN32_NO_NATIVE
printf("C runtime streams, %lld bytes\n", (long long)size);
#else
printf("native streams, %lld bytes\n", (long long)size);
#endif
sequential(path, 1);
sequential(path, 16);
sequential(path, 256);
sequential(path, 4096);
sequential(path, 65536);
sequential(path, 1 << 20);
random_reads(path, 0);
random_reads(path, 1);
threaded_reads(path);
small_files();
sequential_writes(16);
sequential_writes(256);
sequential_writes(4096);
sequential_writes(65536);
sequential_writes(1 << 20);
random_writes();
small_writes();
if (made)
remove(path);
free(buf);
return 0;
}
@@ -23,9 +23,11 @@
* Unix host, which always has pread(), or on Windows NT, which always
* has a second handle, a -1 there fails instead - the build without
* HAVE_MMAP (vfs_positional_nommap_test) holds that as Android builds,
* vfs_positional_pathreopen_test the path reopen, and
* vfs_positional_pathreopen_test the path reopen,
* vfs_positional_ntverify_test the handle opened relative to the
* stream's, checked against it as NT before Vista has it checked. */
* stream's, checked against it as NT before Vista has it checked, and
* vfs_positional_wide_test twelve threads at once - more than a
* Windows stream keeps events for, so some calls make their own. */
#include <stdio.h>
#include <stdlib.h>
@@ -67,7 +69,9 @@ static void thread_join(test_thread_t t)
#endif
#define FILE_SIZE (4u << 20)
#ifndef THREADS
#define THREADS 4
#endif
#define READS 20000
#define RECORDS 5000
#define RECORD_LEN 64
@@ -0,0 +1,149 @@
/* Copyright (C) 2010-2026 The RetroArch team
*
* ---------------------------------------------------------------------------------------
* The following license statement only applies to this file (vfs_seek_read_mix_test.c).
* ---------------------------------------------------------------------------------------
*
* Permission is hereby granted, free of charge,
* to any person obtaining a copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
* and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
/* A read-only stream read the way parsers read it: seeks to random
* places, reads of every size from 1 byte to past 64 KiB - some
* across where a refill ends - runs of sequential reads after a seek,
* seeks back into what was just read, seeks past the end, and the
* position after each. Every byte is checked against the fixture, whose
* bytes depend on where they lie. On Windows this is the stream's own
* buffer sizing its refills by how it is being read. */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <streams/file_stream.h>
#define FILE_BYTES ((3u << 20) + 1234)
#define ROUNDS 20000
static int failures;
#define CHECK(c) do { if (!(c)) { fprintf(stderr, "%s:%d: check failed: %s\n", \
__FILE__, __LINE__, #c); failures++; } } while (0)
static uint8_t at(uint64_t i)
{
return (uint8_t)((i * 2654435761u) >> 11);
}
static int same(const uint8_t *p, uint64_t off, uint64_t n)
{
uint64_t i;
for (i = 0; i < n; i++)
if (p[i] != at(off + i))
return 0;
return 1;
}
int main(void)
{
static const char path[] = "vfs_seek_read_mix.bin";
static const unsigned sizes[] = { 1, 3, 16, 100, 512, 4095, 4096, 4097,
30000, 65535, 65536, 65537, 200000 };
uint8_t *data = (uint8_t*)malloc(FILE_BYTES);
uint8_t *buf = (uint8_t*)malloc(200000);
RFILE *f;
unsigned x = 4242, k;
uint64_t i;
if (!data || !buf)
return 1;
for (i = 0; i < FILE_BYTES; i++)
data[i] = at(i);
CHECK(filestream_write_file(path, data, FILE_BYTES));
free(data);
f = filestream_open(path, RETRO_VFS_FILE_ACCESS_READ,
RETRO_VFS_FILE_ACCESS_HINT_NONE);
CHECK(f != NULL);
if (!f)
return 1;
CHECK(filestream_get_size(f) == FILE_BYTES);
for (k = 0; k < ROUNDS; k++)
{
int64_t pos, got, want, left;
unsigned run, r;
x = x * 1103515245u + 12345u;
switch ((x >> 28) & 3)
{
case 0: /* anywhere */
pos = (int64_t)((x >> 3) % FILE_BYTES);
break;
case 1: /* back a little, into what was read */
pos = filestream_tell(f) - (int64_t)((x >> 8) % 70000);
if (pos < 0)
pos = 0;
break;
case 2: /* near the end, or past it */
pos = FILE_BYTES - 70000 + (int64_t)((x >> 8) % 80000);
break;
default: /* on from where it is */
pos = filestream_tell(f);
break;
}
CHECK(filestream_seek(f, pos, RETRO_VFS_SEEK_POSITION_START) >= 0);
CHECK(filestream_tell(f) == pos);
run = 1 + ((x >> 12) & 7);
for (r = 0; r < run; r++)
{
x = x * 1103515245u + 12345u;
want = (int64_t)sizes[(x >> 20) % (sizeof(sizes) / sizeof(sizes[0]))];
left = pos < (int64_t)FILE_BYTES ? (int64_t)FILE_BYTES - pos : 0;
got = filestream_read(f, buf, want);
CHECK(got == (want < left ? want : left));
if (got > 0)
{
CHECK(same(buf, (uint64_t)pos, (uint64_t)got));
pos += got;
}
CHECK(filestream_tell(f) == pos);
if (failures > 20)
break;
}
if (failures > 20)
break;
}
/* and straight through, after all that */
CHECK(filestream_seek(f, 0, RETRO_VFS_SEEK_POSITION_START) >= 0);
for (i = 0; i < FILE_BYTES; )
{
int64_t got = filestream_read(f, buf, 7777);
if (got <= 0 || !same(buf, i, (uint64_t)got))
{
failures++;
break;
}
i += (uint64_t)got;
}
CHECK(i == FILE_BYTES);
CHECK(filestream_read(f, buf, 10) == 0);
filestream_close(f);
remove(path);
free(buf);
printf("vfs seek/read mix: %s\n", failures ? "FAIL" : "ok");
return failures ? 1 : 0;
}
@@ -42,6 +42,8 @@
#if !defined(_WIN32)
#include <unistd.h> /* geteuid: root ignores mode bits */
#else
#include <windows.h> /* CreateFileA: whether the system holds to it */
#endif
#define DIR_NAME "v5_meta_dir"
@@ -102,6 +104,21 @@ static bool files_equal(const char *a, const char *b)
return same;
}
#ifdef _WIN32
/* Whether the system itself refuses @path to a writer: Windows does
* for a read-only file, Wine run as root does not, as root ignores the
* mode bits Wine keeps the attribute in */
static bool system_refuses_write(const char *path)
{
HANDLE h = CreateFileA(path, GENERIC_WRITE, FILE_SHARE_READ, NULL,
OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
if (h == INVALID_HANDLE_VALUE)
return true;
CloseHandle(h);
return false;
}
#endif
static void test_readonly(const char *dir)
{
char p[512];
@@ -120,6 +137,10 @@ static void test_readonly(const char *dir)
if (geteuid() == 0)
SKIP("open-for-write-denied check (running as root, mode bits are not enforced)");
else
#else
if (!system_refuses_write(p))
SKIP("open-for-write-denied check (the system lets this process write a read-only file: Wine as root)");
else
#endif
{
RFILE *f = filestream_open(p, RETRO_VFS_FILE_ACCESS_WRITE, RETRO_VFS_FILE_ACCESS_HINT_NONE);
@@ -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);
@@ -23,10 +23,19 @@ SOURCES := \
OBJS := $(call sample_objs,$(SOURCES:.c=.o))
# -DHAVE_MMAP so the local branch really produces a mapping; without
# HAVE_MMAP so the local branch really produces a mapping; without
# it the zero-copy check has nothing to assert and only reports a skip.
CFLAGS += -Wall -pedantic -std=gnu99 -g1 -DHAVE_MMAP \
-I$(LIBRETRO_COMM_DIR)/include
# Windows maps through vfs.h's _WIN32 backend and has no <sys/mman.h>.
UNAME_S := $(shell uname -s 2>/dev/null)
ifneq (,$(filter MINGW% MSYS% CYGWIN%,$(UNAME_S)))
MMAP ?= 0
else
MMAP ?= 1
endif
CFLAGS += -Wall -pedantic -std=gnu99 -g1 -I$(LIBRETRO_COMM_DIR)/include
ifeq ($(MMAP),1)
CFLAGS += -DHAVE_MMAP
endif
# The wrapper-allocation failure cases exist because that path is
# otherwise unreachable: calloc does not fail on demand, so the branch
@@ -52,6 +52,11 @@
* either, or the loose-file probe cost that the design promises to
* preserve is gone.
*
* 4. Positional I/O (v6). A local handle reads and writes at an offset
* without moving its position; a frontend handle reaches the
* frontend's read_at and write_at only when it advertised v6, and
* reports -1 otherwise so the caller seeks and reads.
*
* Build: make (SANITIZER=address,undefined for a checked run)
* Run: ./vfs_hybrid_test
*/
@@ -170,6 +175,42 @@ static int fe_closedir(struct retro_vfs_dir_handle *d)
return 0;
}
/* ---- v6 ---- */
static int fe_read_ats;
static int64_t fe_read_at(struct retro_vfs_file_handle *s, void *d,
uint64_t l, int64_t o)
{
(void)s; (void)o;
fe_read_ats++;
memset(d, 'P', (size_t)l);
return (int64_t)l;
}
static int64_t fe_write_at(struct retro_vfs_file_handle *s, const void *d,
uint64_t l, int64_t o)
{
(void)s; (void)d; (void)o;
return (int64_t)l;
}
static int64_t fe_read_at_poison(struct retro_vfs_file_handle *s, void *d,
uint64_t l, int64_t o)
{
(void)s; (void)d; (void)l; (void)o;
fe_above_version = 1;
return 0;
}
static int64_t fe_write_at_poison(struct retro_vfs_file_handle *s,
const void *d, uint64_t l, int64_t o)
{
(void)s; (void)d; (void)l; (void)o;
fe_above_version = 1;
return 0;
}
/* A frontend that filled only its v1 members. Everything above is
* poisoned rather than left NULL, so a version-gating slip is a
* reported failure and not a NULL check that happens to save us. */
@@ -190,6 +231,31 @@ static struct retro_vfs_interface iface_v3 =
fe_dirent_name, fe_dirent_is_dir, fe_closedir
};
/* v5 with its v6 slots poisoned, and v6 */
static struct retro_vfs_interface iface_v5 =
{
fe_get_path, fe_open, fe_close, fe_size, fe_tell, fe_seek,
fe_read, fe_write, fe_flush, fe_remove, fe_rename,
fe_truncate,
fe_stat, fe_mkdir, fe_opendir, fe_readdir,
fe_dirent_name, fe_dirent_is_dir, fe_closedir,
NULL,
NULL, NULL, NULL, NULL, NULL, NULL, NULL,
fe_read_at_poison, fe_write_at_poison
};
static struct retro_vfs_interface iface_v6 =
{
fe_get_path, fe_open, fe_close, fe_size, fe_tell, fe_seek,
fe_read, fe_write, fe_flush, fe_remove, fe_rename,
fe_truncate,
fe_stat, fe_mkdir, fe_opendir, fe_readdir,
fe_dirent_name, fe_dirent_is_dir, fe_closedir,
NULL,
NULL, NULL, NULL, NULL, NULL, NULL, NULL,
fe_read_at, fe_write_at
};
static struct retro_vfs_interface *iface_in_use;
static bool env_cb(unsigned cmd, void *data)
@@ -366,6 +432,85 @@ int main(void)
remove("hybrid_plain.bin");
}
/* ---- 4: positional I/O ---- */
{
RFILE *f;
char buf[8];
int64_t got;
{
FILE *w = fopen("hybrid_at.bin", "wb");
fwrite("0123456789", 1, 10, w);
fclose(w);
}
fe_reset(6);
iface_in_use = &iface_v6;
vfs_hybrid_init(env_cb, NULL);
f = filestream_open("hybrid_at.bin",
RETRO_VFS_FILE_ACCESS_READ | RETRO_VFS_FILE_ACCESS_WRITE
| RETRO_VFS_FILE_ACCESS_UPDATE_EXISTING,
RETRO_VFS_FILE_ACCESS_HINT_NONE);
CHECK(f != NULL, "v6 local: plain path opens");
if (f)
{
memset(buf, 0, sizeof(buf));
got = filestream_read_at(f, buf, 3, 6);
#if defined(_WIN32) || defined(__unix__) || defined(__APPLE__)
CHECK(got == 3 && memcmp(buf, "678", 3) == 0,
"v6 local: read_at reads at the offset");
CHECK(filestream_write_at(f, "AB", 2, 2) == 2,
"v6 local: write_at writes at the offset");
memset(buf, 0, sizeof(buf));
got = filestream_read_at(f, buf, 4, 0);
CHECK(got == 4 && memcmp(buf, "01AB", 4) == 0,
"v6 local: the write is on disk");
CHECK(filestream_tell(f) == 0,
"v6 local: the position is where it was");
#else
printf("skip: no positional I/O on this platform (%d)\n", (int)got);
#endif
CHECK(fe_read_ats == 0, "v6 local: the frontend is not called");
filestream_close(f);
}
f = filestream_open("content://media/item",
RETRO_VFS_FILE_ACCESS_READ, RETRO_VFS_FILE_ACCESS_HINT_NONE);
CHECK(f != NULL, "v6: uri path opens through the frontend");
if (f)
{
memset(buf, 0, sizeof(buf));
got = filestream_read_at(f, buf, 2, 4);
CHECK(got == 2 && buf[0] == 'P' && fe_read_ats == 1,
"v6: read_at reaches the frontend's read_at");
CHECK(filestream_write_at(f, "x", 1, 0) == 1,
"v6: write_at reaches the frontend's write_at");
filestream_close(f);
}
fe_reset(5);
iface_in_use = &iface_v5;
vfs_hybrid_init(env_cb, NULL);
f = filestream_open("content://media/item",
RETRO_VFS_FILE_ACCESS_READ, RETRO_VFS_FILE_ACCESS_HINT_NONE);
CHECK(f != NULL, "v5: uri path opens through the frontend");
if (f)
{
got = filestream_read_at(f, buf, 2, 4);
CHECK(got == -1 && !fe_above_version,
"v5: read_at reports -1 without calling a v6 member");
got = filestream_write_at(f, "x", 1, 0);
CHECK(got == -1 && !fe_above_version,
"v5: write_at reports -1 without calling a v6 member");
filestream_close(f);
}
CHECK(fe_files_live == 0, "v6: frontend file handles released");
remove("hybrid_at.bin");
}
if (test_fails)
{
printf("== %d FAILURES ==\n", test_fails);
@@ -20,7 +20,7 @@ COMMON_SOURCES := \
$(LIBRETRO_COMM_DIR)/file/file_path_io.c \
$(LIBRETRO_COMM_DIR)/formats/audio_transfer.c \
$(LIBRETRO_COMM_DIR)/formats/mp3/rmp3.c \
$(LIBRETRO_COMM_DIR)/formats/data_transfer.c \
$(LIBRETRO_COMM_DIR)/formats/data_transfer.c $(LIBRETRO_COMM_DIR)/streams/data_transfer_file.c \
$(LIBRETRO_COMM_DIR)/formats/aac/raac.c \
$(LIBRETRO_COMM_DIR)/formats/opus/ropus.c \
$(LIBRETRO_COMM_DIR)/formats/vorbis/rvorbis.c \
@@ -56,6 +56,7 @@
#include <formats/audio.h>
#include <formats/data_transfer.h>
#include <streams/data_transfer_file.h>
#include <formats/rwebm.h>
#if defined(__linux__) || defined(__unix__) || defined(__APPLE__)
@@ -15,7 +15,7 @@ SOURCES := audio_transfer_flac_test.c \
$(LIBRETRO_COMM_DIR)/file/file_path.c \
$(LIBRETRO_COMM_DIR)/file/file_path_io.c \
$(LIBRETRO_COMM_DIR)/formats/audio_transfer.c \
$(LIBRETRO_COMM_DIR)/formats/data_transfer.c \
$(LIBRETRO_COMM_DIR)/formats/data_transfer.c $(LIBRETRO_COMM_DIR)/streams/data_transfer_file.c \
$(LIBRETRO_COMM_DIR)/formats/flac/rflac.c \
$(LIBRETRO_COMM_DIR)/formats/webm/rwebm.c \
$(LIBRETRO_COMM_DIR)/memmap/memmap.c \
@@ -9,7 +9,10 @@
* the frames as delimited packets, all given up front;
* - demuxed input grown a packet at a time, each growth only after
* the decoder has run dry - the progressive source the growth
* contract in <formats/audio.h> is written for.
* contract in <formats/audio.h> is written for;
* - seeks in a native .flac buffer that carries a seek table, which
* take the table's shortcut, each landing on the frame asked for
* (rflac_seek_point gives the seek point's own frame as well).
*
* FLAC is lossless, so every lane must give back exactly the PCM that
* went in: every sample, and not one frame more or less. The fixture
@@ -27,6 +30,7 @@
#include <math.h>
#include <formats/audio.h>
#include <formats/rflac.h>
#include <formats/rwebm.h>
#include <streams/file_stream.h>
@@ -263,6 +267,64 @@ static void lane_demuxed(const struct demuxed *d, int grow)
free(pcm);
}
/* Seeks in a buffer with a seek table: points at frames 0, 4096, 8192
* and 12288. Each lands on the frame asked for, between the points and
* on them, forward and back; resumed from a point at its own frame, not
* at frame 0 (which landed a seek the point's frames past its target). */
static void lane_seek(const char *path)
{
static const uint64_t targets[] = { 10000, 4096, 15000, 100, 12288,
19999, 6000 };
const char *lane = "native .flac buffer, seek table";
size_t len, i;
void *buf = load(path, &len);
void *ctx = audio_transfer_new(AUDIO_TYPE_FLAC);
int16_t pcm[1000 * 2];
rflac_t *f = rflac_new();
uint64_t at = 0, point = 0;
/* The point itself, straight from rflac */
CHECK(f != NULL, "%s: rflac_new", lane);
if (f)
{
rflac_set_in(f, (const uint8_t*)buf, len);
rflac_set_eof(f);
while (!rflac_format(f))
if (rflac_process(f, NULL, NULL) != RFLAC_PROCESS_NEXT)
break;
CHECK(rflac_seek_point(f, 10000, &at, &point) == RFLAC_PROCESS_NEXT
&& point == 8192 && at > 0 && at < len,
"%s: the point for frame 10000 is frame %lu at byte %lu", lane,
(unsigned long)point, (unsigned long)at);
rflac_free(f);
}
audio_transfer_set_buffer_ptr(ctx, AUDIO_TYPE_FLAC, buf, len);
CHECK(audio_transfer_start(ctx, AUDIO_TYPE_FLAC), "%s: start failed", lane);
for (i = 0; i < sizeof(targets) / sizeof(targets[0]); i++)
{
const size_t want = SRC_FRAMES - targets[i] < 1000
? (size_t)(SRC_FRAMES - targets[i]) : 1000;
size_t got, k;
CHECK(audio_transfer_seek(ctx, AUDIO_TYPE_FLAC, targets[i]),
"%s: seek to %lu refused", lane, (unsigned long)targets[i]);
got = drain(ctx, pcm, 0, want, 1000);
CHECK(got == want, "%s: %lu frames after a seek to %lu, %lu wanted",
lane, (unsigned long)got, (unsigned long)targets[i],
(unsigned long)want);
for (k = 0; k < got; k++)
if ( pcm[k * 2] != src_pcm[(targets[i] + k) * 2]
|| pcm[k * 2 + 1] != src_pcm[(targets[i] + k) * 2 + 1])
{
CHECK(0, "%s: after a seek to %lu, frame %lu differs", lane,
(unsigned long)targets[i], (unsigned long)k);
break;
}
}
audio_transfer_free(ctx, AUDIO_TYPE_FLAC);
free(buf);
}
int main(void)
{
struct demuxed d;
@@ -273,6 +335,7 @@ int main(void)
lane_buffer("native .flac buffer", "fixtures/tone.flac");
lane_buffer("Matroska buffer", "fixtures/tone.mka");
lane_windowed("fixtures/tone.mka", 4096, 2048);
lane_seek("fixtures/tone_seektable.flac");
mka = load("fixtures/tone.mka", &mka_len);
demux_mka((const uint8_t*)mka, mka_len, &d);
@@ -1,12 +1,13 @@
#!/usr/bin/env python3
"""Regenerates fixtures/tone.flac and fixtures/tone.mka.
"""Regenerates fixtures/tone.flac, fixtures/tone.mka and
fixtures/tone_seektable.flac.
20000 stereo frames at 44100 Hz - not a multiple of the encoder's block
size, so each stream ends on a short frame - of two sines with a little
low-order noise so the predictor has residual to code. The test
regenerates the same PCM from the same formula and compares every
sample, so keep the two in step. Needs ffmpeg; the committed fixtures
are what CI uses."""
sample, so keep the two in step. Needs ffmpeg, and flac and metaflac
for the seek table; the committed fixtures are what CI uses."""
import math
import os
@@ -34,4 +35,13 @@ for out in ("tone.flac", "tone.mka"):
"-f", "s16le", "-ar", str(RATE), "-ac", "2",
"-i", raw, "-c:a", "flac",
os.path.join(fix, out)])
# The same PCM through the reference encoder, with a seek table: a point
# at each of the first frames (ffmpeg writes no table). The seek lane
# lands between them.
seek = os.path.join(fix, "tone_seektable.flac")
subprocess.check_call(["flac", "--silent", "--force", "--force-raw-format",
"--endian=little", "--sign=signed", "--channels=2",
"--bps=16", "--sample-rate=" + str(RATE), "-S-",
"-o", seek, raw])
subprocess.check_call(["metaflac", "--add-seekpoint=5x", seek])
os.remove(raw)
+11 -2
View File
@@ -3,6 +3,7 @@ include ../../sample_objdir.mk
TARGET := cdfs_dir_record_test$(EXE)
TARGET_TEST2 := cdfs_pure_api_test$(EXE)
TARGET_TEST3 := cdfs_io_error_test$(EXE)
TARGET_TEST4 := cdfs_file_test$(EXE)
.DEFAULT_GOAL := all
LIBRETRO_COMM_DIR := ../../..
@@ -53,6 +54,10 @@ endif
TEST3_OBJS := $(SAMPLE_OBJDIR)/cdfs_io_error_test.o $(call sample_objs,$(filter-out cdfs_pure_api_test.o $(LIBRETRO_COMM_DIR)/formats/cdfs/cdfs.o,$(TEST2_SOURCES:.c=.o)))
# cdfs_file_test: the same stack and the path openers over it
TEST4_OBJS := $(SAMPLE_OBJDIR)/cdfs_file_test.o $(call sample_objs,$(filter-out cdfs_pure_api_test.o,$(TEST2_SOURCES:.c=.o))) \
$(call sample_objs,$(LIBRETRO_COMM_DIR)/streams/cdfs_file.o)
ifeq ($(OS),Windows_NT)
LDLIBS += -lwinmm
endif
@@ -64,7 +69,10 @@ $(SAMPLE_OBJDIR)/cdfs_io_error_test.o: cdfs_io_error_test.c $(LIBRETRO_COMM_DIR)
$(TARGET_TEST3): $(TEST3_OBJS)
$(CC) -o $@ $^ $(LDFLAGS) $(LDLIBS)
all: $(TARGET) $(TARGET_TEST2) $(TARGET_TEST3)
$(TARGET_TEST4): $(TEST4_OBJS)
$(CC) -o $@ $^ $(LDFLAGS) $(LDLIBS)
all: $(TARGET) $(TARGET_TEST2) $(TARGET_TEST3) $(TARGET_TEST4)
%.o: %.c
$(CC) -c -o $@ $< $(CFLAGS)
@@ -81,6 +89,7 @@ $(TARGET_TEST2): $(TEST2_OBJS)
clean:
rm -rf $(SAMPLE_OBJDIR)
rm -f $(TARGET) $(TARGET_TEST2) $(TARGET_TEST3) $(OBJS) $(TEST2_OBJS) $(TEST3_OBJS)
rm -f $(TARGET) $(TARGET_TEST2) $(TARGET_TEST3) $(TARGET_TEST4) $(OBJS) \
$(TEST2_OBJS) $(TEST3_OBJS) $(TEST4_OBJS)
.PHONY: clean
@@ -0,0 +1,193 @@
/* Copyright (C) 2010-2026 The RetroArch team
*
* ---------------------------------------------------------------------------------------
* The following license statement only applies to this file (cdfs_file_test.c).
* ---------------------------------------------------------------------------------------
*
* Permission is hereby granted, free of charge,
* to any person obtaining a copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
* and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
/* streams/cdfs_file.c: tracks opened by filename.
*
* - a cooked .iso and a raw .bin with no volume descriptor take their
* sector layout from their length (2048 and 2352 bytes a sector);
* - a cue sheet in a directory of its own names its bin relatively, is
* resolved against its own directory, and gives the MODE1/2352 layout
* and the INDEX 01 offset of the track it selects;
* - cdfs_open_data_track takes a cue's first data track, an .iso as raw;
* - each opened track reads back the marker written in its sector 3;
* - an unknown extension opens only as track 1, and then only if it is
* a bin or an iso.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <formats/cdfs.h>
#include <file/file_path.h>
#include <streams/cdfs_file.h>
#include <streams/file_stream.h>
#define SECTORS 24
static int failures;
#define CHECK(c) do { if (!(c)) { fprintf(stderr, "%s:%d: check failed: %s\n", \
__FILE__, __LINE__, #c); failures++; } } while (0)
static const char dir[] = "cdfs_file_tmp";
static const char iso[] = "cdfs_file_tmp/cooked.iso";
static const char bin[] = "cdfs_file_tmp/raw.bin";
static const char cue[] = "cdfs_file_tmp/disc.cue";
static const char pregap[] = "cdfs_file_tmp/pregap.bin";
static const char pre_cue[] = "cdfs_file_tmp/pregap.cue";
static const char odd[] = "cdfs_file_tmp/disc.img";
/* @sectors of @size bytes, user data at @header; sector s's data
* starts with "S<s>" and the rest of every byte is s */
static int write_image(const char *path, unsigned sectors, unsigned size,
unsigned header, unsigned lead)
{
uint8_t buf[2352];
unsigned s;
RFILE *f = filestream_open(path, RETRO_VFS_FILE_ACCESS_WRITE,
RETRO_VFS_FILE_ACCESS_HINT_NONE);
if (!f)
return 0;
memset(buf, 0xEE, sizeof(buf));
for (s = 0; s < lead; s++)
filestream_write(f, buf, size);
for (s = 0; s < sectors; s++)
{
memset(buf, (int)s, size);
sprintf((char*)buf + header, "S%u", s);
if (filestream_write(f, buf, size) != (int64_t)size)
{
filestream_close(f);
return 0;
}
}
filestream_close(f);
return 1;
}
static int write_text(const char *path, const char *text)
{
return filestream_write_file(path, text, (int64_t)strlen(text));
}
/* Sector 3 of the track, through the cdfs raw view */
static void reads_marker(cdfs_track_t *track, const char *what)
{
cdfs_file_t file;
char got[8];
(void)what;
memset(got, 0, sizeof(got));
CHECK(cdfs_open_file(&file, track, NULL) == 1);
cdfs_seek_sector(&file, 3);
CHECK(cdfs_read_file(&file, got, 2) == 2);
CHECK(memcmp(got, "S3", 2) == 0);
cdfs_close_file(&file);
}
int main(void)
{
cdfs_track_t *t;
path_mkdir(dir);
CHECK(write_image(iso, SECTORS, 2048, 0, 0));
CHECK(write_image(bin, SECTORS, 2352, 16, 0));
/* two sectors ahead of INDEX 01 */
CHECK(write_image(pregap, SECTORS, 2352, 16, 2));
CHECK(write_image(odd, SECTORS, 2048, 0, 0));
CHECK(write_text(cue,
"FILE \"raw.bin\" BINARY\n"
" TRACK 01 MODE1/2352\n"
" INDEX 01 00:00:00\n"));
CHECK(write_text(pre_cue,
"FILE \"pregap.bin\" BINARY\n"
" TRACK 01 MODE1/2352\n"
" INDEX 00 00:00:00\n"
" INDEX 01 00:00:02\n"));
/* a cooked iso: 2048 from the length */
if ((t = cdfs_open_raw_track(iso)))
{
CHECK(t->stream_sector_size == 2048 && t->stream_sector_header_size == 0);
reads_marker(t, "iso");
cdfs_close_track(t);
}
else
failures++;
/* a raw bin: 2352 from the length */
if ((t = cdfs_open_raw_track(bin)))
{
CHECK(t->stream_sector_size == 2352);
cdfs_close_track(t);
}
else
failures++;
/* the cue, its bin found beside it */
if ((t = cdfs_open_track(cue, 1)))
{
CHECK(t->stream_sector_size == 2352 && t->stream_sector_header_size == 16);
CHECK(t->first_sector_offset == 0);
reads_marker(t, "cue");
cdfs_close_track(t);
}
else
failures++;
/* INDEX 01 two sectors in */
if ((t = cdfs_open_track(pre_cue, 1)))
{
CHECK(t->first_sector_offset == 2 * 2352);
reads_marker(t, "cue pregap");
cdfs_close_track(t);
}
else
failures++;
/* first data track: of the cue, and an iso as raw */
if ((t = cdfs_open_data_track(cue)))
{
CHECK(t->stream_sector_size == 2352);
cdfs_close_track(t);
}
else
failures++;
if ((t = cdfs_open_data_track(iso)))
{
CHECK(t->stream_sector_size == 2048);
cdfs_close_track(t);
}
else
failures++;
/* not a bin, iso, cue or chd */
CHECK(cdfs_open_track(odd, 2) == NULL);
CHECK(cdfs_open_track(odd, 1) == NULL);
CHECK(cdfs_open_track("cdfs_file_tmp/missing.cue", 1) == NULL);
remove(iso); remove(bin); remove(pregap); remove(odd);
remove(cue); remove(pre_cue);
remove(dir);
printf("cdfs file: %s\n", failures ? "FAIL" : "ok");
return failures ? 1 : 0;
}
@@ -161,7 +161,7 @@ int main(void)
memset(&layout, 0, sizeof(layout));
layout.stream = track.stream;
sparse_size = UINT64_C(4194307) * sizes[mode];
cdfs_determine_sector_size_from_file_size(&layout);
cdfs_track_sector_size_from_length(&layout);
CHECK(layout.stream_sector_size == sizes[mode]);
sparse_size = UINT64_C(4194304) * sizes[mode] + 12345;
}
@@ -13,7 +13,7 @@ COMMON_SOURCES := \
$(LIBRETRO_COMM_DIR)/encodings/encoding_utf.c \
$(LIBRETRO_COMM_DIR)/file/file_path.c \
$(LIBRETRO_COMM_DIR)/file/file_path_io.c \
$(LIBRETRO_COMM_DIR)/formats/data_transfer.c \
$(LIBRETRO_COMM_DIR)/formats/data_transfer.c $(LIBRETRO_COMM_DIR)/streams/data_transfer_file.c \
$(LIBRETRO_COMM_DIR)/memmap/memmap.c \
$(LIBRETRO_COMM_DIR)/streams/file_stream.c \
$(LIBRETRO_COMM_DIR)/string/rstrtod.c \
@@ -63,6 +63,7 @@
#include <boolean.h>
#include <formats/data_transfer.h>
#include <streams/data_transfer_file.h>
static int test_fails;
@@ -49,6 +49,7 @@
#include <string.h>
#include <stdint.h>
#include <formats/data_transfer.h>
#include <streams/data_transfer_file.h>
#include <memmap.h>
#ifdef DT_COUNT_IO
#include <streams/file_stream.h>
@@ -66,6 +66,7 @@
#include <string.h>
#include <stdint.h>
#include <formats/data_transfer.h>
#include <streams/data_transfer_file.h>
#include <memmap.h>
#ifdef DT_COUNT_IO
#include <streams/file_stream.h>
@@ -1046,6 +1047,76 @@ static int callback_window_check(void)
return 0;
}
/* A consumer far into a large file - one that starts there, moves on,
* then steps back, which the feed takes for a lap - is fed from margin
* bytes behind it: the gap it has passed is never read in, whatever the
* frontier was. */
typedef struct far_source { size_t length; size_t bytes; } far_source;
static int64_t far_read_at(void *ud, uint64_t offset, uint8_t *dst,
size_t bytes)
{
far_source *src = (far_source*)ud;
size_t i;
if (offset > src->length || bytes > src->length - (size_t)offset)
return -1;
for (i = 0; i < bytes; i++)
dst[i] = (uint8_t)((offset + i) * 29 + 7);
src->bytes += bytes;
return (int64_t)bytes;
}
static int far_matches(const uint8_t *base, size_t at, size_t n)
{
size_t i;
for (i = 0; i < n; i++)
if (base[at + i] != (uint8_t)((at + i) * 29 + 7))
return 0;
return 1;
}
static int far_feed_check(void)
{
far_source src;
data_transfer_t *dt;
const uint8_t *base;
size_t blen = 0, step, tell, bound = MARGIN + LOOKAHD + (2u << 20);
const size_t len = 256u << 20;
if (!data_transfer_reserve_supported())
{
printf("[skip] far feed: no reservations on this build\n");
return 0;
}
memset(&src, 0, sizeof(src));
src.length = len;
if (!(dt = data_transfer_open_window_reader(len, KEEP, far_read_at, &src)))
{ fail("far feed: open"); return 1; }
base = data_transfer_window_base(dt, &blen);
for (step = 0; step < 3; step++)
{
size_t before = src.bytes;
tell = len - (1u << 20) + (step == 1 ? 300u * 1024 : step == 2 ? 260u * 1024 : 0);
if (!data_transfer_window_feed(dt, tell, LOOKAHD, MARGIN)
|| !far_matches(base, tell, 4096))
{
printf("[FAIL] far feed: step %u, bytes at tell\n", (unsigned)step);
bad = 1;
break;
}
if (src.bytes - before > bound
|| data_transfer_window_resident(dt) > KEEP + bound)
{
printf("[FAIL] far feed: step %u read %lu bytes, resident %lu\n",
(unsigned)step, (unsigned long)(src.bytes - before),
(unsigned long)data_transfer_window_resident(dt));
bad = 1;
break;
}
}
data_transfer_free(dt);
if (step < 3)
return 1;
ok("far feed: started, moved and stepped back far into a file, read only near the consumer");
return 0;
}
int main(void)
{
const char *path = "dtwin.bin";
@@ -1154,6 +1225,7 @@ int main(void)
#endif
bad |= callback_window_check();
bad |= far_feed_check();
bad |= sparse_skip_check();
bad |= pin_check();
bad |= feed_budget_check();
+6 -6
View File
@@ -34,11 +34,11 @@ $(TARGET_TEST3): rdvd_ifo_test.c ../../../formats/dvd/rdvd_ifo.c ../../../includ
$(TARGET_TEST4): rdvd_source_test.c ../../../formats/dvd/rdvd_source.c ../../../formats/dvd/rdvd_ifo.c ../../../include/formats/rdvd_source.h ../../../include/formats/rdvd_ifo.h
$(CC) $(CFLAGS) rdvd_source_test.c ../../../formats/dvd/rdvd_source.c ../../../formats/dvd/rdvd_ifo.c -o $@ $(LDFLAGS)
DVD_STREAM_HEADERS := ../../../include/formats/rdvd_stream.h ../../../include/formats/rdvd_demux.h
DVD_STREAM_SOURCES := ../../../formats/dvd/rdvd_stream.c ../../../formats/dvd/rdvd_demux.c ../../../formats/dvd/rdvd_packet.c ../../../formats/mpeg1/rmpeg1_ps.c
DVD_STREAM_HEADERS := ../../../include/streams/rdvd_stream.h ../../../include/formats/rdvd_demux.h
DVD_STREAM_SOURCES := ../../../streams/rdvd_stream.c ../../../formats/dvd/rdvd_demux.c ../../../formats/dvd/rdvd_packet.c ../../../formats/mpeg1/rmpeg1_ps.c
$(TARGET_TEST5): $(DVD_STREAM_HEADERS) $(DVD_STREAM_SOURCES) rdvd_vfs_test.c ../../../formats/dvd/rdvd_vfs.c ../../../formats/dvd/rdvd_source.c ../../../formats/dvd/rdvd_ifo.c ../../../queues/retro_spsc.c ../../../include/formats/rdvd_vfs.h
$(CC) $(CFLAGS) -DHAVE_THREADS $(DVD_STREAM_SOURCES) rdvd_vfs_test.c ../../../formats/dvd/rdvd_vfs.c ../../../formats/dvd/rdvd_source.c ../../../formats/dvd/rdvd_ifo.c ../../../queues/retro_spsc.c -o $@ $(LDFLAGS)
$(TARGET_TEST5): $(DVD_STREAM_HEADERS) $(DVD_STREAM_SOURCES) rdvd_vfs_test.c ../../../streams/rdvd_vfs.c ../../../formats/dvd/rdvd_source.c ../../../formats/dvd/rdvd_ifo.c ../../../queues/retro_spsc.c ../../../include/streams/rdvd_vfs.h
$(CC) $(CFLAGS) -DHAVE_THREADS $(DVD_STREAM_SOURCES) rdvd_vfs_test.c ../../../streams/rdvd_vfs.c ../../../formats/dvd/rdvd_source.c ../../../formats/dvd/rdvd_ifo.c ../../../queues/retro_spsc.c -o $@ $(LDFLAGS)
ifneq ($(findstring mingw,$(shell $(CC) -dumpmachine)),)
VFS_REAL_LIBS :=
@@ -46,8 +46,8 @@ else
VFS_REAL_LIBS := -lpthread -lrt
endif
VFS_REAL_SOURCES := ../../../formats/dvd/rdvd_lpcm.c ../../../formats/lpcm/rlpcm.c ../../../formats/dvd/rdvd_ac3.c ../../../formats/ac3/rac3_frame.c ../../../formats/ac3/rac3_decode.c ../../../rthreads/tpool.c ../../../rthreads/retro_eventcount.c ../../../rthreads/rthreads.c ../../../features/features_cpu.c ../../../compat/fopen_utf8.c ../../../compat/compat_strl.c ../../../encodings/encoding_utf.c ../../../file/file_path.c ../../../file/file_path_io.c ../../../file/retro_dirent.c ../../../streams/file_stream.c ../../../string/rstrtod.c ../../../time/rtime.c ../../../vfs/vfs_implementation.c
$(TARGET_TEST6): $(DVD_STREAM_HEADERS) $(DVD_STREAM_SOURCES) rdvd_vfs_real_test.c ../../../formats/dvd/rdvd_vfs.c ../../../formats/dvd/rdvd_source.c ../../../formats/dvd/rdvd_ifo.c ../../../queues/retro_spsc.c $(VFS_REAL_SOURCES)
$(CC) $(filter-out -std=c89 -Werror,$(CFLAGS)) -std=gnu99 -DHAVE_THREADS $(DVD_STREAM_SOURCES) rdvd_vfs_real_test.c ../../../formats/dvd/rdvd_vfs.c ../../../formats/dvd/rdvd_source.c ../../../formats/dvd/rdvd_ifo.c ../../../queues/retro_spsc.c $(VFS_REAL_SOURCES) -o $@ $(LDFLAGS) $(VFS_REAL_LIBS) -lm
$(TARGET_TEST6): $(DVD_STREAM_HEADERS) $(DVD_STREAM_SOURCES) rdvd_vfs_real_test.c ../../../streams/rdvd_vfs.c ../../../formats/dvd/rdvd_source.c ../../../formats/dvd/rdvd_ifo.c ../../../queues/retro_spsc.c $(VFS_REAL_SOURCES)
$(CC) $(filter-out -std=c89 -Werror,$(CFLAGS)) -std=gnu99 -DHAVE_THREADS $(DVD_STREAM_SOURCES) rdvd_vfs_real_test.c ../../../streams/rdvd_vfs.c ../../../formats/dvd/rdvd_source.c ../../../formats/dvd/rdvd_ifo.c ../../../queues/retro_spsc.c $(VFS_REAL_SOURCES) -o $@ $(LDFLAGS) $(VFS_REAL_LIBS) -lm
AC3_SOURCES := ../../../formats/dvd/rdvd_ac3.c ../../../formats/ac3/rac3_frame.c
$(TARGET_TEST7): rdvd_ac3_test.c $(AC3_SOURCES) ../../../include/formats/rdvd_ac3.h
@@ -9,8 +9,8 @@
#else
#include <unistd.h>
#endif
#include <formats/rdvd_vfs.h>
#include <formats/rdvd_stream.h>
#include <streams/rdvd_vfs.h>
#include <streams/rdvd_stream.h>
#include <rthreads/rthreads.h>
#include <retro_atomic.h>
#include <features/features_cpu.h>
@@ -1,8 +1,8 @@
/* Copyright (C) 2026 The RetroArch team. SPDX-License-Identifier: MIT */
#include <stdio.h>
#include <string.h>
#include <formats/rdvd_vfs.h>
#include <formats/rdvd_stream.h>
#include <streams/rdvd_vfs.h>
#include <streams/rdvd_stream.h>
struct RFILE
{
uint64_t size, position;
@@ -18,9 +18,9 @@ SOURCES := image_loader_test.c \
$(LIBRETRO_COMM_DIR)/features/features_cpu.c \
$(LIBRETRO_COMM_DIR)/file/file_path.c \
$(LIBRETRO_COMM_DIR)/file/file_path_io.c \
$(LIBRETRO_COMM_DIR)/formats/data_transfer.c \
$(LIBRETRO_COMM_DIR)/formats/data_transfer.c $(LIBRETRO_COMM_DIR)/streams/data_transfer_file.c \
$(LIBRETRO_COMM_DIR)/formats/dds/rdds.c \
$(LIBRETRO_COMM_DIR)/formats/image_texture.c \
$(LIBRETRO_COMM_DIR)/formats/image_texture.c $(LIBRETRO_COMM_DIR)/streams/image_texture_file.c \
$(LIBRETRO_COMM_DIR)/formats/image_texture_scale.c \
$(LIBRETRO_COMM_DIR)/formats/image_texture_set.c \
$(LIBRETRO_COMM_DIR)/formats/image_transfer.c \
@@ -27,6 +27,7 @@
#include <boolean.h>
#include <encodings/crc32.h>
#include <formats/image.h>
#include <streams/image_texture_file.h>
#define W 24
#define H 16
@@ -0,0 +1,63 @@
include ../../sample_objdir.mk
TARGET := rflac_s16_test
TARGET_TEST := rflac_s16_nosimd_test
LIBRETRO_COMM_DIR := ../../..
SOURCES := \
$(LIBRETRO_COMM_DIR)/compat/fopen_utf8.c \
$(LIBRETRO_COMM_DIR)/compat/compat_strl.c \
$(LIBRETRO_COMM_DIR)/encodings/encoding_utf.c \
$(LIBRETRO_COMM_DIR)/features/features_cpu.c \
$(LIBRETRO_COMM_DIR)/file/file_path.c \
$(LIBRETRO_COMM_DIR)/file/file_path_io.c \
$(LIBRETRO_COMM_DIR)/streams/file_stream.c \
$(LIBRETRO_COMM_DIR)/string/rstrtod.c \
$(LIBRETRO_COMM_DIR)/string/stdstring.c \
$(LIBRETRO_COMM_DIR)/time/rtime.c \
$(LIBRETRO_COMM_DIR)/vfs/vfs_implementation.c
OBJS := $(call sample_objs,$(SOURCES:.c=.o))
CFLAGS += -Wall -pedantic -std=gnu99 -g1 -O1 -I$(LIBRETRO_COMM_DIR)/include
LDFLAGS += -lm
ifneq ($(SANITIZER),)
CFLAGS := -fsanitize=$(SANITIZER) -fno-omit-frame-pointer $(CFLAGS)
LDFLAGS := -fsanitize=$(SANITIZER) $(LDFLAGS)
endif
all: $(TARGET) $(TARGET_TEST)
$(SAMPLE_OBJDIR)/%.o: $$(call sample_src,$$*).c
@mkdir -p $(@D)
$(CC) -c -o $@ $< $(CFLAGS)
# rflac itself twice: with the SIMD loops the target has, and without
$(SAMPLE_OBJDIR)/rflac_simd.o: $(LIBRETRO_COMM_DIR)/formats/flac/rflac.c
@mkdir -p $(@D)
$(CC) -c -o $@ $< $(CFLAGS)
$(SAMPLE_OBJDIR)/rflac_nosimd.o: $(LIBRETRO_COMM_DIR)/formats/flac/rflac.c
@mkdir -p $(@D)
$(CC) -c -o $@ $< $(CFLAGS) -DRFLAC_NO_SIMD
$(TARGET): rflac_s16_test.o $(SAMPLE_OBJDIR)/rflac_simd.o $(OBJS)
$(CC) -o $@ $^ $(LDFLAGS)
$(TARGET_TEST): rflac_s16_test.o $(SAMPLE_OBJDIR)/rflac_nosimd.o $(OBJS)
$(CC) -o $@ $^ $(LDFLAGS)
rflac_s16_test.o: rflac_s16_test.c
$(CC) -c -o $@ $< $(CFLAGS)
check: all
./$(TARGET)
./$(TARGET_TEST)
clean:
rm -rf $(SAMPLE_OBJDIR)
rm -f $(TARGET) $(TARGET_TEST) rflac_s16_test.o
.PHONY: all check clean
@@ -0,0 +1,104 @@
#!/usr/bin/env python3
"""Regenerates the fixtures of rflac_s16_test: FLAC streams of 16, 24 and
32 bits, mono, stereo and six channels, made by the reference encoder
from PCM built with integers only, which the test builds again the same
way and so knows every sample of. The stereo streams are made so that the
encoder picks each of the four channel assignments somewhere (checked
here with flac --analyze), and the 32-bit one so that its side channel
needs 33 bits. Needs flac; the committed fixtures are what CI uses."""
import os
import subprocess
import struct
FRAMES = 5003 # not a multiple of 4: the SIMD loops' scalar tails run too
here = os.path.dirname(os.path.abspath(__file__))
fix = os.path.join(here, "fixtures")
os.makedirs(fix, exist_ok=True)
def lcg(seed):
while True:
seed = (seed * 1103515245 + 12345) & 0xffffffff
yield seed
def tri(i, period, amp):
"""A triangle wave, integer only: rises then falls over period frames."""
p = i % period
half = period // 2
v = p if p < half else period - p
return (v * 2 * amp) // half - amp
def sample(kind, bps, ch, i, c, noise):
top = (1 << (bps - 1)) - 1
n = next(noise) >> 20 # 12 bits of noise
if kind == "stereo":
# Frames 0-1023 the two noisy about one wave (mid/side), 1024-2047
# left clean and right the same with noise (left/side), 2048-3071
# the other way round (side/right), the rest unrelated
# (independent)
base = tri(i, 397, top // 2)
seg = i // 1024
if seg == 0:
v = base + (n if c else -n)
elif seg == 1:
v = base if c == 0 else base + n
elif seg == 2:
v = base + n if c == 0 else base
else:
v = tri(i, 397 + 113 * c, top // 3) + n * (c + 1)
elif kind == "wide":
# Full scale and opposite: left - right needs 33 bits
v = top - (n & 255) if c == 0 else -top + (n & 255)
if i % 7 == 0:
v = -v
else:
v = tri(i, 211 + 37 * c, top // 2) + n
v = max(-top - 1, min(top, v))
return v
def pcm(kind, bps, ch):
noise = lcg(bps * 131 + ch)
out = bytearray()
width = (bps + 7) // 8
for i in range(FRAMES):
for c in range(ch):
v = sample(kind, bps, ch, i, c, noise)
out += (v & ((1 << (width * 8)) - 1)).to_bytes(width, "little")
return out
FIXTURES = [
("s16_stereo.flac", "stereo", 16, 2),
("s24_stereo.flac", "stereo", 24, 2),
("s24_mono.flac", "mono", 24, 1),
("s24_6ch.flac", "multi", 24, 6),
("s32_wide.flac", "wide", 32, 2),
]
seen = set()
for name, kind, bps, ch in FIXTURES:
raw = os.path.join(fix, "src.raw")
out = os.path.join(fix, name)
with open(raw, "wb") as f:
f.write(pcm(kind, bps, ch))
subprocess.check_call(["flac", "--silent", "--force", "--force-raw-format",
"--endian=little", "--sign=signed",
"--channels=" + str(ch), "--bps=" + str(bps),
"--sample-rate=48000", "-8", "-b", "1024",
"-o", out, raw])
os.remove(raw)
if ch == 2:
a = subprocess.run(["flac", "--analyze", "--stdout", out],
capture_output=True, text=True).stdout
for line in a.splitlines():
for tag in ("INDEPENDENT", "LEFT_SIDE", "RIGHT_SIDE", "MID_SIDE"):
if "channel_assignment=" + tag in line:
seen.add(tag)
missing = {"INDEPENDENT", "LEFT_SIDE", "RIGHT_SIDE", "MID_SIDE"} - seen
if missing:
raise SystemExit("channel assignments not made: " + ", ".join(sorted(missing)))
print("assignments:", ", ".join(sorted(seen)))
@@ -0,0 +1,157 @@
/* rflac's 16-bit output, against the PCM each fixture was encoded from:
*
* - a stream of 16 bits comes out exactly as it went in;
* - one of 24 or 32 bits comes out rounded to the nearest 16-bit step
* (halves up) and clamped at the top - not cut down, which left every
* sample half a step low on average;
* - both, through every way a stereo frame's channels are coded
* (independent, left/side, side/right, mid/side, which the stereo
* fixtures hold each of), a 32-bit stream's 33-bit side channel, mono
* and six channels, and the SIMD loops and their scalar tails (the
* fixtures are 5003 frames).
*
* Built twice: as the target builds (SSE2 on x86-64, NEON on ARM) and with
* RFLAC_NO_SIMD, the scalar loops. The PCM is rebuilt here exactly as
* mkfixtures.py builds it, with integers only. */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <formats/rflac.h>
#include <streams/file_stream.h>
#define FRAMES 5003
static unsigned failures;
#define CHECK(cond, ...) do { if (!(cond)) { failures++; \
fprintf(stderr, "FAIL %s:%d: ", __FILE__, __LINE__); \
fprintf(stderr, __VA_ARGS__); fputc('\n', stderr); } } while (0)
static int64_t tri(int64_t i, int64_t period, int64_t amp)
{
int64_t p = i % period;
int64_t half = period / 2;
int64_t v = p < half ? p : period - p;
return (v * 2 * amp) / half - amp;
}
/* mkfixtures.py's sample(), step for step */
static int64_t sample(const char *kind, unsigned bps, unsigned c, int64_t i,
uint32_t *seed)
{
int64_t top = ((int64_t)1 << (bps - 1)) - 1;
int64_t n, v;
*seed = *seed * 1103515245u + 12345u;
n = (int64_t)(*seed >> 20);
if (!strcmp(kind, "stereo"))
{
int64_t base = tri(i, 397, top / 2);
int64_t seg = i / 1024;
if (seg == 0)
v = base + (c ? n : -n);
else if (seg == 1)
v = c == 0 ? base : base + n;
else if (seg == 2)
v = c == 0 ? base + n : base;
else
v = tri(i, 397 + 113 * c, top / 3) + n * (c + 1);
}
else if (!strcmp(kind, "wide"))
{
v = c == 0 ? top - (n & 255) : -top + (n & 255);
if (i % 7 == 0)
v = -v;
}
else
v = tri(i, 211 + 37 * c, top / 2) + n;
if (v > top)
v = top;
if (v < -top - 1)
v = -top - 1;
return v;
}
/* The 16-bit value a sample of @bps bits should give: its value at 16
* bits, rounded to the nearest step, halves up, the top clamped */
static int16_t expect(int64_t v, unsigned bps)
{
int64_t r;
if (bps <= 16)
return (int16_t)(v * ((int64_t)1 << (16 - bps)));
r = (v + ((int64_t)1 << (bps - 17))) >> (bps - 16);
return (int16_t)(r > 32767 ? 32767 : r);
}
static void run(const char *name, const char *kind, unsigned bps, unsigned ch)
{
char path[256];
void *buf = NULL;
int64_t len = 0;
rflac_t *f = rflac_new();
int16_t *out = (int16_t*)calloc((size_t)(FRAMES + 64) * ch, sizeof(int16_t));
size_t got = 0, i;
uint32_t seed = bps * 131 + ch;
unsigned bad = 0, idle = 0;
long sum_err = 0;
int result;
snprintf(path, sizeof(path), "fixtures/%s", name);
if (!filestream_read_file(path, &buf, &len) || len <= 0)
{
fprintf(stderr, "cannot read %s (run from the sample directory)\n", path);
exit(2);
}
CHECK(f != NULL, "%s: rflac_new", name);
rflac_set_in(f, (const uint8_t*)buf, (size_t)len);
rflac_set_eof(f);
do
{
size_t wrote = 0;
rflac_set_out_s16(f, out + got * ch, FRAMES + 64 - got);
result = rflac_process(f, NULL, &wrote);
got += wrote;
idle = wrote ? 0 : idle + 1;
} while (result == RFLAC_PROCESS_NEXT && idle < 4);
CHECK(got == FRAMES, "%s: %lu frames, %d wanted", name, (unsigned long)got,
FRAMES);
for (i = 0; i < got * ch && i < (size_t)FRAMES * ch; i++)
{
int64_t v = sample(kind, bps, (unsigned)(i % ch), (int64_t)(i / ch), &seed);
int16_t want = expect(v, bps);
if (out[i] != want)
{
if (!bad)
fprintf(stderr, " %s: sample %lu is %d, %d wanted (from %lld)\n",
name, (unsigned long)i, out[i], want, (long long)v);
bad++;
}
sum_err += out[i] - want;
}
CHECK(bad == 0, "%s: %u of %lu samples off", name, bad,
(unsigned long)(got * ch));
(void)sum_err;
rflac_free(f);
free(out);
free(buf);
}
int main(void)
{
run("s16_stereo.flac", "stereo", 16, 2);
run("s24_stereo.flac", "stereo", 24, 2);
run("s24_mono.flac", "mono", 24, 1);
run("s24_6ch.flac", "multi", 24, 6);
run("s32_wide.flac", "wide", 32, 2);
if (failures)
{
fprintf(stderr, "%u failure(s)\n", failures);
return 1;
}
printf("[pass] rflac_s16_test\n");
return 0;
}
@@ -1,6 +1,7 @@
/* rvorbis' codebook size bound: libvorbis' limit, so every table
* sized from an accepted codebook stays under 2^24 entries, and a real
* stream still decodes in full. */
* stream still decodes in full. A sparse codebook with no entry in use
* and a lookup table frees the table's scratch (LeakSanitizer). */
#include <stdio.h>
@@ -62,10 +63,63 @@ static void test_fixture_decodes(void)
rvorbis_close(v);
}
/* Vorbis packs header fields from the low bit up */
struct bitw { unsigned char *p; int bit; };
static void put_bits(struct bitw *w, uint32_t v, int n)
{
int i;
for (i = 0; i < n; i++, w->bit++)
if ((v >> i) & 1)
w->p[w->bit >> 3] |= (unsigned char)(1 << (w->bit & 7));
}
static void test_sparse_unused_lookup(void)
{
static const unsigned char id[30] = {
1, 'v', 'o', 'r', 'b', 'i', 's', 0, 0, 0, 0, /* version 0 */
1, 0x44, 0xAC, 0, 0, /* mono, 44100 Hz */
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0xB8, 1 /* blocks 256/2048 */
};
unsigned char setup[128];
struct bitw w;
int i, err = 0;
rvorbis *v;
memset(setup, 0, sizeof(setup));
memcpy(setup, "\005vorbis", 7);
w.p = setup;
w.bit = 7 * 8;
put_bits(&w, 0, 8); /* one codebook */
put_bits(&w, 0x564342, 24); /* its sync pattern */
put_bits(&w, 1, 16); /* 1 dimension */
put_bits(&w, 8, 24); /* 8 entries */
put_bits(&w, 0, 1); /* not ordered */
put_bits(&w, 1, 1); /* sparse */
for (i = 0; i < 8; i++)
put_bits(&w, 0, 1); /* no entry in use */
put_bits(&w, 1, 4); /* lookup type 1 */
put_bits(&w, 0, 32); /* minimum */
put_bits(&w, 0, 32); /* delta */
put_bits(&w, 3, 4); /* 4-bit values */
put_bits(&w, 0, 1); /* no sequence */
for (i = 0; i < 8; i++)
put_bits(&w, (uint32_t)i, 4); /* the lookup table */
/* what follows the codebook is not a valid setup; the codebook is
* read either way */
v = rvorbis_open_packets(id, (int)sizeof(id), setup, (int)sizeof(setup),
&err, NULL);
if (v)
rvorbis_close(v);
}
int main(void)
{
test_bounds();
test_fixture_decodes();
test_sparse_unused_lookup();
printf("rvorbis_codebook: %s (%d failures)\n",
failures ? "FAIL" : "PASS", failures);
return failures ? 1 : 0;
@@ -30,6 +30,14 @@
* simulate with a small buffer and a size_t value that would
* pre-patch have truncated; post-patch we get an early
* IMAGE_PROCESS_ERROR without even calling the decoder.
*
* 5. An indexed TGA whose colour map is cut short is refused after
* the surface is made, and the surface is freed (LeakSanitizer).
*
* 6. rtga_decode_rows gives the bytes rtga_process_image gives, for
* every type, depth and origin, raw and RLE, a file cut short,
* either byte order, into the caller's rows and into rtga's own,
* on one decoder reused across sizes.
*/
#include <stdio.h>
@@ -200,6 +208,34 @@ static void test_happy_path_rgb(void)
rtga_free(rtga);
}
static void test_cut_palette(void)
{
/* 2x2 indexed, a 256-entry 24-bit colour map of which 9 bytes
* are there */
uint8_t file[32];
rtga_t *rtga;
void *out = NULL;
unsigned w = 0, h = 0;
int rc;
int hdr_len;
memset(file, 0, sizeof(file));
hdr_len = make_tga_header(file, 0, 1, 1, 0, 256, 24, 2, 2, 8, 0);
rtga = rtga_alloc();
rtga_set_buf_ptr(rtga, file);
rc = rtga_process_image(rtga, &out, (size_t)hdr_len + 9, &w, &h, true);
if (rc != IMAGE_PROCESS_ERROR || out)
{
printf("[ERROR] cut colour map not refused (rc=%d)\n", rc);
failures++;
free(out);
}
else
printf("[SUCCESS] TGA with a cut colour map refused\n");
rtga_free(rtga);
}
static void test_oversized_size(void)
{
/* A size > INT_MAX should be rejected before the (int)size
@@ -228,12 +264,234 @@ static void test_oversized_size(void)
rtga_free(rtga);
}
/* ---- 5: rtga_decode_rows against rtga_process_image ---- */
static uint32_t rng_state = 12345;
static unsigned rng(void)
{
rng_state = rng_state * 1103515245u + 12345u;
return (rng_state >> 16) & 0x7FFF;
}
struct rows_dst
{
uint8_t *pic; /* the caller's rows, or NULL for rtga's own */
uint8_t *copy; /* where done() puts rtga's own rows */
unsigned w, h;
int asked, told, bad_order;
int refuse;
};
static bool rows_size(void *user, unsigned w, unsigned h)
{
struct rows_dst *d = (struct rows_dst*)user;
if (d->refuse)
return false;
d->w = w;
d->h = h;
d->pic = (uint8_t*)malloc((size_t)w * h * 4);
d->copy = (uint8_t*)malloc((size_t)w * h * 4);
return d->pic && d->copy;
}
static uint8_t *rows_row(void *user, unsigned y)
{
struct rows_dst *d = (struct rows_dst*)user;
d->asked++;
return (y & 1) ? NULL : d->pic + (size_t)y * d->w * 4;
}
static void rows_done(void *user, unsigned y, const uint8_t *px)
{
struct rows_dst *d = (struct rows_dst*)user;
d->told++;
if (y >= d->h)
d->bad_order++;
else
memcpy(d->copy + (size_t)y * d->w * 4, px, (size_t)d->w * 4);
}
/* A TGA of the given type, bpp and origin with random pixels; RLE
* types get runs and literals. Returns the length. */
static size_t make_tga(uint8_t *out, int type, int bpp, int w, int h,
int top_down, int pal_bits)
{
int indexed = ((type & 7) == 1);
int pal_len = indexed ? 1 + (int)(rng() % 300) : 0;
int pix_bytes = indexed ? 1 : bpp / 8;
int npix = w * h;
size_t n = (size_t)make_tga_header(out, (uint8_t)(rng() % 3),
(uint8_t)indexed, (uint8_t)type, 0, (uint16_t)pal_len,
(uint8_t)(indexed ? pal_bits : 0), (uint16_t)w, (uint16_t)h,
(uint8_t)(indexed ? 8 : bpp), (uint8_t)(top_down ? 0x20 : 0));
int i, k;
for (i = 0; i < out[0]; i++)
out[n++] = (uint8_t)rng();
for (i = 0; i < pal_len * ((pal_bits + 7) / 8); i++)
out[n++] = (uint8_t)rng();
if (type < 8)
{
for (i = 0; i < npix * pix_bytes; i++)
out[n++] = (uint8_t)rng();
return n;
}
for (i = 0; i < npix; )
{
int run = 1 + (int)(rng() % 128);
if (run > npix - i)
run = npix - i;
if (rng() & 1)
{
out[n++] = (uint8_t)(0x80 | (run - 1));
for (k = 0; k < pix_bytes; k++)
out[n++] = (uint8_t)rng();
}
else
{
out[n++] = (uint8_t)(run - 1);
for (k = 0; k < run * pix_bytes; k++)
out[n++] = (uint8_t)rng();
}
i += run;
}
return n;
}
static int compare_rows(rtga_t *dec, const uint8_t *file, size_t len,
bool rgba, const char *what)
{
rtga_t *ref = rtga_alloc();
void *out = NULL;
unsigned w = 0, h = 0, rw = 0, rh = 0, y, x;
struct rtga_rows rows;
struct rows_dst d;
int rc, bad = 0;
rtga_set_buf_ptr(ref, (void*)file);
do
{
rc = rtga_process_image(ref, &out, len, &w, &h, rgba);
} while (rc == IMAGE_PROCESS_NEXT);
rtga_free(ref);
memset(&d, 0, sizeof(d));
rows.size = rows_size;
rows.row = rows_row;
rows.done = rows_done;
rows.user = &d;
if (!rtga_decode_rows(dec, file, len, rgba, &rows, &rw, &rh))
bad = (rc == IMAGE_PROCESS_END);
else if (rc != IMAGE_PROCESS_END || rw != w || rh != h
|| d.asked != (int)h || d.told != (int)h || d.bad_order)
bad = 1;
else
{
for (y = 0; y < h && !bad; y++)
for (x = 0; x < w; x++)
{
uint32_t v = ((const uint32_t*)out)[(size_t)y * w + x];
const uint8_t *p = ((y & 1) ? d.copy : d.pic)
+ ((size_t)y * w + x) * 4;
const uint8_t *c = d.copy + ((size_t)y * w + x) * 4;
if ( p[0] != (uint8_t)v || p[1] != (uint8_t)(v >> 8)
|| p[2] != (uint8_t)(v >> 16) || p[3] != (uint8_t)(v >> 24)
|| memcmp(p, c, 4))
{
bad = 1;
break;
}
}
}
if (bad)
printf("[ERROR] decode_rows differs: %s (%ux%u)\n", what, w, h);
free(out);
free(d.pic);
free(d.copy);
return bad;
}
static void test_decode_rows(void)
{
static const int types[] = { 1, 2, 3, 9, 10, 11 };
static const int sizes[][2] = { {1, 1}, {7, 3}, {64, 5}, {300, 17}, {2, 40} };
uint8_t *file = (uint8_t*)malloc(1 << 20);
rtga_t *dec = rtga_alloc();
char what[96];
int t, b, z, o, rgba, cut, cases = 0, bad = 0;
for (t = 0; t < 6; t++)
for (b = 8; b <= 32; b += 8)
for (z = 0; z < 5; z++)
for (o = 0; o < 2; o++)
for (cut = 0; cut < 2; cut++)
for (rgba = 0; rgba < 2; rgba++)
{
int indexed = ((types[t] & 7) == 1);
int pal_bits;
size_t len;
if (types[t] == 3 || types[t] == 11)
{
if (b > 16)
continue;
}
else if (!indexed && b < 24)
continue;
pal_bits = (b == 8) ? 15 : (b == 16 ? 16 : b);
rng_state = (uint32_t)(t * 1000 + b * 100 + z * 10 + o);
len = make_tga(file, types[t], b, sizes[z][0],
sizes[z][1], o, pal_bits);
if (cut)
len -= len / 3;
sprintf(what, "type %d bpp %d %s%s%s", types[t], b,
o ? "top-down" : "bottom-up",
cut ? " cut short" : "", rgba ? " rgba" : " bgra");
bad += compare_rows(dec, file, len, rgba ? true : false,
what);
cases++;
}
{
struct rtga_rows rows;
struct rows_dst d;
unsigned w, h;
size_t len = make_tga(file, 2, 32, 4, 4, 0, 0);
memset(&d, 0, sizeof(d));
d.refuse = 1;
rows.size = rows_size;
rows.row = rows_row;
rows.done = rows_done;
rows.user = &d;
if (rtga_decode_rows(dec, file, len, true, &rows, &w, &h)
|| d.asked || w != 4 || h != 4)
{
printf("[ERROR] decode_rows went on after size() said no\n");
bad++;
}
if (rtga_decode_rows(dec, file, 10, true, &rows, &w, &h) || w || h)
{
printf("[ERROR] decode_rows took a cut header\n");
bad++;
}
}
if (bad)
failures++;
else
printf("[SUCCESS] rtga_decode_rows matches rtga_process_image (%d files)\n",
cases);
rtga_free(dec);
free(file);
}
int main(void)
{
test_malicious_palette_bits();
test_empty_indexed_palette();
test_happy_path_rgb();
test_oversized_size();
test_cut_palette();
test_decode_rows();
if (failures)
{
@@ -587,6 +587,149 @@ static const char fs_subgroup[] =
" col = vec4(subgroupMax(gl_FragCoord.x));\n"
"}\n";
/* What an emulator's generated stages say that the frontend's never do:
* #version 430, gl_WorkGroupSize, a uniform block laid out std430 whose
* one member is an array of unstated length (GL_EXT_scalar_block_layout,
* GL_EXT_uniform_buffer_unsized_array), and macros that name themselves. */
static const char cs_work_group[] =
"#version 430\n"
"layout(local_size_x = 32, local_size_y = 4) in;\n"
"layout(std430, set = 0, binding = 0) buffer ssbo { uint data[]; };\n"
"void main()\n"
"{\n"
" uint row = gl_NumWorkGroups.x * gl_WorkGroupSize.x;\n"
" uint at = gl_GlobalInvocationID.y * row + gl_GlobalInvocationID.x;\n"
" data[at] = gl_WorkGroupSize.x + gl_WorkGroupSize.y * 100u + gl_WorkGroupSize.z * 10000u;\n"
"}\n";
static const char vs_unsized_uniform[] =
"#version 450\n"
"#extension GL_EXT_scalar_block_layout : require\n"
"#extension GL_EXT_uniform_buffer_unsized_array : require\n"
"struct ctx_t { mat4 m; uint a; uint b; float c; float tail[3]; };\n"
"layout(std430, set = 0, binding = 3) uniform Contexts { ctx_t ctxs[]; };\n"
"layout(std430, set = 0, binding = 4) uniform Constants { vec4 vc[]; };\n"
"layout(push_constant) uniform push { uint which; };\n"
"layout(location = 0) in vec4 pos;\n"
"void main()\n"
"{\n"
" gl_Position = ctxs[which].m * pos * ctxs[which].tail[2] + vc[ctxs[which].a];\n"
"}\n";
/* draw_offset names itself through params(): the name stays a member */
static const char vs_self_macro[] =
"#version 450\n"
"struct params_t { uint draw_offset; uint bits; };\n"
"layout(std140, set = 0, binding = 0) uniform Params { params_t all_params[4]; };\n"
"layout(push_constant) uniform push { uint which; };\n"
"#define params() all_params[which]\n"
"#define draw_offset params().draw_offset\n"
"#define bits_of() params().bits\n"
"#define get_bits(v, at, n) (((v) >> (at)) & ((1u << (n)) - 1u))\n"
"#define enabled(i) (get_bits(bits_of() + draw_offset, (i) * 2, 2) != 0u)\n"
"layout(location = 0) in vec4 pos;\n"
"void main()\n"
"{\n"
" gl_Position = enabled(1) ? pos : pos * float(draw_offset);\n"
"}\n";
/* -- and ++ through several components of a vector at once; a fragment
* stage's part of a push block that starts where the vertex stage's ends;
* the stencil reference written by the stage. */
static const char cs_swizzle_step[] =
"#version 450\n"
"layout(local_size_x = 8) in;\n"
"layout(std430, set = 0, binding = 0) buffer ssbo { uvec4 data[]; };\n"
"struct inv_t { uvec3 size; uvec3 size_log2; };\n"
"void main()\n"
"{\n"
" inv_t inv;\n"
" uvec2 was;\n"
" inv.size = uvec3(64u, 32u, 4u);\n"
" inv.size_log2 = uvec3(6u, 5u, 2u);\n"
" inv.size_log2.xy --;\n"
" was = inv.size.xy++;\n"
" ++inv.size.zx;\n"
" data[gl_GlobalInvocationID.x] = uvec4(inv.size_log2.xy + was, inv.size.xz);\n"
"}\n";
static const char fs_member_offset[] =
"#version 450\n"
"layout(push_constant) uniform FragmentConfiguration\n"
"{\n"
" layout(offset = 68)\n"
" uint config;\n"
" float timestamp;\n"
" vec4 params;\n"
"};\n"
"layout(location = 0) out vec4 col;\n"
"void main() { col = params * timestamp * float(config); }\n";
static const char fs_stencil_export[] =
"#version 450\n"
"#extension GL_ARB_shader_stencil_export : enable\n"
"layout(location = 0) in vec2 uv;\n"
"void main()\n"
"{\n"
" gl_FragDepth = uv.x;\n"
" gl_FragStencilRefARB = int(uv.y * 255.0);\n"
"}\n";
/* One multisampled image, read and written a sample at a time: as a
* storage image by a compute stage that resolves it, and as a sampler by
* a fragment stage that resolves depth and stencil. */
static const char cs_resolve_ms[] =
"#version 450\n"
"layout(local_size_x = 8, local_size_y = 8) in;\n"
"layout(set = 0, binding = 0, rg16f) uniform readonly restrict image2DMS multisampled;\n"
"layout(set = 0, binding = 1) uniform writeonly restrict image2D resolve;\n"
"void main()\n"
"{\n"
" ivec2 size = imageSize(multisampled);\n"
" ivec2 at = ivec2(gl_GlobalInvocationID.xy);\n"
" vec4 sum = vec4(0.0);\n"
" int i;\n"
" if (any(greaterThanEqual(at, size)))\n"
" return;\n"
" for (i = 0; i < 4; i++)\n"
" sum += imageLoad(multisampled, at, i);\n"
" imageStore(resolve, at, sum * 0.25);\n"
"}\n";
static const char cs_unresolve_ms[] =
"#version 450\n"
"layout(local_size_x = 8, local_size_y = 8) in;\n"
"layout(set = 0, binding = 0) uniform writeonly restrict image2DMS multisampled;\n"
"layout(set = 0, binding = 1, rgba8) uniform readonly restrict image2D resolve;\n"
"void main()\n"
"{\n"
" ivec2 at = ivec2(gl_GlobalInvocationID.xy);\n"
" imageStore(multisampled, at, int(gl_LocalInvocationID.x & 3u), imageLoad(resolve, at));\n"
"}\n";
static const char fs_resolve_ms[] =
"#version 450\n"
"layout(set = 0, binding = 0) uniform sampler2DMS fs0;\n"
"layout(set = 0, binding = 1) uniform usampler2DMS fs1;\n"
"layout(location = 0) out vec4 col;\n"
"void main()\n"
"{\n"
" ivec2 at = ivec2(gl_FragCoord.xy) % textureSize(fs0);\n"
" col = vec4(texelFetch(fs0, at, 2).x, float(texelFetch(fs1, at, gl_SampleID).x), 0.0, 1.0);\n"
"}\n";
static const char cs_read_no_format[] =
"#version 450\n"
"layout(local_size_x = 8) in;\n"
"layout(set = 0, binding = 0) uniform readonly image2D src;\n"
"layout(set = 0, binding = 1, rgba8) uniform writeonly image2D dst;\n"
"void main() { imageStore(dst, ivec2(0), imageLoad(src, ivec2(0))); }\n";
static const char fs_old_version[] =
"#version 410\n"
"layout(location = 0) out vec4 col;\n"
"void main() { col = vec4(1.0); }\n";
static enum rslc_stage cur_stage = RSLC_STAGE_FRAGMENT;
static bool compile(const char *src, enum rslc_dst dst, unsigned version,
@@ -602,6 +745,75 @@ static bool compile(const char *src, enum rslc_dst dst, unsigned version,
}
/* Whether the module declares the capability. */
/* GL_EXT_fragment_shader_barycentric: a varying as each vertex's value,
* interpolated by the stage itself. */
static const char fs_barycentric[] =
"#version 450\n"
"#extension GL_EXT_fragment_shader_barycentric : require\n"
"layout(location = 0) pervertexEXT in vec4 tc_raw[3];\n"
"layout(location = 1) in vec4 plain;\n"
"layout(location = 0) out vec4 col;\n"
"vec4 interpolate(const in vec4[3] v)\n"
"{\n"
" return v[0] + (v[1] - v[0]) * gl_BaryCoordEXT.y + (v[2] - v[0]) * gl_BaryCoordEXT.z;\n"
"}\n"
"void main() { col = interpolate(tc_raw) + plain * gl_BaryCoordNoPerspEXT.x; }\n";
/* A multisampled attachment read a sample at a time, and a multisampled
* sampler asked how many it has. */
static const char fs_subpass_ms[] =
"#version 450\n"
"layout(input_attachment_index = 0, set = 0, binding = 0) uniform subpassInputMS att;\n"
"layout(input_attachment_index = 1, set = 0, binding = 1) uniform subpassInput plain;\n"
"layout(set = 0, binding = 2) uniform sampler2DMS tex;\n"
"layout(location = 0) out vec4 col;\n"
"void main()\n"
"{\n"
" int n = textureSamples(tex);\n"
" col = subpassLoad(att, gl_SampleID) + subpassLoad(plain) + vec4(float(n));\n"
"}\n";
/* What the SPIR-V for ordinary code should look like: one access chain
* for a path into a block, a const in parameter handed over as a value,
* each pointer type and each constant vector made once. */
static const char fs_lean[] =
"#version 450\n"
"struct item { vec4 a; vec4 b[2]; };\n"
"layout(set = 0, binding = 0, std140) uniform Block { item items[4]; int which; };\n"
"layout(location = 0) in vec4 v;\n"
"layout(location = 0) out vec4 col;\n"
"vec4 pick(const in vec4 x, const in vec4[3] arr, in vec4 y, const in int i)\n"
"{\n"
" y += x + vec4(0.0, 0.0, 0.0, 1.0);\n"
" return arr[i] + y + arr[1];\n"
"}\n"
"void main()\n"
"{\n"
" vec4 arr[3] = vec4[3](v, v * 2.0, vec4(0.0, 0.0, 0.0, 1.0));\n"
" item whole = items[which];\n"
" col = pick(items[which].b[1], arr, vec4(0.0, 0.0, 0.0, 1.0), which)\n"
" * items[which].b[1].z + whole.a + items[which].a;\n"
"}\n";
static const char fs_const_in_written[] =
"#version 450\n"
"layout(location = 0) out vec4 col;\n"
"float f(const in float x) { x = 1.0; return x; }\n"
"void main() { col = vec4(f(2.0)); }\n";
/* length() of a buffer block's unsized last member: the buffer's to say,
* named bare and through the block's instance. */
static const char cs_array_length[] =
"#version 450\n"
"layout(local_size_x = 32) in;\n"
"layout(set = 0, binding = 0, std430) readonly buffer In { uint head; uint src[]; };\n"
"layout(set = 0, binding = 1, std430) buffer Out { uint sum; uint seen[]; } o;\n"
"void main()\n"
"{\n"
" if (gl_GlobalInvocationID.x < src.length() && int(gl_GlobalInvocationID.x) < o.seen.length())\n"
" atomicAdd(o.sum, src[gl_GlobalInvocationID.x] + head);\n"
"}\n";
static bool has_capability(const uint32_t *w, size_t n, uint32_t cap)
{
size_t i = 5;
@@ -1348,6 +1560,373 @@ int main(int argc, char **argv)
}
}
/* An emulator's generated stages */
{
char err[256];
struct rslc_opts o;
struct rslc_result r;
size_t at = 0;
/* #version 430 and gl_WorkGroupSize: a constant, (32, 4, 1) */
cur_stage = RSLC_STAGE_COMPUTE;
memset(&r, 0, sizeof(r));
CHECK(compile(cs_work_group, RSLC_DST_SPIRV, 0, &o, &r, err, sizeof(err)));
if (err[0])
fprintf(stderr, "work_group: %s\n", err);
if (r.blob)
{
size_t i = 5;
unsigned c32 = 0, folded = 0;
while (i < r.spirv_len && (r.spirv[i] >> 16))
{
/* OpConstant: the x size the row is multiplied by, and the
* three sizes folded into one number */
if ((r.spirv[i] & 0xffffu) == 43 && (r.spirv[i] >> 16) == 4)
{
c32 += r.spirv[i + 3] == 32;
folded += r.spirv[i + 3] == 32 + 4 * 100 + 1 * 10000;
}
i += r.spirv[i] >> 16;
}
CHECK(c32 == 1 && folded == 1);
write_spirv(dir, "work_group", &r);
rslc_result_free(&r, &o);
}
memset(&r, 0, sizeof(r));
CHECK(compile(cs_work_group, RSLC_DST_GLSL, 450, &o, &r, err, sizeof(err)));
if (r.blob)
rslc_result_free(&r, &o);
/* 410 is still not this dialect */
cur_stage = RSLC_STAGE_FRAGMENT;
memset(&r, 0, sizeof(r));
CHECK(!compile(fs_old_version, RSLC_DST_SPIRV, 0, &o, &r, err, sizeof(err)));
CHECK(strstr(err, "#version") != NULL);
/* A uniform block that says std430 and ends unsized: two runtime
* arrays (29), the struct's float[3] straight after its three
* scalars at 76 - std140 would round it up to 80 - and SPIR-V only */
cur_stage = RSLC_STAGE_VERTEX;
memset(&r, 0, sizeof(r));
CHECK(compile(vs_unsized_uniform, RSLC_DST_SPIRV, 0, &o, &r, err, sizeof(err)));
if (err[0])
fprintf(stderr, "unsized_uniform: %s\n", err);
if (r.blob)
{
size_t i = 5;
unsigned tail_at_76 = 0, tail_at_80 = 0;
CHECK(count_op(r.spirv, r.spirv_len, 29, &at) == 2);
while (i < r.spirv_len && (r.spirv[i] >> 16))
{
/* OpMemberDecorate type member Offset n, of member 4 */
if ((r.spirv[i] & 0xffffu) == 72 && (r.spirv[i] >> 16) == 5
&& r.spirv[i + 2] == 4 && r.spirv[i + 3] == 35)
{
tail_at_76 += r.spirv[i + 4] == 76;
tail_at_80 += r.spirv[i + 4] == 80;
}
i += r.spirv[i] >> 16;
}
CHECK(tail_at_76 == 1 && tail_at_80 == 0);
write_spirv(dir, "unsized_uniform", &r);
rslc_result_free(&r, &o);
}
memset(&r, 0, sizeof(r));
CHECK(!compile(vs_unsized_uniform, RSLC_DST_GLSL, 450, &o, &r, err, sizeof(err)));
CHECK(strstr(err, "SPIR-V") != NULL);
/* A macro that names itself through another, as an argument that
* is scanned twice: it compiles, the name a member the second time */
memset(&r, 0, sizeof(r));
CHECK(compile(vs_self_macro, RSLC_DST_SPIRV, 0, &o, &r, err, sizeof(err)));
if (err[0])
fprintf(stderr, "self_macro: %s\n", err);
if (r.blob)
{
write_spirv(dir, "self_macro", &r);
rslc_result_free(&r, &o);
}
/* v.xy-- and ++v.zx: no one pointer to step through, so read,
* stepped and written back; the module is spirv-val's to check */
cur_stage = RSLC_STAGE_COMPUTE;
memset(&r, 0, sizeof(r));
CHECK(compile(cs_swizzle_step, RSLC_DST_SPIRV, 0, &o, &r, err, sizeof(err)));
if (err[0])
fprintf(stderr, "swizzle_step: %s\n", err);
if (r.blob)
{
write_spirv(dir, "swizzle_step", &r);
rslc_result_free(&r, &o);
}
/* A member that says its offset is placed there (68), the next
* ones after it (72, then the vec4 at 80); SPIR-V only */
cur_stage = RSLC_STAGE_FRAGMENT;
memset(&r, 0, sizeof(r));
CHECK(compile(fs_member_offset, RSLC_DST_SPIRV, 0, &o, &r, err, sizeof(err)));
if (err[0])
fprintf(stderr, "member_offset: %s\n", err);
if (r.blob)
{
size_t i = 5;
unsigned right = 0, members = 0;
while (i < r.spirv_len && (r.spirv[i] >> 16))
{
if ((r.spirv[i] & 0xffffu) == 72 && (r.spirv[i] >> 16) == 5 && r.spirv[i + 3] == 35)
{
static const uint32_t want[3] = { 68, 72, 80 };
members++;
right += r.spirv[i + 2] < 3 && r.spirv[i + 4] == want[r.spirv[i + 2]];
}
i += r.spirv[i] >> 16;
}
CHECK(members == 3 && right == 3);
CHECK(r.refl->push.size == 96);
write_spirv(dir, "member_offset", &r);
rslc_result_free(&r, &o);
}
memset(&r, 0, sizeof(r));
CHECK(!compile(fs_member_offset, RSLC_DST_GLSL, 450, &o, &r, err, sizeof(err)));
CHECK(strstr(err, "SPIR-V") != NULL);
/* gl_FragStencilRefARB: StencilExportEXT (5013), its extension and
* StencilRefReplacingEXT (5027) on the entry point; SPIR-V only */
memset(&r, 0, sizeof(r));
CHECK(compile(fs_stencil_export, RSLC_DST_SPIRV, 0, &o, &r, err, sizeof(err)));
if (err[0])
fprintf(stderr, "stencil_export: %s\n", err);
if (r.blob)
{
size_t i = 5;
unsigned mode = 0;
CHECK(has_capability(r.spirv, r.spirv_len, 5013));
CHECK(count_op(r.spirv, r.spirv_len, 10 /* OpExtension */, &at) == 1);
while (i < r.spirv_len && (r.spirv[i] >> 16))
{
mode += (r.spirv[i] & 0xffffu) == 16 && r.spirv[i + 2] == 5027;
i += r.spirv[i] >> 16;
}
CHECK(mode == 1);
write_spirv(dir, "stencil_export", &r);
rslc_result_free(&r, &o);
}
memset(&r, 0, sizeof(r));
CHECK(!compile(fs_stencil_export, RSLC_DST_GLSL, 450, &o, &r, err, sizeof(err)));
CHECK(strstr(err, "SPIR-V") != NULL);
/* A multisampled storage image: StorageImageMultisample (27), the
* rg16f format with StorageImageExtendedFormats (49), a write-only
* image without a format with StorageImageWriteWithoutFormat (56);
* the sample an operand of the read. SPIR-V only. */
cur_stage = RSLC_STAGE_COMPUTE;
memset(&r, 0, sizeof(r));
CHECK(compile(cs_resolve_ms, RSLC_DST_SPIRV, 0, &o, &r, err, sizeof(err)));
if (err[0])
fprintf(stderr, "resolve_ms: %s\n", err);
if (r.blob)
{
CHECK(has_capability(r.spirv, r.spirv_len, 27));
CHECK(has_capability(r.spirv, r.spirv_len, 49));
CHECK(has_capability(r.spirv, r.spirv_len, 56));
/* OpImageRead with an operand mask: 7 words, the mask Sample (0x40) */
CHECK(count_op(r.spirv, r.spirv_len, 98, &at) == 1);
CHECK((r.spirv[at - 1] >> 16) == 7 && r.spirv[at + 4] == 0x40);
write_spirv(dir, "resolve_ms", &r);
rslc_result_free(&r, &o);
}
memset(&r, 0, sizeof(r));
CHECK(!compile(cs_resolve_ms, RSLC_DST_GLSL, 450, &o, &r, err, sizeof(err)));
CHECK(strstr(err, "SPIR-V") != NULL);
/* The other way: OpImageWrite (99) with the sample */
memset(&r, 0, sizeof(r));
CHECK(compile(cs_unresolve_ms, RSLC_DST_SPIRV, 0, &o, &r, err, sizeof(err)));
if (err[0])
fprintf(stderr, "unresolve_ms: %s\n", err);
if (r.blob)
{
CHECK(has_capability(r.spirv, r.spirv_len, 27));
CHECK(!has_capability(r.spirv, r.spirv_len, 49));
CHECK(count_op(r.spirv, r.spirv_len, 99, &at) == 1);
CHECK((r.spirv[at - 1] >> 16) == 6 && r.spirv[at + 3] == 0x40);
write_spirv(dir, "unresolve_ms", &r);
rslc_result_free(&r, &o);
}
/* An image that is read still needs its format */
memset(&r, 0, sizeof(r));
CHECK(!compile(cs_read_no_format, RSLC_DST_SPIRV, 0, &o, &r, err, sizeof(err)));
CHECK(strstr(err, "format") != NULL);
/* sampler2DMS and usampler2DMS: two fetches (95), each with its sample */
cur_stage = RSLC_STAGE_FRAGMENT;
memset(&r, 0, sizeof(r));
CHECK(compile(fs_resolve_ms, RSLC_DST_SPIRV, 0, &o, &r, err, sizeof(err)));
if (err[0])
fprintf(stderr, "fs_resolve_ms: %s\n", err);
if (r.blob)
{
size_t i = 5;
unsigned ms_images = 0;
CHECK(count_op(r.spirv, r.spirv_len, 95, &at) == 2);
CHECK((r.spirv[at - 1] >> 16) == 7 && r.spirv[at + 4] == 0x40);
while (i < r.spirv_len && (r.spirv[i] >> 16))
{
/* OpTypeImage: 2D (1), not arrayed, multisampled, sampled */
ms_images += (r.spirv[i] & 0xffffu) == 25 && r.spirv[i + 3] == 1
&& r.spirv[i + 5] == 0 && r.spirv[i + 6] == 1 && r.spirv[i + 7] == 1;
i += r.spirv[i] >> 16;
}
CHECK(ms_images == 2);
write_spirv(dir, "fs_resolve_ms", &r);
rslc_result_free(&r, &o);
}
memset(&r, 0, sizeof(r));
CHECK(!compile(fs_resolve_ms, RSLC_DST_GLSL, 450, &o, &r, err, sizeof(err)));
cur_stage = RSLC_STAGE_FRAGMENT;
/* pervertexEXT and gl_BaryCoordEXT: FragmentBarycentricKHR (5284)
* with its extension, PerVertexKHR (5285) on the one input that asks
* for it, the two built-ins (5286, 5287); SPIR-V only */
memset(&r, 0, sizeof(r));
CHECK(compile(fs_barycentric, RSLC_DST_SPIRV, 0, &o, &r, err, sizeof(err)));
if (err[0])
fprintf(stderr, "barycentric: %s\n", err);
if (r.blob)
{
size_t i = 5;
unsigned pervertex = 0, bary = 0, nopersp = 0;
CHECK(has_capability(r.spirv, r.spirv_len, 5284));
CHECK(count_op(r.spirv, r.spirv_len, 10 /* OpExtension */, &at) == 1);
while (i < r.spirv_len && (r.spirv[i] >> 16))
{
if ((r.spirv[i] & 0xffffu) == 71 /* OpDecorate */)
{
pervertex += r.spirv[i + 2] == 5285;
bary += r.spirv[i + 2] == 11 && r.spirv[i + 3] == 5286;
nopersp += r.spirv[i + 2] == 11 && r.spirv[i + 3] == 5287;
}
i += r.spirv[i] >> 16;
}
CHECK(pervertex == 1 && bary == 1 && nopersp == 1);
write_spirv(dir, "barycentric", &r);
rslc_result_free(&r, &o);
}
memset(&r, 0, sizeof(r));
CHECK(!compile(fs_barycentric, RSLC_DST_GLSL, 450, &o, &r, err, sizeof(err)));
CHECK(strstr(err, "SPIR-V") != NULL);
/* subpassInputMS: its image type multisampled where the plain one's
* is not, the read with a Sample operand; textureSamples is
* OpImageQuerySamples (107). SPIR-V only. */
memset(&r, 0, sizeof(r));
CHECK(compile(fs_subpass_ms, RSLC_DST_SPIRV, 0, &o, &r, err, sizeof(err)));
if (err[0])
fprintf(stderr, "subpass_ms: %s\n", err);
if (r.blob)
{
size_t i = 5;
unsigned ms = 0, single = 0, sampled = 0, unsampled = 0;
while (i < r.spirv_len && (r.spirv[i] >> 16))
{
unsigned op = r.spirv[i] & 0xffffu, len = r.spirv[i] >> 16;
if (op == 25 /* OpTypeImage */ && r.spirv[i + 3] == 6 /* SubpassData */)
{
ms += r.spirv[i + 6] == 1;
single += r.spirv[i + 6] == 0;
}
if (op == 98 /* OpImageRead */)
{
sampled += len == 7 && r.spirv[i + 5] == 0x40;
unsampled += len == 5;
}
i += len;
}
CHECK(ms == 1 && single == 1);
CHECK(sampled == 1 && unsampled == 1);
CHECK(count_op(r.spirv, r.spirv_len, 107, &at) == 1);
CHECK(has_capability(r.spirv, r.spirv_len, 50 /* ImageQuery */));
write_spirv(dir, "subpass_ms", &r);
rslc_result_free(&r, &o);
}
memset(&r, 0, sizeof(r));
CHECK(!compile(fs_subpass_ms, RSLC_DST_GLSL, 450, &o, &r, err, sizeof(err)));
CHECK(strstr(err, "SPIR-V") != NULL);
/* Lean code. No access chain starts from another's result; no
* pointer type and no constant vector is made twice; of pick's
* four parameters only the plain in one is a pointer; a const in
* parameter cannot be written. */
memset(&r, 0, sizeof(r));
CHECK(compile(fs_lean, RSLC_DST_SPIRV, 0, &o, &r, err, sizeof(err)));
if (err[0])
fprintf(stderr, "lean: %s\n", err);
if (r.blob)
{
size_t i, j;
unsigned chained = 0, longest = 0, twice = 0, params = 0, by_pointer = 0;
for (i = 5; i < r.spirv_len && (r.spirv[i] >> 16); i += r.spirv[i] >> 16)
{
unsigned op = r.spirv[i] & 0xffffu, len = r.spirv[i] >> 16;
if (op == 65 /* OpAccessChain */)
{
if (len - 4 > longest)
longest = len - 4;
for (j = 5; j < r.spirv_len && (r.spirv[j] >> 16); j += r.spirv[j] >> 16)
chained += (r.spirv[j] & 0xffffu) == 65 && r.spirv[j + 2] == r.spirv[i + 3];
}
if (op == 32 /* OpTypePointer */ || op == 44 /* OpConstantComposite */)
for (j = i + len; j < r.spirv_len && (r.spirv[j] >> 16); j += r.spirv[j] >> 16)
if (r.spirv[j] == r.spirv[i]
&& (op == 32 ? r.spirv[j + 2] == r.spirv[i + 2] && r.spirv[j + 3] == r.spirv[i + 3]
: r.spirv[j + 1] == r.spirv[i + 1]
&& !memcmp(r.spirv + j + 3, r.spirv + i + 3, (len - 3) * sizeof(uint32_t))))
twice++;
if (op == 55 /* OpFunctionParameter */)
{
params++;
for (j = 5; j < r.spirv_len && (r.spirv[j] >> 16); j += r.spirv[j] >> 16)
by_pointer += (r.spirv[j] & 0xffffu) == 32 && r.spirv[j + 1] == r.spirv[i + 1];
}
}
CHECK(chained == 0);
CHECK(longest == 4); /* items, [which], b, [1] */
CHECK(twice == 0);
CHECK(params == 4 && by_pointer == 1);
write_spirv(dir, "lean", &r);
rslc_result_free(&r, &o);
}
memset(&r, 0, sizeof(r));
CHECK(!compile(fs_const_in_written, RSLC_DST_SPIRV, 0, &o, &r, err, sizeof(err)));
/* length() of an unsized array is OpArrayLength (68) of the block
* and the member, not a constant: member 1 of each block here */
cur_stage = RSLC_STAGE_COMPUTE;
memset(&r, 0, sizeof(r));
CHECK(compile(cs_array_length, RSLC_DST_SPIRV, 0, &o, &r, err, sizeof(err)));
if (err[0])
fprintf(stderr, "array_length: %s\n", err);
if (r.blob)
{
size_t i;
unsigned lengths = 0, of_member_1 = 0;
for (i = 5; i < r.spirv_len && (r.spirv[i] >> 16); i += r.spirv[i] >> 16)
if ((r.spirv[i] & 0xffffu) == 68)
{
lengths++;
of_member_1 += r.spirv[i + 4] == 1;
}
CHECK(lengths == 2 && of_member_1 == 2);
write_spirv(dir, "array_length", &r);
rslc_result_free(&r, &o);
}
memset(&r, 0, sizeof(r));
CHECK(!compile(cs_array_length, RSLC_DST_GLSL, 450, &o, &r, err, sizeof(err)));
CHECK(strstr(err, "SPIR-V") != NULL);
cur_stage = RSLC_STAGE_FRAGMENT;
}
if (fails)
{
fprintf(stderr, "rslc_ext_test: %d failure(s)\n", fails);
@@ -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;
}
Loaded 100 of 168 files, more files were not shown because too many files have changed in this diff. Show more