Compare commits

...
Author SHA1 Message Date
libretroadmin eba3436f57 libretro-common/xxh3: short inputs without the seed's cost
xxh3_64 takes inputs up to 240 octets through one function per range,
and one per range for seed 0, where the secret folds into constants.
The ranges no longer share a frame, so none pays another's register
saves. Hashes are unchanged; at every length up to 240 the short path
now executes fewer instructions than xxHash's own, 70 against 75 for
64 octets, and runs about 6% faster than it there instead of 8% slower.
2026-11-13 05:29:08 +01:00
LibretroAdmin 6e7ad2318b rslc: invariant on a declared variable, and a double scalar
RSLC_EXTENDED, SPIR-V only. Both are what the parallel-ps3 vertex
programs carry on a GPU whose driver is NVIDIA's and that has 64-bit
floats: every vertex program a game has was refused there, which
stopped the emulator. The core's test built its programs as a software
driver gets them and never made either.

- invariant gl_Position; (and any output or built-in named after its
  declaration) makes the variable invariant: the Invariant decoration,
  on the gl_PerVertex member for a built-in.
- double: a 64-bit scalar. It is converted to and from the other
  numbers (implicitly from int, uint and float), calculated with,
  negated and compared; abs, sign, floor, ceil, trunc, round, fract,
  sqrt, min, max, clamp and mix take it. A literal converted to double
  keeps the value it was written with (0.000001 is the double, not the
  float widened), as glslang's does. A double is never folded: no
  vectors, block members or constant expressions of it. Float64 is
  declared.

rslc_ext_test has the vertex stage's depth function word for word; it
checks the member's decoration, the 64-bit type, the conversions and
the literal's bits, and the module validates.
2026-11-13 03:56:50 +01:00
LibretroAdmin 07bea38175 rslc: the builder can ask an array's length
rslc_b_length makes what length() makes when parsed: the declared
length of a sized array, and for the unsized last member of a storage
block OpArrayLength of the block (SPIR-V only). A stage that loops to
the end of a buffer could not be built by calls without it.

rslc_ext_build_test builds a compute stage that sums a storage block's
unsized array up to its length, and gets the words the same stage
parsed from GLSL compiles to.
2026-11-13 03:21:28 +01:00
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
171 changed files with 7386 additions and 1081 deletions

No files matched your search

@@ -235,6 +235,27 @@ jobs:
./vfs_positional_nommap_test.exe ./vfs_positional_nommap_test.exe
./vfs_positional_ntverify_test.exe ./vfs_positional_ntverify_test.exe
./vfs_positional_pathreopen_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 # The Win64 fastjmp_set stores xmm6-xmm15 with movaps and faults
# on a misaligned buffer. Linux has no such stores, so the Linux # 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_dir_record_test
./cdfs_pure_api_test ./cdfs_pure_api_test
./cdfs_io_error_test ./cdfs_io_error_test
./cdfs_file_test
env: env:
ASAN_OPTIONS: detect_leaks=1 ASAN_OPTIONS: detect_leaks=1
UBSAN_OPTIONS: print_stacktrace=1:halt_on_error=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_dir_record_test.exe
WINEDEBUG=-all wine ./cdfs_pure_api_test.exe WINEDEBUG=-all wine ./cdfs_pure_api_test.exe
WINEDEBUG=-all wine ./cdfs_io_error_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. # the same run as vfs_positional_test here.
vfs_positional_pathreopen_test vfs_positional_pathreopen_test
vfs_positional_ntverify_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 cdrom_cuesheet_overflow_test
rdvd_h262_test rdvd_h262_test
rdvd_lpcm_test rdvd_lpcm_test
@@ -318,6 +324,8 @@ jobs:
cdfs_dir_record_test cdfs_dir_record_test
cdfs_pure_api_test cdfs_pure_api_test
cdfs_io_error_test cdfs_io_error_test
# cdfs tracks opened by filename: iso, bin, cue beside its bin
cdfs_file_test
chd_meta_overflow_test chd_meta_overflow_test
chd_stream_threads_test chd_stream_threads_test
chd_stream_ahead_test chd_stream_ahead_test
@@ -462,6 +470,9 @@ jobs:
# Twenty threads change one record two million times in all # Twenty threads change one record two million times in all
# while three copy it: no change lost, no copy torn. # while three copy it: no change lost, no copy torn.
retro_cas_record_test 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 sthread_ex_test
fast_cores_test fast_cores_test
memshm_test memshm_test
@@ -491,6 +502,11 @@ jobs:
# The FLAC arm, sample-exact in every input mode, a grown # The FLAC arm, sample-exact in every input mode, a grown
# demuxed packet set and a raised WebM wall both resuming. # demuxed packet set and a raised WebM wall both resuming.
audio_transfer_flac_test 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 # The HDR video blits: PQ and HLG to linear scRGB against
# double-precision references, and HLG tone-mapped as HDR. # double-precision references, and HLG tone-mapped as HDR.
image_hdr_blit_test image_hdr_blit_test
@@ -545,6 +561,10 @@ jobs:
# self-contained regression tests came to be compiled every # self-contained regression tests came to be compiled every
# run and never executed. # run and never executed.
declare -a NORUN_TARGETS=( 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. # Fixture-driven checks run in Linux-samples-record.yml.
flv_remux_test flv_remux_test
rtmp_socket_native_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 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 ./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 - name: Load RARCH_VERSION from version.all
id: version id: version
shell: bash shell: bash
+2
View File
@@ -1,4 +1,6 @@
# Future # 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 - 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 - 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 - 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 OBJ += $(LIBRETRO_COMM_DIR)/compat/compat_strl.o
endif 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_scale.o \
$(LIBRETRO_COMM_DIR)/formats/image_texture_set.o \ $(LIBRETRO_COMM_DIR)/formats/image_texture_set.o \
$(LIBRETRO_COMM_DIR)/formats/image/image_yuv_blit.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_packet.o \
$(LIBRETRO_COMM_DIR)/formats/dvd/rdvd_spu.o \ $(LIBRETRO_COMM_DIR)/formats/dvd/rdvd_spu.o \
$(LIBRETRO_COMM_DIR)/formats/dvd/rdvd_ifo.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_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 # 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. # wants sound from an MPEG-1 stream needs HAVE_RMP3 as well.
endif 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/image_transfer.o \
$(LIBRETRO_COMM_DIR)/formats/video_player.o \ $(LIBRETRO_COMM_DIR)/formats/video_player.o \
$(LIBRETRO_COMM_DIR)/formats/audio_transfer.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)/memmap/memmap.o \
$(LIBRETRO_COMM_DIR)/formats/m3u/rm3u.o $(LIBRETRO_COMM_DIR)/formats/m3u/rm3u.o
@@ -2889,7 +2889,7 @@ ifeq ($(HAVE_NETWORKING), 1)
cheevos/cheevos_client.o \ cheevos/cheevos_client.o \
cheevos/cheevos_menu.o \ cheevos/cheevos_menu.o \
cheevos/cheevos_badge.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_client.o \
deps/rcheevos/src/rc_compat.o \ deps/rcheevos/src/rc_compat.o \
deps/rcheevos/src/rc_libretro.o \ deps/rcheevos/src/rc_libretro.o \
+1
View File
@@ -23,6 +23,7 @@
#include <streams/file_stream.h> #include <streams/file_stream.h>
#include <features/features_cpu.h> #include <features/features_cpu.h>
#include <formats/cdfs.h> #include <formats/cdfs.h>
#include <streams/cdfs_file.h>
#include <compat/strl.h> #include <compat/strl.h>
#include <retro_miscellaneous.h> #include <retro_miscellaneous.h>
#include <retro_math.h> #include <retro_math.h>
+4 -6
View File
@@ -33,7 +33,6 @@
typedef struct typedef struct
{ {
int64_t pos; /* in the disc image */ int64_t pos; /* in the disc image */
uint64_t file_pos; /* where the file stands; seeks to it are skipped */
ciso_t *ciso; ciso_t *ciso;
RFILE *file; RFILE *file;
} rcheevos_ciso_t; } rcheevos_ciso_t;
@@ -51,12 +50,12 @@ static int rcheevos_ciso_pump(rcheevos_ciso_t *handle, bool opening)
if (err != CISO_PENDING) if (err != CISO_PENDING)
return err; return err;
if ( handle->file_pos == req.offset /* At the offset in one call; seek and read where there is none */
|| filestream_seek(handle->file, (int64_t)req.offset, 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) RETRO_VFS_SEEK_POSITION_START) == 0)
got = filestream_read(handle->file, req.dst, (int64_t)req.length); 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); 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) if (!handle)
return NULL; return NULL;
handle->pos = 0; handle->pos = 0;
handle->file_pos = 0;
handle->ciso = NULL; handle->ciso = NULL;
/* the descriptor path: no stdio buffer between the file and the /* the descriptor path: no stdio buffer between the file and the
* buffers rc_hash reads into */ * buffers rc_hash reads into */
+6
View File
@@ -571,6 +571,10 @@
* times. Off: the fixed-timer pacing threaded video always had. */ * times. Off: the fixed-timer pacing threaded video always had. */
#define DEFAULT_VIDEO_THREADED_DISPLAY_PACING false #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 /* The Direct3D 9 drivers on a Direct3D9Ex device: a window presented
* through the flip model, and the frame latency settings honoured. * through the flip model, and the frame latency settings honoured.
* Off: the discard swap effect and the runtime's own queue, as before. */ * 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_SUBLABELS_CURRENT_SELECTION_ONLY false
#define DEFAULT_MENU_SHOW_CONFIRM true #define DEFAULT_MENU_SHOW_CONFIRM true
#define DEFAULT_MENU_DYNAMIC_WALLPAPER_ENABLE true #define DEFAULT_MENU_DYNAMIC_WALLPAPER_ENABLE true
#define DEFAULT_MENU_SCROLL_QUICK false
#define DEFAULT_MENU_SCROLL_FAST false #define DEFAULT_MENU_SCROLL_FAST false
#define DEFAULT_MENU_SCROLL_DELAY 256 #define DEFAULT_MENU_SCROLL_DELAY 256
+2
View File
@@ -754,6 +754,7 @@ typedef struct settings
bool video_threaded_display_pacing; bool video_threaded_display_pacing;
bool video_d3d9ex_present; bool video_d3d9ex_present;
bool video_present_timing_from_display; bool video_present_timing_from_display;
bool video_skip_dupe_frames;
bool video_font_enable; bool video_font_enable;
bool video_disable_composition; bool video_disable_composition;
bool video_post_filter_record; bool video_post_filter_record;
@@ -924,6 +925,7 @@ typedef struct settings
bool menu_linear_filter; bool menu_linear_filter;
bool menu_texture_mipmapping; bool menu_texture_mipmapping;
bool menu_horizontal_animation; bool menu_horizontal_animation;
bool menu_scroll_quick;
bool menu_scroll_fast; bool menu_scroll_fast;
bool menu_show_online_updater; bool menu_show_online_updater;
bool menu_show_full_paths; bool menu_show_full_paths;
+1
View File
@@ -30,6 +30,7 @@
#include "stb_image.h" #include "stb_image.h"
#else #else
#include <formats/image.h> #include <formats/image.h>
#include <streams/image_texture_file.h>
#endif #endif
#include <libretro.h> #include <libretro.h>
+1
View File
@@ -27,6 +27,7 @@
#include <retro_miscellaneous.h> #include <retro_miscellaneous.h>
#include <streams/file_stream.h> #include <streams/file_stream.h>
#include <formats/data_transfer.h> #include <formats/data_transfer.h>
#include <streams/data_transfer_file.h>
#include <streams/file_stream.h> #include <streams/file_stream.h>
#include <formats/rwebm.h> #include <formats/rwebm.h>
#include <formats/rwebm_video.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 ui_menubar_enable = settings->bools.ui_menubar_enable;
bool window_show_decor = settings->bools.video_window_show_decorations; bool window_show_decor = settings->bools.video_window_show_decorations;
unsigned win_w = unsigned win_w = VIDEO_SCALE_W(g_win32->pos_dims);
VIDEO_SCALE_W(g_win32->pos_dims); unsigned win_h = VIDEO_SCALE_H(g_win32->pos_dims);
unsigned win_h =
VIDEO_SCALE_H(g_win32->pos_dims);
settings->uints.window_position_pos = g_win32->pos;
/* The frame the window reports includes whatever chrome it /* The frame the window reports includes whatever chrome it
* is wearing; the setting holds the client area, so take the * is wearing; the setting holds the client area, so take the
* chrome off both axes before the pair is stored. */ * chrome off both axes before the pair is stored. */
@@ -695,11 +693,12 @@ static void win32_save_position(void)
} }
if (ui_menubar_enable) if (ui_menubar_enable)
{ {
int menu_bar_height = GetSystemMetrics(SM_CYMENU); int menu_bar_height = GetSystemMetrics(SM_CYMENU);
win_h -= menu_bar_height; 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) if (g_win32_flags & WIN32_CMN_FLAG_RESIZED)
{ {
*resize = true; *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; g_win32_flags &= ~WIN32_CMN_FLAG_RESIZED;
/* Save the current position and size if set so. */
win32_save_position();
} }
} }
#endif #endif
@@ -2235,37 +2236,50 @@ bool win32_has_focus(void *data)
{ {
settings_t *settings = config_get_ptr(); settings_t *settings = config_get_ptr();
/* Ensure window size is big enough for core geometry. */ /* Apply/enforce windowed mode size. */
if ( settings if ( settings
&& !settings->bools.video_fullscreen && !settings->bools.video_fullscreen)
&& !settings->bools.video_window_save_positions)
{ {
unsigned extra_width = 0; bool window_save_positions = settings->bools.video_window_save_positions;
unsigned extra_height = 0; unsigned extra_w = 0;
unsigned min_width = 0; unsigned extra_h = 0;
unsigned min_height = 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) if (settings->bools.video_window_show_decorations)
{ {
int border_thickness = GetSystemMetrics(SM_CXSIZEFRAME); int border_thickness = GetSystemMetrics(SM_CXSIZEFRAME);
int title_bar_height = GetSystemMetrics(SM_CYCAPTION); int title_bar_height = GetSystemMetrics(SM_CYCAPTION);
extra_width += border_thickness * 2; extra_w += border_thickness * 2;
extra_height += border_thickness * 2 + title_bar_height; extra_h += border_thickness * 2 + title_bar_height;
} }
if (settings->bools.ui_menubar_enable) if (settings->bools.ui_menubar_enable)
extra_height += GetSystemMetrics(SM_CYMENU); extra_h += GetSystemMetrics(SM_CYMENU);
if ( ( g_win32_resize_width < min_width if ( ( window_save_positions
|| g_win32_resize_height < min_height) && ( win_w != g_win32_resize_width
&& min_width - g_win32_resize_width < MIN_WIDTH / 1.5f || win_h != g_win32_resize_height))
&& min_height - g_win32_resize_height < MIN_HEIGHT / 1.5f) || ( !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, SetWindowPos(main_window.hwnd, NULL, 0, 0,
min_width + extra_width, win_w + extra_w,
min_height + extra_height, win_h + extra_h,
SWP_NOMOVE); 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; win32_common_state_t *g_win32 = (win32_common_state_t*)&win32_st;
bool position_set_from_config = false; 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; bool window_show_decor = settings->bools.video_window_show_decorations;
*style = WS_OVERLAPPEDWINDOW | WS_CLIPSIBLINGS | WS_CLIPCHILDREN; *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); AdjustWindowRect(rect, *style, FALSE);
if (video_window_save_positions) if (window_save_positions)
{ {
/* Set position from config */ /* Set position from config */
int border_thickness = window_show_decor ? GetSystemMetrics(SM_CXSIZEFRAME) : 0; int border_thickness = window_show_decor ? GetSystemMetrics(SM_CXSIZEFRAME) : 0;
int title_bar_height = window_show_decor ? GetSystemMetrics(SM_CYCAPTION) : 0; int title_bar_height = window_show_decor ? GetSystemMetrics(SM_CYCAPTION) : 0;
unsigned window_position_width = unsigned window_position_width = VIDEO_SCALE_W(settings->uints.window_position_dims);
VIDEO_SCALE_W(settings->uints.window_position_dims); unsigned window_position_height = VIDEO_SCALE_H(settings->uints.window_position_dims);
unsigned window_position_height =
VIDEO_SCALE_H(settings->uints.window_position_dims);
g_win32->pos = g_win32->pos = settings->uints.window_position_pos;
settings->uints.window_position_pos;
g_win32->pos_set = true; g_win32->pos_set = true;
g_win32->pos_dims = VIDEO_SCALE_PACK( g_win32->pos_dims = VIDEO_SCALE_PACK(
window_position_width + border_thickness * 2, window_position_width + border_thickness * 2,
+1
View File
@@ -27,6 +27,7 @@
#include <retro_math.h> #include <retro_math.h>
#include <compat/strl.h> #include <compat/strl.h>
#include <formats/image.h> #include <formats/image.h>
#include <streams/image_texture_file.h>
#ifdef HAVE_CONFIG_H #ifdef HAVE_CONFIG_H
#include "../../config.h" #include "../../config.h"
+1
View File
@@ -37,6 +37,7 @@
#include <encodings/utf.h> #include <encodings/utf.h>
#include <lists/string_list.h> #include <lists/string_list.h>
#include <formats/image.h> #include <formats/image.h>
#include <streams/image_texture_file.h>
#include <dxgi.h> #include <dxgi.h>
+1
View File
@@ -42,6 +42,7 @@
#include <encodings/utf.h> #include <encodings/utf.h>
#include <lists/string_list.h> #include <lists/string_list.h>
#include <formats/image.h> #include <formats/image.h>
#include <streams/image_texture_file.h>
#include <formats/image_yuv_blit.h> #include <formats/image_yuv_blit.h>
#include "../common/video_import_color.h" #include "../common/video_import_color.h"
+1
View File
@@ -40,6 +40,7 @@
#include <encodings/utf.h> #include <encodings/utf.h>
#include <lists/string_list.h> #include <lists/string_list.h>
#include <formats/image.h> #include <formats/image.h>
#include <streams/image_texture_file.h>
#include <formats/image_yuv_blit.h> #include <formats/image_yuv_blit.h>
#include "../common/video_import_color.h" #include "../common/video_import_color.h"
+1
View File
@@ -26,6 +26,7 @@
#define CINTERFACE #define CINTERFACE
#include <formats/image.h> #include <formats/image.h>
#include <streams/image_texture_file.h>
#include <compat/strl.h> #include <compat/strl.h>
#include <compat/posix_string.h> #include <compat/posix_string.h>
#include <file/file_path.h> #include <file/file_path.h>
+1
View File
@@ -31,6 +31,7 @@
#endif #endif
#include <formats/image.h> #include <formats/image.h>
#include <streams/image_texture_file.h>
#include <compat/strl.h> #include <compat/strl.h>
#include <compat/posix_string.h> #include <compat/posix_string.h>
#include <file/file_path.h> #include <file/file_path.h>
+1
View File
@@ -45,6 +45,7 @@
#include <gfx/scaler/scaler.h> #include <gfx/scaler/scaler.h>
#include <gfx/math/matrix_4x4.h> #include <gfx/math/matrix_4x4.h>
#include <formats/image.h> #include <formats/image.h>
#include <streams/image_texture_file.h>
#include <retro_inline.h> #include <retro_inline.h>
#include <retro_miscellaneous.h> #include <retro_miscellaneous.h>
#include <retro_math.h> #include <retro_math.h>
+1
View File
@@ -24,6 +24,7 @@
#include <encodings/utf.h> #include <encodings/utf.h>
#include <formats/image.h> #include <formats/image.h>
#include <streams/image_texture_file.h>
#include <file/file_path.h> #include <file/file_path.h>
#include <string/stdstring.h> #include <string/stdstring.h>
+1
View File
@@ -34,6 +34,7 @@
#include <gfx/scaler/scaler.h> #include <gfx/scaler/scaler.h>
#include <gfx/video_frame.h> #include <gfx/video_frame.h>
#include <formats/image.h> #include <formats/image.h>
#include <streams/image_texture_file.h>
#include <formats/image_yuv_blit.h> #include <formats/image_yuv_blit.h>
#include <retro_inline.h> #include <retro_inline.h>
#include <retro_miscellaneous.h> #include <retro_miscellaneous.h>
+1
View File
@@ -25,6 +25,7 @@
#include <compat/strl.h> #include <compat/strl.h>
#include <string/stdstring.h> #include <string/stdstring.h>
#include <formats/image.h> #include <formats/image.h>
#include <streams/image_texture_file.h>
#include <retro_miscellaneous.h> #include <retro_miscellaneous.h>
#include "slang_process.h" #include "slang_process.h"
+1
View File
@@ -22,6 +22,7 @@
#include <compat/strl.h> #include <compat/strl.h>
#include <formats/image.h> #include <formats/image.h>
#include <streams/image_texture_file.h>
#include <string/stdstring.h> #include <string/stdstring.h>
#include <gfx/math/matrix_4x4.h> #include <gfx/math/matrix_4x4.h>
#include <retro_miscellaneous.h> #include <retro_miscellaneous.h>
+1
View File
@@ -28,6 +28,7 @@
#include <streams/file_stream.h> #include <streams/file_stream.h>
#include <formats/image.h> #include <formats/image.h>
#include <formats/data_transfer.h> #include <formats/data_transfer.h>
#include <streams/data_transfer_file.h>
#include <formats/video_player.h> #include <formats/video_player.h>
#include <retro_atomic.h> #include <retro_atomic.h>
#include <features/features_cpu.h> #include <features/features_cpu.h>
+1
View File
@@ -19,6 +19,7 @@
#include <retro_atomic.h> #include <retro_atomic.h>
#include <retro_miscellaneous.h> #include <retro_miscellaneous.h>
#include <formats/image_yuv_blit.h> #include <formats/image_yuv_blit.h>
#include <streams/image_texture_file.h>
#include "gfx_surface.h" #include "gfx_surface.h"
#include "gfx_instrument.h" #include "gfx_instrument.h"
+28
View File
@@ -9749,6 +9749,34 @@ void video_driver_frame(const void *data, unsigned width,
if (data) if (data)
video_st->main_flags &= ~VIDEO_FLAG_FF_FRAME_OWED; 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 /* What the shader chains read as FrameTimeDelta, from the reading
* taken at the top of this function. Measured between the frames * taken at the top of this function. Measured between the frames
* the driver is handed, so a frame dropped by fast-forward * 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; unsigned driver_pacing_interval_us;
uint8_t driver_pacing_mode; uint8_t driver_pacing_mode;
uint8_t driver_pacing_reason; 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; 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 /* 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 * union, not a cast through a pointer, so it stays inside what the
* standard allows and MSVC's aliasing rules. */ * 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 #ifdef HAVE_THREADS
#define VIDEO_CACHE_KEEPER ((size_t)sthread_get_current_thread_id()) #define VIDEO_CACHE_KEEPER ((size_t)sthread_get_current_thread_id())
#else #else
+3
View File
@@ -229,6 +229,7 @@ ACHIEVEMENTS
#define RC_CLIENT_SUPPORTS_HASH 1 #define RC_CLIENT_SUPPORTS_HASH 1
#include "../libretro-common/formats/cdfs/cdfs.c" #include "../libretro-common/formats/cdfs/cdfs.c"
#include "../libretro-common/streams/cdfs_file.c"
#include "../cheevos/cheevos.c" #include "../cheevos/cheevos.c"
#include "../cheevos/cheevos_client.c" #include "../cheevos/cheevos_client.c"
@@ -575,6 +576,7 @@ VIDEO IMAGE
============================================================ */ ============================================================ */
#include "../libretro-common/formats/image_texture.c" #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/image_yuv_blit.c"
#include "../libretro-common/formats/image_texture_scale.c" #include "../libretro-common/formats/image_texture_scale.c"
#include "../libretro-common/formats/image_texture_set.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/image_transfer.c"
#include "../libretro-common/formats/video_player.c" #include "../libretro-common/formats/video_player.c"
#include "../libretro-common/formats/data_transfer.c" #include "../libretro-common/formats/data_transfer.c"
#include "../libretro-common/streams/data_transfer_file.c"
#ifdef HAVE_RPNG #ifdef HAVE_RPNG
#include "../libretro-common/formats/png/rpng.c" #include "../libretro-common/formats/png/rpng.c"
#include "../libretro-common/formats/png/rpng_apng.c" #include "../libretro-common/formats/png/rpng_apng.c"
+29 -6
View File
@@ -199,6 +199,14 @@ static const uint64_t xxh3_init_acc[8] = {
#define XXH3_NOINLINE #define XXH3_NOINLINE
#endif #endif
#if defined(_MSC_VER)
#define XXH3_FORCEINLINE __forceinline
#elif defined(__GNUC__)
#define XXH3_FORCEINLINE INLINE __attribute__((always_inline))
#else
#define XXH3_FORCEINLINE INLINE
#endif
static INLINE uint64_t xxh3_rd64(const uint8_t *p) static INLINE uint64_t xxh3_rd64(const uint8_t *p)
{ {
uint64_t v; uint64_t v;
@@ -272,7 +280,8 @@ static INLINE uint64_t xxh3_mix16(const uint8_t *in, const uint8_t *sec,
/* ---- up to 240 octets ---- */ /* ---- up to 240 octets ---- */
static uint64_t xxh3_0to16(const uint8_t *in, size_t len, uint64_t seed) static XXH3_FORCEINLINE uint64_t xxh3_0to16(const uint8_t *in, size_t len,
uint64_t seed)
{ {
const uint8_t *k = xxh3_secret; const uint8_t *k = xxh3_secret;
@@ -303,7 +312,8 @@ static uint64_t xxh3_0to16(const uint8_t *in, size_t len, uint64_t seed)
return xxh3_xxh64_avalanche(seed ^ (xxh3_rd64(k + 56) ^ xxh3_rd64(k + 64))); return xxh3_xxh64_avalanche(seed ^ (xxh3_rd64(k + 56) ^ xxh3_rd64(k + 64)));
} }
static uint64_t xxh3_17to128(const uint8_t *in, size_t len, uint64_t seed) static XXH3_FORCEINLINE uint64_t xxh3_17to128(const uint8_t *in, size_t len,
uint64_t seed)
{ {
const uint8_t *k = xxh3_secret; const uint8_t *k = xxh3_secret;
uint64_t acc = (uint64_t)len * XXH3_P64_1; uint64_t acc = (uint64_t)len * XXH3_P64_1;
@@ -328,7 +338,7 @@ static uint64_t xxh3_17to128(const uint8_t *in, size_t len, uint64_t seed)
return xxh3_avalanche(acc); return xxh3_avalanche(acc);
} }
static XXH3_NOINLINE uint64_t xxh3_129to240(const uint8_t *in, size_t len, static XXH3_FORCEINLINE uint64_t xxh3_129to240(const uint8_t *in, size_t len,
uint64_t seed) uint64_t seed)
{ {
const uint8_t *k = xxh3_secret; const uint8_t *k = xxh3_secret;
@@ -1240,15 +1250,28 @@ static XXH3_NOINLINE uint64_t xxh3_long(const uint8_t *in, size_t len,
#endif #endif
} }
/* Seed 0 apart, where the secret folds into constants; each range its
* own function, so none pays another's register saves */
#define XXH3_SHORT(name, body) \
static XXH3_NOINLINE uint64_t name##_0(const uint8_t *in, size_t len) \
{ return body(in, len, 0); } \
static XXH3_NOINLINE uint64_t name##_s(const uint8_t *in, size_t len, \
uint64_t seed) \
{ return body(in, len, seed); }
XXH3_SHORT(xxh3_short16, xxh3_0to16)
XXH3_SHORT(xxh3_short128, xxh3_17to128)
XXH3_SHORT(xxh3_short240, xxh3_129to240)
#undef XXH3_SHORT
uint64_t xxh3_64(const void *data, size_t len, uint64_t seed) uint64_t xxh3_64(const void *data, size_t len, uint64_t seed)
{ {
const uint8_t *in = (const uint8_t*)data; const uint8_t *in = (const uint8_t*)data;
if (len <= 16) if (len <= 16)
return xxh3_0to16(in, len, seed); return seed ? xxh3_short16_s(in, len, seed) : xxh3_short16_0(in, len);
if (len <= 128) if (len <= 128)
return xxh3_17to128(in, len, seed); return seed ? xxh3_short128_s(in, len, seed) : xxh3_short128_0(in, len);
if (len <= 240) if (len <= 240)
return xxh3_129to240(in, len, seed); return seed ? xxh3_short240_s(in, len, seed) : xxh3_short240_0(in, len);
return xxh3_long(in, len, seed); return xxh3_long(in, len, seed);
} }
+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); return rzstd_blocks_step(ctx->zs, data, len);
for (;;) for (;;)
{ {
int64_t got;
size_t want = rzstd_blocks_want(ctx->zs); size_t want = rzstd_blocks_want(ctx->zs);
/* all of it out, and nothing after it */ /* all of it out, and nothing after it */
if (!want) if (!want)
@@ -117,8 +118,13 @@ static int zstd_src_next(struct zstd_file_context *ctx,
? RZSTD_PROCESS_END : RZSTD_PROCESS_ERROR; ? RZSTD_PROCESS_END : RZSTD_PROCESS_ERROR;
if (want > RZSTD_WANT_MAX) if (want > RZSTD_WANT_MAX)
return RZSTD_PROCESS_ERROR; return RZSTD_PROCESS_ERROR;
filestream_seek(f, ctx->in_at, RETRO_VFS_SEEK_POSITION_START); /* At the offset in one call; seek and read where there is none */
if (filestream_read(f, ctx->in, (int64_t)want) != (int64_t)want) 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; return RZSTD_PROCESS_ERROR;
ctx->in_at += (int64_t)want; ctx->in_at += (int64_t)want;
if (rzstd_blocks_give(ctx->zs, ctx->in, want, data, len) 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 */ /* 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) uint32_t folder, size_t pos, int state)
{ {
r7z_progress_state(a, 0); 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 #endif
&& fl->data) && fl->data)
{ {
uint64_t at = 0; uint64_t at = 0;
if (rflac_seek(fl->handle, frame, &at) == RFLAC_PROCESS_NEXT uint64_t point = 0;
if (rflac_seek_point(fl->handle, frame, &at, &point)
== RFLAC_PROCESS_NEXT
&& at < fl->size) && at < fl->size)
{ {
uint64_t landed; 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, rflac_set_in(fl->handle, (const uint8_t*)fl->data + at,
fl->size - (size_t)at); fl->size - (size_t)at);
rflac_set_eof(fl->handle); rflac_set_eof(fl->handle);
+12 -179
View File
@@ -1,31 +1,21 @@
#include <retro_posix_source.h> #include <retro_posix_source.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <formats/cdfs.h> #include <formats/cdfs.h>
#include <retro_miscellaneous.h> #include <retro_miscellaneous.h>
#include <compat/strl.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 /* ISO-9660 sector/directory/file logic plus cue-sheet parsing. It only
* organised in two layers: * ever reads through an already-open track->stream or a caller-supplied
* * memory buffer, and never opens a path: streams/cdfs_file.c resolves
* - the pure layer (everything through cdfs_parse_cue): ISO-9660 * filenames, opens cue/bin/iso/chd files and hands the stream in here.
* sector/directory/file logic plus cue-sheet parsing. It only ever * Entry points: cdfs_track_from_stream(), cdfs_parse_cue(),
* reads through an already-open track->stream or a caller-supplied * cdfs_open_file() and the read/seek family. */
* 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. */
static void cdfs_determine_sector_size(cdfs_track_t* track) 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); int64_t _len = intfstream_get_size(track->stream);
if (_len <= 0) if (_len <= 0)
return; return;
@@ -686,163 +676,6 @@ bool cdfs_parse_cue(const char *cue_text, size_t len,
return false; 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) void cdfs_close_track(cdfs_track_t* track)
{ {
if (track) if (track)
+54 -91
View File
@@ -29,8 +29,8 @@
* avail() for the buffer and how much of it is real, and the * avail() for the buffer and how much of it is real, and the
* complete()/failed()/capped() terminals. * complete()/failed()/capped() terminals.
* *
* - prefix (data_transfer_open_prefix): a file whose length is known * - prefix (data_transfer_open_prefix_reader): a source whose length
* at open. Address space for the whole length is reserved up * 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 * 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 * DT_COMMIT_STEP at a time as the fill advances, in DT_READ_CHUNK
* reads. commit_cap bounds the committed bytes and turns overrun * 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 * sequential consumer holds a constant window over a file of any
* size. * 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 * cyclically, for sequential consumers that loop. A permanent
* head of 'keep' bytes plus a moving [wlo, whi) that feed() * head of 'keep' bytes plus a moving [wlo, whi) that feed()
* advances behind the consumer and extends ahead of it; * advances behind the consumer and extends ahead of it;
@@ -59,8 +59,11 @@
* growable buffer that shares this file and header but touches no * growable buffer that shares this file and header but touches no
* data_transfer_t and no page machinery. * data_transfer_t and no page machinery.
* *
* File access goes through filestream/VFS throughout, so 64-bit * No file I/O here: every read is the reader's. streams/
* offsets and VFS-only paths work. Where the platform cannot reserve * 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 * address space, every mode degrades to a plain allocation of
* min(len, commit_cap) - the whole file when there is no cap - and a * min(len, commit_cap) - the whole file when there is no cap - and a
* window simply holds the whole file; reserve_supported() and * window simply holds the whole file; reserve_supported() and
@@ -68,8 +71,8 @@
* *
* NOT IMPLEMENTED * NOT IMPLEMENTED
* *
* - Asynchrony. Every read is a synchronous filestream_read on the * - Asynchrony. Every read is a synchronous call of the reader on
* calling thread: iterate() blocks for the length of its budget, * the calling thread: iterate() blocks for the length of its budget,
* and there is nothing in flight for free() to cancel. Pacing is * and there is nothing in flight for free() to cancel. Pacing is
* by byte budget and the caller does the time-slicing. The module * by byte budget and the caller does the time-slicing. The module
* began as a wrapper over nbio, which is where its vocabulary came * began as a wrapper over nbio, which is where its vocabulary came
@@ -162,7 +165,6 @@
#include <stdlib.h> #include <stdlib.h>
#include <memmap.h> #include <memmap.h>
#include <streams/file_stream.h>
#include <string.h> #include <string.h>
#include <formats/data_transfer.h> #include <formats/data_transfer.h>
@@ -302,9 +304,10 @@ struct data_transfer_inert { size_t lo, hi; };
struct data_transfer struct data_transfer
{ {
/* file-backed reader (open_prefix, and open_window over it) */ /* the reader every prefix and window fills from, and what frees
RFILE *f; * its user data with the transfer, if anything */
data_transfer_read_at_t read_at; data_transfer_read_at_t read_at;
data_transfer_release_t release;
void *read_ud; void *read_ud;
uint8_t *map; /* reserved (or fallback-allocated) buffer */ uint8_t *map; /* reserved (or fallback-allocated) buffer */
size_t map_len; /* reserved bytes (page-rounded), 0 = fallback */ 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, static int data_transfer_read_at(data_transfer_t *dt, size_t off,
uint8_t *dst, size_t n); uint8_t *dst, size_t n);
/* open_prefix's body. allow_pool is 0 for open_window, which builds /* The prefix body. allow_pool is 0 for a window, which builds on a
* on a prefix handle but reads map_len in punch/peek and so needs it * prefix handle but reads map_len in punch/peek and so needs it to be
* to be the file's own rounded length rather than a pool slot's. */ * the source's own rounded length rather than a pool slot's. */
static data_transfer_t *data_transfer_open_reader_ex(const char *path, static data_transfer_t *data_transfer_open_reader_ex(size_t commit_cap,
size_t commit_cap, int allow_pool, data_transfer_read_at_t read_at, int allow_pool, data_transfer_read_at_t read_at, size_t length,
size_t length, void *ud); 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 fill proper. data_transfer_iterate() refuses to run it on a /* The fill proper. data_transfer_iterate() refuses to run it on a
* window; open_window's no-reservation path genuinely needs it and * window; open_window's no-reservation path genuinely needs it and
* calls it here directly. */ * 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; 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_t *data_transfer_open_window_reader(size_t length,size_t keep,
data_transfer_read_at_t read_at,void *ud) data_transfer_read_at_t read_at,void *ud)
{ {
if (!read_at) return NULL; if (!read_at) return NULL;
return data_transfer_start_window( 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) 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; dt->wtell = tell;
if (tell > margin && tell - margin > dt->wlo) if (tell > margin && tell - margin > dt->wlo)
data_transfer_window_advance(dt, tell - margin); 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; hi = tell + lookahead;
/* The ceiling applies only while the frontier covers the consumer. /* The ceiling applies only while the frontier covers the consumer.
* A frontier behind tell means the consumer's next read lands on * 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); margin, 0, NULL);
} }
static data_transfer_t *data_transfer_open_reader_ex(const char *path, static data_transfer_t *data_transfer_open_reader_ex(size_t commit_cap,
size_t commit_cap, int allow_pool, data_transfer_read_at_t read_at, int allow_pool, data_transfer_read_at_t read_at, size_t length,
size_t length, void *ud) void *ud)
{ {
data_transfer_t *dt; data_transfer_t *dt;
RFILE *f = NULL; if (!read_at || !length || (uint64_t)length > INT64_MAX)
int64_t l; return NULL;
/* 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 (!(dt = (data_transfer_t*)calloc(1, sizeof(*dt)))) if (!(dt = (data_transfer_t*)calloc(1, sizeof(*dt))))
{
if (f) filestream_close(f);
return NULL; return NULL;
}
dt->f = f;
dt->read_at = read_at; dt->read_at = read_at;
dt->read_ud = ud; dt->read_ud = ud;
dt->len = (size_t)l; dt->len = length;
dt->cap = commit_cap; dt->cap = commit_cap;
{ {
@@ -1019,7 +1001,6 @@ static data_transfer_t *data_transfer_open_reader_ex(const char *path,
dt->cap = w; dt->cap = w;
if (!(dt->map = (uint8_t*)malloc(w ? w : 1))) if (!(dt->map = (uint8_t*)malloc(w ? w : 1)))
{ {
if (f) filestream_close(f);
free(dt); free(dt);
return NULL; return NULL;
} }
@@ -1028,10 +1009,10 @@ static data_transfer_t *data_transfer_open_reader_ex(const char *path,
return dt; return dt;
} }
data_transfer_t *data_transfer_open_prefix(const char *path, data_transfer_t *data_transfer_open_prefix_reader(size_t length,
size_t commit_cap) 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. /* 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 end = dt->inert[lo].hi;
size_t release_end; size_t release_end;
if (dt->cap && end > dt->cap) end = dt->cap; 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 /* Commit growth may have armed pages past the preceding
* read. Keep an inert interval unreadable after crossing * read. Keep an inert interval unreadable after crossing
* it, including in strict builds and after refill. */ * it, including in strict builds and after refill. */
@@ -1224,9 +1203,8 @@ static size_t data_transfer_prefix_iterate(data_transfer_t *dt,
continue; continue;
} }
{ {
int64_t r = dt->read_at int64_t r = dt->read_at(dt->read_ud, dt->avail,
? dt->read_at(dt->read_ud,dt->avail,dt->map+dt->avail,chunk) dt->map + dt->avail, chunk);
: filestream_read(dt->f,dt->map+dt->avail,(int64_t)chunk);
got = r > 0 && (uint64_t)r <= chunk ? (size_t)r : 0; got = r > 0 && (uint64_t)r <= chunk ? (size_t)r : 0;
} }
dt->avail += got; 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, static int data_transfer_read_at(data_transfer_t *dt, size_t off,
uint8_t *dst, size_t n) uint8_t *dst, size_t n)
{ {
int64_t save, r; return dt->read_at(dt->read_ud, off, dst, n) == (int64_t)n;
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;
} }
bool data_transfer_prefix_inert(data_transfer_t *dt, size_t from, size_t to) bool data_transfer_prefix_inert(data_transfer_t *dt, size_t from, size_t to)
{ {
size_t lo, hi, first, last, count; 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; return false;
if (!dt->map_len || dt->pooled) return true; if (!dt->map_len || dt->pooled) return true;
lo = (from / dt->page + (from % dt->page != 0)) * dt->page; 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) void data_transfer_discard(data_transfer_t *dt, size_t up_to)
{ {
size_t lo; 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 */ return; /* source or fallback: bytes stay */
if (dt->window) if (dt->window)
return; /* not this surface - see below */ 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; size_t lo, end, at, i;
if (!dt) if (!dt)
return false; 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 */ return !dt->failed; /* nothing was released there */
lo = (from / dt->page) * dt->page; lo = (from / dt->page) * dt->page;
end = dt->low; 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. /* 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 * otherwise pass their own guards on one and then disagree with the
* window's bookkeeping. discard() is the dangerous one: its 'low' * window's bookkeeping. discard() is the dangerous one: its 'low'
* frontier knows nothing of keep/wlo/whi/wfreed, so it releases from * 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; return 0;
if (dt->window) if (dt->window)
return dt->avail; /* not this surface - see above */ 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, return data_transfer_prefix_iterate(dt, max_bytes,
should_continue, ud); should_continue, ud);
return dt->avail; return dt->avail;
@@ -1447,8 +1410,8 @@ void data_transfer_free(data_transfer_t *dt)
{ {
if (!dt) if (!dt)
return; return;
if (dt->f) if (dt->release)
filestream_close(dt->f); dt->release(dt->read_ud);
free(dt->inert); free(dt->inert);
if (dt->map) 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) #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) static INLINE __m128i rflac__mm_packs_interleaved_epi32(__m128i a, __m128i b)
{ {
__m128i r; __m128i r;
@@ -4387,6 +4394,17 @@ static rflac* rflac_open_with_metadata_private(rflac_read_proc onRead,
return pFlac; 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( static INLINE void rflac_read_pcm_frames_s16__decode_left_side__scalar(
rflac* pFlac, uint64_t frameCount, uint32_t unusedBitsPerSample, rflac* pFlac, uint64_t frameCount, uint32_t unusedBitsPerSample,
const int32_t* pInputSamples0, const int32_t* pInputSamples1, 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 right2 = left2 - side2;
uint32_t right3 = left3 - side3; uint32_t right3 = left3 - side3;
left0 >>= 16; pOutputSamples[i*8+0] = rflac__s16_round(left0);
left1 >>= 16; pOutputSamples[i*8+1] = rflac__s16_round(right0);
left2 >>= 16; pOutputSamples[i*8+2] = rflac__s16_round(left1);
left3 >>= 16; pOutputSamples[i*8+3] = rflac__s16_round(right1);
pOutputSamples[i*8+4] = rflac__s16_round(left2);
right0 >>= 16; pOutputSamples[i*8+5] = rflac__s16_round(right2);
right1 >>= 16; pOutputSamples[i*8+6] = rflac__s16_round(left3);
right2 >>= 16; pOutputSamples[i*8+7] = rflac__s16_round(right3);
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;
} }
for (i = (frameCount4 << 2); i < frameCount; ++i) 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 side = pInputSamples1U32[i] << shift1;
uint32_t right = left - side; uint32_t right = left - side;
left >>= 16; pOutputSamples[i*2+0] = rflac__s16_round(left);
right >>= 16; pOutputSamples[i*2+1] = rflac__s16_round(right);
pOutputSamples[i*2+0] = (int16_t)left;
pOutputSamples[i*2+1] = (int16_t)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 side = _mm_slli_epi32(_mm_loadu_si128((const __m128i*)pInputSamples1 + i), shift1);
__m128i right = _mm_sub_epi32(left, side); __m128i right = _mm_sub_epi32(left, side);
left = _mm_srai_epi32(left, 16); left = rflac__mm_round_s16_epi32(left);
right = _mm_srai_epi32(right, 16); right = rflac__mm_round_s16_epi32(right);
_mm_storeu_si128((__m128i*)(pOutputSamples + i*8), rflac__mm_packs_interleaved_epi32(left, 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 side = pInputSamples1U32[i] << shift1;
uint32_t right = left - side; uint32_t right = left - side;
left >>= 16; pOutputSamples[i*2+0] = rflac__s16_round(left);
right >>= 16; pOutputSamples[i*2+1] = rflac__s16_round(right);
pOutputSamples[i*2+0] = (int16_t)left;
pOutputSamples[i*2+1] = (int16_t)right;
} }
} }
#endif #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 side = vshlq_u32(vld1q_u32(pInputSamples1U32 + i*4), shift1_4);
uint32x4_t right = vsubq_u32(left, side); uint32x4_t right = vsubq_u32(left, side);
left = vshrq_n_u32(left, 16); left = vreinterpretq_u32_s32(vrshrq_n_s32(vreinterpretq_s32_u32(left), 16));
right = vshrq_n_u32(right, 16); right = vreinterpretq_u32_s32(vrshrq_n_s32(vreinterpretq_s32_u32(right), 16));
{ {
/* interleaving store; replaces vzip + contiguous store */ /* interleaving store; replaces vzip + contiguous store */
uint16x4x2_t v; uint16x4x2_t v;
v.val[0] = vmovn_u32(left); v.val[0] = vreinterpret_u16_s16(vqmovn_s32(vreinterpretq_s32_u32(left)));
v.val[1] = vmovn_u32(right); v.val[1] = vreinterpret_u16_s16(vqmovn_s32(vreinterpretq_s32_u32(right)));
vst2_u16((uint16_t*)pOutputSamples + i*8, v); 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 side = pInputSamples1U32[i] << shift1;
uint32_t right = left - side; uint32_t right = left - side;
left >>= 16; pOutputSamples[i*2+0] = rflac__s16_round(left);
right >>= 16; pOutputSamples[i*2+1] = rflac__s16_round(right);
pOutputSamples[i*2+0] = (int16_t)left;
pOutputSamples[i*2+1] = (int16_t)right;
} }
} }
#endif #endif
@@ -4585,24 +4584,14 @@ static INLINE void rflac_read_pcm_frames_s16__decode_right_side__scalar(
uint32_t left2 = right2 + side2; uint32_t left2 = right2 + side2;
uint32_t left3 = right3 + side3; uint32_t left3 = right3 + side3;
left0 >>= 16; pOutputSamples[i*8+0] = rflac__s16_round(left0);
left1 >>= 16; pOutputSamples[i*8+1] = rflac__s16_round(right0);
left2 >>= 16; pOutputSamples[i*8+2] = rflac__s16_round(left1);
left3 >>= 16; pOutputSamples[i*8+3] = rflac__s16_round(right1);
pOutputSamples[i*8+4] = rflac__s16_round(left2);
right0 >>= 16; pOutputSamples[i*8+5] = rflac__s16_round(right2);
right1 >>= 16; pOutputSamples[i*8+6] = rflac__s16_round(left3);
right2 >>= 16; pOutputSamples[i*8+7] = rflac__s16_round(right3);
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;
} }
for (i = (frameCount4 << 2); i < frameCount; ++i) 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 right = pInputSamples1U32[i] << shift1;
uint32_t left = right + side; uint32_t left = right + side;
left >>= 16; pOutputSamples[i*2+0] = rflac__s16_round(left);
right >>= 16; pOutputSamples[i*2+1] = rflac__s16_round(right);
pOutputSamples[i*2+0] = (int16_t)left;
pOutputSamples[i*2+1] = (int16_t)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 right = _mm_slli_epi32(_mm_loadu_si128((const __m128i*)pInputSamples1 + i), shift1);
__m128i left = _mm_add_epi32(right, side); __m128i left = _mm_add_epi32(right, side);
left = _mm_srai_epi32(left, 16); left = rflac__mm_round_s16_epi32(left);
right = _mm_srai_epi32(right, 16); right = rflac__mm_round_s16_epi32(right);
_mm_storeu_si128((__m128i*)(pOutputSamples + i*8), rflac__mm_packs_interleaved_epi32(left, 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 right = pInputSamples1U32[i] << shift1;
uint32_t left = right + side; uint32_t left = right + side;
left >>= 16; pOutputSamples[i*2+0] = rflac__s16_round(left);
right >>= 16; pOutputSamples[i*2+1] = rflac__s16_round(right);
pOutputSamples[i*2+0] = (int16_t)left;
pOutputSamples[i*2+1] = (int16_t)right;
} }
} }
#endif #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 right = vshlq_u32(vld1q_u32(pInputSamples1U32 + i*4), shift1_4);
uint32x4_t left = vaddq_u32(right, side); uint32x4_t left = vaddq_u32(right, side);
left = vshrq_n_u32(left, 16); left = vreinterpretq_u32_s32(vrshrq_n_s32(vreinterpretq_s32_u32(left), 16));
right = vshrq_n_u32(right, 16); right = vreinterpretq_u32_s32(vrshrq_n_s32(vreinterpretq_s32_u32(right), 16));
{ {
/* interleaving store; replaces vzip + contiguous store */ /* interleaving store; replaces vzip + contiguous store */
uint16x4x2_t v; uint16x4x2_t v;
v.val[0] = vmovn_u32(left); v.val[0] = vreinterpret_u16_s16(vqmovn_s32(vreinterpretq_s32_u32(left)));
v.val[1] = vmovn_u32(right); v.val[1] = vreinterpret_u16_s16(vqmovn_s32(vreinterpretq_s32_u32(right)));
vst2_u16((uint16_t*)pOutputSamples + i*8, v); 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 right = pInputSamples1U32[i] << shift1;
uint32_t left = right + side; uint32_t left = right + side;
left >>= 16; pOutputSamples[i*2+0] = rflac__s16_round(left);
right >>= 16; pOutputSamples[i*2+1] = rflac__s16_round(right);
pOutputSamples[i*2+0] = (int16_t)left;
pOutputSamples[i*2+1] = (int16_t)right;
} }
} }
#endif #endif
@@ -4775,24 +4755,14 @@ static INLINE void rflac_read_pcm_frames_s16__decode_mid_side__scalar(
temp2R = (mid2 - side2) << shift; temp2R = (mid2 - side2) << shift;
temp3R = (mid3 - side3) << shift; temp3R = (mid3 - side3) << shift;
temp0L >>= 16; pOutputSamples[i*8+0] = rflac__s16_round(temp0L);
temp1L >>= 16; pOutputSamples[i*8+1] = rflac__s16_round(temp0R);
temp2L >>= 16; pOutputSamples[i*8+2] = rflac__s16_round(temp1L);
temp3L >>= 16; pOutputSamples[i*8+3] = rflac__s16_round(temp1R);
pOutputSamples[i*8+4] = rflac__s16_round(temp2L);
temp0R >>= 16; pOutputSamples[i*8+5] = rflac__s16_round(temp2R);
temp1R >>= 16; pOutputSamples[i*8+6] = rflac__s16_round(temp3L);
temp2R >>= 16; pOutputSamples[i*8+7] = rflac__s16_round(temp3R);
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;
} }
} }
else else
@@ -4833,24 +4803,14 @@ static INLINE void rflac_read_pcm_frames_s16__decode_mid_side__scalar(
temp2R = ((int32_t)(mid2 - side2) >> 1); temp2R = ((int32_t)(mid2 - side2) >> 1);
temp3R = ((int32_t)(mid3 - side3) >> 1); temp3R = ((int32_t)(mid3 - side3) >> 1);
temp0L >>= 16; pOutputSamples[i*8+0] = rflac__s16_round(temp0L);
temp1L >>= 16; pOutputSamples[i*8+1] = rflac__s16_round(temp0R);
temp2L >>= 16; pOutputSamples[i*8+2] = rflac__s16_round(temp1L);
temp3L >>= 16; pOutputSamples[i*8+3] = rflac__s16_round(temp1R);
pOutputSamples[i*8+4] = rflac__s16_round(temp2L);
temp0R >>= 16; pOutputSamples[i*8+5] = rflac__s16_round(temp2R);
temp1R >>= 16; pOutputSamples[i*8+6] = rflac__s16_round(temp3L);
temp2R >>= 16; pOutputSamples[i*8+7] = rflac__s16_round(temp3R);
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;
} }
} }
@@ -4861,8 +4821,8 @@ static INLINE void rflac_read_pcm_frames_s16__decode_mid_side__scalar(
mid = (mid << 1) | (side & 0x01); mid = (mid << 1) | (side & 0x01);
pOutputSamples[i*2+0] = (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] = (int16_t)(((uint32_t)((int32_t)(mid - side) >> 1) << unusedBitsPerSample) >> 16); 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); left = _mm_srai_epi32(_mm_add_epi32(mid, side), 1);
right = _mm_srai_epi32(_mm_sub_epi32(mid, side), 1); right = _mm_srai_epi32(_mm_sub_epi32(mid, side), 1);
left = _mm_srai_epi32(left, 16); left = rflac__mm_round_s16_epi32(left);
right = _mm_srai_epi32(right, 16); right = rflac__mm_round_s16_epi32(right);
_mm_storeu_si128((__m128i*)(pOutputSamples + i*8), rflac__mm_packs_interleaved_epi32(left, 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); mid = (mid << 1) | (side & 0x01);
pOutputSamples[i*2+0] = (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] = (int16_t)(((int32_t)(mid - side) >> 1) >> 16); pOutputSamples[i*2+1] = rflac__s16_round((uint32_t)(((int32_t)(mid - side) >> 1)));
} }
} }
else 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); left = _mm_slli_epi32(_mm_add_epi32(mid, side), shift);
right = _mm_slli_epi32(_mm_sub_epi32(mid, side), shift); right = _mm_slli_epi32(_mm_sub_epi32(mid, side), shift);
left = _mm_srai_epi32(left, 16); left = rflac__mm_round_s16_epi32(left);
right = _mm_srai_epi32(right, 16); right = rflac__mm_round_s16_epi32(right);
_mm_storeu_si128((__m128i*)(pOutputSamples + i*8), rflac__mm_packs_interleaved_epi32(left, 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); mid = (mid << 1) | (side & 0x01);
pOutputSamples[i*2+0] = (int16_t)(((mid + side) << shift) >> 16); pOutputSamples[i*2+0] = rflac__s16_round((uint32_t)(((mid + side) << shift)));
pOutputSamples[i*2+1] = (int16_t)(((mid - side) << shift) >> 16); 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); left = vshrq_n_s32(vreinterpretq_s32_u32(vaddq_u32(mid, side)), 1);
right = vshrq_n_s32(vreinterpretq_s32_u32(vsubq_u32(mid, side)), 1); right = vshrq_n_s32(vreinterpretq_s32_u32(vsubq_u32(mid, side)), 1);
left = vshrq_n_s32(left, 16); left = vrshrq_n_s32(left, 16);
right = vshrq_n_s32(right, 16); right = vrshrq_n_s32(right, 16);
{ {
/* interleaving store; replaces vzip + contiguous store */ /* interleaving store; replaces vzip + contiguous store */
int16x4x2_t v; int16x4x2_t v;
v.val[0] = vmovn_s32(left); v.val[0] = vqmovn_s32(left);
v.val[1] = vmovn_s32(right); v.val[1] = vqmovn_s32(right);
vst2_s16(pOutputSamples + i*8, v); 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); mid = (mid << 1) | (side & 0x01);
pOutputSamples[i*2+0] = (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] = (int16_t)(((int32_t)(mid - side) >> 1) >> 16); pOutputSamples[i*2+1] = rflac__s16_round((uint32_t)(((int32_t)(mid - side) >> 1)));
} }
} }
else 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)); left = vreinterpretq_s32_u32(vshlq_u32(vaddq_u32(mid, side), shift4));
right = vreinterpretq_s32_u32(vshlq_u32(vsubq_u32(mid, side), shift4)); right = vreinterpretq_s32_u32(vshlq_u32(vsubq_u32(mid, side), shift4));
left = vshrq_n_s32(left, 16); left = vrshrq_n_s32(left, 16);
right = vshrq_n_s32(right, 16); right = vrshrq_n_s32(right, 16);
{ {
/* interleaving store; replaces vzip + contiguous store */ /* interleaving store; replaces vzip + contiguous store */
int16x4x2_t v; int16x4x2_t v;
v.val[0] = vmovn_s32(left); v.val[0] = vqmovn_s32(left);
v.val[1] = vmovn_s32(right); v.val[1] = vqmovn_s32(right);
vst2_s16(pOutputSamples + i*8, v); 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); mid = (mid << 1) | (side & 0x01);
pOutputSamples[i*2+0] = (int16_t)(((mid + side) << shift) >> 16); pOutputSamples[i*2+0] = rflac__s16_round((uint32_t)(((mid + side) << shift)));
pOutputSamples[i*2+1] = (int16_t)(((mid - side) << shift) >> 16); 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 tempR2 = pInputSamples1U32[i*4+2] << shift1;
uint32_t tempR3 = pInputSamples1U32[i*4+3] << shift1; uint32_t tempR3 = pInputSamples1U32[i*4+3] << shift1;
tempL0 >>= 16; pOutputSamples[i*8+0] = rflac__s16_round(tempL0);
tempL1 >>= 16; pOutputSamples[i*8+1] = rflac__s16_round(tempR0);
tempL2 >>= 16; pOutputSamples[i*8+2] = rflac__s16_round(tempL1);
tempL3 >>= 16; pOutputSamples[i*8+3] = rflac__s16_round(tempR1);
pOutputSamples[i*8+4] = rflac__s16_round(tempL2);
tempR0 >>= 16; pOutputSamples[i*8+5] = rflac__s16_round(tempR2);
tempR1 >>= 16; pOutputSamples[i*8+6] = rflac__s16_round(tempL3);
tempR2 >>= 16; pOutputSamples[i*8+7] = rflac__s16_round(tempR3);
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;
} }
for (i = (frameCount4 << 2); i < frameCount; ++i) { for (i = (frameCount4 << 2); i < frameCount; ++i) {
pOutputSamples[i*2+0] = (int16_t)((pInputSamples0U32[i] << shift0) >> 16); pOutputSamples[i*2+0] = rflac__s16_round((uint32_t)((pInputSamples0U32[i] << shift0)));
pOutputSamples[i*2+1] = (int16_t)((pInputSamples1U32[i] << shift1) >> 16); 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 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); __m128i right = _mm_slli_epi32(_mm_loadu_si128((const __m128i*)pInputSamples1 + i), shift1);
left = _mm_srai_epi32(left, 16); left = rflac__mm_round_s16_epi32(left);
right = _mm_srai_epi32(right, 16); right = rflac__mm_round_s16_epi32(right);
/* At this point we have results. We can now pack and interleave these /* 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. */ * 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) { for (i = (frameCount4 << 2); i < frameCount; ++i) {
pOutputSamples[i*2+0] = (int16_t)((pInputSamples0U32[i] << shift0) >> 16); pOutputSamples[i*2+0] = rflac__s16_round((uint32_t)((pInputSamples0U32[i] << shift0)));
pOutputSamples[i*2+1] = (int16_t)((pInputSamples1U32[i] << shift1) >> 16); pOutputSamples[i*2+1] = rflac__s16_round((uint32_t)((pInputSamples1U32[i] << shift1)));
} }
} }
#endif #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)); 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)); right = vreinterpretq_s32_u32(vshlq_u32(vld1q_u32(pInputSamples1U32 + i*4), shift1_4));
left = vshrq_n_s32(left, 16); left = vrshrq_n_s32(left, 16);
right = vshrq_n_s32(right, 16); right = vrshrq_n_s32(right, 16);
{ {
/* interleaving store; replaces vzip + contiguous store */ /* interleaving store; replaces vzip + contiguous store */
int16x4x2_t v; int16x4x2_t v;
v.val[0] = vmovn_s32(left); v.val[0] = vqmovn_s32(left);
v.val[1] = vmovn_s32(right); v.val[1] = vqmovn_s32(right);
vst2_s16(pOutputSamples + i*8, v); vst2_s16(pOutputSamples + i*8, v);
} }
} }
for (i = (frameCount4 << 2); i < frameCount; ++i) for (i = (frameCount4 << 2); i < frameCount; ++i)
{ {
pOutputSamples[i*2+0] = (int16_t)((pInputSamples0U32[i] << shift0) >> 16); pOutputSamples[i*2+0] = rflac__s16_round((uint32_t)((pInputSamples0U32[i] << shift0)));
pOutputSamples[i*2+1] = (int16_t)((pInputSamples1U32[i] << shift1) >> 16); pOutputSamples[i*2+1] = rflac__s16_round((uint32_t)((pInputSamples1U32[i] << shift1)));
} }
} }
#endif #endif
@@ -5219,12 +5169,12 @@ static void rflac_read_pcm_frames_s16__decode_left_side__wide(rflac* pFlac,
for (i = 0; i < frameCount; ++i) for (i = 0; i < frameCount; ++i)
{ {
int64_t left = (int64_t)pInLeft[i] << shiftL; int64_t left = (int64_t)((uint64_t)(int64_t)pInLeft[i] << shiftL);
int64_t side = pInSide[i] << shiftS; int64_t side = (int64_t)((uint64_t)pInSide[i] << shiftS);
int64_t right = left - side; int64_t right = left - side;
pOutputSamples[i*2+0] = (int16_t)(left >> 16); pOutputSamples[i*2+0] = rflac__s16_round((uint32_t)(left));
pOutputSamples[i*2+1] = (int16_t)(right >> 16); 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) for (i = 0; i < frameCount; ++i)
{ {
int64_t side = pInSide[i] << shiftS; int64_t side = (int64_t)((uint64_t)pInSide[i] << shiftS);
int64_t right = (int64_t)pInRight[i] << shiftR; int64_t right = (int64_t)((uint64_t)(int64_t)pInRight[i] << shiftR);
int64_t left = right + side; int64_t left = right + side;
pOutputSamples[i*2+0] = (int16_t)(left >> 16); pOutputSamples[i*2+0] = rflac__s16_round((uint32_t)(left));
pOutputSamples[i*2+1] = (int16_t)(right >> 16); 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) for (i = 0; i < frameCount; ++i)
{ {
int64_t mid = (int64_t)pInMid[i] << shiftM; int64_t mid = (int64_t)((uint64_t)(int64_t)pInMid[i] << shiftM);
int64_t side = pInSide[i] << shiftS; int64_t side = (int64_t)((uint64_t)pInSide[i] << shiftS);
int64_t left; int64_t left;
int64_t right; int64_t right;
mid = (mid << 1) | (side & 0x01); mid = (int64_t)((uint64_t)mid << 1) | (side & 0x01);
left = (mid + side) >> 1; left = (mid + side) >> 1;
right = (mid - side) >> 1; right = (mid - side) >> 1;
pOutputSamples[i*2+0] = (int16_t)(left >> 16); pOutputSamples[i*2+0] = rflac__s16_round((uint32_t)(left));
pOutputSamples[i*2+1] = (int16_t)(right >> 16); 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) for (i = 0; i < frameCount; ++i)
{ {
int64_t left = (int64_t)pInLeft[i] << shiftL; int64_t left = (int64_t)((uint64_t)(int64_t)pInLeft[i] << shiftL);
int64_t side = pInSide[i] << shiftS; int64_t side = (int64_t)((uint64_t)pInSide[i] << shiftS);
int64_t right = left - side; int64_t right = left - side;
pOutputSamples[i*2+0] = (int32_t)left * factor; 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) for (i = 0; i < frameCount; ++i)
{ {
int64_t side = pInSide[i] << shiftS; int64_t side = (int64_t)((uint64_t)pInSide[i] << shiftS);
int64_t right = (int64_t)pInRight[i] << shiftR; int64_t right = (int64_t)((uint64_t)(int64_t)pInRight[i] << shiftR);
int64_t left = right + side; int64_t left = right + side;
pOutputSamples[i*2+0] = (int32_t)left * factor; 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) for (i = 0; i < frameCount; ++i)
{ {
int64_t mid = (int64_t)pInMid[i] << shiftM; int64_t mid = (int64_t)((uint64_t)(int64_t)pInMid[i] << shiftM);
int64_t side = pInSide[i] << shiftS; int64_t side = (int64_t)((uint64_t)pInSide[i] << shiftS);
int64_t left; int64_t left;
int64_t right; int64_t right;
mid = (mid << 1) | (side & 0x01); mid = (int64_t)((uint64_t)mid << 1) | (side & 0x01);
left = (mid + side) >> 1; left = (mid + side) >> 1;
right = (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; unsigned int j;
for (j = 0; j < channelCount; ++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)); 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) 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 i;
uint32_t best = 0; uint32_t best = 0;
int found = 0; int found = 0;
if (!f || !f->dec || !byte_offset) if (!f || !f->dec || !byte_offset || !point_frame)
return RFLAC_PROCESS_ERROR; return RFLAC_PROCESS_ERROR;
if (!f->dec->pSeekpoints || !f->dec->seekpointCount) if (!f->dec->pSeekpoints || !f->dec->seekpointCount)
return RFLAC_PROCESS_ERROR; 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 *byte_offset = f->dec->firstFLACFramePosInBytes
+ f->dec->pSeekpoints[best].flacFrameOffset; + f->dec->pSeekpoints[best].flacFrameOffset;
*point_frame = f->dec->pSeekpoints[best].firstPCMFrame;
return RFLAC_PROCESS_NEXT; return RFLAC_PROCESS_NEXT;
} }
+1 -63
View File
@@ -494,7 +494,7 @@ static void image_texture_clear(struct texture_image *img)
img->fp16 = false; 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, bool *kept, struct texture_image *out_img,
enum image_type_enum type, const void *ptr, size_t len, enum image_type_enum type, const void *ptr, size_t len,
const image_texture_request_t *req, 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); 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 /* The request the plain loads carry: the caller's image, whose
* ->pix10 asks for 10-bit on the way in */ * ->pix10 asks for 10-bit on the way in */
static void image_texture_request_of(const struct texture_image *img, 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; 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) 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); 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 <boolean.h>
#include <formats/image.h> #include <formats/image.h>
#include <formats/data_transfer.h> #include <formats/data_transfer.h>
#include <streams/image_texture_file.h>
#ifdef HAVE_GCD #ifdef HAVE_GCD
#include <dispatch/dispatch.h> #include <dispatch/dispatch.h>
#elif defined(HAVE_THREADS) #elif defined(HAVE_THREADS)
+153 -28
View File
@@ -88,10 +88,49 @@ struct rtga
* process() and nothing below changes behaviour. */ * process() and nothing below changes behaviour. */
uint8_t *true_end; uint8_t *true_end;
size_t avail_req; /* frontier asked for, clamped at IDLE */ 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 avail_set;
bool need_more; /* last slice stopped at the wall */ 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) static INLINE uint8_t rtga_get8(rtga_context *s)
{ {
if (s->img_buffer < s->img_buffer_end) 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 ctx;
rtga_context *s = &ctx; rtga_context *s = &ctx;
uint32_t *output = tga->output_image; uint32_t *dst = tga->dst;
const uint32_t *p32 = tga->pal32; const uint32_t *p32 = tga->pal32;
int width = tga->width; int width = tga->width;
int height = tga->height; 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) && s->img_buffer >= s->img_buffer_end)
break; break;
dst_row = inverted ? (height - 1 - cur_row) : cur_row; dst_row = inverted ? (height - 1 - cur_row) : cur_row;
output[(size_t)dst_row * (size_t)width + (size_t)cur_col] = if (!dst)
p32[rtga_get8(s)]; dst = rtga_row(tga, dst_row);
dst[cur_col] = p32[rtga_get8(s)];
if (++cur_col >= width) if (++cur_col >= width)
{ {
rtga_row_done(tga, dst_row, dst);
dst = NULL;
cur_col = 0; cur_col = 0;
++cur_row; ++cur_row;
} }
} }
tga->dst = dst;
tga->pixel_i = i; tga->pixel_i = i;
tga->cur_col = cur_col; tga->cur_col = cur_col;
tga->cur_row = cur_row; 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) if (tga_width > 0x4000 || tga_height > 0x4000)
return false; return false;
#endif #endif
output = (uint32_t*)malloc( tga->width = tga_width;
(size_t)tga_width * (size_t)tga_height * sizeof(uint32_t)); tga->height = tga_height;
if (!output) if (tga->rows)
return false; {
tga->output_image = output; 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) */ /* skip to the data's starting position (offset usually = 0) */
rtga_skip(s, tga_offset); rtga_skip(s, tga_offset);
@@ -439,7 +504,6 @@ static void rtga_fast_rows(rtga_t *tga, int nrows)
* decoder. */ * decoder. */
rtga_context ctx; rtga_context ctx;
rtga_context *s = &ctx; rtga_context *s = &ctx;
uint32_t *output = tga->output_image;
int tga_width = tga->width; int tga_width = tga->width;
int tga_height = tga->height; int tga_height = tga->height;
int tga_comp = tga->comp; int tga_comp = tga->comp;
@@ -458,15 +522,13 @@ static void rtga_fast_rows(rtga_t *tga, int nrows)
for (; row < last; ++row) for (; row < last; ++row)
{ {
int dst_row = tga_inverted ? (tga_height - 1 - row) : row; int dst_row = tga_inverted ? (tga_height - 1 - row) : row;
/* size_t index so dst_row * tga_width does not overflow uint32_t *dst;
* 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;
int bytes_needed = tga_width * tga_comp; int bytes_needed = tga_width * tga_comp;
int col; int col;
if (s->img_buffer + bytes_needed > s->img_buffer_end) if (s->img_buffer + bytes_needed > s->img_buffer_end)
break; break;
dst = rtga_row(tga, dst_row);
if (tga_comp == 4) if (tga_comp == 4)
{ {
@@ -535,6 +597,7 @@ static void rtga_fast_rows(rtga_t *tga, int nrows)
} }
s->img_buffer += bytes_needed; s->img_buffer += bytes_needed;
rtga_row_done(tga, dst_row, dst);
} }
/* The body's only early exit is the truncation check, so stopping /* 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; 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; size_t n = (size_t)(tga_height - row) * (size_t)tga_width;
uint32_t fill = (tga_comp == 4) ? 0u : 0xFF000000u; uint32_t fill = (tga_comp == 4) ? 0u : 0xFF000000u;
uint32_t *p = tga_inverted uint32_t *p = tga_inverted
@@ -592,7 +669,7 @@ static void rtga_generic_pixels(rtga_t *tga, int npix)
* decoder. */ * decoder. */
rtga_context ctx; rtga_context ctx;
rtga_context *s = &ctx; rtga_context *s = &ctx;
uint32_t *output = tga->output_image; uint32_t *dst = tga->dst;
int tga_width = tga->width; int tga_width = tga->width;
int tga_height = tga->height; int tga_height = tga->height;
int tga_comp = tga->comp; 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; | ((uint32_t)g << 8) | (uint32_t)b;
/* Write to correct position using tracked row/col /* Write to correct position using tracked row/col
* (avoids per-pixel division and modulo). Use size_t for * (avoids per-pixel division and modulo). */
* the index so dst_row * tga_width does not overflow
* signed int for a legitimate 65535 x 65535 image. */
dst_row = tga_inverted ? (tga_height - 1 - cur_row) : cur_row; 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) if (++cur_col >= tga_width)
{ {
rtga_row_done(tga, dst_row, dst);
dst = NULL;
cur_col = 0; cur_col = 0;
++cur_row; ++cur_row;
} }
@@ -726,6 +805,7 @@ static void rtga_generic_pixels(rtga_t *tga, int npix)
--RLE_count; --RLE_count;
} }
tga->dst = dst;
tga->pixel_i = i; tga->pixel_i = i;
tga->cur_col = cur_col; tga->cur_col = cur_col;
tga->cur_row = cur_row; tga->cur_row = cur_row;
@@ -737,17 +817,14 @@ static void rtga_generic_pixels(rtga_t *tga, int npix)
(void)tga_height; (void)tga_height;
} }
/* Abandon an in-flight decode. The END path clears output_image /* Abandon an in-flight decode, or one rtga_begin() refused after
* first, transferring ownership, so this only frees what nobody * making its surface. The END path clears output_image first,
* received. */ * transferring ownership, so this only frees what nobody received. */
static void rtga_proc_reset(rtga_t *rtga) static void rtga_proc_reset(rtga_t *rtga)
{ {
if (rtga->phase != RTGA_PHASE_IDLE) free(rtga->output_image);
{ rtga->output_image = NULL;
free(rtga->output_image); rtga->phase = RTGA_PHASE_IDLE;
rtga->output_image = NULL;
}
rtga->phase = RTGA_PHASE_IDLE;
} }
/* Smallest prefix that rtga_begin can parse: the 18-byte header plus /* 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; 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) bool rtga_set_buf_ptr(rtga_t *rtga, void *data)
{ {
if (!rtga) if (!rtga)
@@ -957,6 +1081,7 @@ void rtga_free(rtga_t *rtga)
/* A finished decode handed its surface to the caller; an abandoned /* A finished decode handed its surface to the caller; an abandoned
* one still holds a partial surface. */ * one still holds a partial surface. */
rtga_proc_reset(rtga); rtga_proc_reset(rtga);
free(rtga->own_row);
free(rtga); free(rtga);
} }
+4
View File
@@ -4814,7 +4814,11 @@ static int start_decoder_setup(vorb *f)
if (sparse) if (sparse)
{ {
if (c->sorted_entries == 0) if (c->sorted_entries == 0)
{
setup_temp_free(f, mults,
sizeof(mults[0]) * c->lookup_values);
goto skip; goto skip;
}
c->multiplicands = (rvorbis_codetype *) setup_malloc(f, c->multiplicands = (rvorbis_codetype *) setup_malloc(f,
sizeof(c->multiplicands[0]) * len * c->dimensions); sizeof(c->multiplicands[0]) * len * c->dimensions);
} else } else
+260 -31
View File
@@ -853,7 +853,15 @@ static bool rslc_pp_expand_token(struct rslc_pp *pp, const struct rslc_tok *t,
if (!m) if (!m)
return true; return true;
if (sym->flags & RSLC_SYM_BUSY) 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; return true;
}
if (m->function_like) 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 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))) && !(pp->c->es && (pp->c->version == 310 || pp->c->version == 320)))
{ {
rslc_err(pp->c, "unsupported #version", NULL, line); rslc_err(pp->c, "unsupported #version", NULL, line);
@@ -1767,6 +1782,7 @@ struct rslc_quals
bool has_storage; bool has_storage;
bool push_constant; bool push_constant;
bool std140; bool std140;
bool std430; /* said so: RSLC_EXTENDED honours it on a uniform block */
bool is_uniform; bool is_uniform;
bool is_const; bool is_const;
bool is_in; bool is_in;
@@ -2051,6 +2067,9 @@ static bool rslc_ty_converts(const struct rslc_type *from, const struct rslc_typ
return false; return false;
if (from->kind != RSLC_TY_SCALAR && from->kind != RSLC_TY_VEC) if (from->kind != RSLC_TY_SCALAR && from->kind != RSLC_TY_VEC)
return false; return false;
if (to->base == RSLC_BASE_DOUBLE)
return to->kind == RSLC_TY_SCALAR && (from->base == RSLC_BASE_INT
|| from->base == RSLC_BASE_UINT || from->base == RSLC_BASE_FLOAT);
if (to->base == RSLC_BASE_FLOAT) if (to->base == RSLC_BASE_FLOAT)
return from->base == RSLC_BASE_INT || from->base == RSLC_BASE_UINT; return from->base == RSLC_BASE_INT || from->base == RSLC_BASE_UINT;
if (to->base == RSLC_BASE_UINT) if (to->base == RSLC_BASE_UINT)
@@ -2146,6 +2165,17 @@ static uint32_t rslc_p_convert(struct rslc_parser *p, uint32_t e,
return e; return e;
if (n->op == RSLC_OP_CONST && n->type->kind == RSLC_TY_SCALAR && to->kind == RSLC_TY_SCALAR) if (n->op == RSLC_OP_CONST && n->type->kind == RSLC_TY_SCALAR && to->kind == RSLC_TY_SCALAR)
{ {
/* A literal made a double keeps the value it was written with:
* the node carries the 64 bits, as a float literal's does */
if (to->base == RSLC_BASE_DOUBLE && n->type->base != RSLC_BASE_BOOL)
{
double d = n->type->base == RSLC_BASE_FLOAT ? rslc_p_const_double(n)
: n->type->base == RSLC_BASE_INT ? (double)(int32_t)n->a : (double)n->a;
uint64_t bits;
memcpy(&bits, &d, sizeof(bits));
return rslc_p_mk(p, RSLC_OP_CONST, to, rslc_float_bits((float)d),
(uint32_t)bits, (uint32_t)(bits >> 32), 0, n->line, 0);
}
/* Fold a scalar literal. */ /* Fold a scalar literal. */
if (to->base == RSLC_BASE_FLOAT) if (to->base == RSLC_BASE_FLOAT)
{ {
@@ -2848,6 +2878,9 @@ static bool rslc_p_eval(struct rslc_parser *p, uint32_t e, struct rslc_cval *out
/* a failed compile's nodes may name lists and nodes never made */ /* a failed compile's nodes may name lists and nodes never made */
if (!e || p->c->failed || n->nonconstant) if (!e || p->c->failed || n->nonconstant)
return false; return false;
/* a double is calculated where it runs: no constant has 64 bits here */
if (n->type && n->type->base == RSLC_BASE_DOUBLE)
return false;
switch (n->op) switch (n->op)
{ {
case RSLC_OP_CONST: case RSLC_OP_CONST:
@@ -3051,11 +3084,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 * 2 3 4 vec2 vec3 vec4; i2.. ivec, u2.. uvec, b2.. bvec
* S sampler2D * S sampler2D
* A N Z sampler2DArray, sampler2DMSArray, samplerBuffer * A N Z sampler2DArray, sampler2DMSArray, samplerBuffer
* J K image2D, image2DArray; g the gvec4 of the image's base, * J K L image2D, image2DArray, image2DMS; g the gvec4 of the
* matched exactly * image's base, matched exactly
* D1 D3 DC DR sampler1D, sampler3D, samplerCube, samplerCubeArray * D1 D3 DC DR DM sampler1D, sampler3D, samplerCube, samplerCubeArray,
* sampler2DMS
* H1 H2 HA HC HR the shadow sampler1D, 2D, 2DArray, Cube, CubeArray * 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) * (RSLC_EXTENDED; their overloads are absent otherwise)
* M any matrix; Q square matrix; T transposed M result * M any matrix; Q square matrix; T transposed M result
* X any scalar or vector, same type as other X * X any scalar or vector, same type as other X
@@ -3132,7 +3166,7 @@ static const struct rslc_bsig rslc_bsigs[] = {
{ RSLC_BFN_BITFIELDREVERSE, "bitfieldReverse", "I:I;U:U" }, { RSLC_BFN_BITFIELDREVERSE, "bitfieldReverse", "I:I;U:U" },
{ RSLC_BFN_BITCOUNT, "bitCount", "I:I;U:I" }, { 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_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") }, ";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" { 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") }, ";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 +3181,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" { 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" ";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") }, ";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_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_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" { RSLC_BFN_TEXTURELODOFFSET, "textureLodOffset", "S 2 f i2:4" RSLC_X(";A 3 f i2:4"
@@ -3185,10 +3219,11 @@ static const struct rslc_bsig rslc_bsigs[] = {
{ RSLC_BFN_MEMORYBARRIERBUFFER, "memoryBarrierBuffer", ":v" }, { RSLC_BFN_MEMORYBARRIERBUFFER, "memoryBarrierBuffer", ":v" },
{ RSLC_BFN_MEMORYBARRIERIMAGE, "memoryBarrierImage", ":v" }, { RSLC_BFN_MEMORYBARRIERIMAGE, "memoryBarrierImage", ":v" },
{ RSLC_BFN_GROUPMEMORYBARRIER, "groupMemoryBarrier", ":v" }, { RSLC_BFN_GROUPMEMORYBARRIER, "groupMemoryBarrier", ":v" },
{ RSLC_BFN_IMAGESTORE, "imageStore", "J i2 g:v;K i3 g:v" }, { 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" }, { RSLC_BFN_IMAGELOAD, "imageLoad", "J i2:4;K i3:4;L i2 i:4" },
{ RSLC_BFN_IMAGESIZE, "imageSize", "J:i2;K:i3" }, { RSLC_BFN_IMAGESIZE, "imageSize", "J:i2;K:i3;L:i2" },
{ RSLC_BFN_SUBPASSLOAD, "subpassLoad", "P:4" }, { RSLC_BFN_SUBPASSLOAD, "subpassLoad", "P:4;W i:4" },
{ RSLC_BFN_TEXTURESAMPLES, "textureSamples", "DM:i;N:i" },
{ RSLC_BFN_SUBGROUPELECT, "subgroupElect", ":b" }, { RSLC_BFN_SUBGROUPELECT, "subgroupElect", ":b" },
{ RSLC_BFN_SUBGROUPMIN, "subgroupMin", "F:F;I:I;U:U" }, { RSLC_BFN_SUBGROUPMIN, "subgroupMin", "F:F;I:I;U:U" },
{ RSLC_BFN_SUBGROUPMAX, "subgroupMax", "F:F;I:I;U:U" } { RSLC_BFN_SUBGROUPMAX, "subgroupMax", "F:F;I:I;U:U" }
@@ -3284,20 +3319,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')) else if (len == 2 && (tok[0] == 'D' || tok[0] == 'H'))
{ {
unsigned dim = tok[1] == '1' ? RSLC_SDIM_1D 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; : tok[1] == '3' ? RSLC_SDIM_3D : RSLC_SDIM_CUBE;
unsigned fl = ((tok[1] == 'A' || tok[1] == 'R') ? RSLC_SX_ARRAY : 0) 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); | (tok[0] == 'H' ? RSLC_SX_SHADOW : 0);
if (t->kind != RSLC_TY_SAMPLERX || t->rows != dim || t->cols != fl) if (t->kind != RSLC_TY_SAMPLERX || t->rows != dim || t->cols != fl)
return false; return false;
*sbase = t->base; *sbase = t->base;
return true; return true;
} }
else if (len == 1 && tok[0] == 'P') else if (len == 1 && (tok[0] == 'P' || tok[0] == 'W'))
return t->kind == RSLC_TY_SUBPASSINPUT; return t->kind == RSLC_TY_SUBPASSINPUT && t->cols == (tok[0] == 'W' ? 2u : 1u);
else if (len == 1 && (tok[0] == 'J' || tok[0] == 'K')) 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; return false;
*sbase = t->base; *sbase = t->base;
return true; return true;
@@ -3391,7 +3428,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')) if (len == 1 && (tok[0] == 'S' || tok[0] == 'A' || tok[0] == 'N' || tok[0] == 'Z'))
return actual; return actual;
#ifdef RSLC_EXTENDED #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; return actual;
if (len == 2 && (tok[0] == 'D' || tok[0] == 'H')) if (len == 2 && (tok[0] == 'D' || tok[0] == 'H'))
return actual; return actual;
@@ -3612,11 +3649,12 @@ static bool rslc_p_is_lvalue(struct rslc_parser *p, uint32_t e)
|| v->storage == RSLC_ST_MEMBER) || v->storage == RSLC_ST_MEMBER)
return false; return false;
if (v->storage == RSLC_ST_PARAM && v->dir == RSLC_DIR_IN) 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) if (v->storage == RSLC_ST_BUILTIN)
return v->builtin == RSLC_BI_POSITION || v->builtin == RSLC_BI_FRAGDEPTH 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_POINTSIZE || v->builtin == RSLC_BI_CLIPDISTANCE
|| v->builtin == RSLC_BI_CULLDISTANCE || v->builtin == RSLC_BI_CULLDISTANCE
|| v->builtin == RSLC_BI_FRAGSTENCILREF
|| (v->builtin == RSLC_BI_LAYER && v->dir == RSLC_DIR_OUT); || (v->builtin == RSLC_BI_LAYER && v->dir == RSLC_DIR_OUT);
return true; return true;
} }
@@ -3927,6 +3965,42 @@ static uint32_t rslc_p_call(struct rslc_parser *p, struct rslc_sym *name, uint32
const struct rslc_type *ret = NULL; const struct rslc_type *ret = NULL;
uint8_t prec = 0; uint8_t prec = 0;
uint32_t i; uint32_t i;
#ifdef RSLC_EXTENDED
/* The rounding, range and selection functions on doubles: every
* number among the arguments a double, and so the result */
for (i = 0; i < nargs; i++)
if (rslc_p_node(p, args[i])->type->base == RSLC_BASE_DOUBLE)
break;
if (i < nargs)
{
const struct rslc_type *dt = rslc_type_basic(p->c, RSLC_TY_SCALAR, RSLC_BASE_DOUBLE, 1, 1);
unsigned want = (name->bfn == RSLC_BFN_MIX || name->bfn == RSLC_BFN_CLAMP) ? 3
: (name->bfn == RSLC_BFN_MIN || name->bfn == RSLC_BFN_MAX) ? 2
: (name->bfn == RSLC_BFN_ABS || name->bfn == RSLC_BFN_SIGN || name->bfn == RSLC_BFN_FLOOR
|| name->bfn == RSLC_BFN_CEIL || name->bfn == RSLC_BFN_TRUNC || name->bfn == RSLC_BFN_ROUND
|| name->bfn == RSLC_BFN_FRACT || name->bfn == RSLC_BFN_SQRT) ? 1 : 0;
if (!want || nargs != want)
{
rslc_err(p->c, "not a function of doubles: ", name->str, line);
return 0;
}
for (i = 0; i < nargs; i++)
{
const struct rslc_type *at = rslc_p_node(p, args[i])->type;
if (name->bfn == RSLC_BFN_MIX && i == 2 && at->kind == RSLC_TY_SCALAR && at->base == RSLC_BASE_BOOL)
continue;
if (!rslc_ty_converts(at, dt))
{
rslc_err(p->c, "an argument that is not a number, in ", name->str, line);
return 0;
}
args[i] = rslc_p_convert(p, args[i], dt);
}
r = rslc_p_mk(p, RSLC_OP_BUILTIN, dt, name->bfn, rslc_list_new(p->c, args, nargs), 0, 0, line, 0);
rslc_scratch_release(p->c, &mark);
return r;
}
#endif
if (!rslc_p_resolve_builtin(p, bs, args, nargs, &ret, line)) if (!rslc_p_resolve_builtin(p, bs, args, nargs, &ret, line))
return 0; return 0;
#ifdef RSLC_EXTENDED #ifdef RSLC_EXTENDED
@@ -3975,7 +4049,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 */ v->precision = rslc_p_default_prec(p, v->type, 0); /* as glslang: the default applies */
#ifdef RSLC_EXTENDED #ifdef RSLC_EXTENDED
if (v->storage == RSLC_ST_BUILTIN 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; p->c->prog.spirv_only = v->name->str;
#endif #endif
e = rslc_p_mk(p, RSLC_OP_VAR, v->type, v->id, 0, 0, 0, line, v->precision); e = rslc_p_mk(p, RSLC_OP_VAR, v->type, v->id, 0, 0, 0, line, v->precision);
@@ -4053,9 +4128,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, return rslc_p_mk(p, RSLC_OP_MEMBER, f->type, e, sym->decl->member, 0, 0, line,
rslc_prec_max(v->precision, f->precision)); 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 #endif
if (sym->decl && sym->decl->kind == RSLC_DK_VAR) if (sym->decl && sym->decl->kind == RSLC_DK_VAR)
return rslc_p_var_ref(p, (struct rslc_var*)sym->decl->ptr, line); 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); rslc_err(p->c, "undeclared identifier ", sym->str, line);
return 0; return 0;
} }
@@ -4185,6 +4284,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, "')'")) if (!rslc_p_expect(p, RSLC_P_LPAREN, "'('") || !rslc_p_expect(p, RSLC_P_RPAREN, "')'"))
return 0; 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); e = rslc_p_const_int(p, (int32_t)bt->array_len, line);
continue; continue;
} }
@@ -4370,7 +4480,9 @@ static const struct rslc_type *rslc_p_binary_type(struct rslc_parser *p, uint16_
if (ta->base != tb->base) if (ta->base != tb->base)
{ {
const struct rslc_type *ca, *cb; const struct rslc_type *ca, *cb;
enum rslc_base base = (ta->base == RSLC_BASE_FLOAT || tb->base == RSLC_BASE_FLOAT) enum rslc_base base = (ta->base == RSLC_BASE_DOUBLE || tb->base == RSLC_BASE_DOUBLE)
? RSLC_BASE_DOUBLE
: (ta->base == RSLC_BASE_FLOAT || tb->base == RSLC_BASE_FLOAT)
? RSLC_BASE_FLOAT : RSLC_BASE_UINT; ? RSLC_BASE_FLOAT : RSLC_BASE_UINT;
ca = rslc_type_basic(p->c, ta->kind, base, ta->rows, ta->cols); ca = rslc_type_basic(p->c, ta->kind, base, ta->rows, ta->cols);
cb = rslc_type_basic(p->c, tb->kind, base, tb->rows, tb->cols); cb = rslc_type_basic(p->c, tb->kind, base, tb->rows, tb->cols);
@@ -4621,6 +4733,11 @@ static bool rslc_p_image_format(const char *s, uint32_t len, uint8_t *fmt)
{ "rgba32f", RSLC_IMGFMT_RGBA32F }, { "rgba16f", RSLC_IMGFMT_RGBA16F }, { "rgba32f", RSLC_IMGFMT_RGBA32F }, { "rgba16f", RSLC_IMGFMT_RGBA16F },
{ "r32f", RSLC_IMGFMT_R32F }, { "rgba8", RSLC_IMGFMT_RGBA8 }, { "r32f", RSLC_IMGFMT_R32F }, { "rgba8", RSLC_IMGFMT_RGBA8 },
{ "rg8", RSLC_IMGFMT_RG8 }, { "r8", RSLC_IMGFMT_R8 }, { "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 }, { "rgba32i", RSLC_IMGFMT_RGBA32I }, { "rgba16i", RSLC_IMGFMT_RGBA16I },
{ "rgba8i", RSLC_IMGFMT_RGBA8I }, { "r32i", RSLC_IMGFMT_R32I }, { "rgba8i", RSLC_IMGFMT_RGBA8I }, { "r32i", RSLC_IMGFMT_R32I },
{ "rgba32ui", RSLC_IMGFMT_RGBA32UI }, { "rgba16ui", RSLC_IMGFMT_RGBA16UI }, { "rgba32ui", RSLC_IMGFMT_RGBA32UI }, { "rgba16ui", RSLC_IMGFMT_RGBA16UI },
@@ -4658,7 +4775,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 == 6 && !memcmp(s, "offset", 6)) slot = &q->offset;
else if (len == 13 && !memcmp(s, "push_constant", 13)) q->push_constant = true; 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, "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 == 12 && !memcmp(s, "column_major", 12)) { }
else if (len == 9 && !memcmp(s, "row_major", 9)) else if (len == 9 && !memcmp(s, "row_major", 9))
return rslc_p_fail(p, "row_major is not supported", NULL); return rslc_p_fail(p, "row_major is not supported", NULL);
@@ -4751,6 +4868,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_WRITEONLY: q->writeonly = true; break;
case RSLC_KW_RESTRICT: case RSLC_KW_RESTRICT:
case RSLC_KW_VOLATILE: break; 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 #endif
default: default:
return true; return true;
@@ -4819,13 +4942,41 @@ static const struct rslc_type *rslc_p_type_spec(struct rslc_parser *p,
ty = rslc_type_sampler2d(p->c, RSLC_BASE_UINT); ty = rslc_type_sampler2d(p->c, RSLC_BASE_UINT);
else if (k == RSLC_KW_SUBPASSINPUT) else if (k == RSLC_KW_SUBPASSINPUT)
ty = rslc_type_basic(p->c, RSLC_TY_SUBPASSINPUT, RSLC_BASE_FLOAT, 1, 1); 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_DOUBLE)
{
/* a 64-bit scalar: converted to and from, calculated with,
* compared; never a constant, a vector or a block member */
ty = rslc_type_basic(p->c, RSLC_TY_SCALAR, RSLC_BASE_DOUBLE, 1, 1);
#ifdef RSLC_EXTENDED
p->c->prog.spirv_only = "double";
#endif
}
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 */ /* 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 }; static const uint8_t bases[3] = { RSLC_BASE_FLOAT, RSLC_BASE_INT, RSLC_BASE_UINT };
unsigned i = k - RSLC_KW_IMAGE2D; 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); (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) else if (k >= RSLC_KW_SAMPLER2DARRAY && k <= RSLC_KW_USAMPLERBUFFER)
{ {
@@ -5013,7 +5164,7 @@ static bool rslc_p_starts_declaration(struct rslc_parser *p)
s = rslc_tok_sym(p->c, t); s = rslc_tok_sym(p->c, t);
if (s->kw >= RSLC_KW_CONST && s->kw <= RSLC_KW_STRUCT) if (s->kw >= RSLC_KW_CONST && s->kw <= RSLC_KW_STRUCT)
return true; 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 */ return true; /* RSLC_EXTENDED: only seeded there */
if (s->kw >= RSLC_KW_VOID && s->kw <= RSLC_KW_LAST_TYPE) if (s->kw >= RSLC_KW_VOID && s->kw <= RSLC_KW_LAST_TYPE)
{ {
@@ -5613,18 +5764,30 @@ static bool rslc_p_block_make(struct rslc_parser *p, const struct rslc_quals *q,
b->push = idx == 1; b->push = idx == 1;
b->std140 = idx == 0 || q->std140; b->std140 = idx == 0 || q->std140;
#ifdef RSLC_EXTENDED #ifdef RSLC_EXTENDED
/* a further uniform block is std140 as the first is; a storage /* a further uniform block is std140 as the first is, unless it says
* buffer std430 unless it says otherwise, and only its last member * std430 (GL_EXT_scalar_block_layout lets a uniform block say so, and
* may be an array of unstated length */ * 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->buffer = q->is_buffer;
b->readonly = q->readonly; b->readonly = q->readonly;
b->coherent = q->coherent; b->coherent = q->coherent;
if (idx >= 2 && !q->is_buffer) if (idx >= 2 && !q->is_buffer)
b->std140 = true; b->std140 = true;
if (idx != 1 && !q->is_buffer && q->std430 && !q->std140)
b->std140 = false;
for (i = 0; i < nfields; i++) for (i = 0; i < nfields; i++)
if (fields[i].type->kind == RSLC_TY_ARRAY && !fields[i].type->array_len 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 (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 #endif
pr->has_block[idx] = true; pr->has_block[idx] = true;
@@ -5706,6 +5869,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].name = rslc_tok_sym(p->c, nt);
fields[nfields].line = nt->line; fields[nfields].line = nt->line;
fields[nfields].precision = rslc_p_default_prec(p, mt, fq.precision); 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++; nfields++;
if (rslc_p_accept(p, RSLC_P_COMMA)) if (rslc_p_accept(p, RSLC_P_COMMA))
continue; continue;
@@ -6189,11 +6364,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); rslc_p_fail(p, "an image must be a global uniform", NULL);
return 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; 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 */ /* 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, 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, q->writeonly ? RSLC_IMG_WRITEONLY : q->readonly ? RSLC_IMG_READONLY : RSLC_IMG_READWRITE,
@@ -6328,6 +6507,31 @@ static uint32_t rslc_p_declaration(struct rslc_parser *p, bool global)
return 0; return 0;
#ifdef RSLC_EXTENDED #ifdef RSLC_EXTENDED
/* invariant gl_Position; - a variable already declared, a built-in
* or an output, made invariant after the fact */
if (global && (q.vq & RSLC_VQ_INVARIANT) && P_TOK(p)->kind == RSLC_TK_IDENT
&& rslc_tok_sym(p->c, P_TOK(p))->kw == RSLC_KW_NONE
&& rslc_tok_sym(p->c, P_TOK(p))->decl
&& rslc_tok_sym(p->c, P_TOK(p))->decl->kind == RSLC_DK_VAR
&& (rslc_tok_is_punct(P_PEEK(p, 1), RSLC_P_SEMI) || rslc_tok_is_punct(P_PEEK(p, 1), RSLC_P_COMMA)))
{
for (;;)
{
struct rslc_sym *s = P_TOK(p)->kind == RSLC_TK_IDENT ? rslc_tok_sym(p->c, P_TOK(p)) : NULL;
struct rslc_var *v;
if (!s || !s->decl || s->decl->kind != RSLC_DK_VAR)
return rslc_p_fail(p, "invariant names a variable", NULL);
v = (struct rslc_var*)s->decl->ptr;
if (v->storage != RSLC_ST_OUT && v->storage != RSLC_ST_BUILTIN)
return rslc_p_fail(p, "only an output can be made invariant", NULL);
v->quals |= RSLC_VQ_INVARIANT;
p->c->prog.spirv_only = "invariant on a declared variable";
rslc_p_next(p);
if (!rslc_p_accept(p, RSLC_P_COMMA))
break;
}
return rslc_p_expect(p, RSLC_P_SEMI, "';'") ? 1 : 0;
}
if (global && P_TOK(p)->kind == RSLC_TK_IDENT && P_TOK(p)->len == 20 if (global && P_TOK(p)->kind == RSLC_TK_IDENT && P_TOK(p)->len == 20
&& !memcmp(rslc_tok_text(p->c, P_TOK(p)), "spirv_execution_mode", 20)) && !memcmp(rslc_tok_text(p->c, P_TOK(p)), "spirv_execution_mode", 20))
return rslc_p_spirv_execution_mode(p) ? 1 : 0; return rslc_p_spirv_execution_mode(p) ? 1 : 0;
@@ -6611,6 +6815,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)) || !rslc_p_builtin_var(p, "gl_FrontFacing", p->t_bool, RSLC_BI_FRONTFACING, RSLC_DIR_IN))
return false; return false;
#ifdef RSLC_EXTENDED #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) 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_p_builtin_var(p, "gl_HelperInvocation", p->t_bool,
RSLC_BI_HELPERINVOCATION, RSLC_DIR_IN) RSLC_BI_HELPERINVOCATION, RSLC_DIR_IN)
@@ -7301,6 +7514,22 @@ uint32_t rslc_b_member(struct rslc_builder *b, uint32_t e, uint32_t field)
rslc_prec_max(rslc_b_node(b, e)->prec, bt->fields[field].precision)); rslc_prec_max(rslc_b_node(b, e)->prec, bt->fields[field].precision));
} }
#ifdef RSLC_EXTENDED
uint32_t rslc_b_length(struct rslc_builder *b, uint32_t e)
{
const struct rslc_type *bt;
if (!rslc_b_ok(b) || !e)
return 0;
bt = rslc_b_node(b, e)->type;
if (bt->kind != RSLC_TY_ARRAY)
return rslc_b_fail(b, "length() of what is not an array", NULL);
if (bt->array_len)
return rslc_p_const_int(b->p, (int32_t)bt->array_len, b->line);
b->c.prog.spirv_only = "length() of an unsized array";
return rslc_p_mk(b->p, RSLC_OP_UNARY, b->p->t_int, e, 0, 0, RSLC_UN_ARRAYLEN, b->line, 0);
}
#endif
uint32_t rslc_b_index(struct rslc_builder *b, uint32_t e, uint32_t idx) uint32_t rslc_b_index(struct rslc_builder *b, uint32_t e, uint32_t idx)
{ {
if (!rslc_b_ok(b) || !e || !idx) if (!rslc_b_ok(b) || !e || !idx)
+363 -47
View File
@@ -427,9 +427,13 @@ static const char *const rslc_keywords[] = {
"samplerCubeArray", "isamplerCubeArray", "usamplerCubeArray", "samplerCubeArray", "isamplerCubeArray", "usamplerCubeArray",
"sampler1DShadow", "sampler2DShadow", "sampler2DArrayShadow", "sampler1DShadow", "sampler2DShadow", "sampler2DArrayShadow",
"samplerCubeShadow", "samplerCubeArrayShadow", "samplerCubeShadow", "samplerCubeArrayShadow",
"sampler2DMS", "isampler2DMS", "usampler2DMS",
"image2D", "iimage2D", "uimage2D", "image2DArray", "iimage2DArray", "uimage2DArray", "image2D", "iimage2D", "uimage2D", "image2DArray", "iimage2DArray", "uimage2DArray",
"subpassInput", "image2DMS", "iimage2DMS", "uimage2DMS",
"buffer", "readonly", "writeonly", "coherent", "restrict", "volatile", "shared" "subpassInput", "subpassInputMS",
"double",
"buffer", "readonly", "writeonly", "coherent", "restrict", "volatile", "shared",
"pervertexEXT"
#endif #endif
}; };
@@ -984,6 +988,8 @@ uint32_t rslc_layout_field_offset(const struct rslc_type *t, uint32_t field,
uint32_t fa, fs, fst; uint32_t fa, fs, fst;
rslc_layout_of(t->fields[i].type, std140, &fa, &fs, &fst); rslc_layout_of(t->fields[i].type, std140, &fa, &fs, &fst);
off = (off + fa - 1) / fa * fa; off = (off + fa - 1) / fa * fa;
if (t->fields[i].offset_p1)
off = t->fields[i].offset_p1 - 1;
if (i == field) if (i == field)
return off; return off;
off += fs; off += fs;
@@ -1209,6 +1215,12 @@ struct spv_const
uint32_t id; uint32_t id;
}; };
struct spv_vconst
{
uint32_t type, id;
uint32_t c[4];
};
struct spv_var struct spv_var
{ {
uint32_t id; /* the OpVariable, or the constant for a folded const */ uint32_t id; /* the OpVariable, or the constant for a folded const */
@@ -1216,6 +1228,7 @@ struct spv_var
uint32_t tid; /* type id of the variable's value */ uint32_t tid; /* type id of the variable's value */
uint8_t sc; /* storage class */ uint8_t sc; /* storage class */
bool is_const; /* id is a constant value, not a pointer */ 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 */ bool in_block; /* RSLC_ST_MEMBER / BLOCK: id is the block variable */
}; };
@@ -1235,6 +1248,10 @@ struct spv_emit
uint32_t *func_ids; /* by func id */ uint32_t *func_ids; /* by func id */
uint32_t *func_types; uint32_t *func_types;
struct spv_const *consts; 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 entry_iface; /* interface ids for OpEntryPoint */
struct rslc_wbuf func_vars; /* OpVariable Function words, hoisted */ struct rslc_wbuf func_vars; /* OpVariable Function words, hoisted */
struct rslc_wbuf fntypes; /* interned OpTypeFunction: id, n, words[n] */ struct rslc_wbuf fntypes; /* interned OpTypeFunction: id, n, words[n] */
@@ -1252,6 +1269,8 @@ struct spv_emit
uint32_t entry_label; uint32_t entry_label;
bool need_imagequery; bool need_imagequery;
bool need_storageimageextended; bool need_storageimageextended;
bool need_storageimagemultisample;
bool need_storageimagewritewithoutformat;
bool need_inputattachment; bool need_inputattachment;
bool need_groupnonuniform; bool need_groupnonuniform;
bool need_groupnonuniformarith; bool need_groupnonuniformarith;
@@ -1262,6 +1281,9 @@ struct spv_emit
bool need_geometry; /* a pixel stage reading gl_Layer */ bool need_geometry; /* a pixel stage reading gl_Layer */
bool need_clipdistance; bool need_clipdistance;
bool writes_fragdepth; /* the entry point declares DepthReplacing */ bool writes_fragdepth; /* the entry point declares DepthReplacing */
bool need_float64; /* Float64 */
bool need_barycentric; /* FragmentBarycentricKHR, with its extension */
bool writes_stencilref; /* and StencilRefReplacingEXT, with its extension */
bool need_culldistance; bool need_culldistance;
bool in_main; bool in_main;
}; };
@@ -1553,6 +1575,11 @@ static uint32_t spv_type(struct spv_emit *e, const struct rslc_type *t)
if (!spv_inst3(e, SPV_SEC_TYPES, SpvOpTypeInt, id, 32, 0)) if (!spv_inst3(e, SPV_SEC_TYPES, SpvOpTypeInt, id, 32, 0))
return 0; return 0;
break; break;
case RSLC_BASE_DOUBLE:
if (!spv_inst2(e, SPV_SEC_TYPES, SpvOpTypeFloat, id, 64))
return 0;
e->need_float64 = true;
break;
default: default:
if (!spv_inst2(e, SPV_SEC_TYPES, SpvOpTypeFloat, id, 32)) if (!spv_inst2(e, SPV_SEC_TYPES, SpvOpTypeFloat, id, 32))
return 0; return 0;
@@ -1625,7 +1652,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[0] = img; w[1] = s; w[2] = (uint32_t)t->rows - 1;
w[3] = (t->cols & RSLC_SX_SHADOW) ? 1 : 0; w[3] = (t->cols & RSLC_SX_SHADOW) ? 1 : 0;
w[4] = (t->cols & RSLC_SX_ARRAY) ? 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) if (t->rows == RSLC_SDIM_1D)
e->need_sampled1d = true; e->need_sampled1d = true;
if (t->rows == RSLC_SDIM_CUBE && (t->cols & RSLC_SX_ARRAY)) if (t->rows == RSLC_SDIM_CUBE && (t->cols & RSLC_SX_ARRAY))
@@ -1639,6 +1666,7 @@ static uint32_t spv_type(struct spv_emit *e, const struct rslc_type *t)
} }
case RSLC_TY_IMAGE2D: case RSLC_TY_IMAGE2D:
case RSLC_TY_IMAGE2DARRAY: case RSLC_TY_IMAGE2DARRAY:
case RSLC_TY_IMAGE2DMS:
{ {
/* a storage image: Sampled 2, its format from the layout (cols) */ /* 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)); uint32_t s = spv_type(e, rslc_type_basic(e->c, RSLC_TY_SCALAR, (enum rslc_base)t->base, 1, 1));
@@ -1646,9 +1674,14 @@ static uint32_t spv_type(struct spv_emit *e, const struct rslc_type *t)
return 0; return 0;
ti = &e->tinfo[t->id]; ti = &e->tinfo[t->id];
w[0] = id; w[1] = s; w[2] = 1 /* 2D */; w[3] = 0; 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; w[4] = t->kind == RSLC_TY_IMAGE2DARRAY; w[5] = t->kind == RSLC_TY_IMAGE2DMS;
if (t->cols == RSLC_IMGFMT_R8 || t->cols == RSLC_IMGFMT_RG8) 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; e->need_storageimageextended = true;
if (t->kind == RSLC_TY_IMAGE2DMS)
e->need_storageimagemultisample = true;
if (!spv_inst(e, SPV_SEC_TYPES, SpvOpTypeImage, w, 8)) if (!spv_inst(e, SPV_SEC_TYPES, SpvOpTypeImage, w, 8))
return 0; return 0;
ti->img = id; ti->img = id;
@@ -1661,7 +1694,7 @@ static uint32_t spv_type(struct spv_emit *e, const struct rslc_type *t)
if (!s) if (!s)
return 0; return 0;
ti = &e->tinfo[t->id]; 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)) if (!spv_inst(e, SPV_SEC_TYPES, SpvOpTypeImage, w, 8))
return 0; return 0;
ti->img = id; ti->img = id;
@@ -1726,16 +1759,49 @@ static uint32_t spv_ptr(struct spv_emit *e, uint32_t tid, unsigned sc)
return 0; return 0;
return e->tinfo[i].ptr[sc]; 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); uint32_t p = spv_id(e);
if (!spv_inst3(e, SPV_SEC_TYPES, SpvOpTypePointer, p, sc, tid)) if (!spv_inst3(e, SPV_SEC_TYPES, SpvOpTypePointer, p, sc, tid))
return 0; 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; 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) static unsigned spv_param_sc(const struct rslc_type *t)
{ {
#ifdef RSLC_EXTENDED #ifdef RSLC_EXTENDED
@@ -1852,11 +1918,38 @@ static uint32_t spv_const_composite(struct spv_emit *e, const struct rslc_type *
} }
else else
return 0; 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); id = spv_id(e);
words[0] = tid; words[0] = tid;
words[1] = id; words[1] = id;
if (!spv_inst(e, SPV_SEC_TYPES, SpvOpConstantComposite, words, 2 + n)) if (!spv_inst(e, SPV_SEC_TYPES, SpvOpConstantComposite, words, 2 + n))
return 0; 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; return id;
} }
@@ -2077,10 +2170,13 @@ static uint32_t spv_convert(struct spv_emit *e, uint32_t v, const struct rslc_ty
return 0; return 0;
if (from->base == to->base) if (from->base == to->base)
return v; return v;
if (to->base == RSLC_BASE_FLOAT) /* float to double and back */
if (RSLC_BASE_IS_REAL(to->base) && RSLC_BASE_IS_REAL(from->base))
return spv_r1(e, (enum spv_op)115 /* OpFConvert */, tid, v, prec);
if (RSLC_BASE_IS_REAL(to->base))
return spv_r1(e, from->base == RSLC_BASE_INT ? SpvOpConvertSToF return spv_r1(e, from->base == RSLC_BASE_INT ? SpvOpConvertSToF
: from->base == RSLC_BASE_UINT ? SpvOpConvertUToF : SpvOpSelect, tid, v, prec); : from->base == RSLC_BASE_UINT ? SpvOpConvertUToF : SpvOpSelect, tid, v, prec);
if (from->base == RSLC_BASE_FLOAT) if (RSLC_BASE_IS_REAL(from->base))
return spv_r1(e, to->base == RSLC_BASE_INT ? SpvOpConvertFToS : SpvOpConvertFToU, tid, v, prec); return spv_r1(e, to->base == RSLC_BASE_INT ? SpvOpConvertFToS : SpvOpConvertFToU, tid, v, prec);
if ((from->base == RSLC_BASE_INT && to->base == RSLC_BASE_UINT) if ((from->base == RSLC_BASE_INT && to->base == RSLC_BASE_UINT)
|| (from->base == RSLC_BASE_UINT && to->base == RSLC_BASE_INT)) || (from->base == RSLC_BASE_UINT && to->base == RSLC_BASE_INT))
@@ -2175,7 +2271,7 @@ static uint32_t spv_mat_columnwise(struct spv_emit *e, enum spv_op op, const str
static enum spv_op spv_binop(const struct rslc_type *t, uint16_t op) static enum spv_op spv_binop(const struct rslc_type *t, uint16_t op)
{ {
bool f = t->base == RSLC_BASE_FLOAT; bool f = RSLC_BASE_IS_REAL(t->base);
bool s = t->base == RSLC_BASE_INT; bool s = t->base == RSLC_BASE_INT;
switch (op) switch (op)
{ {
@@ -2345,10 +2441,52 @@ static uint32_t spv_temp_of(struct spv_emit *e, uint32_t val, const struct rslc_
return tmp; 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 /* The pointer for an lvalue expression: an access chain from a
* variable. sc receives its storage class, laid whether the pointee * variable. sc receives its storage class, laid whether the pointee
* is a laid-out block type. */ * 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); const struct rslc_node *n = rslc_node_at(e->c, node);
switch (n->op) switch (n->op)
@@ -2392,6 +2530,28 @@ static uint32_t spv_lvalue(struct spv_emit *e, uint32_t node, unsigned *sc, bool
} }
return sv->ptr; 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); return spv_var_ptr(e, v, sc, laid, std140);
} }
case RSLC_OP_MEMBER: case RSLC_OP_MEMBER:
@@ -2400,8 +2560,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); const struct rslc_node *bn = rslc_node_at(e->c, n->a);
uint32_t base; uint32_t base;
uint32_t idx; uint32_t idx;
uint32_t tid, ptrt, id; uint32_t tid;
uint32_t w[4];
if (n->op == RSLC_OP_MEMBER && bn->op == RSLC_OP_VAR if (n->op == RSLC_OP_MEMBER && bn->op == RSLC_OP_VAR
&& e->c->prog.vars[bn->a]->storage == RSLC_ST_BLOCK) && e->c->prog.vars[bn->a]->storage == RSLC_ST_BLOCK)
{ {
@@ -2415,7 +2574,7 @@ static uint32_t spv_lvalue(struct spv_emit *e, uint32_t node, unsigned *sc, bool
e->member_active[bv->block][n->b] = 1; e->member_active[bv->block][n->b] = 1;
} }
else else
base = spv_lvalue(e, n->a, sc, laid, std140); base = spv_lvalue_root(e, n->a, sc, laid, std140, ch);
if (!base) if (!base)
return 0; return 0;
if (n->op == RSLC_OP_MEMBER) if (n->op == RSLC_OP_MEMBER)
@@ -2441,40 +2600,25 @@ static uint32_t spv_lvalue(struct spv_emit *e, uint32_t node, unsigned *sc, bool
#ifdef RSLC_EXTENDED #ifdef RSLC_EXTENDED
have_tid: have_tid:
#endif #endif
if (!tid) if (!tid || !spv_chain_push(e, &base, ch, *sc, idx, tid))
return 0; return 0;
ptrt = spv_ptr(e, tid, *sc); return base;
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;
} }
case RSLC_OP_SWIZZLE: case RSLC_OP_SWIZZLE:
{ {
/* single-component swizzle is an access chain */ /* single-component swizzle is an access chain */
unsigned cnt = n->aux & 3u; unsigned cnt = n->aux & 3u;
uint32_t base, idx, tid, ptrt, id; uint32_t base, idx, tid;
uint32_t w[4];
if (cnt != 1) if (cnt != 1)
return 0; return 0;
base = spv_lvalue(e, n->a, sc, laid, std140); base = spv_lvalue_root(e, n->a, sc, laid, std140, ch);
if (!base) if (!base)
return 0; return 0;
idx = spv_const_int(e, (int32_t)((n->aux >> 2) & 3u)); idx = spv_const_int(e, (int32_t)((n->aux >> 2) & 3u));
tid = spv_type(e, n->type); tid = spv_type(e, n->type);
if (!idx || !tid) if (!idx || !tid || !spv_chain_push(e, &base, ch, *sc, idx, tid))
return 0; return 0;
ptrt = spv_ptr(e, tid, *sc); return base;
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;
} }
default: default:
{ {
@@ -2488,6 +2632,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 /* Stores val into the lvalue node; multi-component swizzles become a
* load, insert, store. */ * load, insert, store. */
static bool spv_assign_to(struct spv_emit *e, uint32_t node, uint32_t val) static bool spv_assign_to(struct spv_emit *e, uint32_t node, uint32_t val)
@@ -2897,7 +3053,7 @@ static uint32_t spv_construct_node(struct spv_emit *e, const struct rslc_node *n
/* ext-inst builtin map; 0 = core op handled elsewhere */ /* ext-inst builtin map; 0 = core op handled elsewhere */
static uint32_t spv_bfn_ext(unsigned bfn, const struct rslc_type *t) static uint32_t spv_bfn_ext(unsigned bfn, const struct rslc_type *t)
{ {
bool f = t->base == RSLC_BASE_FLOAT; bool f = RSLC_BASE_IS_REAL(t->base);
bool s = t->base == RSLC_BASE_INT; bool s = t->base == RSLC_BASE_INT;
switch (bfn) switch (bfn)
{ {
@@ -3147,7 +3303,8 @@ static uint32_t spv_builtin_node(struct spv_emit *e, const struct rslc_node *n)
return 0; return 0;
#ifdef RSLC_EXTENDED #ifdef RSLC_EXTENDED
/* multisampled and buffer images have no levels */ /* 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); return spv_r1(e, SpvOpImageQuerySize, tid, img, prec);
#endif #endif
return spv_r2(e, SpvOpImageQuerySizeLod, tid, img, args[1], prec); return spv_r2(e, SpvOpImageQuerySizeLod, tid, img, args[1], prec);
@@ -3173,7 +3330,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 */ /* a buffer has no level; a multisampled image takes the sample */
if (t0->kind == RSLC_TY_SAMPLERBUFFER) if (t0->kind == RSLC_TY_SAMPLERBUFFER)
return spv_image_op(e, SpvOpImageFetch, tid, img, args[1], 0, 0, NULL, 0, prec); 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]; ops[0] = args[2];
return spv_image_op(e, SpvOpImageFetch, tid, img, args[1], 0, SPV_IMG_SAMPLE, ops, 1, prec); return spv_image_op(e, SpvOpImageFetch, tid, img, args[1], 0, SPV_IMG_SAMPLE, ops, 1, prec);
@@ -3376,13 +3534,28 @@ static uint32_t spv_builtin_node(struct spv_emit *e, const struct rslc_node *n)
} }
case RSLC_BFN_IMAGESTORE: case RSLC_BFN_IMAGESTORE:
{ {
uint32_t w[3]; uint32_t w[5];
w[0] = args[0]; w[1] = args[1]; w[2] = args[2]; 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)) if (!spv_fn_inst(e, SpvOpImageWrite, w, 3))
return 0; return 0;
return tid; return tid;
} }
case RSLC_BFN_IMAGELOAD: 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); return spv_r2(e, SpvOpImageRead, tid, args[0], args[1], prec);
case RSLC_BFN_IMAGESIZE: case RSLC_BFN_IMAGESIZE:
e->need_imagequery = true; e->need_imagequery = true;
@@ -3395,8 +3568,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); rslc_type_basic(e->c, RSLC_TY_VEC, RSLC_BASE_INT, 2, 1), zero, &used);
if (!coord) if (!coord)
return 0; 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); 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: case RSLC_BFN_SUBGROUPELECT:
{ {
/* subgroup scope; SPIR-V 1.3 */ /* subgroup scope; SPIR-V 1.3 */
@@ -3741,6 +3929,39 @@ static uint32_t spv_rvalue_impl(struct spv_emit *e, uint32_t node)
switch (n->op) switch (n->op)
{ {
case RSLC_OP_CONST: case RSLC_OP_CONST:
if (n->type->base == RSLC_BASE_DOUBLE)
{
/* 64 bits, low word first; one constant for each value */
uint32_t dt = spv_type(e, n->type), k, id;
uint32_t w[4];
if (!dt)
return 0;
for (k = 0; k < e->nvconsts; k++)
if (e->vconsts[k].type == dt && e->vconsts[k].c[0] == n->b && e->vconsts[k].c[1] == n->c)
return e->vconsts[k].id;
id = spv_id(e);
w[0] = dt; w[1] = id; w[2] = n->b; w[3] = n->c;
if (!spv_inst(e, SPV_SEC_TYPES, SpvOpConstant, w, 4))
return 0;
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;
}
memset(&e->vconsts[e->nvconsts], 0, sizeof(e->vconsts[0]));
e->vconsts[e->nvconsts].type = dt;
e->vconsts[e->nvconsts].id = id;
e->vconsts[e->nvconsts].c[0] = n->b;
e->vconsts[e->nvconsts].c[1] = n->c;
e->nvconsts++;
return id;
}
return spv_const_bits(e, n->type, n->a); return spv_const_bits(e, n->type, n->a);
case RSLC_OP_VAR: case RSLC_OP_VAR:
{ {
@@ -3749,6 +3970,8 @@ static uint32_t spv_rvalue_impl(struct spv_emit *e, uint32_t node)
uint32_t ptr; uint32_t ptr;
if (e->vars[v->id].is_const) if (e->vars[v->id].is_const)
return spv_const_var(e, v); 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); ptr = spv_var_ptr(e, v, &sc, &laid, &std140);
if (!ptr) if (!ptr)
return 0; return 0;
@@ -3763,7 +3986,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; || 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 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); || 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; base_is_ptr = false;
if (!base_is_ptr && const_idx) if (!base_is_ptr && const_idx)
{ {
@@ -3841,6 +4065,13 @@ static uint32_t spv_rvalue_impl(struct spv_emit *e, uint32_t node)
return 0; return 0;
continue; 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); temps[i] = spv_local(e, pv->type, NULL, pv->precision);
if (!temps[i]) if (!temps[i])
return 0; return 0;
@@ -3887,6 +4118,37 @@ static uint32_t spv_rvalue_impl(struct spv_emit *e, uint32_t node)
return 0; return 0;
switch (n->aux) 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: case RSLC_UN_NEG:
{ {
uint32_t v = spv_rvalue(e, n->a); uint32_t v = spv_rvalue(e, n->a);
@@ -3907,7 +4169,7 @@ static uint32_t spv_rvalue_impl(struct spv_emit *e, uint32_t node)
} }
return spv_construct(e, n->type, cols, n->type->cols, prec); return spv_construct(e, n->type, cols, n->type->cols, prec);
} }
return spv_r1(e, n->type->base == RSLC_BASE_FLOAT ? SpvOpFNegate : SpvOpSNegate, tid, v, prec); return spv_r1(e, RSLC_BASE_IS_REAL(n->type->base) ? SpvOpFNegate : SpvOpSNegate, tid, v, prec);
} }
case RSLC_UN_NOT: case RSLC_UN_NOT:
{ {
@@ -3928,9 +4190,12 @@ static uint32_t spv_rvalue_impl(struct spv_emit *e, uint32_t node)
const struct rslc_type *t = an->type; const struct rslc_type *t = an->type;
bool inc = n->aux == RSLC_UN_PREINC || n->aux == RSLC_UN_POSTINC; bool inc = n->aux == RSLC_UN_PREINC || n->aux == RSLC_UN_POSTINC;
bool post = n->aux == RSLC_UN_POSTINC || n->aux == RSLC_UN_POSTDEC; 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; return 0;
old = spv_load(e, t, ptr, prec); old = ptr ? spv_load(e, t, ptr, prec) : spv_rvalue(e, n->a);
if (!old) if (!old)
return 0; return 0;
if (t->base == RSLC_BASE_FLOAT) if (t->base == RSLC_BASE_FLOAT)
@@ -3951,7 +4216,7 @@ static uint32_t spv_rvalue_impl(struct spv_emit *e, uint32_t node)
} }
else else
nv = one ? spv_r2(e, spv_binop(t, inc ? RSLC_BIN_ADD : RSLC_BIN_SUB), tid, old, one, prec) : 0; 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 0;
return post ? old : nv; return post ? old : nv;
} }
@@ -4413,7 +4678,8 @@ static bool spv_function(struct spv_emit *e, const struct rslc_func *f)
w[0] = 0; w[1] = rtid; w[0] = 0; w[1] = rtid;
for (i = 0; i < f->nparams; i++) 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]) if (!w[2 + i])
return false; return false;
} }
@@ -4427,10 +4693,12 @@ static bool spv_function(struct spv_emit *e, const struct rslc_func *f)
for (i = 0; i < f->nparams; i++) for (i = 0; i < f->nparams; i++)
{ {
const struct rslc_var *pv = f->params[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); uint32_t pid = spv_id(e);
if (!ptr || !spv_inst2(e, SPV_SEC_FUNCS, SpvOpFunctionParameter, ptr, pid)) if (!ptr || !spv_inst2(e, SPV_SEC_FUNCS, SpvOpFunctionParameter, ptr, pid))
return false; return false;
e->vars[pv->id].byval = spv_param_byval(pv);
e->vars[pv->id].id = pid; e->vars[pv->id].id = pid;
e->vars[pv->id].sc = (uint8_t)spv_param_sc(pv->type); e->vars[pv->id].sc = (uint8_t)spv_param_sc(pv->type);
if (pv->name && !spv_name(e, pid, pv->name->str)) if (pv->name && !spv_name(e, pid, pv->name->str))
@@ -4696,6 +4964,12 @@ static bool spv_global_var(struct spv_emit *e, const struct rslc_var *v)
return false; return false;
if (v->interp == RSLC_INTERP_NOPERSPECTIVE && !spv_decorate(e, id, SPV_DEC_NOPERSPECTIVE)) if (v->interp == RSLC_INTERP_NOPERSPECTIVE && !spv_decorate(e, id, SPV_DEC_NOPERSPECTIVE))
return false; 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)) if ((v->quals & RSLC_VQ_CENTROID) && !spv_decorate(e, id, SPV_DEC_CENTROID))
return false; return false;
if ((v->quals & RSLC_VQ_SAMPLE) && !spv_decorate(e, id, SPV_DEC_SAMPLE)) if ((v->quals & RSLC_VQ_SAMPLE) && !spv_decorate(e, id, SPV_DEC_SAMPLE))
@@ -4776,6 +5050,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_LOCALINVOCATIONINDEX ? SPV_BI_LOCALINVOCATIONINDEX
: v->builtin == RSLC_BI_HELPERINVOCATION ? SPV_BI_HELPERINVOCATION : v->builtin == RSLC_BI_HELPERINVOCATION ? SPV_BI_HELPERINVOCATION
: v->builtin == RSLC_BI_POINTCOORD ? SPV_BI_POINTCOORD : 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; : v->builtin == RSLC_BI_PRIMITIVEID ? SPV_BI_PRIMITIVEID : SPV_BI_POSITION;
if (!ptr) if (!ptr)
return false; return false;
@@ -4784,6 +5061,10 @@ static bool spv_builtin_var(struct spv_emit *e, const struct rslc_var *v)
e->need_samplerateshading = true; e->need_samplerateshading = true;
if (v->builtin == RSLC_BI_FRAGDEPTH) if (v->builtin == RSLC_BI_FRAGDEPTH)
e->writes_fragdepth = true; 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); id = spv_id(e);
if (!spv_inst3(e, SPV_SEC_TYPES, SpvOpVariable, ptr, id, sc)) if (!spv_inst3(e, SPV_SEC_TYPES, SpvOpVariable, ptr, id, sc))
return false; return false;
@@ -4923,6 +5204,20 @@ static bool spv_pervertex(struct spv_emit *e)
|| !spv_mdecorate1(e, st, 2, SPV_DEC_BUILTIN, SPV_BI_CLIPDISTANCE) || !spv_mdecorate1(e, st, 2, SPV_DEC_BUILTIN, SPV_BI_CLIPDISTANCE)
|| !spv_mdecorate1(e, st, 3, SPV_DEC_BUILTIN, SPV_BI_CULLDISTANCE)) || !spv_mdecorate1(e, st, 3, SPV_DEC_BUILTIN, SPV_BI_CULLDISTANCE))
return false; return false;
/* a member made invariant: invariant gl_Position; */
{
uint32_t k;
for (k = 1; k < e->c->prog.nvars; k++)
{
const struct rslc_var *bv = e->c->prog.vars[k];
if (bv->storage == RSLC_ST_BUILTIN && (bv->quals & RSLC_VQ_INVARIANT)
&& RSLC_BI_IN_PERVERTEX(bv->builtin)
&& !spv_mdecorate(e, st, bv->builtin == RSLC_BI_POSITION ? 0
: bv->builtin == RSLC_BI_POINTSIZE ? 1 : bv->builtin == RSLC_BI_CLIPDISTANCE ? 2 : 3,
SPV_DEC_INVARIANT))
return false;
}
}
ptr = spv_ptr(e, st, SPV_SC_OUTPUT); ptr = spv_ptr(e, st, SPV_SC_OUTPUT);
if (!ptr) if (!ptr)
return false; return false;
@@ -5183,6 +5478,12 @@ static bool rslc_emit_spirv(struct rslc_ctx *c, struct rslc_result *result)
return false; return false;
if (e->need_clipdistance && !spv_inst1(e, SPV_SEC_CAPS, SpvOpCapability, SPV_CAP_CLIPDISTANCE)) if (e->need_clipdistance && !spv_inst1(e, SPV_SEC_CAPS, SpvOpCapability, SPV_CAP_CLIPDISTANCE))
return false; 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_float64 && !spv_inst1(e, SPV_SEC_CAPS, SpvOpCapability, 10u /* Float64 */))
return false;
if (e->need_culldistance && !spv_inst1(e, SPV_SEC_CAPS, SpvOpCapability, SPV_CAP_CULLDISTANCE)) if (e->need_culldistance && !spv_inst1(e, SPV_SEC_CAPS, SpvOpCapability, SPV_CAP_CULLDISTANCE))
return false; return false;
if (e->need_imagequery && !spv_inst1(e, SPV_SEC_CAPS, SpvOpCapability, SPV_CAP_IMAGEQUERY)) if (e->need_imagequery && !spv_inst1(e, SPV_SEC_CAPS, SpvOpCapability, SPV_CAP_IMAGEQUERY))
@@ -5190,6 +5491,12 @@ static bool rslc_emit_spirv(struct rslc_ctx *c, struct rslc_result *result)
if (e->need_storageimageextended && if (e->need_storageimageextended &&
!spv_inst1(e, SPV_SEC_CAPS, SpvOpCapability, SPV_CAP_STORAGEIMAGEEXTENDED)) !spv_inst1(e, SPV_SEC_CAPS, SpvOpCapability, SPV_CAP_STORAGEIMAGEEXTENDED))
return false; 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 #ifdef RSLC_EXTENDED
if (e->need_inputattachment if (e->need_inputattachment
&& !spv_inst1(e, SPV_SEC_CAPS, SpvOpCapability, SPV_CAP_INPUTATTACHMENT)) && !spv_inst1(e, SPV_SEC_CAPS, SpvOpCapability, SPV_CAP_INPUTATTACHMENT))
@@ -5237,6 +5544,12 @@ static bool rslc_emit_spirv(struct rslc_ctx *c, struct rslc_result *result)
return false; return false;
} }
#endif #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)) if (!spv_inst_str(e, SPV_SEC_CAPS, SpvOpExtInstImport, &e->ext_id, 1, "GLSL.std.450", NULL, 0))
return false; return false;
if (!spv_inst2(e, SPV_SEC_CAPS, SpvOpMemoryModel, 0 /* Logical */, 1 /* GLSL450 */)) if (!spv_inst2(e, SPV_SEC_CAPS, SpvOpMemoryModel, 0 /* Logical */, 1 /* GLSL450 */))
@@ -5263,6 +5576,9 @@ static bool rslc_emit_spirv(struct rslc_ctx *c, struct rslc_result *result)
if (e->writes_fragdepth if (e->writes_fragdepth
&& !spv_inst2(e, SPV_SEC_ENTRY, SpvOpExecutionMode, main_id, 12u /* DepthReplacing */)) && !spv_inst2(e, SPV_SEC_ENTRY, SpvOpExecutionMode, main_id, 12u /* DepthReplacing */))
return false; return false;
if (e->writes_stencilref
&& !spv_inst2(e, SPV_SEC_ENTRY, SpvOpExecutionMode, main_id, 5027u /* StencilRefReplacingEXT */))
return false;
#ifdef RSLC_EXTENDED #ifdef RSLC_EXTENDED
for (i = 0; i < c->prog.nxmodes; i += 1 + c->prog.xmodes[i]) for (i = 0; i < c->prog.nxmodes; i += 1 + c->prog.xmodes[i])
{ {
+38 -4
View File
@@ -224,14 +224,19 @@ enum rslc_kw
/* RSLC_EXTENDED: the depth comparing ones */ /* RSLC_EXTENDED: the depth comparing ones */
RSLC_KW_SAMPLER1DSHADOW, RSLC_KW_SAMPLER2DSHADOW, RSLC_KW_SAMPLER2DARRAYSHADOW, RSLC_KW_SAMPLER1DSHADOW, RSLC_KW_SAMPLER2DSHADOW, RSLC_KW_SAMPLER2DARRAYSHADOW,
RSLC_KW_SAMPLERCUBESHADOW, RSLC_KW_SAMPLERCUBEARRAYSHADOW, 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_EXTENDED image types: float, int, uint of each */
RSLC_KW_IMAGE2D, RSLC_KW_IIMAGE2D, RSLC_KW_UIMAGE2D, RSLC_KW_IMAGE2D, RSLC_KW_IIMAGE2D, RSLC_KW_UIMAGE2D,
RSLC_KW_IMAGE2DARRAY, RSLC_KW_IIMAGE2DARRAY, RSLC_KW_UIMAGE2DARRAY, 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_EXTENDED: a fragment stage's input attachment */
RSLC_KW_SUBPASSINPUT, RSLC_KW_SUBPASSINPUT, RSLC_KW_SUBPASSINPUTMS,
/* RSLC_EXTENDED: a 64-bit float scalar */
RSLC_KW_DOUBLE,
/* RSLC_EXTENDED qualifiers */ /* RSLC_EXTENDED qualifiers */
RSLC_KW_BUFFER, RSLC_KW_READONLY, RSLC_KW_WRITEONLY, RSLC_KW_COHERENT, 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 RSLC_KW_COUNT
}; };
@@ -260,6 +265,7 @@ enum rslc_tkind
/* RSLC_EXTENDED storage images; cols is the RSLC_IMGFMT_* */ /* RSLC_EXTENDED storage images; cols is the RSLC_IMGFMT_* */
RSLC_TY_IMAGE2D, RSLC_TY_IMAGE2D,
RSLC_TY_IMAGE2DARRAY, 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_EXTENDED: a float input attachment */
RSLC_TY_SUBPASSINPUT, RSLC_TY_SUBPASSINPUT,
/* RSLC_EXTENDED: any other sampler; rows is its RSLC_SDIM_*, cols its /* RSLC_EXTENDED: any other sampler; rows is its RSLC_SDIM_*, cols its
@@ -273,8 +279,10 @@ enum rslc_tkind
#define RSLC_SDIM_CUBE 4 #define RSLC_SDIM_CUBE 4
#define RSLC_SX_ARRAY 1u #define RSLC_SX_ARRAY 1u
#define RSLC_SX_SHADOW 2u #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. */ /* A storage image type's rows: its access. */
#define RSLC_IMG_READWRITE 1 #define RSLC_IMG_READWRITE 1
#define RSLC_IMG_WRITEONLY 2 #define RSLC_IMG_WRITEONLY 2
@@ -286,6 +294,19 @@ enum rslc_tkind
#define RSLC_IMGFMT_RGBA16F 2 #define RSLC_IMGFMT_RGBA16F 2
#define RSLC_IMGFMT_R32F 3 #define RSLC_IMGFMT_R32F 3
#define RSLC_IMGFMT_RGBA8 4 #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_RG8 13
#define RSLC_IMGFMT_R8 15 #define RSLC_IMGFMT_R8 15
#define RSLC_IMGFMT_RGBA32I 21 #define RSLC_IMGFMT_RGBA32I 21
@@ -298,7 +319,10 @@ enum rslc_tkind
#define RSLC_IMGFMT_R32UI 33 #define RSLC_IMGFMT_R32UI 33
/* The last keyword that names a type. */ /* The last keyword that names a type. */
#define RSLC_KW_LAST_TYPE RSLC_KW_SUBPASSINPUT #define RSLC_KW_LAST_TYPE RSLC_KW_DOUBLE
/* float or double: the bases the floating-point operations take */
#define RSLC_BASE_IS_REAL(b) ((b) == RSLC_BASE_FLOAT || (b) == RSLC_BASE_DOUBLE)
/* Every sampler kind: sampler2D, and the RSLC_EXTENDED ones. */ /* Every sampler kind: sampler2D, and the RSLC_EXTENDED ones. */
#define RSLC_TY_IS_SAMPLER(k) ((k) == RSLC_TY_SAMPLER2D \ #define RSLC_TY_IS_SAMPLER(k) ((k) == RSLC_TY_SAMPLER2D \
@@ -316,6 +340,10 @@ struct rslc_field
uint8_t interp; /* RSLC_EXTENDED: an in / out block member's */ uint8_t interp; /* RSLC_EXTENDED: an in / out block member's */
uint8_t quals; /* RSLC_VQ_* of that member */ uint8_t quals; /* RSLC_VQ_* of that member */
uint8_t pad; 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 struct rslc_type
@@ -364,6 +392,7 @@ enum rslc_storage
#define RSLC_INTERP_FLAT 1 #define RSLC_INTERP_FLAT 1
#define RSLC_INTERP_NOPERSPECTIVE 2 #define RSLC_INTERP_NOPERSPECTIVE 2
#define RSLC_INTERP_SMOOTH 3 #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_CENTROID 1u
#define RSLC_VQ_SAMPLE 2u #define RSLC_VQ_SAMPLE 2u
@@ -425,6 +454,9 @@ struct rslc_var
#define RSLC_BI_HELPERINVOCATION 18 /* RSLC_EXTENDED, fragment */ #define RSLC_BI_HELPERINVOCATION 18 /* RSLC_EXTENDED, fragment */
#define RSLC_BI_POINTCOORD 19 /* RSLC_EXTENDED, fragment */ #define RSLC_BI_POINTCOORD 19 /* RSLC_EXTENDED, fragment */
#define RSLC_BI_PRIMITIVEID 20 /* RSLC_EXTENDED, geometry gl_PrimitiveIDIn */ #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. */ /* RSLC_EXTENDED: a geometry stage's primitives, in and out. */
#define RSLC_GS_POINTS 1 #define RSLC_GS_POINTS 1
@@ -527,6 +559,7 @@ enum rslc_op
#define RSLC_UN_PREDEC 5 #define RSLC_UN_PREDEC 5
#define RSLC_UN_POSTINC 6 #define RSLC_UN_POSTINC 6
#define RSLC_UN_POSTDEC 7 #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_NONE 0
#define RSLC_BIN_ADD 1 #define RSLC_BIN_ADD 1
@@ -616,6 +649,7 @@ enum rslc_bfn
RSLC_BFN_MEMORYBARRIERBUFFER, RSLC_BFN_MEMORYBARRIERIMAGE, RSLC_BFN_GROUPMEMORYBARRIER, RSLC_BFN_MEMORYBARRIERBUFFER, RSLC_BFN_MEMORYBARRIERIMAGE, RSLC_BFN_GROUPMEMORYBARRIER,
RSLC_BFN_IMAGESTORE, RSLC_BFN_IMAGELOAD, RSLC_BFN_IMAGESIZE, RSLC_BFN_IMAGESTORE, RSLC_BFN_IMAGELOAD, RSLC_BFN_IMAGESIZE,
RSLC_BFN_SUBPASSLOAD, /* RSLC_EXTENDED, fragment */ RSLC_BFN_SUBPASSLOAD, /* RSLC_EXTENDED, fragment */
RSLC_BFN_TEXTURESAMPLES, /* RSLC_EXTENDED: a multisampled sampler's count */
/* RSLC_EXTENDED: subgroup operations */ /* RSLC_EXTENDED: subgroup operations */
RSLC_BFN_SUBGROUPELECT, RSLC_BFN_SUBGROUPMIN, RSLC_BFN_SUBGROUPMAX, RSLC_BFN_SUBGROUPELECT, RSLC_BFN_SUBGROUPMIN, RSLC_BFN_SUBGROUPMAX,
RSLC_BFN_COUNT 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 * 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 * (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 * the caller must fill in stream_sector_size / stream_sector_header_size
* itself (the path adapters below derive them from the file size or * itself (the path adapters in streams/cdfs_file.c derive them from the
* the cue sheet). */ * file size or the cue sheet). */
cdfs_track_t* cdfs_track_from_stream( cdfs_track_t* cdfs_track_from_stream(
intfstream_t* stream, intfstream_t* stream,
unsigned first_sector_offset, unsigned first_sector_offset,
@@ -132,37 +132,14 @@ typedef struct cdfs_cue_info
bool cdfs_parse_cue(const char *cue_text, size_t len, bool cdfs_parse_cue(const char *cue_text, size_t len,
unsigned track_index, cdfs_cue_info_t *out); 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_track, cdfs_open_data_track and cdfs_open_raw_track, which
* cdfs_open_file to get access to a file within the CD. * open a track by filename, are in <streams/cdfs_file.h>. */
*
* 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);
/* closes the CD or virtual CD track and frees the associated memory */ /* closes the CD or virtual CD track and frees the associated memory */
void cdfs_close_track(cdfs_track_t* track); 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. */ * NULL unless the transfer completed successfully. */
uint8_t *data_transfer_source_detach(data_transfer_t *dt, size_t *len); 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 /* Borrow a seekable source for the window's lifetime. The callback writes
* directly into final window storage, returning bytes read or -1 on error. * 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, typedef int64_t (*data_transfer_read_at_t)(void *ud, uint64_t offset,
uint8_t *dst, size_t bytes); uint8_t *dst, size_t bytes);
data_transfer_t *data_transfer_open_window_reader(size_t length, size_t keep, data_transfer_t *data_transfer_open_window_reader(size_t length, size_t keep,
data_transfer_read_at_t read_at, void *ud); 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); const uint8_t *data_transfer_window_base(data_transfer_t *dt, size_t *len);
/* True when the transfer is genuinely windowed - address space was /* True when the transfer is genuinely windowed - address space was
* reserved and only the window is committed. False when the platform * 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 * behind the consumer. There the bytes at tell itself are what is
* missing - a lap that landed past the head, an open that rebased - * 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 * and leaving them for a later tick leaves the consumer's very next
* read on unresident pages; that catch-up is one unbudgeted extend, * read on unresident pages; that catch-up is one unbudgeted extend
* exactly what window_feed has always done. budget == 0 disables * 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. * the ceiling entirely, making this window_feed by another name.
* *
* *resident_hi (may be NULL) reports the sequential bound this call * *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 * 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 * 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. */ * refused at open rather than presented as a capped prefix. */
data_transfer_t *data_transfer_open_prefix(const char *path, data_transfer_t *data_transfer_open_prefix_reader(size_t length,
size_t commit_cap); 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. /* The fill stopped at commit_cap with the file continuing beyond it.
* Terminal: iterate() will not advance further. */ * Terminal: iterate() will not advance further. */
+19 -29
View File
@@ -33,6 +33,8 @@
RETRO_BEGIN_DECLS RETRO_BEGIN_DECLS
struct data_transfer;
enum image_process_code enum image_process_code
{ {
IMAGE_PROCESS_ERROR = -2, 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, bool image_texture_load_buffer(struct texture_image *img,
enum image_type_enum type, void *s, size_t len); enum image_type_enum type, void *s, size_t len);
/* The loader run to completion over a whole buffer or a file, with /* The loader run to completion over a whole buffer, with @req naming
* @req naming what the caller takes (NULL: the ordinary image). The * what the caller takes (NULL: the ordinary image). The abort hook is
* abort hook is asked between steps; it may be NULL. False leaves * asked between steps; it may be NULL. False leaves @img empty. Loading
* @img empty. */ * 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, bool image_texture_load_buffer_request(struct texture_image *img,
enum image_type_enum type, const void *buf, size_t len, enum image_type_enum type, const void *buf, size_t len,
const image_texture_request_t *req, const image_texture_request_t *req,
bool (*should_abort)(void *ud), void *ud); 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 /* image_texture_load_buffer_request over bytes @keep, a data_transfer,
* the file is an animated PNG: 1 yes, 0 a still, -1 not a PNG or no * holds: an image that can go on reading them (a compressed texture)
* decode. Spares a caller that keeps an animation's bytes a second * takes @keep, frees it with itself and sets *@kept; *@png_probe, where
* read of every still to ask. @png_probe may be NULL. */ * not NULL, says whether the bytes are an animated PNG - 1 yes, 0 a
bool image_texture_load_request_ex(struct texture_image *img, * still, -1 not a PNG or no decode. @keep and @kept may be NULL. */
const char *path, const image_texture_request_t *req, 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); bool (*should_abort)(void *ud), void *ud, int *png_probe);
/* Run @fn for every index below @n, across the cores at once where /* 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)); void (*thread_done)(void));
/* Decode the @n files @paths name into @imgs together, each as /* Decode the @n files @paths name into @imgs together, each as
* image_texture_load_request does with @req; a path that is NULL, * image_texture_load_request (<streams/image_texture_file.h>) does with
* empty or unreadable leaves its image empty. Returns how many * @req; a path that is NULL, empty or unreadable leaves its image
* decoded. In formats/image_texture_set.c. */ * empty. Returns how many decoded. In formats/image_texture_set.c. */
unsigned image_texture_load_set(const char *const *paths, unsigned image_texture_load_set(const char *const *paths,
struct texture_image *imgs, unsigned n, struct texture_image *imgs, unsigned n,
const image_texture_request_t *req); 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); void image_texture_free(struct texture_image *img);
/* Force a CPU decode of a compressed texture_image into ->pixels (RGBA8). /* 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 * 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 * 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. * 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); 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: * rflac_seek_resumed:
* @f : decoder * @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); 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); void rtga_free(rtga_t *rtga);
rtga_t *rtga_alloc(void); rtga_t *rtga_alloc(void);
+14 -2
View File
@@ -48,7 +48,18 @@
* stage's gl_ClipDistance / gl_CullDistance (sized by the highest * stage's gl_ClipDistance / gl_CullDistance (sized by the highest
* constant element written; HLSL and MSL refuse them yet), in / out * constant element written; HLSL and MSL refuse them yet), in / out
* interface blocks, RSLC_STAGE_GEOMETRY (SPIR-V and GLSL only), and * 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 * Without it
* none of that is accepted, nothing is compiled in for it, and every * 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 * destination is word for word what it was. RSLC_NO_DXBC omits the
@@ -94,7 +105,8 @@ enum rslc_base
RSLC_BASE_BOOL, RSLC_BASE_BOOL,
RSLC_BASE_INT, RSLC_BASE_INT,
RSLC_BASE_UINT, RSLC_BASE_UINT,
RSLC_BASE_FLOAT RSLC_BASE_FLOAT,
RSLC_BASE_DOUBLE /* RSLC_EXTENDED: a 64-bit scalar, never a constant; SPIR-V only */
}; };
#define RSLC_MAX_SAMPLERS 16 #define RSLC_MAX_SAMPLERS 16
@@ -290,6 +290,9 @@ struct rslc_var *rslc_b_io_block(struct rslc_builder *b, const struct rslc_b_qua
const char *type_name, const char *instance, const char *type_name, const char *instance,
const struct rslc_type *const *types, const char *const *names, const struct rslc_type *const *types, const char *const *names,
const unsigned *interps, const unsigned *mquals, uint32_t n, int32_t array_len); const unsigned *interps, const unsigned *mquals, uint32_t n, int32_t array_len);
/* length() of an array: its declared length, or for the unsized last
* member of a storage block what the buffer bound has (SPIR-V only). */
uint32_t rslc_b_length(struct rslc_builder *b, uint32_t e);
/* A layout on the stage's in or out alone: early_fragment_tests, a /* A layout on the stage's in or out alone: early_fragment_tests, a
* local size, a geometry primitive, invocations or max_vertices. */ * local size, a geometry primitive, invocations or max_vertices. */
bool rslc_b_layout(struct rslc_builder *b, const struct rslc_b_quals *q); bool rslc_b_layout(struct rslc_builder *b, const struct rslc_b_quals *q);
@@ -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 #define _DEFAULT_SOURCE 1
#endif #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
#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 #ifndef LIBRETRO_RDVD_STREAM_H
#define LIBRETRO_RDVD_STREAM_H #define LIBRETRO_RDVD_STREAM_H
#include <formats/rdvd_demux.h> #include <formats/rdvd_demux.h>
#include <formats/rdvd_vfs.h> #include <streams/rdvd_vfs.h>
RETRO_BEGIN_DECLS RETRO_BEGIN_DECLS
enum rdvd_stream_status enum rdvd_stream_status
{ {
+22
View File
@@ -187,6 +187,9 @@ struct libretro_vfs_implementation_file
#endif #endif
{ {
int64_t size; 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 mappos;
uint64_t mapsize; uint64_t mapsize;
#if defined(_WIN32) && !defined(_XBOX) && !defined(HAVE_MMAP) #if defined(_WIN32) && !defined(_XBOX) && !defined(HAVE_MMAP)
@@ -199,6 +202,14 @@ struct libretro_vfs_implementation_file
HANDLE fh; HANDLE fh;
/* CreateFileMapping handle backing `mapped`; NULL when unmapped */ /* CreateFileMapping handle backing `mapped`; NULL when unmapped */
HANDLE map_handle; 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) #if !defined(_XBOX) && !defined(HAVE_MMAP)
void *warm; /* mapped read-ahead thread, NULL until used */ void *warm; /* mapped read-ahead thread, NULL until used */
#endif #endif
@@ -247,6 +258,17 @@ struct libretro_vfs_implementation_file
retro_atomic_size_t append_end; retro_atomic_size_t append_end;
#ifdef VFS_HAVE_AT_HANDLE #ifdef VFS_HAVE_AT_HANDLE
retro_atomic_ptr_t 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
}; };
#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. */ * running or parked, or one finished and not yet retired. */
static bool task_threaded_busy(void) 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) return retro_atomic_load_acquire_int(&tasks_running_count)
> retro_atomic_load_acquire_int(&tasks_scheduled_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) || retro_atomic_load_acquire_ptr(&task_finished_stack)
|| task_retire.front; || task_retire.front;
} }
@@ -58,6 +58,8 @@ static uint8_t *data;
static uint8_t *frame; static uint8_t *frame;
static size_t frame_len; static size_t frame_len;
static uint64_t read_octets; 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) 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; 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 void install_vfs(bool lend)
{ {
static struct retro_vfs_interface iface; static struct retro_vfs_interface iface;
@@ -99,10 +112,16 @@ static void install_vfs(bool lend)
iface.remove = retro_vfs_file_remove_impl; iface.remove = retro_vfs_file_remove_impl;
iface.rename = retro_vfs_file_rename_impl; iface.rename = retro_vfs_file_rename_impl;
info.required_interface_version = FILESTREAM_REQUIRED_VFS_VERSION; info.required_interface_version = FILESTREAM_REQUIRED_VFS_VERSION;
if (positional)
{
iface.read_at = count_read_at;
info.required_interface_version = 6;
}
info.iface = &iface; info.iface = &iface;
filestream_vfs_init(&info); filestream_vfs_init(&info);
filestream_set_mapped_ptr_cb(lend ? lend_mapped_ptr : NULL); 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, static int extract_cb(const char *name, const char *valid_exts,
@@ -280,11 +299,20 @@ int main(void)
return 2; return 2;
fclose(f); 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, plain file", false);
check_read("read, lent", true); check_read("read, lent", true);
check_extract("extract, plain file", false); check_extract("extract, plain file", false);
check_extract("extract, lent", true); check_extract("extract, lent", true);
check_window(); 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); install_vfs(false);
if ( file_archive_get_file_crc32(ZST_PATH "#archive_zst_test.bin") if ( file_archive_get_file_crc32(ZST_PATH "#archive_zst_test.bin")
!= encoding_crc32(0, data, DATA_LEN)) != 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_TEST16 := vfs_positional_off32_test
TARGET_TEST17 := vfs_positional_pathreopen_test TARGET_TEST17 := vfs_positional_pathreopen_test
TARGET_TEST18 := vfs_positional_ntverify_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 := ../../.. LIBRETRO_COMM_DIR := ../../..
@@ -122,6 +127,11 @@ endif
ifeq ($(MMAP),1) ifeq ($(MMAP),1)
CFLAGS += -DHAVE_MMAP CFLAGS += -DHAVE_MMAP
endif 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 # 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 # interesting configuration - descriptor I/O with no file mapping - is
@@ -134,6 +144,14 @@ ifeq ($(DESCRIPTOR_IO),1)
CFLAGS += -DVFS_HAVE_DESCRIPTOR_IO=1 CFLAGS += -DVFS_HAVE_DESCRIPTOR_IO=1
endif 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 # 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 # 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 # 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_TEST5) $(TARGET_TEST6) $(TARGET_TEST8) $(TARGET_TEST9) \
$(TARGET_TEST10) $(TARGET_TEST11) $(TARGET_TEST12) $(TARGET_TEST13) \ $(TARGET_TEST10) $(TARGET_TEST11) $(TARGET_TEST12) $(TARGET_TEST13) \
$(TARGET_TEST14) $(TARGET_TEST15) $(TARGET_TEST16) $(TARGET_TEST17) \ $(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 %.o: %.c
$(CC) -c -o $@ $< $(CFLAGS) $(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) $(TARGET_TEST18): vfs_positional_required.o $(NTVERIFY_OBJS)
$(CC) -o $@ $^ $(LDFLAGS) -lpthread $(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) $(TARGET_TEST10): vfs_view_test.o $(VIEW_OBJS)
$(CC) -o $@ $^ $(LDFLAGS) $(CC) -o $@ $^ $(LDFLAGS)
@@ -326,7 +378,11 @@ clean:
rm -f $(TARGET) $(TARGET_TEST) $(TARGET_TEST2) $(TARGET_TEST3) \ rm -f $(TARGET) $(TARGET_TEST) $(TARGET_TEST2) $(TARGET_TEST3) \
$(TARGET_TEST4) $(TARGET_TEST5) $(TARGET_TEST6) \ $(TARGET_TEST4) $(TARGET_TEST5) $(TARGET_TEST6) \
$(TARGET_TEST7) $(TARGET_TEST8) $(TARGET_TEST9) $(TARGET_TEST10) \ $(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) \ $(OBJS) $(FAULT_FS_OBJ) $(RENAME_VFS_OBJ) \
$(RA_VFS_OBJ) $(HOOK_VFS_OBJ) rthreads_ra.o retro_eventcount_ra.o \ $(RA_VFS_OBJ) $(HOOK_VFS_OBJ) rthreads_ra.o retro_eventcount_ra.o \
$(NOMMAP_VFS_OBJ) vfs_positional_required.o $(OFF32_VFS_OBJ) \ $(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); 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, s = retro_vfs_file_open_impl(path, RETRO_VFS_FILE_ACCESS_READ,
RETRO_VFS_FILE_ACCESS_HINT_NONE); RETRO_VFS_FILE_ACCESS_HINT_NONE);
CHECK(s != NULL, "plain open failed"); CHECK(s != NULL, "plain open failed");
if (s) 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"); CHECK(s->fp != NULL, "plain open left stdio");
#endif
retro_vfs_file_close_impl(s); 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 /* Write modes take the descriptor path where the platform lists
* descriptor writes, and stay buffered where it does not. */ * descriptor writes, and stay buffered where it does not. */
s = retro_vfs_file_open_impl("bulk_select_w.bin", 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 * Unix host, which always has pread(), or on Windows NT, which always
* has a second handle, a -1 there fails instead - the build without * has a second handle, a -1 there fails instead - the build without
* HAVE_MMAP (vfs_positional_nommap_test) holds that as Android builds, * 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 * 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 <stdio.h>
#include <stdlib.h> #include <stdlib.h>
@@ -67,7 +69,9 @@ static void thread_join(test_thread_t t)
#endif #endif
#define FILE_SIZE (4u << 20) #define FILE_SIZE (4u << 20)
#ifndef THREADS
#define THREADS 4 #define THREADS 4
#endif
#define READS 20000 #define READS 20000
#define RECORDS 5000 #define RECORDS 5000
#define RECORD_LEN 64 #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) #if !defined(_WIN32)
#include <unistd.h> /* geteuid: root ignores mode bits */ #include <unistd.h> /* geteuid: root ignores mode bits */
#else
#include <windows.h> /* CreateFileA: whether the system holds to it */
#endif #endif
#define DIR_NAME "v5_meta_dir" #define DIR_NAME "v5_meta_dir"
@@ -102,6 +104,21 @@ static bool files_equal(const char *a, const char *b)
return same; 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) static void test_readonly(const char *dir)
{ {
char p[512]; char p[512];
@@ -120,6 +137,10 @@ static void test_readonly(const char *dir)
if (geteuid() == 0) if (geteuid() == 0)
SKIP("open-for-write-denied check (running as root, mode bits are not enforced)"); SKIP("open-for-write-denied check (running as root, mode bits are not enforced)");
else 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 #endif
{ {
RFILE *f = filestream_open(p, RETRO_VFS_FILE_ACCESS_WRITE, RETRO_VFS_FILE_ACCESS_HINT_NONE); 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++; descriptor_closes++;
return fail_descriptor ? -1 : result; 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 fclose injected_fclose
#define close injected_close #define close injected_close
#include "../../../vfs/vfs_implementation.c" #include "../../../vfs/vfs_implementation.c"
#undef fclose #undef fclose
#undef close #undef close
#ifdef _WIN32
#undef CloseHandle
#endif
static int RETRO_CALLCONV callback_close(struct retro_vfs_file_handle *file) static int RETRO_CALLCONV callback_close(struct retro_vfs_file_handle *file)
{ {
@@ -49,11 +64,20 @@ int main(void)
if (file) if (file)
{ {
CHECK(filestream_write(file, "data", 4) == 4); 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; before = stdio_closes;
fail_stdio = 1; fail_stdio = 1;
CHECK(filestream_close(file) == -1); CHECK(filestream_close(file) == -1);
CHECK(stdio_closes == before + 1); CHECK(stdio_closes == before + 1);
fail_stdio = 0; fail_stdio = 0;
#endif
} }
native = retro_vfs_file_open_impl("close_result.bin", RETRO_VFS_FILE_ACCESS_READ, 0); native = retro_vfs_file_open_impl("close_result.bin", RETRO_VFS_FILE_ACCESS_READ, 0);
CHECK(native != NULL); CHECK(native != NULL);
@@ -23,10 +23,19 @@ SOURCES := \
OBJS := $(call sample_objs,$(SOURCES:.c=.o)) 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. # it the zero-copy check has nothing to assert and only reports a skip.
CFLAGS += -Wall -pedantic -std=gnu99 -g1 -DHAVE_MMAP \ # Windows maps through vfs.h's _WIN32 backend and has no <sys/mman.h>.
-I$(LIBRETRO_COMM_DIR)/include 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 # The wrapper-allocation failure cases exist because that path is
# otherwise unreachable: calloc does not fail on demand, so the branch # 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 * either, or the loose-file probe cost that the design promises to
* preserve is gone. * 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) * Build: make (SANITIZER=address,undefined for a checked run)
* Run: ./vfs_hybrid_test * Run: ./vfs_hybrid_test
*/ */
@@ -170,6 +175,42 @@ static int fe_closedir(struct retro_vfs_dir_handle *d)
return 0; 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 /* A frontend that filled only its v1 members. Everything above is
* poisoned rather than left NULL, so a version-gating slip is a * poisoned rather than left NULL, so a version-gating slip is a
* reported failure and not a NULL check that happens to save us. */ * 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 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 struct retro_vfs_interface *iface_in_use;
static bool env_cb(unsigned cmd, void *data) static bool env_cb(unsigned cmd, void *data)
@@ -366,6 +432,85 @@ int main(void)
remove("hybrid_plain.bin"); 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) if (test_fails)
{ {
printf("== %d FAILURES ==\n", 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)/file/file_path_io.c \
$(LIBRETRO_COMM_DIR)/formats/audio_transfer.c \ $(LIBRETRO_COMM_DIR)/formats/audio_transfer.c \
$(LIBRETRO_COMM_DIR)/formats/mp3/rmp3.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/aac/raac.c \
$(LIBRETRO_COMM_DIR)/formats/opus/ropus.c \ $(LIBRETRO_COMM_DIR)/formats/opus/ropus.c \
$(LIBRETRO_COMM_DIR)/formats/vorbis/rvorbis.c \ $(LIBRETRO_COMM_DIR)/formats/vorbis/rvorbis.c \
@@ -56,6 +56,7 @@
#include <formats/audio.h> #include <formats/audio.h>
#include <formats/data_transfer.h> #include <formats/data_transfer.h>
#include <streams/data_transfer_file.h>
#include <formats/rwebm.h> #include <formats/rwebm.h>
#if defined(__linux__) || defined(__unix__) || defined(__APPLE__) #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.c \
$(LIBRETRO_COMM_DIR)/file/file_path_io.c \ $(LIBRETRO_COMM_DIR)/file/file_path_io.c \
$(LIBRETRO_COMM_DIR)/formats/audio_transfer.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/flac/rflac.c \
$(LIBRETRO_COMM_DIR)/formats/webm/rwebm.c \ $(LIBRETRO_COMM_DIR)/formats/webm/rwebm.c \
$(LIBRETRO_COMM_DIR)/memmap/memmap.c \ $(LIBRETRO_COMM_DIR)/memmap/memmap.c \
@@ -9,7 +9,10 @@
* the frames as delimited packets, all given up front; * the frames as delimited packets, all given up front;
* - demuxed input grown a packet at a time, each growth only after * - demuxed input grown a packet at a time, each growth only after
* the decoder has run dry - the progressive source the growth * 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 * 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 * went in: every sample, and not one frame more or less. The fixture
@@ -27,6 +30,7 @@
#include <math.h> #include <math.h>
#include <formats/audio.h> #include <formats/audio.h>
#include <formats/rflac.h>
#include <formats/rwebm.h> #include <formats/rwebm.h>
#include <streams/file_stream.h> #include <streams/file_stream.h>
@@ -263,6 +267,64 @@ static void lane_demuxed(const struct demuxed *d, int grow)
free(pcm); 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) int main(void)
{ {
struct demuxed d; struct demuxed d;
@@ -273,6 +335,7 @@ int main(void)
lane_buffer("native .flac buffer", "fixtures/tone.flac"); lane_buffer("native .flac buffer", "fixtures/tone.flac");
lane_buffer("Matroska buffer", "fixtures/tone.mka"); lane_buffer("Matroska buffer", "fixtures/tone.mka");
lane_windowed("fixtures/tone.mka", 4096, 2048); lane_windowed("fixtures/tone.mka", 4096, 2048);
lane_seek("fixtures/tone_seektable.flac");
mka = load("fixtures/tone.mka", &mka_len); mka = load("fixtures/tone.mka", &mka_len);
demux_mka((const uint8_t*)mka, mka_len, &d); demux_mka((const uint8_t*)mka, mka_len, &d);
@@ -1,12 +1,13 @@
#!/usr/bin/env python3 #!/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 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 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 low-order noise so the predictor has residual to code. The test
regenerates the same PCM from the same formula and compares every regenerates the same PCM from the same formula and compares every
sample, so keep the two in step. Needs ffmpeg; the committed fixtures sample, so keep the two in step. Needs ffmpeg, and flac and metaflac
are what CI uses.""" for the seek table; the committed fixtures are what CI uses."""
import math import math
import os import os
@@ -34,4 +35,13 @@ for out in ("tone.flac", "tone.mka"):
"-f", "s16le", "-ar", str(RATE), "-ac", "2", "-f", "s16le", "-ar", str(RATE), "-ac", "2",
"-i", raw, "-c:a", "flac", "-i", raw, "-c:a", "flac",
os.path.join(fix, out)]) 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) os.remove(raw)
+11 -2
View File
@@ -3,6 +3,7 @@ include ../../sample_objdir.mk
TARGET := cdfs_dir_record_test$(EXE) TARGET := cdfs_dir_record_test$(EXE)
TARGET_TEST2 := cdfs_pure_api_test$(EXE) TARGET_TEST2 := cdfs_pure_api_test$(EXE)
TARGET_TEST3 := cdfs_io_error_test$(EXE) TARGET_TEST3 := cdfs_io_error_test$(EXE)
TARGET_TEST4 := cdfs_file_test$(EXE)
.DEFAULT_GOAL := all .DEFAULT_GOAL := all
LIBRETRO_COMM_DIR := ../../.. 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))) 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) ifeq ($(OS),Windows_NT)
LDLIBS += -lwinmm LDLIBS += -lwinmm
endif endif
@@ -64,7 +69,10 @@ $(SAMPLE_OBJDIR)/cdfs_io_error_test.o: cdfs_io_error_test.c $(LIBRETRO_COMM_DIR)
$(TARGET_TEST3): $(TEST3_OBJS) $(TARGET_TEST3): $(TEST3_OBJS)
$(CC) -o $@ $^ $(LDFLAGS) $(LDLIBS) $(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 %.o: %.c
$(CC) -c -o $@ $< $(CFLAGS) $(CC) -c -o $@ $< $(CFLAGS)
@@ -81,6 +89,7 @@ $(TARGET_TEST2): $(TEST2_OBJS)
clean: clean:
rm -rf $(SAMPLE_OBJDIR) 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 .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)); memset(&layout, 0, sizeof(layout));
layout.stream = track.stream; layout.stream = track.stream;
sparse_size = UINT64_C(4194307) * sizes[mode]; 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]); CHECK(layout.stream_sector_size == sizes[mode]);
sparse_size = UINT64_C(4194304) * sizes[mode] + 12345; sparse_size = UINT64_C(4194304) * sizes[mode] + 12345;
} }
@@ -13,7 +13,7 @@ COMMON_SOURCES := \
$(LIBRETRO_COMM_DIR)/encodings/encoding_utf.c \ $(LIBRETRO_COMM_DIR)/encodings/encoding_utf.c \
$(LIBRETRO_COMM_DIR)/file/file_path.c \ $(LIBRETRO_COMM_DIR)/file/file_path.c \
$(LIBRETRO_COMM_DIR)/file/file_path_io.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)/memmap/memmap.c \
$(LIBRETRO_COMM_DIR)/streams/file_stream.c \ $(LIBRETRO_COMM_DIR)/streams/file_stream.c \
$(LIBRETRO_COMM_DIR)/string/rstrtod.c \ $(LIBRETRO_COMM_DIR)/string/rstrtod.c \
@@ -63,6 +63,7 @@
#include <boolean.h> #include <boolean.h>
#include <formats/data_transfer.h> #include <formats/data_transfer.h>
#include <streams/data_transfer_file.h>
static int test_fails; static int test_fails;
@@ -49,6 +49,7 @@
#include <string.h> #include <string.h>
#include <stdint.h> #include <stdint.h>
#include <formats/data_transfer.h> #include <formats/data_transfer.h>
#include <streams/data_transfer_file.h>
#include <memmap.h> #include <memmap.h>
#ifdef DT_COUNT_IO #ifdef DT_COUNT_IO
#include <streams/file_stream.h> #include <streams/file_stream.h>
@@ -66,6 +66,7 @@
#include <string.h> #include <string.h>
#include <stdint.h> #include <stdint.h>
#include <formats/data_transfer.h> #include <formats/data_transfer.h>
#include <streams/data_transfer_file.h>
#include <memmap.h> #include <memmap.h>
#ifdef DT_COUNT_IO #ifdef DT_COUNT_IO
#include <streams/file_stream.h> #include <streams/file_stream.h>
@@ -1046,6 +1047,76 @@ static int callback_window_check(void)
return 0; 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) int main(void)
{ {
const char *path = "dtwin.bin"; const char *path = "dtwin.bin";
@@ -1154,6 +1225,7 @@ int main(void)
#endif #endif
bad |= callback_window_check(); bad |= callback_window_check();
bad |= far_feed_check();
bad |= sparse_skip_check(); bad |= sparse_skip_check();
bad |= pin_check(); bad |= pin_check();
bad |= feed_budget_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 $(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) $(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_HEADERS := ../../../include/streams/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_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 $(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 ../../../formats/dvd/rdvd_vfs.c ../../../formats/dvd/rdvd_source.c ../../../formats/dvd/rdvd_ifo.c ../../../queues/retro_spsc.c -o $@ $(LDFLAGS) $(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)),) ifneq ($(findstring mingw,$(shell $(CC) -dumpmachine)),)
VFS_REAL_LIBS := VFS_REAL_LIBS :=
@@ -46,8 +46,8 @@ else
VFS_REAL_LIBS := -lpthread -lrt VFS_REAL_LIBS := -lpthread -lrt
endif 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 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) $(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 ../../../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 $(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 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 $(TARGET_TEST7): rdvd_ac3_test.c $(AC3_SOURCES) ../../../include/formats/rdvd_ac3.h
@@ -9,8 +9,8 @@
#else #else
#include <unistd.h> #include <unistd.h>
#endif #endif
#include <formats/rdvd_vfs.h> #include <streams/rdvd_vfs.h>
#include <formats/rdvd_stream.h> #include <streams/rdvd_stream.h>
#include <rthreads/rthreads.h> #include <rthreads/rthreads.h>
#include <retro_atomic.h> #include <retro_atomic.h>
#include <features/features_cpu.h> #include <features/features_cpu.h>
@@ -1,8 +1,8 @@
/* Copyright (C) 2026 The RetroArch team. SPDX-License-Identifier: MIT */ /* Copyright (C) 2026 The RetroArch team. SPDX-License-Identifier: MIT */
#include <stdio.h> #include <stdio.h>
#include <string.h> #include <string.h>
#include <formats/rdvd_vfs.h> #include <streams/rdvd_vfs.h>
#include <formats/rdvd_stream.h> #include <streams/rdvd_stream.h>
struct RFILE struct RFILE
{ {
uint64_t size, position; uint64_t size, position;
@@ -18,9 +18,9 @@ SOURCES := image_loader_test.c \
$(LIBRETRO_COMM_DIR)/features/features_cpu.c \ $(LIBRETRO_COMM_DIR)/features/features_cpu.c \
$(LIBRETRO_COMM_DIR)/file/file_path.c \ $(LIBRETRO_COMM_DIR)/file/file_path.c \
$(LIBRETRO_COMM_DIR)/file/file_path_io.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/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_scale.c \
$(LIBRETRO_COMM_DIR)/formats/image_texture_set.c \ $(LIBRETRO_COMM_DIR)/formats/image_texture_set.c \
$(LIBRETRO_COMM_DIR)/formats/image_transfer.c \ $(LIBRETRO_COMM_DIR)/formats/image_transfer.c \
@@ -27,6 +27,7 @@
#include <boolean.h> #include <boolean.h>
#include <encodings/crc32.h> #include <encodings/crc32.h>
#include <formats/image.h> #include <formats/image.h>
#include <streams/image_texture_file.h>
#define W 24 #define W 24
#define H 16 #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 /* rvorbis' codebook size bound: libvorbis' limit, so every table
* sized from an accepted codebook stays under 2^24 entries, and a real * 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> #include <stdio.h>
@@ -62,10 +63,63 @@ static void test_fixture_decodes(void)
rvorbis_close(v); 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) int main(void)
{ {
test_bounds(); test_bounds();
test_fixture_decodes(); test_fixture_decodes();
test_sparse_unused_lookup();
printf("rvorbis_codebook: %s (%d failures)\n", printf("rvorbis_codebook: %s (%d failures)\n",
failures ? "FAIL" : "PASS", failures); failures ? "FAIL" : "PASS", failures);
return failures ? 1 : 0; return failures ? 1 : 0;
@@ -30,6 +30,14 @@
* simulate with a small buffer and a size_t value that would * simulate with a small buffer and a size_t value that would
* pre-patch have truncated; post-patch we get an early * pre-patch have truncated; post-patch we get an early
* IMAGE_PROCESS_ERROR without even calling the decoder. * 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> #include <stdio.h>
@@ -200,6 +208,34 @@ static void test_happy_path_rgb(void)
rtga_free(rtga); 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) static void test_oversized_size(void)
{ {
/* A size > INT_MAX should be rejected before the (int)size /* A size > INT_MAX should be rejected before the (int)size
@@ -228,12 +264,234 @@ static void test_oversized_size(void)
rtga_free(rtga); 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) int main(void)
{ {
test_malicious_palette_bits(); test_malicious_palette_bits();
test_empty_indexed_palette(); test_empty_indexed_palette();
test_happy_path_rgb(); test_happy_path_rgb();
test_oversized_size(); test_oversized_size();
test_cut_palette();
test_decode_rows();
if (failures) if (failures)
{ {
@@ -533,6 +533,64 @@ static struct rslc_builder *cs_build(const struct rslc_opts *opts)
return b; return b;
} }
/* ---- compute: a sum over a storage block's unsized array, as long as
* the buffer bound says ---- */
static const char agg_glsl[] =
"#version 450\n"
"layout(local_size_x = 64, local_size_y = 1, local_size_z = 1) in;\n"
"layout(set = 0, binding = 0, std430) readonly buffer ssbo0 { uint src[]; };\n"
"layout(set = 0, binding = 1, std430) writeonly buffer ssbo1 { uint result; };\n"
"void main()\n"
"{\n"
" if (gl_GlobalInvocationID.x < src.length())\n"
" {\n"
" atomicAdd(result, src[gl_GlobalInvocationID.x]);\n"
" }\n"
"}\n";
static struct rslc_builder *agg_build(const struct rslc_opts *opts)
{
struct rslc_builder *b = rslc_b_begin(opts, RSLC_STAGE_COMPUTE);
const struct rslc_type *u1 = rslc_b_vec(b, RSLC_BASE_UINT, 1);
const struct rslc_type *t[1];
const char *src_n[1] = { "src" };
const char *res_n[1] = { "result" };
struct rslc_var *src_m[1], *res_m[1], *gid;
struct rslc_b_quals q;
uint32_t a[2], x, body;
rslc_b_quals_init(&q);
q.storage = RSLC_B_ST_IN;
q.local_size[0] = 64;
q.local_size[1] = 1;
q.local_size[2] = 1;
rslc_b_layout(b, &q);
rslc_b_quals_init(&q);
q.storage = RSLC_B_ST_BUFFER;
q.access = RSLC_B_READONLY;
q.set = 0;
q.binding = 0;
t[0] = rslc_b_array(b, u1, 0);
rslc_b_block_q(b, &q, "ssbo0", NULL, t, src_n, 1, src_m);
q.access = RSLC_B_WRITEONLY;
q.binding = 1;
t[0] = u1;
rslc_b_block_q(b, &q, "ssbo1", NULL, t, res_n, 1, res_m);
rslc_b_func_begin(b, rslc_b_func(b, "main", rslc_b_void(b), 0, NULL, NULL, NULL));
gid = rslc_b_builtin_var(b, "gl_GlobalInvocationID");
x = rslc_b_swizzle(b, rslc_b_ref(b, gid), "x");
rslc_b_begin_block(b);
a[0] = rslc_b_ref(b, res_m[0]);
a[1] = rslc_b_index(b, rslc_b_ref(b, src_m[0]), rslc_b_swizzle(b, rslc_b_ref(b, gid), "x"));
E(rslc_b_builtin(b, "atomicAdd", a, 2));
body = rslc_b_end_block(b);
S(rslc_b_if(b, rslc_b_binary(b, RSLC_B_LT, x, rslc_b_length(b, rslc_b_ref(b, src_m[0]))), body, 0));
rslc_b_func_end(b);
return b;
}
static char err[256]; static char err[256];
static void opts_for(struct rslc_opts *o, enum rslc_dst dst, unsigned version) static void opts_for(struct rslc_opts *o, enum rslc_dst dst, unsigned version)
@@ -667,7 +725,8 @@ int main(void)
{ "compute glsl", cs_glsl, cs_build, RSLC_STAGE_COMPUTE, RSLC_DST_GLSL, 450 }, { "compute glsl", cs_glsl, cs_build, RSLC_STAGE_COMPUTE, RSLC_DST_GLSL, 450 },
{ "compute hlsl", cs_glsl, cs_build, RSLC_STAGE_COMPUTE, RSLC_DST_HLSL, 50 }, { "compute hlsl", cs_glsl, cs_build, RSLC_STAGE_COMPUTE, RSLC_DST_HLSL, 50 },
{ "compute dxbc", cs_glsl, cs_build, RSLC_STAGE_COMPUTE, RSLC_DST_DXBC, 50 }, { "compute dxbc", cs_glsl, cs_build, RSLC_STAGE_COMPUTE, RSLC_DST_DXBC, 50 },
{ "compute msl", cs_glsl, cs_build, RSLC_STAGE_COMPUTE, RSLC_DST_MSL, 20 } { "compute msl", cs_glsl, cs_build, RSLC_STAGE_COMPUTE, RSLC_DST_MSL, 20 },
{ "aggregate spirv", agg_glsl, agg_build, RSLC_STAGE_COMPUTE, RSLC_DST_SPIRV, 0 }
}; };
unsigned i; unsigned i;
for (i = 0; i < sizeof(cases) / sizeof(cases[0]); i++) for (i = 0; i < sizeof(cases) / sizeof(cases[0]); i++)
Loaded 100 of 171 files, more files were not shown because too many files have changed in this diff. Show more