507 Commits
Author SHA1 Message Date
davey a14c02e2bd libretro: enable the Vulkan device extensions the frontend requires
The context negotiation interface passes the device extensions the
frontend needs, but they were never enabled. Frontends such as RetroArch
pass the extensions an OpenXR runtime needs to share the device, and
without them SteamVR crashes in xrCreateSession.
2026-11-04 18:34:34 +01:00
Flyinghead 802874e79d arm64 linux: get page size at runtime. Upload symbols to sentry 2026-11-02 14:59:56 +01:00
raph 1fac3d3fef libretro build fix 2026-11-01 18:38:49 +01:00
James 800d0ec2d8 Added arm64-linux build to CI (#2596)
* Added linux-arm64 build (aarch64) to CI
* Run unit tests on linux-arm64 build
2026-11-01 18:04:39 +01:00
Alex Stapleton 6a6038f910 picotcp: make room for more than ~1,000 timers (#2595)
The timer heap is capped at MAX_BLOCK_COUNT blocks of 1600 bytes, about
1,050 timers. Each TCP socket keeps several (keepalive, stats,
retransmission, linger) and closed sockets linger for 30 seconds, so a few
dozen connections a second fill it. When pico_timer_add() fails,
socket_del() frees the socket at once instead of deferring it, while
pico_tcp_input() is still using it: AddressSanitizer reports a
heap-use-after-free in pico_tcp_input(), and release builds crash.

Raise the cap to 1024 blocks (~67,000 timers). There is a single heap, and
blocks are only allocated as it grows, so this costs 8 KB of pointers.
[ci skip]
2026-11-01 18:04:12 +01:00
Alex Stapleton f86e4c6ea4 picotcp: don't connect a socket whose quad is already in use (#2594)
pico_socket_connect() ignored the result of pico_socket_alter_state(),
which fails when a socket with the same local/remote address and port is
already in the socket tree. The new socket then carried on untracked, and
deleting it later found and unlinked the other socket instead, which was
then freed while still in use: a double free or heap corruption.

picoppp hits this when it forwards an inbound connection whose client
reused a source port while picotcp still holds the previous connection
(Linux reuses loopback source ports at once). Fail the connect with
EADDRINUSE, and have picoppp retry with a port picotcp picks.

Found by forwarding a few hundred connections a second to the Broadband
Adapter; the crash went away with this.
[ci skip]
2026-11-01 18:03:52 +01:00
Alex Stapleton 7b1df94c8e picoppp: don't terminate when shutting down a disconnected host socket (#2593)
TcpSocket called the throwing overloads of asio's socket.shutdown() and
socket.close() when the guest closed its side of a connection. If the
host peer had already disconnected (or reset the connection),
shutdown() throws std::system_error ("Transport endpoint is not
connected"), nothing catches it, and Flycast terminates.

Use the error_code overloads: a peer that is already gone just means the
socket is already closed.

Found forwarding a few hundred connections a second to a KallistiOS
program, where clients and guest close connections in either order:
Flycast terminated within about a thousand connections.
[ci skip]
2026-11-01 18:03:33 +01:00
Alex Stapleton 973e569a7d picotcp: fix double free racing pico_enqueue() with the pico thread (#2592)
Since c52e6cc, the emulation thread calls pico_stack_recv() directly,
which enqueues on the device's protected q_in while the pico thread
dequeues it. pico_enqueue() unlocked the queue and then read q->size for
its return value. If the pico thread dequeued and processed the frame in
between, the size could read 0, and pico_stack_recv() took that as a
failed enqueue and freed a frame the pico thread had already freed.

AddressSanitizer: attempting double-free in pico_stack_recv(), called
from qemu_send_packet() on the emulation thread; freed by
pico_ethernet_receive() on the pico thread. It took a few dozen
forwarded TCP connections to crash a release build.

Read the size under the lock. Also create the queue mutex in
pico_queue_protect(): creating it lazily on first lock can give the two
threads different mutexes.
[ci skip]
2026-11-01 18:03:11 +01:00
flyinghead 13cfe43858 Crowdin Translations
[ci skip]
2026-11-01 18:02:41 +01:00
Flyinghead e778e529be save to temp file then rename over previous one when possible
[ci skip]
2026-11-01 18:00:35 +01:00
Flyinghead 2c83abcce4 libretro: add libjuice to combined lib for switch 2026-10-28 14:41:20 +01:00
Flyinghead 07535d22e8 ci: enable macOS build 2026-10-28 14:39:11 +01:00
Flyinghead 98ceceed1e aica: decrease the aica DMA xfer rate from 25 MB/s to 16 MiB/s
Fixes NCAA College Football 2K2, NBA 2K and NFL 2K right music channel
getting louder.
Issue #2525
2026-10-28 14:37:54 +01:00
Flyinghead 23d212f4f0 ci: disable macOS build 2026-10-27 11:26:55 +01:00
Flyinghead 917a7552f9 disable openmp on macos 2026-10-26 14:20:23 +01:00
Flyinghead 697d15a133 naomi: drop support for .lst files 2026-10-26 13:08:42 +01:00
Flyinghead 34e1211871 mem: return non-null value when reading ext. device (area 5)
Fixes NHL 2K2 freeze when pressing DPad right on Credits menu.
Issue #2535
2026-10-26 12:38:16 +01:00
Flyinghead cd89ae96ab aica: decrease the aica DMA xfer rate from 50 to 25 MiB/s
Fixes NCAA College Football 2K2 right music channel getting louder.
Issue #2525
Small ADPCM decoding optimization
2026-10-26 12:29:50 +01:00
scribam 01db8a203e ci: use openbsd 8.0 (#2587)
[ci skip]
2026-10-26 10:27:20 +01:00
Joe Mattiello 6d49796a15 flashrom: write the flash and nvmem saves atomically (#2579)
WritableChip::Save() wrote the image in place. A crash or a kill while
it is being written leaves a truncated file: the Dreamcast then boots
into the date and time setup with its settings gone, and an arcade
board loses its nvmem (service settings, high scores, unlocked content).

Write to a temp file next to the real one, flush it, then rename it
over the real path, so the file on disk is always either the previous
image or the complete new one. On Windows, rename() refuses to replace
an existing file, so flycast::rename() (which had no callers) now uses
MoveFileEx with MOVEFILE_REPLACE_EXISTING, matching POSIX.

Testing: compiles on macOS arm64; the Windows path is not built here.

Co-authored-by: Claude Fable 5.1 <[email protected]>
[ci skip]
2026-10-26 10:26:57 +01:00
Joe Mattiello 907d15155e rend: NEON for the 0888 and 8888 framebuffer write paths on arm64 (#2557)
0888 KRGB and 8888 ARGB, still went through the scalar per-pixel writer.
One packed pixel is exactly one VRAM word, so they fit the same 8-word
interleaved store as the 16-bit path, 8 pixels per iteration. The store
is factored out of storeFramebuffer16Pixels() so both use it.

888 (24-bit packed) stays scalar: its pixels straddle VRAM words, so the
store pattern only repeats every 4 pixels / 3 words.

The address walk is the same as writeFramebufferLW(), so the output is
identical to the scalar path, including when the padding isn't a
multiple of 4. A harness writing 7,056 frames through both paths (both
channel orders, modes 4 to 6, 6 frame sizes, 7 destination addresses
including bank and VRAM edges, 7 line strides, 4 clip windows) gives
byte-identical VRAM. The same algorithm measured about 2x faster on a
640x480 8888 frame (0.213 ms to 0.105 ms) on Apple Silicon.

tests/src/rend/FramebufferWriteTest.cpp checks both 32-bit modes against
an independent reference (one pvr_write32p per pixel, the scalar walk)
over 3,600 cases: widths around the 8-pixel vector width, padding
including values that aren't a multiple of 4, destinations at the 32-bit
bank boundary and the end of VRAM, and clip windows, for both channel
orders. All of VRAM is compared, so a store past the frame fails too. On
arm64 it exercises the NEON path; elsewhere the scalar one. It fails when
the padding computation is deliberately broken.

Other architectures are unchanged.

Co-authored-by: Claude Opus 5.5 <[email protected]>
[ci skip]
2026-10-26 10:26:27 +01:00
Givo e2f1484571 changed achievement draw shortcut to return false rather than true 2026-10-24 17:24:35 +02:00
Will fe58ee8c20 add the ability to hide achievement popups 2026-10-24 17:24:35 +02:00
Joe Mattiello 7c22066aa3 tests: build and link the unit test binary on macOS (#2575)
ENABLE_CTEST=ON never produced a test runner on macOS. The app's main()
(shell/apple/emulator-osx/SDLApplicationDelegate.mm) was still compiled
in, so the binary launched the emulator UI instead of gtest_main, and the
link needed libcurl, which HttpTest uses directly while the core only
links curl where it is the HTTP client (not on Apple).

- Leave SDLApplicationDelegate.mm, osx-main.mm and the Syphon video
  routing out of the target when BUILD_TESTING is set, like linux-dist
  main.cpp and winmain.cpp already are.
- Stub the os_* / darw_printf / hostfs / i18n glue those files provided,
  in tests/src/test_stubs.cpp, for macOS.
- Link CURL::libcurl into the test target on Apple.

With this the suite builds with both the Xcode and the Ninja generators
and runs on Apple Silicon: 144 tests, 141 pass. The three that fail there
(AicaArmTest.LogicOpsTest, SDLControllerMappingTest.split_axis and
.reverse_axis) and an abort in ~maple_sega_vmu at process exit are
pre-existing and are not changed here. The CI workflow still runs the
tests only on Linux.

Co-authored-by: Claude Fable 5.1 <[email protected]>
[ci skip]
2026-10-24 17:24:11 +02:00
Flyinghead 3a498b1cb5 initialize all scheduler ids to -1
[ci skip]
2026-10-24 17:18:03 +02:00
Flyinghead c52e6ccf56 bba: move frame queueing to net services. Call service to rx frame
New Service::sendEthFrame function to send frame to bba
Let net services implement queuing and synchronization
capcom: don't interfere when not in use
2026-10-22 16:32:34 +02:00
Flyinghead de7597f0bc picotcp: fix race condition in enqueue/dequeue 2026-10-22 15:01:18 +02:00
Flyinghead 5f35ec7d72 Merge remote-tracking branch 'origin/master' into dev 2026-10-22 14:13:58 +02:00
Flyinghead fa9d39a355 bba: skip deserialization if device doesn't exist
regression from 5543a95c4f
2026-10-22 12:36:23 +02:00
Flyinghead db065c5700 audio: put the RingBuffer cursors on separate cache lines 2026-10-21 18:24:07 +02:00
Flyinghead 620699865b Merge remote-tracking branch 'origin/master' into dev 2026-10-21 17:47:06 +02:00
Flyinghead a9bd1c74fd empty commit 2026-10-21 17:09:21 +02:00
Flyinghead 5543a95c4f bba: start polling only when needed. Increase poll interval to 1 ms 2026-10-21 16:54:14 +02:00
Flyinghead 78d5d09c1e ci: fix sentry-cli on android builds 2026-10-21 15:59:34 +02:00
Joseph MattielloandClaude Fable 5.1 31697241b7 sdl: initialize the axis direction in SDLControllerMappingParser
SDL_GameControllerButtonBind2 is a struct over a union whose first member
is `int button`. `bind{}` zero-initializes that member only, so
value.axis.direction holds whatever was on the stack. getBindForAxis()
assigns it only when the mapping has a '+' or '-' prefix or a '~'
suffix; for a plain "lefttrigger:a4" it is returned as read, and the
full-axis branch at the end tests it too. At -O2 on macOS/arm64 a
lefttrigger lookup came back with direction 1 instead of 0 and
"-lefty:-a1" with 1 instead of -1; SDLControllerMappingTest.split_axis
and .reverse_axis fail there. The value just happened to be 0 on Linux.

Set axis and direction to 0 before parsing the axis.

Co-Authored-By: Claude Fable 5.1 <[email protected]>
2026-10-21 12:09:24 +02:00
Joe Mattiello e9f4020c56 maplelink: keep the link registry alive through static destruction (#2577)
MapleDevices[][] in maple_if.cpp holds shared_ptr<maple_device>s with
static storage, so the VMUs are destroyed when the process exits.
~maple_sega_vmu() clears the LCD on any attached maple link, which goes
through MapleLink::GetMapleLink() and locks MapleLink::Mutex, another
object with static storage, in another translation unit, that may
already be gone. With libc++ locking a destroyed mutex throws
("mutex lock failed: Invalid argument") and the process aborts; the
unit test binary exits with SIGABRT on macOS after every test passed.
On glibc the same lock happens to succeed, which is why Linux never
showed it. The Links table is read in the same state.

Give both to function-local statics that are allocated once and never
destroyed, so they outlive everything that can still reach them.

Co-authored-by: Claude Fable 5.1 <[email protected]>
[ci skip]
2026-10-21 12:08:48 +02:00
Joe MattielloandClaude Fable 5.1 5eb52090ad arm7: keep the V flag across logical ops in the arm64 recompiler (#2576)
On ARMv7, AND, EOR, ORR, BIC, MOV, MVN, TST and TEQ with the S bit set
leave V unchanged. The AArch64 instructions the recompiler uses for them
(ANDS, BICS, TST) clear V, and storeFlags() wrote the host NZCV back as
is, so every flag-setting logical op cleared the guest's V. A signed
conditional branch (BVS, BGE, BLT, BGT, BLE) after such an op then went
the wrong way. The x64 recompiler already preserves V for these ops
(saveFlags(save_v_flag)); the interpreter does too.
[ci skip]

TST and TEQ had a save/restore under STRICT_MODE with a note that nobody
probably relies on it. That path was never built, and it did not cover
ANDS, EORS, ORRS, BICS, MOVS or MVNS.

Restore V after the op from the PSR that loadFlags() loaded before it,
in the same place the shifter carry is merged, for every logical op that
sets flags. Three instructions, only on those ops.

AicaArmTest.LogicOpsTest asserted exactly this (teq with C,V set must
keep V) and failed on arm64; it passes now, with the rest of the suite.

Co-authored-by: Claude Fable 5.1 <[email protected]>
2026-10-21 12:07:57 +02:00
jutler 128e6ce29c addrspace: mask the shift count taken from the tagged pointer (#2574)
readConst, writeConst, readt and writet shift the 32-bit guest address by
the whole tagged memInfo_ptr value (pointer | shift). Shifting a u32 by 32
or more is undefined, and GCC 16 targeting 32-bit ARM uses that: once the
pointer is known to be non-null (iirf > 31) it drops the offset, so
read32/write16 compile to a plain load/store at the region base. Every
memory access then hits offset 0, the BIOS trampoline copied to 8C0000E0
is lost and the console never boots (black screen, no sound).

Mask the shift with HANDLER_MAX, which is what x86 does in hardware and
what the code intends. No change in behaviour where the old code worked.

Co-authored-by: hitenmandalia <[email protected]>
[ci skip]
2026-10-21 12:07:14 +02:00
Rich Stokes 38a3d83c77 bba: hand Ethernet frames over on the threads that own them (#2569)
Received frames were passed to the RTL8139 from the network thread,
raising the ASIC interrupt there and racing SIIDRebuild() on the SH-4
thread. Queue them and deliver them from an SH-4 scheduler callback.

Sent frames were pushed into picoTCP from the SH-4 thread, but picoTCP
is built without PICO_SUPPORT_MUTEX so its device queue is unlocked.
Queue them and feed them to picoTCP from the pico thread before each
stack tick.
[ci skip]
2026-10-21 12:05:47 +02:00
Joe Mattiello 19ed307f74 rend: rotate sorted triangle indices for flat shading instead of copying colors (#2553)
Vulkan without VK_EXT_provoking_vertex, DX9 and DX11 take flat-shaded
colors from the first vertex, while on Dreamcast it's the last. For the
depth-sorted triangle list, setFirstProvokingVertex() copied each
triangle's last vertex colors onto its first vertex. But sortTriangles()
builds these triangles from strips, so neighbors share vertices:
tri0=(P0,P1,P2), tri1=(P2,P1,P3). P2 is tri0's source and tri1's
destination, and the list is processed in depth order, so one
triangle's flat color can overwrite another's before it's read. In
strips of three or more triangles, consecutive triangles even share
their first vertex. Flat-shaded translucent polygons then pick up a
neighbor's color, e.g. the player shadow decals in Tony Hawk's Pro
Skater 2 and Mat Hoffman's Pro BMX turn uniformly dark.

Each sorted triangle has its own three slots in idx, so rotate them
(a,b,c) -> (c,a,b) instead. The winding is unchanged and the provoking
vertex comes first, without touching shared vertices. Strips keep the
color copy, which is safe there.

Adds SorterTest, which fails on the old code and passes with this change.

Co-authored-by: Claude Opus 5.5 <[email protected]>
[ci skip]
2026-10-21 12:04:35 +02:00
Joe Mattiello d207cd9198 addrspace: clear the address map when its backing memory is freed (#2546)
When nvmem is disabled, termMappings() frees main RAM, VRAM, ARAM and
the Elan RAM but leaves memInfo_ptr unchanged. Each entry still holds
the old host base, so any guest access before the next remap returns a
pointer into freed memory.

Zero the table, as init() does. A null entry routes to handler 0,
readMemNotMapped/writeMemNotMapped, which only log.

Co-authored-by: Claude Opus 5.5 <[email protected]>
[ci skip]
2026-10-21 11:19:28 +02:00
Flyinghead 1d356d5e55 android: fix swappyvk crash if activity is null
[ci skip]
2026-10-21 11:06:06 +02:00
scribam 32c2304459 ci: maintenance updates
- Bump action versions
- Improve ccache support
- Reformat files
- Replace chkfung/android-version-actions and mathieu-bour/setup-sentry-cli from android workflow
- Update MoltenVK to use the latest official tag
- Update BSD operating systems
2026-10-21 10:59:14 +02:00
davey 34ad8c8262 libretro: avoid exporting symbols on linux, android and the bsds
Export only retro_* from the core, as the osx build does with
libretro.osx.def. Symbols the core exports bind to same-named ones
the frontend already loaded: RetroArch builds with their builtin
glslang made the Vulkan renderer crash in GlslangToSpv on its first
shader compile, and with a system glslang loaded (ffmpeg ->
libplacebo -> shaderc) 33 glslang entry points, zstd, zlib and
stb_image still went to those copies.
2026-10-20 16:33:04 +02:00
Chris Dixon abfc411392 libretro: report a fixed savestate size for each system (#2561)
A frontend may size its savestate buffer once, as libretro.h allows,
but a state grows and shrinks with the devices plugged in and the
frames being drawn, and states then outgrow the buffer. Report the
system's memory regions plus the allowance GGPO makes for everything
else, which is constant for a game.

System SP gets NAOMI's allowance, plus the game's media if it has one:
the media is loaded into memory-backed RAM, so a state holds all of it.
[ci skip]
2026-10-20 16:32:32 +02:00
Flyinghead 9981e84912 breakpad: dwarf5 reader fix 2026-10-20 15:51:14 +02:00
Flyinghead b5acd1b533 wccf: card reader fix. Player names and images for 331j and 400j
Fix card reader error on reinsertion with 116, 212e and 322e.
Add support for player names and images for 331j and 400j
2026-10-20 15:50:07 +02:00
github-actions[bot] 6bb0fc3e18 Fetch translations & Recreate libretro_core_options_intl.h 2026-10-17 00:25:56 +00:00
Joseph MattielloandClaude Opus 5.5 08afcbe6b9 rend: clamp xBRZ texture upscale factor to 2..6
xbrz::scale() only implements factors 2 to 6. Any other value hits an
assert that's compiled out in release builds, and scale() returns
without writing the destination buffer. The texture is then uploaded
from uninitialized memory at the requested size.

rend.TextureUpscale2 can be set to 8 or 16 through emu.cfg or Lua.
Clamp the factor once and use it for the buffer size, the scale call
and the upscaled dimensions so they always agree.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-16 20:14:45 +02:00
Joe Mattiello e9dd6187cb cdipsr: make temp_value a local (#2549)
temp_value was a file-scope global shared by every CDI parse. The boxart
scraper opens discs on its own thread, so it can parse a CDI while a
game is loading and corrupt the other parse's reads.

Use a local in each function. It's a uint32_t initialized to 0 because
every read into it is 4 bytes; a local unsigned long would leave the top
half uninitialized on LP64.

Co-authored-by: Claude Opus 5.5 <[email protected]>
[ci skip]
2026-10-16 20:13:29 +02:00
Joe Mattiello a5dc811814 savestate: reject invalid broadcast/cable/region instead of aborting (#2555)
dc_deserialize() read the broadcast, cable and region settings straight
into the config and checked them with verify(), so a corrupt or
incompatible state killed the process, after already writing the bad
values into the settings.

Read them into locals and throw Deserializer::Exception when one is out
of range. dc_loadstate() and the libretro core already catch it and
report the state as unusable. Valid states load exactly as before.

Co-authored-by: Claude Opus 5.5 <[email protected]>
[ci skip]
2026-10-16 20:12:36 +02:00
Joe Mattiello 5c86428c04 input: stop the emulator while saving an in-RAM savestate (#2554)
The in-RAM save hotkey called gui_saveState(false, true), which runs
dc_serialize() without stopping the emulator. With threaded rendering
on (the default), the SH4 and AICA keep running on the emulator thread
while the input thread serializes, so the state can be torn: memory and
registers from different moments.

Stop and restart around the save like the disk save hotkey and the
in-RAM load hotkey already do.

Co-authored-by: Claude Opus 5.5 <[email protected]>
[ci skip]
2026-10-16 20:11:22 +02:00
Chris Dixon 258f5c11d4 libretro: forget the LED callback on deinit (#2560)
The callback was only ever taken once. A frontend that loads a fresh
instance of itself for the next game, as Kodi does, keeps the core
loaded, so the next GD-ROM light change called into the unloaded one.
[ci skip]
2026-10-16 20:09:21 +02:00
Flyinghead 91c3084d52 imgread: add 2336 to 2340 bytes sector conversion as used by Bleemcast 2026-10-14 13:26:57 +02:00
Flyinghead 506b4efd04 capcom: use peer-to-peer raw mode for in-game communication
Let peers exchange V.42 frames instead of implementing the protocol
locally so that error correction works end to end.
2026-10-14 13:20:12 +02:00
Garrett Brown 80471d57b0 libretro: define the unique texture dumping option
Add the missing dump_unique_textures core-option definition beside the
existing texture-dump options. This prevents findDefinition() from
returning null when the frontend supplies a value for this option.

Default to disabled to match the implicit false config default, and use
the existing setting description for skipping video and animated textures.
2026-10-13 11:28:52 +02:00
Chris Dixon ce8689101f libretro: look for the NAOMI BIOS through the frontend VFS (#2542)
findNaomiBios checked for the BIOS archive with access(), so a frontend
watching its VFS never saw it being looked for, unlike findFlash, which
already uses path_is_valid. Kodi uses that to name the file a core is
missing.
[ci skip]
2026-10-13 11:28:29 +02:00
Flyinghead 518c496251 sh4: set Sh4Cntx address on Reset
RAM base and Sh4Cntx address might change on libretro without Init being
called. So set the correct Sh4Cntx address on Reset and propagate to
dynarec, cycle counter and caches.
[ci skip]
2026-10-13 11:25:52 +02:00
Flyinghead 804edb00d7 dx11,gl: fix clear framebuffer and scissor regressions
dx11: don't use scissor rect for final A-buffer render
gl4: clear geom_fbo[1] (color pass) not geom_fbo[0] (depth pass) when
needed
Issue #1197
2026-10-13 11:14:45 +02:00
Flyinghead f03faffd2f libretro: reset disk_index to 0 when loading a game
Don't use the static disk_index in read_m3u()
Issue #2541
2026-10-12 09:46:45 +02:00
Flyinghead fa23eef593 multiboard: avoid crash when raw input is enabled
Fixes MINIDUMP-A9F
2026-10-12 09:44:10 +02:00
Flyinghead 658abb386a maple: don't crash on invalid maple device. Just ignore it
Fixes MINIDUMP-A9C
2026-10-12 09:42:20 +02:00
Jordi Serra fa3c526e4f ggpo: resolve the peer host name (#2514)
GGPO parses the peer address with inet_pton() only, so a host name in
the Peer field silently becomes 0.0.0.0 and the session never syncs,
without any error. Resolve non-numeric addresses with getaddrinfo()
before handing them to GGPO, and fail with a clear message when the
name cannot be resolved.

Co-authored-by: unjordi <[email protected]>
[ci skip]
2026-10-12 09:34:50 +02:00
Jordi Serra e6d3365c0b Fix MD5Sum hashing hostfs::File pointers instead of file contents (#2513)
Since the switch to hostfs::File (#2299), MD5Sum::add(hostfs::File *)
resolved to the generic add(const T&) template, which hashes the pointer
value. The resulting digests depend on the heap address, so they differ
between peers and between runs. This breaks GGPO peer verification when
a .state.net savestate is used (stateMD5), and with CDI, GDI and CUE
images (gameMD5).

Add an MD5Sum::add(hostfs::File *) overload that hashes the whole file
content, like the std::FILE overload, and reject pointer types in the
template with a static_assert so this cannot happen again silently.

Co-authored-by: unjordi <[email protected]>
[ci skip]
2026-10-12 09:33:48 +02:00
Flyinghead 7422d1b831 wccf: rename ENTER button to PRESSURE 2026-10-12 09:32:19 +02:00
Flyinghead 6dafd41eaa capcom: wrong battle code on win32. fix spawn and hmgeo end of match
An overflow was setting the short battle code to 7fffffff on win32 in
50% of cases.
Fix spawn and hmgeo end of match freeze by returning 0 if the connection
is closed.
2026-10-12 09:25:01 +02:00
github-actions[bot] ed953fcbfd Fetch translations & Recreate libretro_core_options_intl.h 2026-10-10 01:44:50 +00:00
Flyinghead ec56b5f260 sh4: remove penalty cycles for MMU memory access
Issue #1058
2026-10-09 18:49:02 +02:00
Flyinghead bc3b8c71da capcom: support for sf3 MF 2000 v1 and v2. modem hang up
net service throw when connection is closed. Modem catches it and hang
up.
netservice: call custom service start() and stop() like others.
ice: init http client before using it
capcom: send stop message when one end is closed. detect hanged resync
and retry them.
capcom: support sf3 M.F 2000 v1 and v2
2026-10-09 18:46:18 +02:00
Flyinghead 0d9853df9a dcnet: use ipv4 address to select AP 2026-10-07 13:34:54 +02:00
Flyinghead df7aedf506 cue: recheck track file handle
Fixes MINIDUMP-A8T MINIDUMP-A8V
2026-10-07 13:32:45 +02:00
바닷빛andClaude Opus 5 df5feec55c build: pass /utf-8 to tinygettext so non-UTF-8 locales can build (#2532)
Co-authored-by: Claude Opus 5 (1M context) <[email protected]>

[ci skip]
2026-10-07 13:30:36 +02:00
바닷빛andClaude Opus 5.5 bbdef06a09 sh4: clear the FPSCR cause field when the guest writes FPSCR (#2531)
On the SH4 every FPU instruction clears the FPSCR cause field before it
executes, and sets a cause bit only if it raises an FPU exception.
Flycast doesn't update the cause field in FPU instructions, so a cause
bit that the guest writes back with lds/lds.l stays set forever.

Kita He. White Illumination (T14301M) resets to the BIOS when a karaoke
song starts (issue #1058). TRFDRAW.DLL inverts a 4x4 matrix with an LU
decomposition whose pivot comparison helper saves FPSCR, runs fcmp/gt and
reads FPSCR back: a set cause.V means an unordered (NaN) comparison. With
a stale cause.V every comparison looks unordered, no pivot row is chosen,
and the uninitialized pivot index makes the routine read a wild pointer.
The access violation is caught by the game's __except handler, which
calls ResetToFirmware().

Clearing the cause field whenever FPSCR is written matches what the
hardware shows to code that reads FPSCR after an FPU instruction.

Co-authored-by: Claude Opus 5.5 <[email protected]>

[ci skip]
2026-10-07 13:29:53 +02:00
Rich Stokes b4c38d652e input: rebase absolute mouse motion after relative input (#2528)
[ci skip]
2026-10-07 13:27:42 +02:00
Flyinghead 4dca1988da capcom: fix battle code mem location in several games 2026-10-05 14:45:15 +02:00
Flyinghead 552a86086b win32 and switch build fixes 2026-10-04 12:44:39 +02:00
Flyinghead 9ec8b7e658 Merge remote-tracking branch 'origin/master' into dev 2026-10-04 11:56:16 +02:00
Flyinghead 7d69c9cfd4 capcom: use libjuice for in-game p2p connection
fix capcom vs SNK pro battle code address
2026-10-04 11:51:01 +02:00
Nejsplitter 5ff3f974d9 Add stretch to fill screen option (#2515)
[ci skip]
2026-10-04 11:45:46 +02:00
flyinghead f83fda7069 New translations strings.xml (Spanish)
[ci skip]
2026-10-03 11:12:01 +02:00
github-actions[bot] 59ed35a7ea Fetch translations & Recreate libretro_core_options_intl.h 2026-10-03 01:09:55 +00:00
scribam 04669ebe41 ci: fix libusb1 installation on netbsd 2026-10-01 11:32:26 +02:00
nixxou 98ce9fb5d7 Keep spaces inside -config section names (#2516)
Game ids can have spaces, like "INITIAL D Ver.2". The parser removed all
spaces in the section name so it became "INITIALDVer.2". Now only the
spaces before and after the name are removed.

Co-authored-by: nixxou <[email protected]>
[ci skip]
2026-10-01 11:31:59 +02:00
Acts1631 2a57c9ffd9 savestate: validate fixed buffer lengths (#2512)
[ci skip]
2026-10-01 11:31:15 +02:00
Acts1631 2ea7920fdc network: bound DCNet Ethernet frame sizes (#2511)
[ci skip]
2026-10-01 11:31:00 +02:00
Acts1631 9d4c337349 network: validate GGPO application data packets (#2510)
[ci skip]
2026-10-01 11:30:37 +02:00
vkedwardli 89b72a8e3e windows: fix HTTP URL parsing race and GET status handling (#2509)
Use per-request URL buffers for GET and POST to prevent concurrent requests from overwriting each other's host and path.

Return the actual HTTP status for GET requests, retaining an error status when sending the request or querying its status fails
[ci skip]
2026-10-01 11:30:16 +02:00
Flyinghead fe04dd6503 modem: answer mode. network: capcom direct support
modem: implement V42 answerer mode
modem: add callbacks for dial out, answer mode, start handshake, etc.
network: detect capcom direct games and simulate the peer-to-peer connection
2026-09-29 22:08:43 +02:00
flyinghead b1b80d8849 Crowdin Translations (Chinese Simplified)
[ci skip]
2026-09-29 22:07:45 +02:00
Richard Stokes e36e9df2dc vk oit: fix synchronization hazards between render passes and frames
The OIT color and depth/stencil attachments, a-buffers and pixel counter
are shared by consecutive render passes and by all frames in flight, but
the render pass had no external dependency covering them, and the pixel
counter reset wasn't synchronized with the fragment shaders using it.

With MoltenVK using Metal 3 argument buffers and residency sets (macOS 15+),
Metal no longer tracks hazards on resources accessed through argument
buffers, so input attachment reads and storage buffer accesses of a frame
may race with the attachment clears/loads and a-buffer writes of the next
one. This results in sporadic one-frame glitches with per-pixel alpha
sorting.

Issue #2495
2026-09-28 13:49:06 +02:00
github-actions[bot] ea087b9140 Fetch translations & Recreate libretro_core_options_intl.h 2026-09-26 00:36:31 +00:00
Flyinghead 869038f40a scif: use scheduler only after it has been deserialized
Calling sh4_sched_request before will not use the correct current time.
Fixes barcode reader malfunctioning after loading a state (mushiking,
dinoking, etc.)
Issue #2501
2026-09-23 12:01:56 +02:00
flyinghead 0a304f1396 Crowdin Translations (Simplified Chinese, Swedish) 2026-09-22 15:42:21 +02:00
Richard Stokes 628bd3dbb1 macOS: queue document opens with an SDL user event
sdl2-compat (what Homebrew installs as sdl2) doesn't convert the file
name of an SDL_DROPFILE event pushed by the application, then crashes
in strdup(NULL) when converting the event back during window creation.
Carry the path in a registered user event instead.
2026-09-20 20:35:12 +02:00
Flyinghead 2ac5c772f0 vk: ends rendering before clearing texture cache
Fixes validation errors when deleting textures used by the current
command buffer.
2026-09-19 15:24:45 +02:00
Flyinghead fff68b3f57 gdrom: fix regressions
Pause wasn't working. Bust-a-Move 4 puzzle collection music was missing.
Issue #786
2026-09-19 15:21:25 +02:00
Flyinghead e6707469d2 gdrom: improve SecNumber.Status handling with timer
Fixes NFL Blitz 2001 freeze on matchup screen. Issue #2488
Fixes Ms. Pac-Man spurious CDDA pause. Issue #786
Fixes Cool Boarders Burrrn load freeze with real BIOS. Issue #1073

Move scheduler state serialization to respective modules.
2026-09-18 16:45:41 +02:00
Flyinghead a99c8667b4 vk: invalid attachment 1 initial layout when rttCopy2vram is on 2026-09-18 15:19:52 +02:00
Flyinghead 49ad26a7e5 change default log level from debug to info. compilation warnings 2026-09-18 15:15:47 +02:00
Flyinghead da5be1a0a7 Merge remote-tracking branch 'origin/master' into dev 2026-09-18 14:56:43 +02:00
Flyinghead dd7a5f0620 target android 16. Fix Back invoked handler 2026-09-18 12:12:07 +02:00
Flyinghead 6d73f7191c minor changes (error logging) 2026-09-18 12:09:27 +02:00
Ryan McClelland 215a5af165 vmem: ignore the heap pointer tag when matching a fault address
On arm64 Android, apps targeting API 30 or later get tagged heap pointers:
malloc returns addresses with a tag in the top byte, while the fault address
a signal handler receives has that byte cleared. Without nvmem, RAM, VRAM,
AICA RAM and Elan RAM are malloc'd, so the range checks in bm_getRamOffset(),
getVramOffset() and the memwatch watchers never matched a fault on a
protected page, and the first write to a page holding compiled code killed
the core as an unhandled SIGSEGV.

Compare untagged addresses instead. The vmem branches are unchanged: that
memory is mmap'd and carries no tag.

The libandroid link from 8933f9fe0 is still wanted, since it is what lets
ASharedMemory_create resolve and nvmem work, but its subject blamed the
dynarec; this was the crash.
2026-09-16 16:01:28 +02:00
Rich Stokes c0fb036d60 Fix stalled uploads in the picoTCP proxy (#2497)
[ci skip]
2026-09-16 16:01:05 +02:00
Ryan McClelland 8933f9fe0c libretro: link libandroid, or the dynarec dies on the first memory access
flycast's libretro build has never run on Android 11 or later. It loads,
reaches frame one, and then takes a SIGSEGV its own handler cannot place:

    [VMEM] Virtual memory file allocation failed: errno 13
    [VMEM] Warning! nvmem is DISABLED
    [COMMON] SIGSEGV @ 0x71d0c87618 invalid access to 0x71ce6cba00

errno 13 is EACCES. posix_vmem.cpp asks ASharedMemory_create first and
falls back to opening /dev/ashmem when the symbol is null -- and Android
11 took that device away from apps. So the fallback is refused, nvmem is
disabled, and the dynarec's fastmem accesses have no mapping behind
them. rewrite() cannot place the fault, and the handler falls through to
die("segfault").

The good path was there all along and was dead code, because the symbol
is declared weak:

    extern "C" int __attribute__((weak)) ASharedMemory_create(const char*, size_t);

and nothing in the LIBRETRO branch links libandroid, so it resolved to
null every time. readelf on the built core shows only libm, libdl and
libc in NEEDED, and nm shows ASharedMemory_create as an undefined weak.
The app build links `android log` in its own ANDROID block further down;
the libretro build never did.
2026-09-15 11:29:00 +02:00
Rich Stokes 4509ee903c macOS: defer document loading until emulator initialization finishes (#2492)
Route document opens through SDL's event queue
[ci skip]
2026-09-15 11:28:32 +02:00
Flyinghead 04b74c3a11 gdrom: stop cdda when reading a sector
Revert the revert (9869ea8852) to fix
Hydro Thunder. Also needed for NFL Blitz 2001.
Issue #2488
[ci skip]
2026-09-15 11:24:09 +02:00
Edward Li 36c975e391 ci: package artifacts before upload and verify macOS signatures 2026-09-13 15:22:03 +02:00
Flyinghead bcad2d76d0 naomi network: actively refuse connections when max nodes is reached
Issue #2402
2026-09-13 15:19:45 +02:00
Flyinghead 9869ea8852 gdrom: don't stop cdda when reading a sector
Revert dcf5444acc
Issue #2488
2026-09-13 15:12:12 +02:00
chyyran ea22ee8723 lua: expose openGdrom and insertGdrom as lua functions 2026-09-12 13:37:53 +02:00
vkedwardli b777c15fdc docs: restore Homebrew installation via the Flycast tap (#2486)
Document install commands for stable, master, and nightly dev channels.
Link to tap instructions and adjust installation section formatting.
[ci skip]
2026-09-12 13:15:43 +02:00
vkedwardli 93379c489f macOS: backport MoltenVK fixes and verify packaged artifacts (#2485)
[ci skip]
2026-09-12 13:14:52 +02:00
Flyinghead b03335bca9 pvr: swap interval detector improvements
Don't switch to swap interval 2 if actual interval is 3 or more. Fixes
ECCO switching to swap 2 during loading.
Switch to interval 1 when slightly above 30 FPS for too long. Helps
variable refresh rate games (Unreal) and those rendering close to 30 FPS
(ECCO, Armada).
Issue #2467
[ci skip]
2026-09-12 13:14:20 +02:00
github-actions[bot] 604ebd58fe Fetch translations & Recreate libretro_core_options_intl.h 2026-09-12 00:14:46 +00:00
Flyinghead eddf263586 windows: fix http request with path > 256 chars
The path of http requests was limited to 256 chars on Windows, which
isn't enough for some TGDB gets.
Issue #2455
Issue #2470

Only save the database when needed. Better handling of TGDB allowance
limit. Pause if no progress.
2026-09-11 13:37:37 +02:00
Flyinghead 3f70e47adb aica: store last sample and Q at loop start even with ADPCM stream
When reaching LSA, the last sample and Q values should be stored, even
with ADPCM long stream (which doesn't use them). NBA 2K switches a
looping sound from ADPCM long stream to ADPCM and relies on the loop
start values to be restored.
Issue #2469
2026-09-11 10:39:31 +02:00
Romain TISSERANDandClaude Opus 5 0abac3465d chd: MIL-CD support for images with pregaps
CHDs converted from Redump MIL-CD dumps were rejected with "Unsupported
subtype or pre/postgap": the audio tracks of the first session carry their
pregap inside their own .bin file, which chdman records as PREGAP:n
PGTYPE:V<type> in the CDROM_TRACK_METADATA2 entry.

Handle these tracks the way MAME does:
- when PGTYPE is prefixed with 'V', the pregap sectors are part of the track
  data and counted in FRAMES, so INDEX 01 sits `pregap` frames after the
  previous track and the track data starts `pregap` frames into the CHD
- otherwise the pregap only takes up space on the disc
- subcode data doesn't change the sector layout, and postgap sectors are
  never stored: warn about both instead of bailing out

Also:
- audio tracks of CD-ROM CHDs are stored big-endian, just like the ones of
  GD-ROM CHDs using the CHGD metadata tag. Not byte-swapping them was the
  cause of the audio distortion reported with CHD MIL-CDs
- the 11400 frame session gap includes the pregap of the second session's
  first track. Don't count those sectors twice when they're in the image
- only treat a multi-track disc as a MIL-CD when its last track is a data
  track. A mixed mode CD is a single session disc and its data track is the
  first one
- report the lead-out as the first FAD after the last sector, like the CUE
  and GDI loaders do, instead of one frame earlier

Tested on a dozen homebrew MIL-CDs, from 2 to 17 tracks: the CUE and CHD
loaders now return the same tracks, sessions and sector data for each of
them, and they all boot.

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
Claude-Session: https://claude.ai/code/session_01NfFd8rJc9aTQB7au8yLrqG

* cue: don't count the session pregap twice in MIL-CDs

The 11400 frame gap added at REM SESSION 02 is the lead-out of the first
session (01:30:00), the lead-in of the second one (01:00:00) and the pregap
of its first track (00:02:00). When that track has an INDEX 00 of its own,
its pregap sectors are stored in the image and were counted a second time,
placing the data track 150 frames after the session start reported in the
TOC, i.e. after the FAD the boot file is looked up at.

Redump lays MIL-CDs out with no INDEX 00 on the second session's track, so
none of the images I have hits this. It only shows up with images written by
other tools, and is included here because the CHD loader needs the same
adjustment to agree with this one.

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
Claude-Session: https://claude.ai/code/session_01NfFd8rJc9aTQB7au8yLrqG
2026-09-10 11:14:28 +02:00
Flyinghead a9b451c6f2 ini: check if string empty before accessing 1st element
This is UB with c++11.
[ci skip]
2026-09-10 11:12:12 +02:00
Flyinghead 6838fbd642 naomi: fix wccf card reader state when ejecting/loading card 2026-09-08 16:12:53 +02:00
Flyinghead 34cd222e12 vk: use FIFO present mode for all gpus and platfoms
Mailbox mode was used on desktop with NVidia GPUs before, which may
cause some frames to be lost.
Issue #732
2026-09-08 16:06:33 +02:00
WizzardSK fb286f777c cmake: keep the Switch archive's extracted objects apart
Since #2477 put zstd in the combined archive, the libnx job fails at the
final link with `undefined reference to XXH32` - the symbol went missing
from an archive that extracts xxHash first.

ar names a member after the basename of its object, and zstd builds its
own vendored copy of xxhash.c, namespaced to ZSTD_. Both archives
therefore carry a member called xxhash.c.o, and extracting them into one
directory leaves only the second: zstd's, which defines ZSTD_XXH32 and
nothing the core calls.

Extracting each archive into a directory of its own keeps every object,
whatever it is called. Reproduced and checked with a CMake project of the
same shape, two static libraries whose members collide:

  flat, as now:        core_main, ZSTD_XXH32
  a directory each:    core_main, ZSTD_XXH32, XXH32

Not run on the buildbot - the Switch toolchain is not something I have
here.
2026-09-08 08:46:23 +02:00
Flyinghead 0ff538115f vk: fix deprecation warning 2026-09-07 20:24:29 +02:00
flyinghead 5aca173e80 New translations flycast.pot (Chinese Simplified)
[ci skip]
New translations for {languageName}
2026-09-07 19:17:02 +02:00
Flyinghead 2f0917bb7b ui: change Chinese (Traditional) language name
[ci skip]
2026-09-07 19:15:59 +02:00
Flyinghead f2013c652a pvr: reset PCW::Texture if texture is invalid. Fix Xtreme Sports shadows
Handle the case where MVO list are shared between OP and TR but the TR
list is the one to use. Fixes Xtreme Sports shadows. Issue #2479
Reset PCW::Texture to 0 if texture is invalid (hence null) so renderers
don't have to worry about it.
2026-09-07 19:12:47 +02:00
Flyinghead 230d83654f vk: use dynamic rendering local read to implement 2ndary accumulator
Secondary accumulator fixes BANG! Gunship Elite, Evil Dead, Space
Channel 5 part 2 and WCCF in non-OIT mode.
Bind dummy imageView/buffer if a descriptor set doesn't use a slot. This
avoids referencing old resources and fixes validation errors when those
resources are deleted.
oit: lighter headers. Also bind dummy imageView/buffer where needed.
2026-09-07 18:58:08 +02:00
WizzardSK fa04b20837 cmake: the Switch archive was missing zstd and tinygettext 2026-09-07 14:46:25 +02:00
WizzardSK 52c8ecef1e gitlab-ci: the libnx image's cmake is too old for this tree 2026-09-06 16:03:29 +02:00
flyinghead d3c90b0f84 Crowdin Translations (Chinese Simplified) 2026-09-06 15:00:19 +02:00
kihato fe4cf1d586 Traditional Chinese was missing from the UI language drop-down menu (#2473)
[ci skip]
2026-09-06 14:59:53 +02:00
kihato 08ffbe6cf1 Additional games that do not support RGB output (#2472)
[ci skip]
2026-09-06 14:59:30 +02:00
Rubén Tomás b35b829a12 rend: optimize ARM64 framebuffer paths (#2452)
Add NEON framebuffer conversion and VRAM write paths, and validate the physical VRAM footprint before vector loads so both 8 MB and 16 MB configurations remain in bounds.
[ci skip]
2026-09-06 14:59:02 +02:00
WizzardSK a75d1cff53 gitlab-ci: build the Switch core's combined archive (#2471)
The libnx template builds --target ${CORENAME}_libretro, and on Switch that
target is flycast's own objects and nothing else - 251 members. The
dependencies (xxhash, chdr, zip, elf, the resources) are folded in by the
"combined" target that CMakeLists.txt defines for exactly this case, and
that is the archive RetroArch has to link: 409 members.
[ci skip]
2026-09-05 16:36:55 +02:00
flyinghead 62d7a0432a Crowdin Translations (Chinese Traditional) 2026-09-05 10:19:51 +02:00
Flyinghead 066c82ddfd ui: fix dreamcast bios displayed on disk eject. Game filter field
Fix dreamcast BIOS boxart displayed when ejecting a disk. Issue #2468
Show game filter field on all platforms. Issue #2417
[ci skip]
2026-09-05 10:19:07 +02:00
Flyinghead 1145248aa7 save states: drop support for old save states
First version supported is now V42 (release v2.2 2023/10/23)
netdimm: add full network dump (disabled)
2026-09-05 09:28:35 +02:00
WizzardSK daa46ec63e fix for switch (#2466)
uncomment build-static
[ci skip]
2026-09-05 09:11:55 +02:00
github-actions[bot] 9814e2fe40 Fetch translations & Recreate libretro_core_options_intl.h 2026-09-05 00:01:10 +00:00
Aldo Borrero d8a76809e5 OpenGL: fix per-frame VAO leak in the ImGui GL3 backend
ImGui_ImplOpenGL3_SetupRenderState generated the VAO into its
`vertex_array_object` parameter, which is passed by value, so the name never
reached ImGui_ImplOpenGL3_RenderDrawData. There the local was still 0, the
`if (vertex_array_object != 0)` guard was false, and glDeleteVertexArrays never
ran — a VAO was leaked every frame. The OSD is drawn every frame, so on Mesa
(radeonsi) host memory grows unbounded until OOM; Vulkan is unaffected.

Move the glGenVertexArrays back into RenderDrawData and pass the handle to
SetupRenderState for binding only, so the existing glDeleteVertexArrays runs
and the VAO is recreated each frame (staying valid across GL context changes),
matching the upstream ImGui backend.
2026-09-04 09:48:47 +02:00
WizzardSK fd9d39b127 ci: build the Switch core here, with CMake (#2465)
The libretro Switch core on the buildbot still comes from the old
libretro/flycast tree, because the job here was written against the
Makefile-based libnx-static template and this tree has no Makefile. There
is a CMake version of that template, and the CMakeLists already knows how
to build for Switch (NINTENDO_SWITCH + LIBRETRO produces
flycast_libretro_libnx), so point the job at that instead.
[ci skip]
2026-09-04 09:47:52 +02:00
Flyinghead cc4a59b1f2 mmu: restrict wince hacks to wince games. Fix full mmu !wince states
Add windowsCE flag to gate windows CE-specific hacks: system calls
interceptions, page fault handler, memory slot #0 flush.
Detect mmu usage when loading a state and add UTLB entries to cache.
Fixes mario 64 savestates.
2026-09-03 18:32:14 +02:00
Flyinghead 8f511f8960 Remove macOS Homebrew from Install section
Issue #2461
[ci skip]
2026-09-03 16:11:23 +02:00
Flyinghead 9bdb0ad323 fix test 2026-09-03 15:58:04 +02:00
Stefan Schlosser 5c96b69ff0 Turn USE_DREAMLINK_DEVICES into build option
Turning the USE_DREAMLINK_DEVICES variable into a build option (enabled
by default) provides a convenient way to exclude building the
DreamPicoPort-Api dependency, which could use newer libusb APIs than are
available on platforms which provide their own libusb implementation,
i.e. mostly the BSDs.

For example, the newest DreamPicoPort-Api uses libusb_wrap_sys_device,
which isn't available on FreeBSD < 15. Building flycast with an updated
DreamPicoPort-Api on the still supported legacy FreeBSD 14 would need to
disable this option.

The build option needs to be forced off when targeting Android, iOS,
Switch or LibRetro, to keep the same build behavior from before this
change.
This also allows to unnest the configuration block to the top-level.
2026-09-03 15:23:35 +02:00
Flyinghead 7db29cc2fd pvr: improved framebuffer writes detection
Fixes Sega Rally (JP) network menus.
Fixes Ikaruga and Armada loading screens. Issue #1189.
Fixes VF3tb history mode videos in NTSC and VGA mode.
Don't check for framebuffer writes when writing the framebuffer to vram
(Full FBE)
[ci skip]
2026-09-03 15:23:17 +02:00
Flyinghead 162bc02007 minor changes
Catch/rethrow xbyak exceptions in arm7 and sh4 dynarecs. Fixes
MINIDUMP-8P9
naomi: code to generate random main board serial id (not used yet)
Don't log warning for sh4 trapa op.
texcache: get rid of unused FBLineWriter argument.
2026-09-03 14:54:11 +02:00
ShinMikite 12bb43652f Fix Monkey Ball minimap depth masking 2026-09-02 18:10:29 +02:00
Flyinghead 71b739faee android: tentative fix for ANR when calling eglDestroyContext
Flush and finish the GL pipeline.
Release the ANativeWindow for both GL and VK.
[ci skip]
2026-09-02 18:09:57 +02:00
Flyinghead 57e33d323a ui: fix regression where dpad won't scroll up/down the game list
Likely caused by an imgui change
Issue #2455
2026-09-02 17:52:39 +02:00
Florin9doi b4d7e75977 Re-connect the jump pack for the fishing controller 2026-09-01 14:26:21 +02:00
Flyinghead 58b283fc02 wccf improvements
Act as left projector and start right instance when MultiboardSlaves=2
netdimm: fix vf4/vf4efo regression. Get rid of final tuned sync socket
hack not needed anymore. Ignore netdimm server reboot.
Set node number in eeprom for left and right projectors.
ccd cam: don't crash if http server can't start
[ci skip]
2026-09-01 14:25:56 +02:00
Flyinghead 149e651d24 android: don't start swappy if g_activity is null
Probably not the right fix but should mitigate the issue
2026-08-31 08:17:01 +02:00
Flyinghead c37be698db android: fix start up crash on android <= 8 2026-08-31 08:10:46 +02:00
Flyinghead 453dcefc12 wccf: fix card expiration/resign. Manager data transfer support
Emulate 2 cards in reader.
Save and update card write counter.
Detect expired card and insert a blank card with it to allow data
transfer. Replace existing card with new one when saving.
2026-08-30 15:33:41 +02:00
Flyinghead ce3abe5a0b Merge remote-tracking branch 'origin/master' into dev 2026-08-30 15:27:20 +02:00
Flyinghead e2722869ff The Grinch (US) doesn't support RGB output
Issue #2447
2026-08-30 09:24:17 +02:00
RTA bbe606c8f7 gles: enable OIT with OpenGL ES 3.1
Request and propagate explicit OpenGL ES 3.1 contexts in libretro. Add the image-atomic and shader-storage support needed by OIT, validate per-pixel resource limits, and improve A-buffer synchronization and framebuffer buffer reuse.
2026-08-28 14:55:30 +02:00
Flyinghead 5803d8aa14 wccf cam: fix card selection (comm menu). 2k1/2k2/2k4 support
Moving a card now registers as a selection in communication menu.
Changed pitch coordinates to match real hw.
Support 2k1, 2k2 (eu) and 2k4 (eu) for player names and pictures.
[ci skip]
2026-08-28 14:55:03 +02:00
Acts1631 180e670605 Reject unterminated quoted disc filenames
CUE and GDI descriptor parsing continued appending after a quoted
filename reached end of input. A malformed descriptor could consume
CPU and memory until Flycast stopped responding.

Treat failed character extraction as an invalid descriptor while
skipping whitespace and while scanning quoted filenames.
2026-08-27 19:05:33 +02:00
flyinghead f831592f64 Translations: Spanish, Simplified Chinese 2026-08-24 14:42:11 +02:00
Flyinghead ba04273a25 Add turkish and chinese simplified language choices
[ci skip]
2026-08-24 14:40:54 +02:00
Flyinghead 804fcb4a9e texconv: get rid of vq and pal globals. wccf: player card pics endpoint
texconv: Get rid of ::vq_codebook and ::palette_index globals.
wccf: New http endpoint to download player cards.
[ci skip]
2026-08-24 14:34:45 +02:00
Flyinghead c3763d8fc4 sdl: in multiboard mode, handle fullscreen switches locally
Switching to fullscreen and back should be handled by the slave, not
routed to the master like other key presses.
Issue #2439
2026-08-23 13:29:03 +02:00
Flyinghead 9473c0377a android: clear knownDevices when InputDeviceManager starts
Issue #2440
2026-08-23 13:20:05 +02:00
Flyinghead 9c6c8e59d6 atomiswave: reduce bios size to 64KB as the rest is writable
Fixes regression introduced by 8412d3d8a3
that overwrites the loaded .nvmem data with the bios second half.
2026-08-23 13:18:08 +02:00
Flyinghead d367284ddf another msvc build fix 2026-08-22 18:22:09 +02:00
Flyinghead 105f760613 switch and msvc build fixes 2026-08-22 18:11:20 +02:00
Flyinghead 8c5446706e wccf: team pitch api web server
Run REST api to manage player card positions with web server.
Load/save player positions in wccf420e.zip-team.json file.
2026-08-22 17:20:44 +02:00
github-actions[bot] 7fcd6976fa Fetch translations & Recreate libretro_core_options_intl.h 2026-08-21 22:38:44 +00:00
Flyinghead 982f26a0da netdimm: always return back the correct dimm command
Tentative fix for WCCF freezes
2026-08-21 17:17:38 +02:00
Flyinghead d5e74ad756 vk: fix RTT initial color attachment layout
Fixes Miss Moonlight color attachment layout mismatch error.
2026-08-20 15:18:20 +02:00
Flyinghead 9dbca96328 pvr: tile clipping is used for framebuffer size calculation in RTT
Fixes Miss Moonlight vk exception (width=0, height=0)
2026-08-20 15:14:43 +02:00
flyinghead 44e4c7b50d empty commit to trigger CI builds 2026-08-19 13:56:45 +02:00
Bruceleeto 643374c91e load elf sections that were being ignored. 2026-08-18 15:35:02 +02:00
scribam 33d5b38163 ci: remove sentry-cli login (#2435)
[ci skip]
2026-08-18 15:33:31 +02:00
scribam caa55c0297 ci: fix sentry-cli on android builds 2026-08-17 20:45:26 +02:00
Flyinghead a6242fd653 win32 build fix 2026-08-17 15:11:04 +02:00
scribam 02c779ddc1 ci: maintenance updates
- Bump action versions
- Improve ccache support
- Reformat files
- Replace chkfung/android-version-actions and mathieu-bour/setup-sentry-cli from android workflow
- Update MoltenVK to use the latest official tag
- Update BSD operating systems
2026-08-17 14:52:14 +02:00
MetalSlugandMetalSlug b0284ba9ba add pregap support for CHD and CUE (#2429)
Co-authored-by: MetalSlug <>

[ci skip]
2026-08-17 14:51:40 +02:00
Flyinghead 29a54e36ce rend: support secondary accumlator for !OIT and !autsort modes
Secondary accumulator was only supported by OIT renderers in autosort
mode. This adds support for missing renderers (dx9, dx11 and gles) and
modes (dx11 OIT and gl4 OIT in !autosort mode).
Vulkan support will come in a later commit.

Fixes WCCF black pitch issue. Evil Dead and BANG! Gunship Elite now
playable in non-OIT mode.

[ci skip]
2026-08-17 14:50:56 +02:00
Flyinghead 7cc58de0c3 netdimm: reduce the poll time to help with wccf freezes
error out if socket is already busy
2026-08-17 14:32:39 +02:00
Flyinghead 2550d10902 network: support for Sega Rally 2 (JP) online play 2026-08-16 18:18:01 +02:00
vkedwardli 565f5174f7 Add native compressed custom textures and video-memory preloading (#2408) 2026-08-15 19:18:39 +02:00
github-actions[bot] 5aa091fde6 Fetch translations & Recreate libretro_core_options_intl.h 2026-08-14 22:37:38 +00:00
github-actions[bot] a172e00013 Fetch translations & Recreate libretro_core_options_intl.h 2026-08-07 22:53:01 +00:00
James Smith f58719d9b7 Fixed issue with single device mode but installed on B, C, or D port 2026-08-02 18:44:15 +02:00
James Smith 8041f02984 Don't check joystick name for serial number on Windows - this name gets cached and reused for similar VID/PID 2026-08-02 18:44:15 +02:00
github-actions[bot] d4fc077410 Fetch translations & Recreate libretro_core_options_intl.h 2026-07-31 23:14:13 +00:00
Flyinghead 41406fb31a wccf: fix card corruption and player card dispenser
Club card format changed. Cmd 24 and 25 are single block read and write.
Card dispenser correctly reports that the player card has been dispensed
and picked up.
2026-07-24 16:58:28 +02:00
Flyinghead e293832bef netdimm: select() return code was overwritten and lost
Fixes wccf countdown stuck at 20 s.
2026-07-23 18:20:52 +02:00
Flyinghead 253970d8e5 wccf ccd cam emulation
Work in progress
2026-07-22 16:35:29 +02:00
Flyinghead 63aecfd491 Merge remote-tracking branch 'origin/master' into dev 2026-07-21 14:45:21 +02:00
Edward Li 4126f1464f netbsd: bump libusb install to 1.0.30 2026-07-21 12:28:22 +02:00
Flyinghead 670b5001d4 netdimm: set wccf server IP. Close sockets in destructor
Added option naomi:WCCFServer to specify the server IP address (or
domain name). Replace wccf harcoded server IP address with the
configured one at runtime.
2026-07-20 14:12:50 +02:00
Flyinghead 2805df5484 Merge remote-tracking branch 'origin/master' into dev 2026-07-20 11:44:06 +02:00
Flyinghead 0cd66cea5c wccf preliminary support
netdimm: more complete network implementation. Add netdimm server.
wccf ic card reader emulation
wccf inputs
2026-07-18 17:40:47 +02:00
scribam d93612734b core: small optimization for reg alloc 2026-07-17 16:18:46 +02:00
James Smith 95e28f412f Handle unknown keys for manual mapping in Android 2026-07-13 17:21:39 +02:00
scribam a749e51559 tests: use cross-platform sleep function (#2400)
[ci skip]
2026-07-13 17:21:20 +02:00
scribam e82f38a8cc core: headers cleanup (#2399)
[ci skip]
2026-07-13 17:20:55 +02:00
github-actions[bot] f09d1f22ef Fetch translations & Recreate libretro_core_options_intl.h 2026-07-10 23:12:05 +00:00
Edward Li 89bf160a9d macOS: use release MoltenVK dylib directly 2026-07-08 16:58:59 +02:00
Edward Li e9b7fb6510 macOS: prevent F from triggering Fn+F fullscreen shortcut 2026-07-07 18:47:24 +02:00
sonninnos 602da7d447 Libretro updates (#2396)
* Libretro: Use SPG rate in AV info FPS + cleanups
* Libretro: Logging cleanup
* Libretro: Add disc access LED interface
* Libretro: Add coin limit option for arcade
[ci skip]
2026-07-07 18:47:03 +02:00
vkedwardli 8f877e69a2 cmake: pre-seed native macOS Xcode feature checks (#2394)
Speed up macOS Xcode project generation by skipping repeated feature checks, can be disabled with `FLYCAST_PRESEED_DARWIN_XCODE_CHECKS=OFF`
[ci skip]
2026-07-07 18:46:26 +02:00
Flyinghead c8ce4ab57f libretro: poll input with main class IDs
Issue #2393
2026-07-06 13:14:26 +02:00
Flyinghead 1607eb5715 widescreen: disable shenmue 2 cheat on disk 4. Re-enable daytona cheat
Issue #2392
2026-07-05 12:12:13 +02:00
Flyinghead fdbfb0037c libretro ios build fix 2026-07-05 12:09:35 +02:00
Flyinghead 4bc3fde130 aica: fix x86 DSP dynarec
Regression introduced by 8176646c5a
Issue #2249
2026-07-04 17:34:11 +02:00
shinyquagsire23 620ccc4f2a arm7: Implement MSR fully in AICA dynarec
This fixes audio in tip-of-branch KallistiOS versions, due to an FIQ
enable being no-op'd.
2026-07-04 17:34:11 +02:00
Flyinghead 3c99cb3190 ui: show game ID on pause menu
Issue #2389
2026-07-04 17:34:11 +02:00
Edward Li d84c41b349 Fix content grid layout during UI scale changes 2026-07-04 17:34:11 +02:00
Edward Li 347681c5e2 SDL: refresh display metrics on monitor changes 2026-07-04 17:34:11 +02:00
Edward Li 3bc049ed42 Delay OIT PVR resource allocation until game start 2026-07-04 17:34:11 +02:00
jpovixwm a5aabdef69 vk oit: honor maxStorageBufferRange and use BDA if available 2026-07-04 17:34:11 +02:00
Flyinghead 2802e80ab5 alienfnt: online watch support. use original dns name for server
Use original www.alienfrontserver.com domain name in default nvmem
picotcp: Open UDP port 7980 to receive watched game state
2026-07-04 17:34:11 +02:00
Immersion95 8a78914ba0 sh4: end the current timeslice on Stop()
When Stop() is requested, also clear the SH4 cycle counter so the current
timeslice ends as soon as possible.

This prevents execution from continuing past a requested stop point.
2026-07-04 17:34:11 +02:00
david.schkalee 7e7740be64 achievements: retry login when host override is set and login previously failed 2026-07-04 17:34:11 +02:00
David Schkalee 48703f6472 android: allow cleartext HTTP to 127.0.0.1 for local proxy image loading (#2379)
[ci skip]
2026-07-04 17:34:11 +02:00
scribam 9df8866840 vk: use enums from vulkan-hpp (#2376)
[ci skip]
2026-07-04 17:34:11 +02:00
Flyinghead 22c8c46dbd naomi: add clubkrt eeprom. Fix clubkrt network config 2026-07-04 17:34:11 +02:00
flyinghead 0137a91eab Crowdin Translations (#2374)
[ci skip]
2026-07-04 17:34:11 +02:00
Flyinghead 99617be3d3 libretro build fix 2026-07-04 17:34:11 +02:00
Flyinghead 84b57ff6ce i18n: update flycast.pot 2026-07-04 17:34:11 +02:00
Flyinghead b34c4cde8d android: use gamesdk frame-pacing library
Use games-frame-pacing library (aka Swappy) for OpenGL and Vulkan.
Implements proper frame pacing for 30 FPS games and/or 120+ Hz displays.
Don't duplicate Vulkan frames.
2026-07-04 17:34:11 +02:00
Flyinghead 08c63bd39b wsi: get rid of static graphics contexts 2026-07-04 17:34:11 +02:00
Flyinghead 02a335e2dd vk: detect crash when pipeline file is corrupted and ignore on next run
Use emu.cfg flag to detect crash. Ignore pipeline cache if flag remains
set from previous run.
Use assert() instead of verify()
2026-07-04 17:34:11 +02:00
david.schkalee 0e65e6bf5b Add RetroAchievements host override via broadcast 2026-07-04 17:34:11 +02:00
Flyinghead a63c892410 rend: avoid verify() 2026-07-04 17:34:11 +02:00
Flyinghead 624bee9316 naomi: disable audio on sega strike fighter slaves
slave audio is connected to a "bass shaker" and shouldn't be sent to
regular speakers.
Issue #1816
2026-07-04 17:34:11 +02:00
flyinghead 65cdc8f090 Crowdin Translations (#2363) 2026-07-04 17:34:11 +02:00
Flyinghead c941cf094b ui: rename Disabled network mode to Native to avoid confusion
[ci skip]
2026-07-04 17:34:11 +02:00
Flyinghead 3a10f245f8 vk: fix regression on android due to eSuboptimalKHR handling
presentKHR() returns eSuboptimalKHR on Android if not rendering at the
native resolution and orientation of the screen (often vertical).
2026-07-04 17:34:11 +02:00
Flyinghead 5d41d96e39 i18n: update flycast.pot 2026-07-04 17:34:11 +02:00
Flyinghead e698b8a2cb naomi network QoL improvements. fix jvs eeprom regression
Support for satellite nodes (vonot, club kart)
Start network game when known max of nodes is reached.
Add Start Now button to network sync wait window.

Ignore MultiboardSlaves option if not active when loading eeprom. Rely
on game id instead.
2026-07-04 17:34:11 +02:00
Flyinghead 2f7f2fd1cf naomi: derbyoc2 support. fix eeprom mismatch with multiboard slaves.
Save multiboard eeprom under a different name, same as nvmem
derbyoc2 uses the same card reader as WE
Disable M3Comm on slaves
Use non-free play default eeproms with proper DOC coin settings
Slave's eeprom was configured as satellite in some cases
2026-07-04 17:34:11 +02:00
Flyinghead ebfce147ba sdl: ignore hdpi scaling less than 1.2 2026-07-04 17:34:11 +02:00
Flyinghead 8412d3d8a3 naomi: load flash after rom so game id is known (multiboard)
Fixes loading of correct DOC main flash at boot (instead of loading the
satellite one).
2026-07-04 17:34:11 +02:00
scribam 8369fe164d ci: update cross-platform-actions and openbsd (#2358)
[ci skip]
2026-07-04 17:34:11 +02:00
scribam f26a82d262 ios: remove [super dealloc] when using arc (#2356)
[ci skip]
2026-07-04 17:34:11 +02:00
Isaac Marovitz 4b22470a9a Restructure core/rend CMakeLists (#2354)
[ci skip]
2026-07-04 17:34:11 +02:00
Flyinghead d34015b4a4 naomi: support for dinokich and loveber3cn 2026-07-04 17:34:11 +02:00
scribam d79ce95475 rend: rename gl.quad to gl.quadDrawer (#2355)
Prevent confusion with the macro "quad" from newlib (devkitpro, vitasdk)

[ci skip]
2026-07-04 17:34:11 +02:00
mehlis 5400069d3c add HiDPI/scale factor support for Linux (#2326)
Respects GNOME scale settings via:
  1. GDK_SCALE environment variable
  2. GNOME settings (gsettings)
  3. SDL DPI detection fallback

[ci skip]
2026-07-04 17:34:11 +02:00
Joe Mattiello 8b7dad34ee dx9: route renderer teardown on vsync switch through rend_term_renderer (#2325)
Made-with: Cursor

[ci skip]
2026-07-04 17:34:11 +02:00
Joe Mattiello 5201780ec2 vk: enable portability enumeration on Apple platforms (#2324)
Since Vulkan loader 1.3.216 the loader no longer enumerates portability ICDs unless the application
sets VK_INSTANCE_CREATE_ENUMERATE_PORTABILITY_BIT_KHR and enables the
VK_KHR_portability_enumeration instance extension. Without this,
vkCreateInstance returns VK_ERROR_INCOMPATIBLE_DRIVER on macOS / iOS
where MoltenVK is the only available ICD, and Flycast fails to start
the Vulkan renderer entirely.

Made-with: Cursor

[ci skip]
2026-07-04 17:34:11 +02:00
Joe Mattiello fedf54fbb7 vk: recreate swapchain on suboptimal present result (#2323)
vulkan-hpp lists eSuboptimalKHR as a success code for presentKHR, so
the call returns the value rather than throwing. The existing code
ignores it via (void)res and the surface stays stuck in a sub-optimal
configuration until something else triggers a resize.

In practice this shows up across DPI / window-resize / display-mode
changes and on MoltenVK, where the driver more aggressively reports
the swapchain as suboptimal but still presents. Set resized so the
swapchain is rebuilt on the next frame, matching the existing
out-of-date handling that already comes through vk::SystemError.

Made-with: Cursor

[ci skip]
2026-07-04 17:34:11 +02:00
Joe Mattiello f6f5927e1f pvr: bound render-message queue and drain it on renderer term to fix libretro audio drift (#2322)
pvr: bound PvrMessageQueue depth to prevent producer/consumer drift
pvr: drain queue before destroying the renderer in rend_term_renderer

Made-with: Cursor

[ci skip]
2026-07-04 17:34:11 +02:00
Flyinghead 2f84da4966 fix non-multiboard build 2026-07-04 17:34:11 +02:00
Flyinghead baaad2e8b9 naomi: use smart ptrs for multiboard and M3 comm. Refactoring 2026-07-04 17:34:11 +02:00
Flyinghead f0faed63cb win32: escape double quotes in cmd line args with backslash 2026-07-04 17:34:11 +02:00
Flyinghead 938ecb04d2 naomi: fix derbyoc[w] network slots. rename some comm board registers
derbyoc and derbyocw expect the slave count, not the total node count,
when the M3 board connects.
2026-07-04 17:34:11 +02:00
Flyinghead 4ac80670f7 compile warnings
Issue #2347
2026-07-04 17:34:11 +02:00
flyinghead e975526b22 New translations flycast.pot (Spanish) (#2339)
[ci skip]
2026-07-04 17:34:11 +02:00
Flyinghead d2fd5cdfab multiboard: derby owners club support. route keyboard events to master
WIP support for derby owners club (except derbyoc2)
Slaves now send keyboard events to master, which handles them and sets
input focus to its own window, and attempts to raise it.
Half the sync interval for F355 to fix sync regression.
2026-07-04 17:34:11 +02:00
Flyinghead c286463d6b aica: properly align RAM reads/writes from sh4. Avoid crash on last byte 2026-07-04 17:34:11 +02:00
Flyinghead 3b809c8762 jvs: disable eeprom load for slaves. nvmem: fix doc sat/main conflict
Save derby owners club flash file (.nvmem) under a different name for
the main screen.
2026-07-04 17:34:11 +02:00
Flyinghead 3aa2c67782 support quoted config values on command line 2026-07-04 17:34:11 +02:00
Flyinghead c7ef5c6a0a win32 build fix 2026-07-04 17:34:11 +02:00
Flyinghead e2b45eb551 libretro build fix 2026-07-04 17:34:11 +02:00
Flyinghead eb5f74258f naomi: multiboard support for alpilot and sstrkfgt 2026-07-04 17:34:11 +02:00
Flyinghead 5b8a0ae038 naomi: move multiboard code to .cpp. less log 2026-07-04 17:34:11 +02:00
Flyinghead 12d9caff93 maple: create MIE device on multiboard slaves 2026-07-04 17:34:11 +02:00
Flyinghead 10057deebf naomi: reset M3 comm board data when disconnecting 2026-07-04 17:34:10 +02:00
github-actions[bot] 324d9c10bb Fetch translations & Recreate libretro_core_options_intl.h 2026-07-03 22:35:52 +00:00
Flyinghead e64b28a759 aica: fix x86 DSP dynarec
Regression introduced by 8176646c5a
Issue #2249
2026-07-03 23:43:34 +02:00
shinyquagsire23 675ce1b63b arm7: Implement MSR fully in AICA dynarec
This fixes audio in tip-of-branch KallistiOS versions, due to an FIQ
enable being no-op'd.
2026-07-02 16:15:55 +02:00
Flyinghead 63d19693bc ui: show game ID on pause menu
Issue #2389
2026-07-01 13:09:43 +02:00
Edward Li 8b5cb7513b Fix content grid layout during UI scale changes 2026-06-30 11:18:33 +02:00
Edward Li 940f5bb911 SDL: refresh display metrics on monitor changes 2026-06-30 11:18:33 +02:00
github-actions[bot] 7ec978e852 Fetch translations & Recreate libretro_core_options_intl.h 2026-06-26 22:39:45 +00:00
Edward Li d70a265804 Delay OIT PVR resource allocation until game start 2026-06-24 13:16:07 +02:00
Flyinghead 1251a53aed github actions: stick to windows 2022 for now
[ci skip]
2026-06-24 09:20:48 +02:00
jpovixwm fc7b8c247d vk oit: honor maxStorageBufferRange and use BDA if available 2026-06-23 19:45:52 +02:00
Engdyn 59ab660649 docs: fix typo (#2383)
[ci skip]
2026-06-23 09:44:25 +02:00
Flyinghead 526b52b141 alienfnt: online watch support. use original dns name for server
Use original www.alienfrontserver.com domain name in default nvmem
picotcp: Open UDP port 7980 to receive watched game state
2026-06-21 19:15:55 +02:00
github-actions[bot] c6fe11017c Fetch translations & Recreate libretro_core_options_intl.h 2026-06-19 22:33:20 +00:00
Immersion95 bcbc43fad6 sh4: end the current timeslice on Stop()
When Stop() is requested, also clear the SH4 cycle counter so the current
timeslice ends as soon as possible.

This prevents execution from continuing past a requested stop point.
2026-06-18 12:45:10 +02:00
david.schkalee d7c59e9ab5 achievements: retry login when host override is set and login previously failed 2026-06-16 14:47:12 +02:00
David Schkalee 14487d96ec android: allow cleartext HTTP to 127.0.0.1 for local proxy image loading (#2379)
[ci skip]
2026-06-16 14:46:52 +02:00
scribam f52c275c90 vk: use enums from vulkan-hpp (#2376)
[ci skip]
2026-06-16 14:46:18 +02:00
Flyinghead a27a81ba18 Merge remote-tracking branch 'origin/master' into dev 2026-06-14 10:56:38 +02:00
Flyinghead d1d7373d43 naomi: add clubkrt eeprom. Fix clubkrt network config 2026-06-14 10:56:12 +02:00
flyinghead 80cc71952e Crowdin Translations (#2374)
[ci skip]
2026-06-14 10:55:09 +02:00
Flyinghead 8c155b02c8 libretro build fix 2026-06-13 14:50:42 +02:00
Flyinghead 5ff2bc0c42 i18n: update flycast.pot 2026-06-13 14:33:56 +02:00
Flyinghead e435d72aa8 android: use gamesdk frame-pacing library
Use games-frame-pacing library (aka Swappy) for OpenGL and Vulkan.
Implements proper frame pacing for 30 FPS games and/or 120+ Hz displays.
Don't duplicate Vulkan frames.
2026-06-13 14:27:59 +02:00
github-actions[bot] c0f2cf4319 Fetch translations & Recreate libretro_core_options_intl.h 2026-06-12 22:46:27 +00:00
Flyinghead f68e60fe92 wsi: get rid of static graphics contexts 2026-06-11 17:41:20 +02:00
Flyinghead 1e60ef78c7 vk: detect crash when pipeline file is corrupted and ignore on next run
Use emu.cfg flag to detect crash. Ignore pipeline cache if flag remains
set from previous run.
Use assert() instead of verify()
2026-06-11 10:03:27 +02:00
david.schkalee a9c5ef8b41 Add RetroAchievements host override via broadcast 2026-06-11 09:42:53 +02:00
Flyinghead d234bcf7d9 github actions: stick to windows 2022 for now
[ci skip]
2026-06-11 09:38:31 +02:00
github-actions[bot] 751295151e Fetch translations & Recreate libretro_core_options_intl.h 2026-06-05 22:39:03 +00:00
Flyinghead 619082fe46 android: back to target sdk 35 (android 15)
Until we figure out this fucking android 16 Back key mess.
Revert 7fc81607e3
2026-06-05 12:35:37 +02:00
Flyinghead 7fc81607e3 android: ignore Back key on android 13+ with OnBackInvokedCallback
Since targeting android 16 (6a071668be)
the Back key isn't handled by Flycast anymore on android 16, due to
breaking changes by Google. Install an OnBackInvokedCallback to ignore
default handling of the key and get back to normal.
Tentative fix for
Issue #2369
2026-06-05 08:54:19 +02:00
Flyinghead 1e68c83356 rend: avoid verify() 2026-06-04 23:29:38 +02:00
Flyinghead 436e125d59 naomi: disable audio on sega strike fighter slaves
slave audio is connected to a "bass shaker" and shouldn't be sent to
regular speakers.
Issue #1816
2026-06-04 23:17:33 +02:00
Flyinghead e4c96293e4 input: unmapping a button shoud make the InputMapping dirty
Fixes mapping not being saved when only unmapping buttons.
Issue #2367
2026-06-04 11:04:25 +02:00
Flyinghead 3ca5099fb0 Revert "libretro: report 60.00 fps for NTSC instead of 59.94 (#2333)"
This reverts commit 2c7c687240.
2026-06-04 10:53:08 +02:00
flyinghead 444635372e Crowdin Translations (#2363) 2026-06-03 12:48:14 +02:00
Flyinghead abf284fc2d ui: rename Disabled network mode to Native to avoid confusion
[ci skip]
2026-06-03 12:47:26 +02:00
Flyinghead b4f89c9926 vk: fix regression on android due to eSuboptimalKHR handling
presentKHR() returns eSuboptimalKHR on Android if not rendering at the
native resolution and orientation of the screen (often vertical).
2026-06-03 12:45:01 +02:00
Flyinghead b043043568 i18n: update flycast.pot 2026-06-02 16:29:51 +02:00
Flyinghead 8542ff2dc4 naomi network QoL improvements. fix jvs eeprom regression
Support for satellite nodes (vonot, club kart)
Start network game when known max of nodes is reached.
Add Start Now button to network sync wait window.

Ignore MultiboardSlaves option if not active when loading eeprom. Rely
on game id instead.
2026-06-02 16:24:14 +02:00
Craig Carnell 34f2a70a6b webOS: use GLES 3 on 64-bit build 2026-06-01 15:27:19 +02:00
vkedwardli 3d85218f0a Simplify custom texture directory discovery (#2341)
[ci ckip]
2026-06-01 15:26:52 +02:00
2c7c687240 libretro: report 60.00 fps for NTSC instead of 59.94 (#2333)
Aligns the reported core framerate with the typical 60 Hz host display
refresh, eliminating the periodic frame-pacing skip that drift between
59.94 fps content and a 60 Hz monitor produces under strict V-Sync or
Black Frame Insertion (the skip happens roughly every 17 seconds).

PAL (50.0 Hz) is unchanged. The 240p VGA rate is also moved to 60.0
so it benefits from the same alignment. Audio sample rate stays at
44100 Hz; the per-run sample count `sample_rate / fps` adjusts
automatically.

Co-authored-by: jf624708-blip <[email protected]>
Co-authored-by: Claude Opus 4.7 (1M context) <[email protected]>

[ci skip]
2026-06-01 15:26:08 +02:00
Flyinghead f6dead8c39 naomi: derbyoc2 support. fix eeprom mismatch with multiboard slaves.
Save multiboard eeprom under a different name, same as nvmem
derbyoc2 uses the same card reader as WE
Disable M3Comm on slaves
Use non-free play default eeproms with proper DOC coin settings
Slave's eeprom was configured as satellite in some cases
2026-06-01 14:59:42 +02:00
Flyinghead 6de588bf57 sdl: ignore hdpi scaling less than 1.2 2026-05-31 19:41:42 +02:00
Flyinghead 1536d588f6 naomi: load flash after rom so game id is known (multiboard)
Fixes loading of correct DOC main flash at boot (instead of loading the
satellite one).
2026-05-31 13:37:57 +02:00
scribam d3c64bf1f5 ci: update cross-platform-actions and openbsd (#2358)
[ci skip]
2026-05-31 13:35:02 +02:00
scribam 2f10d39910 ios: remove [super dealloc] when using arc (#2356)
[ci skip]
2026-05-31 13:34:38 +02:00
Isaac Marovitz 7e917bc53a Restructure core/rend CMakeLists (#2354)
[ci skip]
2026-05-31 13:34:13 +02:00
Flyinghead 4a9ca07838 naomi: support for dinokich and loveber3cn 2026-05-30 14:44:42 +02:00
scribam 3e81e56499 rend: rename gl.quad to gl.quadDrawer (#2355)
Prevent confusion with the macro "quad" from newlib (devkitpro, vitasdk)

[ci skip]
2026-05-30 14:35:30 +02:00
mehlis 39c991671f add HiDPI/scale factor support for Linux (#2326)
Respects GNOME scale settings via:
  1. GDK_SCALE environment variable
  2. GNOME settings (gsettings)
  3. SDL DPI detection fallback

[ci skip]
2026-05-30 14:31:54 +02:00
Joe Mattiello 10dee60cda dx9: route renderer teardown on vsync switch through rend_term_renderer (#2325)
Made-with: Cursor

[ci skip]
2026-05-30 14:30:00 +02:00
Joe Mattiello 6925586fa2 vk: enable portability enumeration on Apple platforms (#2324)
Since Vulkan loader 1.3.216 the loader no longer enumerates portability ICDs unless the application
sets VK_INSTANCE_CREATE_ENUMERATE_PORTABILITY_BIT_KHR and enables the
VK_KHR_portability_enumeration instance extension. Without this,
vkCreateInstance returns VK_ERROR_INCOMPATIBLE_DRIVER on macOS / iOS
where MoltenVK is the only available ICD, and Flycast fails to start
the Vulkan renderer entirely.

Made-with: Cursor

[ci skip]
2026-05-30 14:28:31 +02:00
Joe Mattiello d447d3d2db vk: recreate swapchain on suboptimal present result (#2323)
vulkan-hpp lists eSuboptimalKHR as a success code for presentKHR, so
the call returns the value rather than throwing. The existing code
ignores it via (void)res and the surface stays stuck in a sub-optimal
configuration until something else triggers a resize.

In practice this shows up across DPI / window-resize / display-mode
changes and on MoltenVK, where the driver more aggressively reports
the swapchain as suboptimal but still presents. Set resized so the
swapchain is rebuilt on the next frame, matching the existing
out-of-date handling that already comes through vk::SystemError.

Made-with: Cursor

[ci skip]
2026-05-30 14:24:20 +02:00
Joe Mattiello 55a8d6094b pvr: bound render-message queue and drain it on renderer term to fix libretro audio drift (#2322)
pvr: bound PvrMessageQueue depth to prevent producer/consumer drift
pvr: drain queue before destroying the renderer in rend_term_renderer

Made-with: Cursor

[ci skip]
2026-05-30 14:13:06 +02:00
github-actions[bot] 8492a4e5e2 Fetch translations & Recreate libretro_core_options_intl.h 2026-05-29 22:44:07 +00:00
Flyinghead 1370b74fb9 fix non-multiboard build 2026-05-29 12:58:01 +02:00
Flyinghead 46deb1dd7a naomi: use smart ptrs for multiboard and M3 comm. Refactoring 2026-05-29 10:03:13 +02:00
Flyinghead 7baf5c0a5b win32: escape double quotes in cmd line args with backslash 2026-05-29 09:57:03 +02:00
Flyinghead ce19baebc5 naomi: fix derbyoc[w] network slots. rename some comm board registers
derbyoc and derbyocw expect the slave count, not the total node count,
when the M3 board connects.
2026-05-29 09:55:41 +02:00
Flyinghead 00892506d7 compile warnings
Issue #2347
2026-05-28 16:24:50 +02:00
flyinghead 34bb486e54 New translations flycast.pot (Spanish) (#2339)
[ci skip]
2026-05-28 16:24:10 +02:00
Flyinghead 1583b75dd7 multiboard: derby owners club support. route keyboard events to master
WIP support for derby owners club (except derbyoc2)
Slaves now send keyboard events to master, which handles them and sets
input focus to its own window, and attempts to raise it.
Half the sync interval for F355 to fix sync regression.
2026-05-28 14:41:03 +02:00
Flyinghead 569dc1cd1a aica: properly align RAM reads/writes from sh4. Avoid crash on last byte 2026-05-27 17:46:45 +02:00
Flyinghead 8c0da03a15 jvs: disable eeprom load for slaves. nvmem: fix doc sat/main conflict
Save derby owners club flash file (.nvmem) under a different name for
the main screen.
2026-05-27 17:44:07 +02:00
Flyinghead 3153ac762e support quoted config values on command line 2026-05-27 17:40:23 +02:00
Flyinghead b49dd9d184 win32 build fix 2026-05-25 21:44:30 +02:00
Flyinghead 4941fdf934 libretro build fix 2026-05-25 20:32:36 +02:00
Flyinghead 81d4756fe9 Merge remote-tracking branch 'origin/master' into dev 2026-05-25 19:59:53 +02:00
Flyinghead f35f0d51ac naomi: multiboard support for alpilot and sstrkfgt 2026-05-25 19:43:32 +02:00
Flyinghead db8147e91f naomi: move multiboard code to .cpp. less log 2026-05-25 15:25:31 +02:00
Flyinghead 91d1430b42 maple: create MIE device on multiboard slaves 2026-05-25 14:47:25 +02:00
Flyinghead cc1f7ca1c1 naomi: reset M3 comm board data when disconnecting 2026-05-25 14:13:16 +02:00
github-actions[bot] bb304918f8 Fetch translations & Recreate libretro_core_options_intl.h 2026-05-22 22:35:23 +00:00
Flyinghead f3047b2ef1 Virtual-On: Oratorio Tangram (JP) widescreen cheat 2026-05-20 18:00:08 +02:00
github-actions[bot] 3e60beb69e Fetch translations & Recreate libretro_core_options_intl.h 2026-05-15 22:32:59 +00:00
Flyinghead 9310bf4799 7z: the seek method must return the current position
Fixes broken 7z support after 16345dfdd0.
Issue #2346
2026-05-15 13:04:23 +02:00
Flyinghead d39b89bfb3 Merge remote-tracking branch 'origin/dev' 2026-05-15 13:01:33 +02:00
Flyinghead ce5f9d32db dcnet: virtual-on support 2026-05-14 20:57:42 +02:00
Edward Li cf144bcfa7 Warm up C++ exceptions on Switch startup 2026-05-14 17:08:51 +02:00
Flyinghead 22d029ba1e egl: check if eglInitialize is null
Fixes MINIDUMP-93J
[ci ckip]
2026-05-14 17:08:36 +02:00
Flyinghead c00a62c909 imgread: check track count when generating ToC
Fixes MINIDUMP-962
2026-05-14 17:05:55 +02:00
Flyinghead 8acadcb66c aica: sample address bound checking
Avoid crashes when audio RAM mirroring is missing (Switch)
2026-05-14 17:00:34 +02:00
Clownacy 4be8a48466 Libretro: Make m3u files support VFS also. 2026-05-10 17:51:41 +02:00
Flyinghead 07e435c55f Merge remote-tracking branch 'origin/dev' 2026-05-09 18:59:15 +02:00
Flyinghead ead8ab2223 dricas auth cheat for Nettou Golf. various minor changes 2026-05-09 18:23:14 +02:00
Flyinghead be1b7f2fce flash: fix netmask for bomberman BBA 2026-05-09 18:17:30 +02:00
scribam 4f356e846a android: update project 2026-05-09 15:55:56 +02:00
flyinghead 85d6a0125c Crowdin Translations (#2329)
[ci skip]
2026-05-09 15:55:29 +02:00
github-actions[bot] 1b6f880ccf Fetch translations & Recreate libretro_core_options_intl.h 2026-05-08 22:33:10 +00:00
github-actions[bot] 744e9e4aa5 Fetch translations & Recreate libretro_core_options_intl.h 2026-05-01 22:31:35 +00:00
flyinghead 5993a20644 Crowdin Translations (Turkish) 2026-04-25 17:11:52 +02:00
Flyinghead 15ecfcc7e8 i18n: update flycast.pot
[ci skip]
2026-04-25 17:11:16 +02:00
Flyinghead 958517a621 force real BIOS for MIL-CDs
Issue #2321
2026-04-25 17:00:54 +02:00
github-actions[bot] acb4966f54 Fetch translations & Recreate libretro_core_options_intl.h 2026-04-24 22:27:37 +00:00
flyinghead 25b2602751 Crowdin Translations (#2319) 2026-04-22 17:28:12 +02:00
Flyinghead 31d5ddb920 rend: simplify scissor/dimensions handling. reduce code duplication
Fix regression with split-screen multi render in Quake 3. Caused by
aef3d2d337.
Move duplicated code into transform_matrix. Handle framebuffer
dimensions/clipping scaling depending on render mode (Full FB emu, copy
RTT to vram).
Handle RTT and screen render similarly when possible.
[ci skip]
2026-04-22 17:27:26 +02:00
Flyinghead dfbbf8913c win32: init winsock in main() 2026-04-20 14:48:05 +02:00
Flyinghead 4505fc5787 macOS: allow building without vulkan 2026-04-20 14:42:29 +02:00
Marc Gardent e279abd0b5 UI: Add option to discard video and animated textures during dump (#2318)
ui: add option to discard video and animated textures during dump
Implement a filter to prevent flooding the texture dump directory with
thousands of redundant frames from FMVs or procedural effects.
- Add a single 'Discard Video and Animated Textures' option (enabled by default).
- Filter logic: skip dumping if the texture was already updated (Updates > 1)
  or if it uses the YUV format (video).
- Update UI in Advanced settings, French translations, and i18n template.
2026-04-20 13:31:56 +02:00
github-actions[bot] ae5416a5cb Fetch translations & Recreate libretro_core_options_intl.h 2026-04-17 22:24:46 +00:00
Flyinghead aef3d2d337 rend: fix clipping with vscale>1. Get rid of scissor matrix
Fix framebuffer clipping when scale>1 (Stretched PAL). Issue #728
Get rid of ScissorMatrix.
Don't scale framebuffer clipping rect in full FB emu.
Refactor rend_context. Use Rect and ivec2.
2026-04-16 18:15:05 +02:00
Flyinghead 9bc041873a dx9 build fix 2026-04-15 17:56:34 +02:00
Flyinghead ccd7525c0b fix explicit template instantiations 2026-04-15 17:32:18 +02:00
Edward Li ce2f566ef3 docs: clarify download channels and desktop build scripts 2026-04-15 17:14:01 +02:00
vkedwardli a3373af43d Windows: support vanilla bootstrap (#2316)
[ci skip]
2026-04-15 17:13:44 +02:00
Flyinghead cc9f764a64 Merge remote-tracking branch 'origin/master' into dev
[ci skip]
2026-04-15 17:13:22 +02:00
Flyinghead 677dc930f8 rend: make transform_matrix.cpp 2026-04-15 17:07:56 +02:00
Flyinghead 99da46b4cc input: close all gamepads/keyboards/mice when terminating
Failure to do so may cause a static deinit order error on exit.
android: use AKEYCODE_ and AMOTION_EVENT_ system constants
2026-04-14 11:57:53 +02:00
Flyinghead 8c773f5ab0 chd: fix use-after-free crash when chd_open_file fails
chd_open_file (almost) always closes the passed file even in case of
error.
Fixes MINIDUMP-8ZK
2026-04-14 11:25:05 +02:00
Flyinghead 00ec2b15a3 samba: round/clamp/bias analog axis values as needed by the game
Issue #2282
2026-04-13 10:05:11 +02:00
Flyinghead 321bdaf08b Force PAL for Evil Twin - Cyprien's Chronicles
Issue #2048
2026-04-12 15:47:35 +02:00
Flyinghead aef9266d92 imgui: g.Font used instead of g.FontBaked
Fixes MINIDUMP-8WK
2026-04-12 15:42:41 +02:00
ClownacyandClownacy 16345dfdd0 Add support for libretro VFS (and file IO indirection in general). (#2299)
* Use custom file IO callbacks for libraries: libzip, 7zip, libchdr, and stb_image.
* Add file IO indirection.
* Add libretro VFS implementation.

---------

Co-authored-by: Clownacy <[email protected]>
2026-04-11 11:26:51 +02:00
github-actions[bot] ed41f982c1 Fetch translations & Recreate libretro_core_options_intl.h 2026-04-10 22:24:04 +00:00
Edward Li b95e7fc4cc deps: update Vulkan-Headers and VulkanMemoryAllocator 2026-04-10 15:49:29 +02:00
Edward Li d16548fc35 macOS: support vanilla macOS bootstrap and update Vulkan SDK install 2026-04-10 15:49:29 +02:00
vkedwardli ad855bd812 add Pause feature
[ci skip]
2026-04-10 15:49:05 +02:00
flyinghead 3395780fd2 Crowdin Translations: Spanish, Swedish 2026-04-09 19:16:12 +02:00
Flyinghead 602191ebe7 generic dricas auth bypass for WinCE games
Intercept the GetProcAddress call and replace the function with an empty
but successful stub.
[ci skip]
2026-04-09 19:15:31 +02:00
Flyinghead ad03e10c16 i18n: protect tinygettext log info set with mutex
Log functions can be called concurrently by multiple threads (game
loader, main, ...)
Fixes MINIDUMP-8WR
2026-04-07 11:55:42 +02:00
Flyinghead f58c86f030 vk: validation error in single-threaded due to timeout
In single-threaded mode, direct framebuffer writes are handled in
vblank, but the Emulator then assumes a timeout has occurred, and the
frame is rendered but not presented.
Ignore this invalid timeout and present the frame is all cases. The
context won't do anything if no frame has been rendered.
Issue #2302
2026-04-06 12:16:31 +02:00
Immersion95 8f2351b1b7 vk: fix non-threaded freeze on AMD GPUs (#2303)
Reset framebufferRendered after PresentFrame() in the Vulkan
framebuffer presentation path.

This fixes a standalone Vulkan freeze on AMD GPUs in non-threaded
mode, where the emulator could hang after the Sega logo until an
alt-tab.
[ci skip]
2026-04-06 12:09:26 +02:00
github-actions[bot] 4dbe02895e Fetch translations & Recreate libretro_core_options_intl.h 2026-04-03 22:23:33 +00:00
Edward Li 4b4d0b6db2 macOS: Use MAP_JIT from now on 2026-04-03 13:53:54 +02:00
flyinghead 3d81741df6 Crowdin Translations (#2291)
[ci skip]
2026-04-03 13:53:16 +02:00
Flyinghead 2814b11bc3 dricas auth bypass cheat for Rune Jade
[ci skip]
2026-04-03 13:52:52 +02:00
Edward Li a5f4ec6310 rend: use thread-local flip state 2026-03-31 15:10:08 +02:00
Craig bd0bf5e3ba libretro: fix typo in gitlab-ci (#2296)
[ci skip]
2026-03-31 15:09:42 +02:00
Craig 99ab85ffe7 webOS: add to gitlab CI (#2295)
[ci skip]
2026-03-31 11:20:14 +02:00
Eric WarmenhovenandClaude Opus 4.6 69d9fa5332 add linux-aarch64 build (#2290)
Co-authored-by: Claude Opus 4.6 (1M context) <[email protected]>

[ci skip]
2026-03-30 20:57:46 +02:00
Flyinghead 476ed40c08 dx11 build fix 2026-03-30 20:54:39 +02:00
Flyinghead c3c710ec07 clubk: set correct node type (disabled/master/slave) and car# 2026-03-30 20:41:01 +02:00
Flyinghead bba324169a ui: replace NULL by nullptr. No functional change 2026-03-30 18:43:35 +02:00
Flyinghead cb364c9ed0 pvr: avoid crash on invalid poly params
Fixes MINIDUMP-8PX
2026-03-30 18:27:58 +02:00
Flyinghead 01dc4f6ec9 naomi: hide implementation details. no functional change 2026-03-30 18:14:39 +02:00
Flyinghead 3e96eec972 rend: clamp config::PerPixelLayers to avoid shader compile errors
Make sure shaders/pipelines are reset when config changes.
Fixes MINIDUMP-8SY
2026-03-30 17:41:41 +02:00
Flyinghead c7780ec324 more robust elf loading
Fixes MINIDUMP-8G9 MINIDUMP-8NH
2026-03-30 17:21:23 +02:00
Flyinghead fae9ed63b5 i18n: update flycast.pot 2026-03-30 16:19:00 +02:00
Edward Li 0bf7570db2 Fix Full Screen menu shortcut on newer macOS versions 2026-03-30 15:41:36 +02:00
Edward Li 625fce5d52 Temp fix for C++17 and C++/CX 2026-03-29 18:35:38 +02:00
Edward Li f5cb17bf82 gui_font.cpp 2026-03-29 18:35:38 +02:00
Edward Li 31a69ac5b8 Reject COLRv1 fonts (latest Fedora and possibly some Androids) 2026-03-29 18:35:38 +02:00
Edward Li 1f4a99ae7f Android: 32-bit unicode support 2026-03-29 18:35:38 +02:00
Edward Li 993250403d Replace largeFont with uiLargeFontSize() and/or boldFont 2026-03-29 18:35:38 +02:00
Edward Li c1e0d07e23 refactor font loading system to support weights, better CJK fallback, dynamic font file path from fontconfig for linux 2026-03-29 18:35:38 +02:00
Edward Li b73190bdb5 Add freetype lib 2026-03-29 18:35:38 +02:00
Edward Li 8c13a7d832 imgui: upgrade to 1.92.6 2026-03-29 18:35:38 +02:00
Edward Li 0cf01f83a6 Font weight/size tuning 2026-03-29 18:35:38 +02:00
Edward Li 6a0c4772e8 Load font based on locale priorities: CJK > User Locale > Other Locale, remove all glyph ranges 2026-03-29 18:35:38 +02:00
Edward Li 13a2639f8d Update imgui backends 2026-03-29 18:35:38 +02:00
Flyinghead 3f6928595a ui: don't call i18n in static init. msvc utf-8 source option
Don't call i18n::T in static init to avoid potential static init order
issue. Tentative fix for Issue #2285
Call i18n::init() in i18n::reloadLanguage()

Set source files to utf-8 for MSVC
2026-03-28 13:02:34 +01:00
Flyinghead c43109deeb ui: handle language change for Controls tab
lua update
2026-03-26 16:18:11 +01:00
Flyinghead 42555bcf4b emu: add locale change event. Simplify i18n UI 2026-03-25 18:25:11 +01:00
Edward Li 80683f9670 Language Picker 2026-03-25 17:13:09 +01:00
flyinghead 875156510b New translations strings.xml (Turkish)
New translations for {languageName}
2026-03-24 19:08:58 +01:00
Flyinghead f5da7ffb8d modem: proper support of V.42 RNR
[ci skip]
2026-03-24 19:08:18 +01:00
Flyinghead 63c2be61a4 Merge remote-tracking branch 'origin/master' into dev 2026-03-24 19:00:12 +01:00
Flyinghead 9954019b13 win32 build fix 2026-03-22 17:26:32 +01:00
Flyinghead 97a0396af8 don't allocate memory when logging
Can lead to deadlock if crash happens in malloc/free
2026-03-22 17:15:43 +01:00
Flyinghead f301ad1f4a cheat: segfault accessing config db in destructor 2026-03-22 17:05:31 +01:00
Flyinghead 6d2befc804 http: allow req and resp headers with GET. 30 s timeout. linux tests
Add http::get function with request and response header params.
Set a 30 s connection timeout for all http requests (except on macOS).
Linux tests with postb.in
2026-03-22 15:39:51 +01:00
Flyinghead 7f26348ed8 android: google auto backup support
When enabled, all .cfg and all files in data except savestates are
backed up. Only the direct children of data are considered so boxart,
wwwcache, texdump, etc. are excluded.
2026-03-21 18:36:55 +01:00
flyinghead ecdf05fef8 Crowdin Translations (#2273) 2026-03-21 12:21:24 +01:00
Flyinghead 45c31c149d sdl: test code leftover
[ci skip]
2026-03-21 12:20:59 +01:00
Flyinghead 45e9b720d1 no swap interval support in dx9. Fix rounding of refresh rate on windows
DX9 windowed mode only supports a swap interval of one (and immediate).
Windows reports a refresh rate of 59.94 Hz or 119.98 Hz, which is
rounded down by SDL. This causes the calculated swap interval for 30 FPS
to be less than 2 (for 60 Hz).
Issue #520
2026-03-20 14:09:22 +01:00
PLCodingStuff 05b270f05c Added missing Linux build requirements to README 2026-03-20 13:46:46 +01:00
Flyinghead 1120aa7fa1 ui: make game filter match both filename and game title 2026-03-19 20:36:18 +01:00
Flyinghead 8d58f6a8db i18n: update pot 2026-03-19 19:45:54 +01:00
Flyinghead e31b0cfd79 missing localizable string 2026-03-19 19:44:16 +01:00
Flyinghead 9feee44de7 i18n: update pot 2026-03-17 16:10:15 +01:00
Flyinghead fb69bd8fbc rend: Detect game FPS and adjust gl/dx swap interval
Detect games running at 30 FPS and double the opengl or directx 9/11
swap interval.
Issue #520
2026-03-17 16:01:42 +01:00
Flyinghead 4ef64d1881 Touch screen emulation for Fish Life
Issue #2234
2026-03-17 15:08:35 +01:00
github-actions[bot] b71fb72f0a Fetch translations & Recreate libretro_core_options_intl.h 2026-03-13 22:22:51 +00:00
Flyinghead c887068253 delete dcnet utility for dreampi
Use https://github.com/scrivanidc/dreampi_custom_scripts instead
2026-03-13 14:21:22 +01:00
Flyinghead 23073131ad ooga booga cheat to disable net state check
Fixes network error when playing against real console (using patched
game as well)
2026-03-13 14:18:22 +01:00
Flyinghead 273fc8fbdd force DCNet for Power Smash and Pro Yakyuu Team de Asobou Net! 2026-03-12 11:30:48 +01:00
scribam ade2d1dece android: update project 2026-03-11 19:20:39 +01:00
scribam 48f9db752e readme: add packaging status (#2266)
[ci skip]
2026-03-11 19:20:20 +01:00
github-actions[bot] adabc694e7 Fetch translations & Recreate libretro_core_options_intl.h 2026-03-06 22:23:21 +00:00
github-actions[bot] 94e9b349f9 Fetch translations & Recreate libretro_core_options_intl.h 2026-02-27 22:22:35 +00:00
eadmaster 3eef0196cf added in-ram savestates (#2252) 2026-02-25 09:52:16 +01:00
vkedwardli 55d310be71 i18n: use GetUserPreferredUILanguages on Windows and NSLocale on macOS (#2256) 2026-02-23 20:34:28 +01:00
github-actions[bot] 3339416089 Fetch translations & Recreate libretro_core_options_intl.h 2026-02-20 22:22:51 +00:00
Flyinghead 2c48c0188a input: add null check 2026-02-20 13:08:13 +01:00
Flyinghead d72856c1cc dcnet: ping APs 5 times instead of 3 2026-02-20 13:05:27 +01:00
Flyinghead ba5b3c71ec Merge remote-tracking branch 'origin/dev' 2026-02-16 17:01:44 +01:00
Flyinghead 75feafbbf0 vk: use mailbox present mode if immediate mode isn't available
Fixes fast forward not working on mobile.
Issue #2219
2026-02-11 18:06:25 +01:00
Flyinghead 5b08957655 input: reset button/axis when unmapping
to avoid the button staying pressed or the axis deflected until
remapped.
2026-02-11 17:56:10 +01:00
Flyinghead 788cbfb64b aica: don't let CA get to close to LEA
VC 2K sport games sometimes miss the end of the sound if the last
reported CA is >= LEA-2. This result in a "stuck" audio channel and
leads to a game freeze after a while (NFL) or when exiting the game
(NBA).
Issue #1481
Issue #2226
2026-02-11 14:57:50 +01:00
Flyinghead 30b42a8a74 sh4 dyna: lighter headers
no functional change
2026-02-11 14:41:00 +01:00
Kyuyrii a6dc61f064 Adding StartupWMClass 2026-02-10 16:54:02 +01:00
Edward Li cd6002595e macOS: Improve menu item handling and add traditional fullscreen shortcuts 2026-02-10 16:53:23 +01:00
Flyinghead 45ebe22d40 enable DCNet by default 2026-02-10 15:31:21 +01:00
Flyinghead 805d014b5d pvr: reset the YUV converter on startrender as on real hw
The YUV converter is reset on STARTRENDER on real hardware.
TA_YUV_TEX_CNT is reset when more YUV data is sent, not when block count
is reached.
Fixes skipping image in pl_mpeg
2026-02-10 15:22:11 +01:00
github-actions[bot] c577e29f43 Fetch translations & Recreate libretro_core_options_intl.h 2026-02-06 22:22:08 +00:00
Flyinghead 02ed5fa340 ui: add notification for next/previous state slot 2026-02-05 17:46:31 +01:00
Flyinghead b31f92f8ea sh4: increase timers resolution
Use cycle_counter to increase the precision of timers.
Use 64-bit where needed to handle underflows. Avoid double operations on
32-bit and 64-bit data.
Add timer test.
Fixes lack of randmness in Soul Calibur. Issue #2093
2026-02-05 17:40:06 +01:00
Windsurf7 2a6e109b1d Adds next/previous save state slot hotkeys (#2229) 2026-02-02 13:50:32 +01:00
Flyinghead 821f049a79 f355: remove US and EU timeout cheats. add dricas bypass cheat for JP 2026-01-30 15:34:38 +01:00
Flyinghead fb43c91ab9 vulkan: throw in case of error instead of dying
Fixes MINIDUMP-8EG
2026-01-30 15:30:41 +01:00
Flyinghead 91ea2c8af1 jvs: refactoring. no functional change 2026-01-30 14:58:41 +01:00
Flyinghead d75b0b1810 cue: make md5 digest before track file is closed 2026-01-30 14:56:24 +01:00
Flyinghead da15c3d4a5 mushi: map P2 hit button to P1 button 4 to show on vgamepad
Also map P2 Hit button to P1 button 4 so it's accessible on mobile
devices, to enable hard mode or change game version
2026-01-29 16:40:02 +01:00
flyinghead 27b30acae4 New translations flycast.pot (Swedish)
New translations for {languageName}
2026-01-29 15:49:21 +01:00
Flyinghead cc3ff811e8 maple/jvs: fix RFID card reader regression (mushiking, vf4)
The RFID card reader/writer is an MIE device. Fixes regression uncovered
by a429fba21d.
Also fixes mushikc
[ci skip]
2026-01-29 15:49:03 +01:00
Edward Li 21eb24f86d Refactor CustomTexture lookup to FIFO for clearer priority logic 2026-01-28 10:59:33 +01:00
Flyinghead 976d3bed28 rend: fix division by zero when output fb is 0x0
Often happens with integer scaling.

Fixes MINIDUMP-8BW MINIDUMP-8CT MINIDUMP-8DP MINIDUMP-85Z
[ci skip]
2026-01-28 10:59:20 +01:00
github-actions[bot] 22e28c0a7c Fetch translations & Recreate libretro_core_options_intl.h 2026-01-23 22:21:18 +00:00
flyinghead 9504ab85be Crowdin Translations 2026-01-23 15:51:53 +01:00
Flyinghead 4254c7edf1 power smash: use symmetric modem speed. get rid of game patches
Power Smash needs a symmetric speed to avoid lag. Handle network issues
server-side and get rid of game patches.
Same for Pro Yakyuu Team de Asobou Net
[ci skip]
2026-01-23 15:51:18 +01:00
James a30c27b471 Fix issues relating to multiple MapleLink devices 2026-01-22 14:45:55 +01:00
Flyinghead f142e34973 i18n: update message file
[ci skip]
2026-01-22 14:45:18 +01:00
Flyinghead d0d3661350 maple: make a copy of the maple_device shared_ptr to avoid deletion
If a MapleLink device is disconnected during a maple dma call, the
corresponding MapleLinkVmu will be deleted. Keep it alive until the end
of the RawDma() call.
Fixes MINIDUMP-8C4
2026-01-22 14:44:21 +01:00
DarkOK ea9464f7c3 maple: implement DreamParaPara controller
a device that is very against spec
2026-01-21 16:00:40 +01:00
Flyinghead c4d757635b gdrom: stop CDDA at lead out only. CUE loader CDDA support
Fixes missing menu music in DreamParaPara
[ci skip]
2026-01-21 15:57:37 +01:00
Flyinghead a429fba21d maple: better handling of non-standard messages. some refactoring
maple: ignore the outgoing message header if the device doesn't have expansion ports. Use port and len from the DMA header.
maplelink: add virtual event handlers for game start and term. Use in DreamLink and DreamPicoPort.
2026-01-20 16:51:20 +01:00
Flyinghead 945376a3a6 pvr: correct handling of exception from Process/Render. Vulkan fix
pvr: reset the TA context and let the emu thread continue in case of renderer exception.
vulkan: reset rendering state when recreating swap chain
ui: only pause the game on renderer exception to allow the user to save a state. Disable scrolling buttons on Settings tab bar.
i18n: update source pot
2026-01-20 15:45:35 +01:00
Edward Li f86e195017 Fixes segfaults on Apple Silicon when C++ exceptions propagate through dynarec code 2026-01-19 15:04:35 +01:00
vkedwardli 75aefbb7b8 Support Windows 7 (0x0601) for MinGW builds
[ci skip]
2026-01-19 15:04:19 +01:00
James Smith 4fc65efbf0 Send gameid in dreampicoport when game is started 2026-01-19 13:36:22 +01:00
James Smith fa3c8f2117 Allow a MapleLink device to handle GetLastError commands which are needed by real VMUs 2026-01-19 13:36:22 +01:00
scribam d8ba8e409a bsd: install libusdb and lua
[ci skip]
2026-01-19 13:35:15 +01:00
Flyinghead fcdcc0d37a vk: set rendContext to null in RenderFramebuffer (failfast)
[ci skip]
2026-01-19 13:34:37 +01:00
Flyinghead 8df7e3be41 libretro: dreampotato storage support 2026-01-18 21:45:46 +01:00
Flyinghead 90301d5c31 pvr: get rid of _pvrrc global. Improve exception handling in renderers
New RendererException for fatal renderer errors.
Avoid propagation of vulkan exceptions and throw known ones instead.
Reset graphics API on error.
2026-01-18 19:36:45 +01:00
scribam 6a071668be android: modernize project 2026-01-18 11:13:39 +01:00
Edward Li 94bf208b5b imgui: CJK-aware text wrapping 2026-01-18 11:13:00 +01:00
Flyinghead 7775b3d0d0 maplelink: notify user on the first write to a remote VMU
[ci skip]
2026-01-18 11:11:48 +01:00
Flyinghead 5fa1886fdf maple: individual device reconnect. Check DreamPotato socket state
Only reconnect maple devices that have changed instead of all of them.
Check DreamPotato socket state every second.
Disconnect linked maple device when disconnected from remote device.
dreampicoport: send port when changing bus
2026-01-17 19:11:56 +01:00
Flyinghead fce8b4d6f9 dreampotato: change timeout values. disconnect on receive error
initial RTT timeout is 500 ms then 100 ms
2026-01-17 18:47:13 +01:00
Flyinghead b7bf027645 maplelink: disable savestates. Change some options default. Bug fix
Disable load/save state if at least one MapleLink with storage enabled.
Change UsePhysicalMemory default to false.
Change Per Game VMU A1 default to true.
Fix DreamLink infinite loop on exit.
dreampotato, dreamconn: don't use stringstream for output
dreampotato: libretro support (no VMU storage yet)
2026-01-17 18:47:13 +01:00
Flyinghead b7dba4b7a4 switch: build fix 2026-01-17 18:47:13 +01:00
Flyinghead 39bdd878ff lr: build fix 2026-01-17 18:47:13 +01:00
Flyinghead 16cfce7aed DreamLink refactoring
Introduce MapleLink base interface and DreamPotato implementation.
2026-01-17 18:47:01 +01:00
Flyinghead e0ad9f6125 Merge remote-tracking branch 'origin/master' into dev 2026-01-17 12:26:20 +01:00
vkedwardli 991d90d089 custom texture: Fix incorrect texture state when skipping texture update (#2193)
Disable GPU Palette texture optimization when custom texture is enabled
2026-01-17 10:17:10 +01:00
github-actions[bot] d676dae7b8 Fetch translations & Recreate libretro_core_options_intl.h 2026-01-16 22:22:05 +00:00
Flyinghead f64341d2b7 modem: fix compiler warnings and a mod 128 typo 2026-01-15 15:57:24 +01:00
Rikki Gibson f3ea74fa28 Fix DreamPotato integration (#2199)
* Revert "dreamconn: only build on win32. remove unused code"
This reverts commit 8511d1ad3c.
* Fix for access violation on exit
* Unlisten when freed
2026-01-15 10:11:10 +01:00
Flyinghead d07c09ea16 switch: ignore rumble status from SDL
Fixes rumble on Switch.
Issue #2201
[ci skip]
2026-01-15 10:10:01 +01:00
530 changed files with 61837 additions and 34123 deletions

No files matched your search

+19 -27
View File
@@ -11,13 +11,16 @@ jobs:
name: Android
runs-on: ubuntu-latest
env:
CCACHE_DIR: ${{ github.workspace }}/.ccache
steps:
- name: Set up build environment
run: |
sudo apt-get update
sudo apt-get -y install ccache cmake libcurl4-openssl-dev ninja-build
sudo apt-get -y install ccache libcurl4-openssl-dev
- uses: actions/checkout@v6
- uses: actions/checkout@v7
with:
fetch-depth: 0
submodules: recursive
@@ -28,21 +31,18 @@ jobs:
distribution: 'temurin'
cache: 'gradle'
- uses: actions/cache@v5
- uses: actions/cache@v6
with:
path: ~/.ccache
key: android-ccache-${{ github.sha }}
restore-keys: android-ccache-
path: ${{ env.CCACHE_DIR }}
key: ccache-android-${{ github.sha }}
restore-keys: ccache-android-
- name: Disable release signing
run: sed -i 's/signingConfig signingConfigs.release/signingConfig signingConfigs.debug/g' shell/android-studio/flycast/build.gradle
run: sed -i 's/signingConfigs.getByName("release")/signingConfigs.getByName("debug")/' shell/android-studio/flycast/build.gradle.kts
if: github.repository != 'flyinghead/flycast' || github.event_name != 'push'
- name: Bump version code
uses: chkfung/[email protected]
with:
gradlePath: shell/android-studio/flycast/build.gradle
versionCode: ${{ github.run_number }}
run: sed -i 's/versionCode = 8/versionCode = ${{ github.run_number }}/' shell/android-studio/flycast/build.gradle.kts
- name: Gradle
working-directory: shell/android-studio
@@ -51,7 +51,7 @@ jobs:
SENTRY_UPLOAD_URL: ${{ secrets.SENTRY_UPLOAD_URL }}
ANDROID_KEYSTORE_PASSWORD: ${{ secrets.ANDROID_KEYSTORE_PASSWORD }}
- uses: actions/upload-artifact@v6
- uses: actions/upload-artifact@v7
with:
name: flycast-release.apk
path: shell/android-studio/flycast/build/outputs/apk/release/flycast-release.apk
@@ -85,18 +85,8 @@ jobs:
rclone copy shell/android-studio/flycast/build/outputs/apk/release scaleway:flycast-builds/android/${GITHUB_REF#refs/}-$GITHUB_SHA --exclude '*.json'
if: github.repository == 'flyinghead/flycast' && github.event_name == 'push'
- name: Setup Sentry CLI
uses: mathieu-bour/setup-sentry-cli@v2
env:
SENTRY_TOKEN: ${{ secrets.SENTRY_TOKEN }}
with:
url: https://sentry.io
token: ${{ env.SENTRY_TOKEN }}
organization: flycast
project: minidump
version: 2.21.2
if: ${{ env.SENTRY_TOKEN != '' }}
- uses: getsentry/action-setup-cli@v1
- name: Upload symbols to Sentry
run: |
VERSION=$(git describe --tags --always)
@@ -105,5 +95,7 @@ jobs:
sentry-cli debug-files upload symbols
shell: bash
env:
SENTRY_TOKEN: ${{ secrets.SENTRY_TOKEN }}
if: ${{ env.SENTRY_TOKEN != '' && github.event_name == 'push' }}
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_TOKEN }}
SENTRY_ORG: flycast
SENTRY_PROJECT: minidump
if: ${{ env.SENTRY_AUTH_TOKEN != '' && github.event_name == 'push' }}
+22 -15
View File
@@ -18,36 +18,43 @@ jobs:
libretro: [ OFF, ON ]
include:
- operating_system: freebsd
version: '15.0'
pkginstall: sudo pkg update && sudo pkg install -y alsa-lib ccache cmake evdev-proto git libao libevdev libudev-devd libzip lua54 miniupnpc ninja pkgconf pulseaudio sdl2 libcdio
version: '15.1'
pkginstall: sudo pkg upgrade -y && sudo pkg install -y alsa-lib ccache cmake evdev-proto git libao libcdio libevdev libudev-devd libzip lua54 miniupnpc ninja pkgconf pulseaudio sdl2
- operating_system: netbsd
version: '10.1'
pkginstall: sudo pkgin update && sudo pkgin -y install alsa-lib ccache clang cmake git libao libzip lua54 miniupnpc ninja-build pkgconf pulseaudio SDL2 libcdio
version: '11.0'
pkginstall: sudo pkgin update && sudo pkgin -y install alsa-lib ccache clang cmake git libao libcdio libusb1 libzip lua54 miniupnpc ninja-build pkgconf pulseaudio SDL2
- operating_system: openbsd
version: '7.8'
pkginstall: sudo pkg_add -u && sudo pkg_add ccache cmake git libao libzip miniupnpc ninja pkgconf pulseaudio sdl2 libcdio
version: '8.0'
pkginstall: sudo pkg_add -u && sudo pkg_add ccache cmake git libao libcdio libusb1-1.0.30 libzip lua-5.4.8p2 miniupnpc ninja pulseaudio sdl2
exclude:
- architecture: arm64
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v7
with:
fetch-depth: 0
submodules: recursive
- uses: actions/cache@v5
- uses: actions/cache@v6
with:
path: ${{ env.CCACHE_DIR }}
key: ccache-${{ matrix.operating_system }}-${{ matrix.architecture }}-${{ github.sha }}
restore-keys: ccache-${{ matrix.operating_system }}-${{ matrix.architecture }}-
key: ccache-${{ matrix.operating_system }}-${{ matrix.architecture }}-${{ matrix.libretro }}-${{ github.sha }}
restore-keys: ccache-${{ matrix.operating_system }}-${{ matrix.architecture }}-${{ matrix.libretro }}-
- uses: cross-platform-actions/[email protected]
- name: Start VM
uses: cross-platform-actions/action@v1
with:
operating_system: ${{ matrix.operating_system }}
architecture: ${{ matrix.architecture }}
version: ${{ matrix.version }}
environment_variables: CCACHE_DIR
run: |
${{ matrix.pkginstall }}
cmake -B build -DUSE_LIBCDIO=ON -DCMAKE_BUILD_TYPE=Release -G Ninja -DLIBRETRO=${{ matrix.libretro }}
cmake --build build --config Release
- name: Set up build environment
run: ${{ matrix.pkginstall }}
shell: cpa.sh {0}
- name: CMake
run: |
cmake -B build -DUSE_LIBCDIO=ON -DCMAKE_BUILD_TYPE=Release -G Ninja -DLIBRETRO=${{ matrix.libretro }}
cmake --build build --config Release
shell: cpa.sh {0}
+68 -35
View File
@@ -21,13 +21,14 @@ jobs:
strategy:
matrix:
config:
- {name: i686-pc-windows-msvc, os: windows-latest, shell: cmd, arch: x86, cmakeArgs: -G Ninja, buildType: Release}
- {name: apple-darwin, os: macos-latest, shell: sh, cmakeArgs: -G Xcode -DUSE_DISCORD=ON, destDir: osx, buildType: RelWithDebInfo}
- {name: x86_64-pc-linux-gnu, os: ubuntu-22.04, shell: sh, cmakeArgs: -G Ninja -DUSE_DISCORD=ON -DUSE_LIBCDIO=ON, destDir: linux, buildType: RelWithDebInfo}
- {name: x86_64-pc-windows-msvc, os: windows-latest, shell: cmd, arch: x64, cmakeArgs: -G Ninja -DUSE_DISCORD=ON, buildType: Release}
- {name: x86_64-w64-mingw32, os: windows-latest, shell: 'msys2 {0}', cmakeArgs: -G Ninja -DUSE_DISCORD=ON -DUSE_LIBCDIO=ON, destDir: win, buildType: RelWithDebInfo}
- {name: libretro-x86_64-pc-linux-gnu, os: ubuntu-22.04, shell: sh, cmakeArgs: -DLIBRETRO=ON -DUSE_LIBCDIO=ON -DCMAKE_POSITION_INDEPENDENT_CODE=TRUE -G Ninja, buildType: Release}
- {name: libretro-x86_64-w64-mingw32, os: windows-latest, shell: 'msys2 {0}', cmakeArgs: -DLIBRETRO=ON -DUSE_LIBCDIO=ON -G Ninja, buildType: Release}
- { name: i686-pc-windows-msvc, os: windows-2022, shell: cmd, arch: x86, cmakeArgs: -G Ninja, buildType: Release }
- { name: apple-darwin, os: macos-latest, shell: sh, cmakeArgs: -G Xcode -DUSE_DISCORD=ON -DMOLTENVK_DYLIB=$MOLTENVK_DYLIB, destDir: osx, buildType: RelWithDebInfo, packagePath: artifact/flycast-apple-darwin.zip }
- { name: x86_64-pc-linux-gnu, os: ubuntu-22.04, shell: sh, cmakeArgs: -G Ninja -DUSE_DISCORD=ON -DUSE_LIBCDIO=ON, destDir: linux, buildType: RelWithDebInfo }
- { name: arm64-pc-linux-gnu, os: ubuntu-22.04-arm, shell: sh, cmakeArgs: -G Ninja -DUSE_DISCORD=ON -DUSE_LIBCDIO=ON, destDir: linux, buildType: RelWithDebInfo }
- { name: x86_64-pc-windows-msvc, os: windows-2022, shell: cmd, arch: x64, cmakeArgs: -G Ninja -DUSE_DISCORD=ON, buildType: Release }
- { name: x86_64-w64-mingw32, os: windows-2022, shell: 'msys2 {0}', cmakeArgs: -G Ninja -DUSE_DISCORD=ON -DUSE_LIBCDIO=ON, destDir: win, buildType: RelWithDebInfo, packagePath: artifact/flycast-x86_64-w64-mingw32.zip }
- { name: libretro-x86_64-pc-linux-gnu, os: ubuntu-22.04, shell: sh, cmakeArgs: -DLIBRETRO=ON -DUSE_LIBCDIO=ON -DCMAKE_POSITION_INDEPENDENT_CODE=TRUE -G Ninja, buildType: Release }
- { name: libretro-x86_64-w64-mingw32, os: windows-2022, shell: 'msys2 {0}', cmakeArgs: -DLIBRETRO=ON -DUSE_LIBCDIO=ON -G Ninja, buildType: Release }
steps:
- name: Set up build environment (macOS)
@@ -36,19 +37,26 @@ jobs:
brew list -1 | grep python | while read formula; do brew unlink $formula; brew link --overwrite $formula; done
brew update || :
brew install libao ldid pulseaudio
brew uninstall --ignore-dependencies zstd
VULKAN_VER=1.3.290.0 && aria2c https://sdk.lunarg.com/sdk/download/$VULKAN_VER/mac/vulkansdk-macos-$VULKAN_VER.dmg
hdiutil attach ./vulkansdk-macos-*.dmg -mountpoint /Volumes/VulkanSDK
sudo /Volumes/VulkanSDK/InstallVulkan.app/Contents/MacOS/InstallVulkan --root $HOME/VulkanSDK --accept-licenses --default-answer --confirm-command install
hdiutil detach /Volumes/VulkanSDK
echo "VULKAN_SDK=$HOME/VulkanSDK/macOS" >> $GITHUB_ENV
moltenvk_tag="v1.4.2"
moltenvk_root="$RUNNER_TEMP/MoltenVK-$moltenvk_tag"
rm -rf "$moltenvk_root"
mkdir -p "$moltenvk_root"
aria2c "https://github.com/KhronosGroup/MoltenVK/releases/download/$moltenvk_tag/MoltenVK-macos.tar" -d "$RUNNER_TEMP" -o "MoltenVK-macos-$moltenvk_tag.tar"
tar -xf "$RUNNER_TEMP/MoltenVK-macos-$moltenvk_tag.tar" -C "$moltenvk_root"
moltenvk_dylib="$(find "$moltenvk_root" -type f -path '*/macOS/*' -name libMoltenVK.dylib | head -n 1)"
if [ -z "$moltenvk_dylib" ]; then
echo "MoltenVK $moltenvk_tag was downloaded, but macOS libMoltenVK.dylib was not found." >&2
exit 1
fi
echo "MOLTENVK_DYLIB=$moltenvk_dylib" >> $GITHUB_ENV
if: runner.os == 'macOS'
- name: Set up build environment (Linux)
run: |
sudo add-apt-repository ppa:christianrauch/libdecoration
sudo apt-get update
sudo apt-get -y install ccache libao-dev libasound2-dev libevdev-dev libgl1-mesa-dev liblua5.3-dev libminiupnpc-dev libpulse-dev libsdl2-dev libudev-dev libzip-dev ninja-build libcurl4-openssl-dev libcdio-dev libfuse2 locales
sudo apt-get -y install ccache libao-dev libasound2-dev libevdev-dev libgl1-mesa-dev liblua5.3-dev libminiupnpc-dev libpulse-dev libsdl2-dev libudev-dev libzip-dev libcurl4-openssl-dev libcdio-dev libfuse2 locales
sudo apt-get -y install libwayland-dev libdecor-0-dev libaudio-dev libjack-dev libsndio-dev libsamplerate0-dev libx11-dev libxext-dev libxrandr-dev libxcursor-dev libxfixes-dev libxi-dev libxss-dev libxkbcommon-dev libdrm-dev libgbm-dev libgles2-mesa-dev libegl1-mesa-dev libdbus-1-dev libibus-1.0-dev libudev-dev fcitx-libs-dev
sudo sed -i 's/# fr_FR.UTF-8 UTF-8/fr_FR.UTF-8 UTF-8/g' /etc/locale.gen
sudo locale-gen
@@ -58,12 +66,13 @@ jobs:
uses: msys2/setup-msys2@v2
with:
msystem: MINGW64
install: git make mingw-w64-x86_64-ccache mingw-w64-x86_64-cmake mingw-w64-x86_64-lua mingw-w64-x86_64-ninja mingw-w64-x86_64-SDL2 mingw-w64-x86_64-toolchain mingw-w64-x86_64-libcdio
install: git make
pacboy: ccache:p cmake:p libcdio:p lua:p ninja:p SDL2:p toolchain:p
if: matrix.config.shell == 'msys2 {0}'
- name: Set up build environment (Windows, Visual Studio)
run: |
choco install ccache directx-sdk ninja
choco install ccache directx-sdk
echo DXSDK_DIR=C:\Program Files (x86)\Microsoft DirectX SDK (June 2010)>> %GITHUB_ENV%
if: matrix.config.shell == 'cmd'
@@ -72,16 +81,36 @@ jobs:
arch: ${{ matrix.config.arch }}
if: matrix.config.shell == 'cmd'
- uses: actions/checkout@v6
- uses: actions/checkout@v7
with:
fetch-depth: 0
submodules: recursive
- name: Compile a universal OpenMP (macOS)
run: HOMEBREW_NO_INSTALLED_DEPENDENTS_CHECK=1 HOMEBREW_DEVELOPER=1 brew reinstall --build-from-source --formula ./shell/apple/libomp.rb
- name: Compile universal dependencies (macOS)
run: |
for formula in ./shell/apple/libpng-flycast.rb; do
libname=$(basename "$formula" .rb)
HOMEBREW_NO_INSTALLED_DEPENDENTS_CHECK=1 HOMEBREW_DEVELOPER=1 brew install --build-from-source --formula "$formula" || \
HOMEBREW_NO_INSTALLED_DEPENDENTS_CHECK=1 HOMEBREW_DEVELOPER=1 brew reinstall --build-from-source --formula "$formula"
prefix=$(brew --prefix $libname)
# Find and verify the static library
libfile=$(find "$prefix/lib" -name "*.a" | head -n1)
lipo -info "$libfile"
# Export prefix for subsequent CMake steps
if [ -z "$CMAKE_PREFIX_PATH" ]; then
CMAKE_PREFIX_PATH="$prefix"
else
CMAKE_PREFIX_PATH="$CMAKE_PREFIX_PATH:$prefix"
fi
done
echo "CMAKE_PREFIX_PATH=$CMAKE_PREFIX_PATH" >> $GITHUB_ENV
# After the last brew download: brew's own curl links brew's zstd
brew uninstall --ignore-dependencies zstd
if: matrix.config.name == 'apple-darwin'
- uses: actions/cache@v5
- uses: actions/cache@v6
with:
path: ${{ env.CCACHE_DIR }}
key: ccache-${{ matrix.config.name }}-${{ github.sha }}
@@ -100,7 +129,7 @@ jobs:
cmake -B build -DCMAKE_BUILD_TYPE=${{ matrix.config.buildType }} -DCMAKE_INSTALL_PREFIX=artifact -DENABLE_CTEST=ON ${{ matrix.config.cmakeArgs }}
cmake --build build --config ${{ matrix.config.buildType }}
./build/flycast
if: matrix.config.name == 'x86_64-pc-linux-gnu'
if: matrix.config.name == 'x86_64-pc-linux-gnu' || matrix.config.name == 'arm64-pc-linux-gnu'
- name: Dump symbols
run: |
@@ -115,7 +144,7 @@ jobs:
./configure
make
sudo make install
if: matrix.config.name == 'x86_64-pc-linux-gnu'
if: matrix.config.name == 'x86_64-pc-linux-gnu' || matrix.config.name == 'arm64-pc-linux-gnu'
- name: Dump symbols and make AppImage (linux)
run: |
@@ -123,29 +152,34 @@ jobs:
dump_syms artifact/bin/flycast > symbols/flycast.sym 2>/dev/null
strip artifact/bin/flycast
shell/linux/make-appimage.sh . artifact/bin appimage
mv flycast-x86_64.AppImage artifact/bin
ARCH=$(uname -m)
mv flycast-${ARCH}.AppImage artifact/bin
rm artifact/bin/flycast
if: matrix.config.name == 'x86_64-pc-linux-gnu'
- uses: actions/upload-artifact@v6
with:
name: flycast-${{ matrix.config.name }}
path: |
artifact/bin
artifact/lib/libretro
if: matrix.config.name == 'x86_64-pc-linux-gnu' || matrix.config.name == 'arm64-pc-linux-gnu'
- name: Package app (macos)
run: |
cd artifact/bin
codesign --verify --deep --strict --all-architectures --verbose=4 Flycast.app
zip -rm flycast.app.zip Flycast.app
cp flycast.app.zip "../../${{ matrix.config.packagePath }}"
if: matrix.config.name == 'apple-darwin'
- name: Package app (windows)
run: |
powershell Compress-Archive artifact/bin/flycast.exe artifact/bin/flycast.zip
powershell Compress-Archive artifact/bin/flycast.exe artifact/bin/flycast.zip
rm artifact/bin/flycast.exe
cp artifact/bin/flycast.zip "${{ matrix.config.packagePath }}"
if: matrix.config.name == 'x86_64-w64-mingw32'
- uses: actions/upload-artifact@v7
with:
name: flycast-${{ matrix.config.name }}
archive: ${{ !matrix.config.packagePath }}
path: |
${{ matrix.config.packagePath || 'artifact/bin' }}
${{ !matrix.config.packagePath && 'artifact/lib/libretro' || '' }}
- name: Setup Rclone
uses: AnimMouse/setup-rclone@v1
with:
@@ -167,7 +201,7 @@ jobs:
project: minidump
version: 2.21.2
if: ${{ env.SENTRY_TOKEN != '' }}
- name: Upload symbols to Sentry (Windows, macOS, Linux)
run: |
VERSION=$(git describe --tags --always)
@@ -177,5 +211,4 @@ jobs:
shell: bash
env:
SENTRY_TOKEN: ${{ secrets.SENTRY_TOKEN }}
if: ${{ env.SENTRY_TOKEN != '' && (matrix.config.name == 'x86_64-w64-mingw32' || matrix.config.name == 'apple-darwin' || matrix.config.name == 'x86_64-pc-linux-gnu') && github.event_name == 'push' }}
if: ${{ env.SENTRY_TOKEN != '' && (matrix.config.name == 'x86_64-w64-mingw32' || matrix.config.name == 'apple-darwin' || matrix.config.name == 'x86_64-pc-linux-gnu' || matrix.config.name == 'arm64-pc-linux-gnu') && github.event_name == 'push' }}
+10 -8
View File
@@ -12,19 +12,22 @@ jobs:
runs-on: ubuntu-latest
container: devkitpro/devkita64:latest
env:
CCACHE_DIR: ${{ github.workspace }}/.ccache
strategy:
matrix:
config:
- {name: standalone, destDir: switch}
- {name: libretro, cmakeArgs: -DLIBRETRO=ON}
- { name: standalone, destDir: switch }
- { name: libretro, cmakeArgs: -DLIBRETRO=ON }
steps:
- name: Set up build environment
run: |
sudo apt-get update
sudo apt-get -y install ccache cmake
sudo apt-get -y install ccache gh
- uses: actions/checkout@v6
- uses: actions/checkout@v7
with:
fetch-depth: 0
submodules: recursive
@@ -32,9 +35,9 @@ jobs:
- name: Mark git repository as safe
run: git config --global --add safe.directory $PWD
- uses: actions/cache@v5
- uses: actions/cache@v6
with:
path: ~/.ccache
path: ${{ env.CCACHE_DIR }}
key: ccache-switch-${{ matrix.config.name }}-${{ github.sha }}
restore-keys: ccache-switch-${{ matrix.config.name }}-
@@ -43,7 +46,7 @@ jobs:
$DEVKITPRO/portlibs/switch/bin/aarch64-none-elf-cmake -B build -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=artifact -G Ninja ${{ matrix.config.cmakeArgs }}
cmake --build build --config Release --target install
- uses: actions/upload-artifact@v6
- uses: actions/upload-artifact@v7
with:
name: flycast-switch-${{ matrix.config.name }}
path: |
@@ -58,7 +61,6 @@ jobs:
uses: AnimMouse/setup-rclone@v1
with:
rclone_config: ${{ secrets.RCLONE_CONFIG }}
version: v1.69.1
- name: Upload to S3
run: rclone copy artifact/bin scaleway:flycast-builds/${{ matrix.config.destDir }}/${GITHUB_REF#refs/}-$GITHUB_SHA
+4 -4
View File
@@ -9,14 +9,14 @@ on:
jobs:
build:
name: UWP
runs-on: windows-latest
runs-on: windows-2022
strategy:
matrix:
arch: [ Win32, x64, ARM64 ]
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v7
with:
fetch-depth: 0
submodules: recursive
@@ -35,7 +35,7 @@ jobs:
cd build\AppPackages\flycast\flycast_*_Test
mkdir tmp
ren *.msix flycast.msix
makeappx.exe unpack /p .\flycast.msix /d tmp
makeappx unpack /p .\flycast.msix /d tmp
copy ..\..\..\Release\*.dll tmp
makeappx pack /d tmp /p ..\..\..\artifact\flycast.appx
@@ -43,7 +43,7 @@ jobs:
run: signtool sign /f shell\uwp\sign_cert.pfx /p '${{ secrets.SIGN_CERT_PWD }}' /v /fd SHA256 build\artifact\flycast.appx
if: github.repository == 'flyinghead/flycast' && github.event_name == 'push'
- uses: actions/upload-artifact@v6
- uses: actions/upload-artifact@v7
with:
name: flycast-uwp-${{ matrix.arch }}
path: build/artifact
+53 -7
View File
@@ -37,6 +37,11 @@
IOS_MINVER: 13.0
MINVER: 13.0
.core-defs-libnx:
extends: .core-defs
variables:
CORE_ARGS: -DLIBRETRO=ON -DUSE_OPENMP=OFF
.core-defs-android:
extends: .core-defs
script:
@@ -80,18 +85,23 @@ include:
################################## CONSOLES ################################
# Nintendo Switch
# - project: 'libretro-infrastructure/ci-templates'
# file: '/libnx-static.yml'
- project: 'libretro-infrastructure/ci-templates'
file: '/libnx-static-cmake.yml'
# tvOS arm64
- project: 'libretro-infrastructure/ci-templates'
file: '/tvos-cmake.yml'
#################################### MISC ##################################
# webOS 32-bit (LGTV)
- project: 'libretro-infrastructure/ci-templates'
file: '/webos-cmake.yml'
# Stages for building
stages:
- build-prepare
- build-shared
# - build-static
- build-static
##############################################################################
#################################### STAGES ##################################
@@ -124,6 +134,13 @@ libretro-build-linux-i686:
- .core-defs-linux
image: $CI_SERVER_HOST:5050/libretro-infrastructure/libretro-build-i386-ubuntu:backports
# Linux 64-bit (ARM)
libretro-build-linux-aarch64:
extends:
- .libretro-linux-cmake-aarch64
- .core-defs-linux
# MacOS 64-bit
libretro-build-osx-x64:
extends:
@@ -170,10 +187,33 @@ libretro-build-ios-arm64:
################################### CONSOLES #################################
# Nintendo Switch
#libretro-build-libnx-aarch64:
# extends:
# - .libretro-libnx-static-retroarch-master
# - .core-defs
libretro-build-libnx-aarch64:
extends:
- .libretro-libnx-static-cmake-retroarch-master
- .core-defs-libnx
# The libnx image ships CMake 3.18.4 and this tree asks for 3.22.1, so the
# job stops at configure. Put a newer one in front of it - in /tmp, since
# the job does not run as a user who can write /opt.
before_script:
- !reference [.libretro-libnx-static-cmake, before_script]
- |
CMAKE_VER=3.31.6
curl -sSL "https://github.com/Kitware/CMake/releases/download/v${CMAKE_VER}/cmake-${CMAKE_VER}-linux-x86_64.tar.gz" | tar -xz -C /tmp
export PATH="/tmp/cmake-${CMAKE_VER}-linux-x86_64/bin:$PATH"
# The template's script, with one word changed. It builds --target
# ${CORENAME}_libretro, and on Switch that target is flycast's own objects
# and nothing else. `combined` is the target CMakeLists.txt defines for
# exactly this case: it folds in xxhash, chdr, zip, elf and the resources,
# which is what RetroArch links against afterwards.
script:
- cmake $CORE_ARGS "$CMAKE_SOURCE_ROOT" -B$BUILD_DIR -DCMAKE_BUILD_TYPE=Release -DLIBRETRO_STATIC=ON -DLIBRETRO_SUFFIX=_libnx -DCMAKE_TOOLCHAIN_FILE=/opt/devkitpro/cmake/Switch.cmake -DNSWITCH=ON -DCFLAGS=-DHAVE_LIBNX=1 -DCXXFLAGS=-DHAVE_LIBNX=1
- cmake --build $BUILD_DIR --target combined --config Release -- -j $NUMPROC
- mv $BUILD_DIR/$EXTRA_PATH/${CORENAME}_libretro_libnx.a retroarch-precompiled/libretro_libnx.a
- cd retroarch-precompiled/
- make -f Makefile.libnx -j10
- mv retroarch_switch.nro ../${CORENAME}_libretro_libnx.nro
- mv retroarch_switch.elf ../${CORENAME}_libretro_libnx.elf
- cd ../
# tvOS arm64
libretro-build-tvos-arm64:
@@ -181,3 +221,9 @@ libretro-build-tvos-arm64:
- .libretro-tvos-cmake-arm64
- .core-defs-ios-arm64
#################################### MISC ##################################
# webOS 32-bit
libretro-build-webos-armv7a:
extends:
- .libretro-webos-armv7a-cmake
- .core-defs
+10 -1
View File
@@ -49,7 +49,7 @@
url = https://github.com/herumi/xbyak.git
[submodule "core/deps/libjuice"]
path = core/deps/libjuice
url = https://github.com/paullouisageneau/libjuice.git
url = https://github.com/flyinghead/libjuice.git
[submodule "core/deps/websocketpp"]
path = core/deps/websocketpp
url = https://github.com/zaphoyd/websocketpp.git
@@ -59,3 +59,12 @@
[submodule "core/deps/tinygettext"]
path = core/deps/tinygettext
url = https://github.com/flyinghead/tinygettext
[submodule "core/deps/freetype"]
path = core/deps/freetype
url = https://gitlab.freedesktop.org/freetype/freetype.git
[submodule "core/deps/gamesdk"]
path = core/deps/gamesdk
url = https://github.com/flyinghead/gamesdk.git
[submodule "core/deps/basis_universal"]
path = core/deps/basis_universal
url = https://github.com/BinomialLLC/basis_universal.git
+209 -227
View File
@@ -21,6 +21,12 @@ if(CMAKE_SYSTEM_NAME STREQUAL "WindowsStore")
set(WINDOWS_STORE TRUE)
endif()
# Set Windows 7 as minimum version for MinGW build
if(MINGW)
add_compile_definitions(_WIN32_WINNT=0x0601 WINVER=0x0601)
add_link_options("-Wl,--subsystem,windows:6.1")
endif()
if(APPLE)
if(CMAKE_SYSTEM_NAME STREQUAL iOS)
set(CMAKE_OSX_DEPLOYMENT_TARGET "12.0" CACHE STRING "Minimum iOS deployment version")
@@ -41,6 +47,11 @@ else()
project(flycast)
endif()
option(FLYCAST_PRESEED_DARWIN_XCODE_CHECKS "Pre-seed stable macOS feature checks for faster Xcode generation" ON)
if(FLYCAST_PRESEED_DARWIN_XCODE_CHECKS)
include(DarwinXcodeProbeCache)
endif()
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
if(CMAKE_SYSTEM_NAME MATCHES "NetBSD|Haiku" OR NINTENDO_SWITCH)
@@ -76,8 +87,10 @@ option(USE_HOST_GLSLANG "Use host glslang" OFF)
option(USE_HOST_LIBZIP "Use host libzip" ON)
option(USE_HOST_SDL "Use host SDL library" ${USE_HOST_SDL_DEFAULT})
option(USE_HOST_LIBCHDR "Use host libchdr" OFF)
option(USE_HOST_LIBJUICE "Use host libjuice" OFF)
option(USE_OPENMP "Use OpenMP if available" ON)
option(USE_VULKAN "Build with Vulkan support" ON)
set(MOLTENVK_DYLIB "" CACHE FILEPATH "Path to macOS MoltenVK dylib for macOS Vulkan builds")
option(USE_DX9 "Build with Direct3D 9 support" ON)
option(USE_DX11 "Build with Direct3D 11 support" ON)
option(LIBRETRO "Build libretro core" OFF)
@@ -94,17 +107,27 @@ option(ENABLE_DC_PROFILER "Build with support for target machine (SH4) profiler"
option(ENABLE_FC_PROFILER "Build with support for host app (Flycast) profiler" OFF)
option(USE_DISCORD "Use Discord Presence API" OFF)
option(USE_LIBCDIO "Use libcdio for CDROM access" OFF)
option(USE_DREAMLINK_DEVICES "Use DreamLink devices" ON)
if(IOS AND NOT LIBRETRO)
set(USE_VULKAN OFF CACHE BOOL "Force vulkan off" FORCE)
endif()
if(WEBOS)
set(USE_GLES2 ON CACHE BOOL "Use GLES2 API" FORCE)
set(USE_OPENMP OFF CACHE BOOL "Force openmp off" FORCE)
if(CMAKE_CXX_COMPILER MATCHES "aarch64")
set(USE_GLES ON CACHE BOOL "Use GLES[3] API" FORCE)
else()
set(USE_GLES2 ON CACHE BOOL "Use GLES2 API" FORCE)
set(USE_OPENMP OFF CACHE BOOL "Force openmp off" FORCE)
endif()
set(USE_VULKAN OFF CACHE BOOL "Force vulkan off" FORCE)
endif()
if(ANDROID OR IOS OR NINTENDO_SWITCH OR LIBRETRO)
# DreamLink only available for non-mobile Linux, MacOS, and Windows
set(USE_DREAMLINK_DEVICES OFF CACHE BOOL "Force DreamLink devices off" FORCE)
endif()
include(GNUInstallDirs)
include(CMakeRC)
@@ -190,6 +213,10 @@ elseif(LIBRETRO)
if(NOT CMAKE_SYSTEM_NAME STREQUAL "OpenBSD")
set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,--no-undefined")
endif()
if(NOT WIN32)
# keep bundled libs like glslang from binding to the frontend's copies
target_link_options(${PROJECT_NAME} PRIVATE "-Wl,--version-script,${CMAKE_CURRENT_SOURCE_DIR}/shell/libretro/link.T")
endif()
endif()
set_target_properties(${PROJECT_NAME} PROPERTIES OUTPUT_NAME "flycast_libretro"
XCODE_ATTRIBUTE_CODE_SIGNING_ALLOWED "NO"
@@ -203,7 +230,10 @@ elseif(LIBRETRO)
target_link_libraries(${PROJECT_NAME} PRIVATE ${FOUNDATION})
endif()
if(ANDROID OR USE_GLES)
target_compile_definitions(${PROJECT_NAME} PRIVATE GLES GLES3 HAVE_OPENGLES HAVE_OPENGLES3 HAVE_OIT)
target_compile_definitions(${PROJECT_NAME} PRIVATE GLES GLES3 HAVE_OPENGLES HAVE_OPENGLES3 HAVE_OPENGLES_3_1 HAVE_OIT)
if(ANDROID)
target_link_libraries(${PROJECT_NAME} PRIVATE android)
endif()
elseif(IOS)
target_compile_definitions(${PROJECT_NAME} PRIVATE GLES GLES3 HAVE_OPENGLES HAVE_OPENGLES3)
find_library(OPENGLES OpenGLES)
@@ -256,6 +286,7 @@ elseif(WIN32)
set_property(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} PROPERTY VS_STARTUP_PROJECT ${PROJECT_NAME})
endif()
elseif(APPLE)
set(USE_OPENMP OFF CACHE BOOL "Force openmp off" FORCE)
add_executable(${PROJECT_NAME} MACOSX_BUNDLE core/emulator.cpp)
else()
add_executable(${PROJECT_NAME} core/emulator.cpp)
@@ -265,6 +296,25 @@ set_target_properties(${PROJECT_NAME} PROPERTIES
CMAKE_CXX_STANDARD 20
CMAKE_CXX_STANDARD_REQUIRED ON)
add_library(flycast_basis_transcoder STATIC
core/deps/basis_universal/transcoder/basisu_transcoder.cpp
core/deps/basis_universal/zstd/zstddeclib.c)
target_compile_features(flycast_basis_transcoder PRIVATE cxx_std_17)
target_compile_definitions(flycast_basis_transcoder PRIVATE
BASISD_SUPPORT_KTX2=1
BASISD_SUPPORT_KTX2_ZSTD=1
BASISD_SUPPORT_DXT1=1
BASISD_SUPPORT_DXT5A=1
BASISD_SUPPORT_ETC2_EAC_A8=1
BASISD_SUPPORT_XUASTC=1)
target_include_directories(flycast_basis_transcoder PUBLIC core/deps)
set_target_properties(flycast_basis_transcoder PROPERTIES POSITION_INDEPENDENT_CODE ON)
if(CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang")
target_compile_options(flycast_basis_transcoder PRIVATE
$<$<COMPILE_LANGUAGE:CXX>:-fno-strict-aliasing>)
endif()
target_link_libraries(${PROJECT_NAME} PRIVATE flycast_basis_transcoder)
if(WINDOWS_STORE)
set(USE_OPENGL OFF)
set(USE_VULKAN OFF)
@@ -272,7 +322,7 @@ endif()
set_target_properties(${PROJECT_NAME} PROPERTIES LINK_FLAGS_RELEASE -s)
if(MSVC)
target_compile_options(${PROJECT_NAME} PRIVATE /GR /GS-)
target_compile_options(${PROJECT_NAME} PRIVATE /GR /GS- /utf-8)
if(WINDOWS_STORE)
target_compile_options(${PROJECT_NAME} PRIVATE /ZW)
endif()
@@ -413,10 +463,6 @@ if(USE_BREAKPAD AND NOT LIBRETRO)
endif()
if(USE_OPENMP)
if(APPLE AND IS_DIRECTORY "/opt/homebrew/opt/libomp")
list(PREPEND CMAKE_PREFIX_PATH "/opt/homebrew/opt/libomp")
message(STATUS "Found and using Homebrew libomp hint.")
endif()
find_package(OpenMP)
if(OpenMP_CXX_FOUND)
if(MINGW)
@@ -457,6 +503,37 @@ if(USE_VULKAN)
target_link_libraries(${PROJECT_NAME} PRIVATE glslang::glslang-default-resource-limits glslang::SPIRV)
endif()
if(USE_DREAMLINK_DEVICES)
target_compile_definitions(${PROJECT_NAME} PRIVATE USE_DREAMLINK_DEVICES=1)
if (WINDOWS_STORE)
# Use winrt implementation of the DreamPicoPort-API
option(DREAMPICOPORT_WITH_LIBUSB "Use libusb to access DreamPicoPort-API" OFF)
elseif (CMAKE_SYSTEM_NAME MATCHES "DragonFly|FreeBSD|NetBSD|OpenBSD|Haiku")
find_package(PkgConfig REQUIRED)
pkg_check_modules(LIBUSB REQUIRED libusb-1.0)
set(DREAMPICOPORT_LIBUSB_LIBRARIES "${LIBUSB_LIBRARIES}" CACHE STRING "Name of the libusb library to link against")
set(DREAMPICOPORT_LIBUSB_INCLUDE_DIRS "${LIBUSB_INCLUDE_DIRS}" CACHE STRING "Path to libusb include directory, when applicable")
else()
if(MINGW)
# Shim 'windowsapp' to prevent libusb from linking against the UWP library
# and link against standard desktop libraries instead for Windows 7 support.
if(NOT TARGET windowsapp)
add_library(windowsapp INTERFACE)
target_link_libraries(windowsapp INTERFACE advapi32 ole32 shell32 user32)
endif()
endif()
# libusb
add_subdirectory(core/deps/DreamPicoPort-API/ext/libusb-cmake)
endif()
# DreamPicoPort-API
option(DREAMPICOPORT_ADD_LIBUSB "Add internal libusb library" OFF) # Already included above, when applicable
add_subdirectory(core/deps/DreamPicoPort-API EXCLUDE_FROM_ALL)
target_link_libraries(${PROJECT_NAME} PRIVATE dream_pico_port_api)
endif()
if(NOT LIBRETRO)
if(USE_OSS)
target_compile_definitions(${PROJECT_NAME} PRIVATE USE_OSS)
@@ -476,34 +553,8 @@ if(NOT LIBRETRO)
endif()
if(NOT ANDROID AND NOT IOS)
if (NOT NINTENDO_SWITCH)
# DreamLink enabled for non-mobile Linux, MacOS, and Windows
target_compile_definitions(${PROJECT_NAME} PRIVATE USE_DREAMLINK_DEVICES=1)
set(USE_DREAMLINK_DEVICES ON) # Must be set before adding core/sdl
if (WINDOWS_STORE)
# Use winrt implementation of the DreamPicoPort-API
option(DREAMPICOPORT_WITH_LIBUSB "Use libusb to access DreamPicoPort-API" OFF)
elseif (CMAKE_SYSTEM_NAME MATCHES "DragonFly|FreeBSD|Haiku")
# A version of libusb is installed in FreeBSD
find_package(PkgConfig REQUIRED)
pkg_check_modules(LIBUSB REQUIRED libusb-1.0)
set(DREAMPICOPORT_LIBUSB_LIBRARIES "${LIBUSB_LIBRARIES}" CACHE STRING "Name of the libusb library to link against")
set(DREAMPICOPORT_LIBUSB_INCLUDE_DIRS "${LIBUSB_INCLUDE_DIRS}" CACHE STRING "Path to libusb include directory, when applicable")
else()
# libusb
add_subdirectory(core/deps/DreamPicoPort-API/ext/libusb-cmake)
endif()
# DreamPicoPort-API
option(DREAMPICOPORT_ADD_LIBUSB "Add internal libusb library" OFF) # Already included above, when applicable
add_subdirectory(core/deps/DreamPicoPort-API EXCLUDE_FROM_ALL)
target_link_libraries(${PROJECT_NAME} PRIVATE dream_pico_port_api)
endif()
if(USE_HOST_SDL)
find_package(SDL2 2.0.9)
find_package(SDL2 2.0.18)
endif()
if(NOT SDL2_FOUND)
set(SDL_TEST_ENABLED_BY_DEFAULT OFF)
@@ -528,11 +579,6 @@ if(NOT LIBRETRO)
target_compile_definitions(${PROJECT_NAME} PRIVATE USE_SDL USE_SDL_AUDIO)
add_subdirectory(core/sdl)
if((UNIX AND NOT APPLE) OR NINTENDO_SWITCH)
find_package(CURL REQUIRED)
target_link_libraries(${PROJECT_NAME} PRIVATE CURL::libcurl)
endif()
endif()
find_package(ZLIB)
@@ -552,6 +598,18 @@ if(NOT LIBRETRO)
endif()
endif()
# http
if((UNIX AND NOT APPLE AND NOT ANDROID) OR NINTENDO_SWITCH)
find_package(CURL REQUIRED)
target_link_libraries(${PROJECT_NAME} PRIVATE CURL::libcurl)
elseif(LIBRETRO AND ANDROID)
# libretro needs to enable prefab and add dependency 'implementation("io.github.vvb2060.ndk:curl:8.18.0")'
find_package(curl REQUIRED CONFIG)
target_link_libraries(${PROJECT_NAME} PRIVATE curl::curl_static)
elseif(LIBRETRO AND WIN32)
target_link_libraries(${PROJECT_NAME} PRIVATE wininet)
endif()
find_package(PkgConfig)
if(NOT WITH_SYSTEM_ZLIB)
@@ -713,7 +771,7 @@ if(NOT MINIUPNP_FOUND)
option(UPNPC_BUILD_SAMPLE "Build sample executables" OFF)
option(UPNPC_NO_INSTALL "Disable installation" ON)
add_subdirectory(core/deps/miniupnpc)
if(WINDOWS_STORE)
if(WINDOWS_STORE OR MINGW)
get_target_property(miniupnpc-private-defs miniupnpc-private INTERFACE_COMPILE_DEFINITIONS)
list(REMOVE_ITEM miniupnpc-private-defs "_WIN32_WINNT=0x0501")
set_property(TARGET miniupnpc-private PROPERTY INTERFACE_COMPILE_DEFINITIONS ${miniupnpc-private-defs})
@@ -767,18 +825,27 @@ if(WIN32)
target_include_directories(${PROJECT_NAME} PRIVATE core/deps/dirent)
endif()
if(NOT LIBRETRO AND NOT NINTENDO_SWITCH)
target_compile_definitions(${PROJECT_NAME} PRIVATE USE_ICE)
# libjuice
# libjuice
target_compile_definitions(${PROJECT_NAME} PRIVATE USE_ICE)
if (USE_HOST_LIBJUICE AND UNIX AND NOT APPLE AND NOT ANDROID)
find_package(LibJuice)
endif()
if(NOT TARGET LibJuice::LibJuiceStatic)
option(BUILD_SHARED_LIBS "Build shared libraries" OFF)
option(NO_SERVER "Disable server support" ON)
option(NO_TESTS "Disable tests build" ON)
add_subdirectory(core/deps/libjuice EXCLUDE_FROM_ALL)
target_link_libraries(${PROJECT_NAME} PRIVATE LibJuice::LibJuiceStatic)
if(NINTENDO_SWITCH)
target_include_directories(juice-static SYSTEM PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/shell/switch)
target_compile_definitions(juice-static PRIVATE getnameinfo=getnameinfo_fixed)
endif()
endif()
target_link_libraries(${PROJECT_NAME} PRIVATE LibJuice::LibJuiceStatic)
# websocket++
# websocket++
if(NOT LIBRETRO AND NOT NINTENDO_SWITCH)
target_include_directories(${PROJECT_NAME} PRIVATE core/deps/websocketpp)
target_compile_definitions(${PROJECT_NAME} PRIVATE ASIO_STANDALONE)
target_compile_definitions(${PROJECT_NAME} PRIVATE ASIO_STANDALONE USE_WEBSOCKET)
if (MSVC)
target_compile_definitions(${PROJECT_NAME} PRIVATE
_WEBSOCKETPP_CPP11_FUNCTIONAL_
@@ -793,8 +860,16 @@ if(NOT LIBRETRO AND NOT NINTENDO_SWITCH)
endif()
endif()
# tinygettext
set(TINYGETTEXT_WO_ICONV ON)
add_subdirectory(core/deps/tinygettext EXCLUDE_FROM_ALL)
if(MSVC)
# language.cpp holds the native language names as non-ASCII literals. MSVC
# decodes sources without a BOM using the active code page, so on a non-UTF-8
# system locale those literals break the build (C2001/C2064). The main target
# already passes /utf-8; this dependency needs it too.
target_compile_options(tinygettext PRIVATE /utf-8)
endif()
target_include_directories(${PROJECT_NAME} PRIVATE core/deps/tinygettext/include)
target_link_libraries(${PROJECT_NAME} PRIVATE tinygettext::tinygettext)
@@ -872,9 +947,20 @@ if(NOT LIBRETRO)
core/deps/imgui/imgui.cpp
core/deps/imgui/imgui_demo.cpp
core/deps/imgui/imgui_draw.cpp
core/deps/imgui/imgui_cjk.cpp
core/deps/imgui/imgui_stdlib.cpp
core/deps/imgui/imgui_tables.cpp
core/deps/imgui/imgui_widgets.cpp)
core/deps/imgui/imgui_widgets.cpp
core/deps/imgui/misc/freetype/imgui_freetype.cpp)
set(FT_DISABLE_BZIP2 ON CACHE BOOL "" FORCE)
set(FT_DISABLE_HARFBUZZ ON CACHE BOOL "" FORCE)
set(FT_DISABLE_BROTLI ON CACHE BOOL "" FORCE)
add_subdirectory(core/deps/freetype EXCLUDE_FROM_ALL)
if(WINDOWS_STORE)
target_compile_definitions(freetype PRIVATE _WINRT_DLL)
endif()
target_link_libraries(${PROJECT_NAME} PRIVATE freetype)
if(ENABLE_FC_PROFILER)
target_include_directories(${PROJECT_NAME} PRIVATE core/deps/implot)
@@ -903,12 +989,15 @@ if(LIBRETRO)
target_sources(${PROJECT_NAME} PRIVATE
core/deps/libretro-common/memmap/memalign.c
core/deps/libretro-common/file/file_path.c
core/deps/libretro-common/file/file_path_io.c
core/deps/libretro-common/vfs/vfs_implementation.c
core/deps/libretro-common/encodings/encoding_utf.c
core/deps/libretro-common/compat/compat_strl.c
core/deps/libretro-common/compat/fopen_utf8.c
core/deps/libretro-common/compat/compat_strcasestr.c
core/deps/libretro-common/file/retro_dirent.c
core/deps/libretro-common/streams/file_stream.c
core/deps/libretro-common/time/rtime.c
core/deps/libretro-common/string/stdstring.c)
if(USE_OPENGL)
target_sources(${PROJECT_NAME} PRIVATE
@@ -933,9 +1022,12 @@ if(LIBRETRO)
shell/libretro/LogManager.h
shell/libretro/option.cpp
shell/libretro/oslib.cpp
shell/libretro/storage.cpp
shell/libretro/vmu_xhair.cpp)
if(APPLE)
target_sources(${PROJECT_NAME} PRIVATE shell/libretro/oslib_apple.mm)
target_sources(${PROJECT_NAME} PRIVATE
shell/libretro/oslib_apple.mm
shell/apple/common/http_client.mm)
endif()
if(WIN32)
target_link_libraries(${PROJECT_NAME} PRIVATE mswsock)
@@ -1022,66 +1114,22 @@ if(NOT LIBRETRO)
target_compile_definitions(${PROJECT_NAME} PRIVATE USE_RACHIEVEMENTS RC_DISABLE_LUA)
endif()
if(USE_OPENGL)
target_compile_definitions(${PROJECT_NAME} PRIVATE USE_OPENGL)
target_sources(${PROJECT_NAME} PRIVATE
core/rend/gles/glcache.h
core/rend/gles/gldraw.cpp
core/rend/gles/gles.cpp
core/rend/gles/gles.h
core/rend/gles/gltex.cpp
core/rend/gles/quad.cpp
core/rend/gles/postprocess.cpp
core/rend/gles/postprocess.h
core/rend/gles/naomi2.cpp
core/rend/gles/naomi2.h)
if(NOT LIBRETRO)
target_sources(${PROJECT_NAME} PRIVATE
core/deps/imgui/backends/imgui_impl_opengl3.cpp
core/deps/imgui/backends/imgui_impl_opengl3.h
core/rend/gles/opengl_driver.cpp
core/rend/gles/opengl_driver.h)
endif()
if(NOT (APPLE OR USE_GLES2))
target_sources(${PROJECT_NAME} PRIVATE
core/rend/gl4/abuffer.cpp
core/rend/gl4/gl4.h
core/rend/gl4/gldraw.cpp
core/rend/gl4/gles.cpp
core/rend/gl4/gl4naomi2.cpp
core/rend/gl4/gl4naomi2.h)
endif()
endif()
target_sources(${PROJECT_NAME} PRIVATE
core/build.h
core/chd.h
core/cheats.cpp
core/cheats.h
core/emulator.h
core/nullDC.cpp
core/serialize.cpp
core/serialize.h
core/stbi.h
core/stdclass.cpp
core/stdclass.h
core/types.h
core/debug/gdb_server.h)
target_sources(${PROJECT_NAME} PRIVATE
core/rend/CustomTexture.cpp
core/rend/CustomTexture.h
core/rend/osd.cpp
core/rend/osd.h
core/rend/sorter.cpp
core/rend/sorter.h
core/rend/tileclip.h
core/rend/TexCache.cpp
core/rend/TexCache.h
core/rend/texconv.cpp
core/rend/texconv.h
core/rend/norend/norend.cpp)
set(IMGUI_BACKENDS_DIR "${CMAKE_CURRENT_SOURCE_DIR}/core/deps/imgui/backends")
if(USE_VULKAN)
target_compile_definitions(${PROJECT_NAME} PUBLIC VK_ENABLE_BETA_EXTENSIONS VK_NO_PROTOTYPES)
@@ -1106,117 +1154,9 @@ if(USE_VULKAN)
add_subdirectory(core/deps/libadrenotools)
target_link_libraries(${PROJECT_NAME} PRIVATE adrenotools)
endif()
target_compile_definitions(${PROJECT_NAME} PRIVATE USE_VULKAN HAVE_VULKAN)
target_sources(${PROJECT_NAME} PRIVATE
core/rend/vulkan/oit/oit_buffer.h
core/rend/vulkan/oit/oit_drawer.cpp
core/rend/vulkan/oit/oit_drawer.h
core/rend/vulkan/oit/oit_pipeline.cpp
core/rend/vulkan/oit/oit_pipeline.h
core/rend/vulkan/oit/oit_renderer.cpp
core/rend/vulkan/oit/oit_renderpass.cpp
core/rend/vulkan/oit/oit_renderpass.h
core/rend/vulkan/oit/oit_shaders.cpp
core/rend/vulkan/oit/oit_shaders.h
core/rend/vulkan/adreno.cpp
core/rend/vulkan/buffer.cpp
core/rend/vulkan/buffer.h
core/rend/vulkan/commandpool.cpp
core/rend/vulkan/commandpool.h
core/rend/vulkan/compiler.cpp
core/rend/vulkan/compiler.h
core/rend/vulkan/drawer.cpp
core/rend/vulkan/drawer.h
core/rend/vulkan/pipeline.cpp
core/rend/vulkan/pipeline.h
core/rend/vulkan/quad.cpp
core/rend/vulkan/quad.h
core/rend/vulkan/shaders.cpp
core/rend/vulkan/shaders.h
core/rend/vulkan/texture.cpp
core/rend/vulkan/texture.h
core/rend/vulkan/utils.h
core/rend/vulkan/vmallocator.cpp
core/rend/vulkan/vmallocator.h
core/rend/vulkan/overlay.cpp
core/rend/vulkan/overlay.h
core/rend/vulkan/vulkan_context.h
core/rend/vulkan/vulkan.h
core/rend/vulkan/vulkan_renderer.cpp)
if(LIBRETRO)
target_sources(${PROJECT_NAME} PRIVATE
core/rend/vulkan/vk_context_lr.cpp
core/rend/vulkan/vk_context_lr.h)
else()
target_compile_definitions(${PROJECT_NAME} PUBLIC IMGUI_IMPL_VULKAN_NO_PROTOTYPES)
target_sources(${PROJECT_NAME} PRIVATE
core/rend/vulkan/vulkan_driver.h
core/rend/vulkan/vulkan_context.cpp
core/deps/imgui/backends/imgui_impl_vulkan.cpp
core/deps/imgui/backends/imgui_impl_vulkan.h)
if(CMAKE_SIZEOF_VOID_P EQUAL 4)
target_compile_definitions(${PROJECT_NAME} PRIVATE ImTextureID=ImU64)
endif()
endif()
endif()
if(WIN32 AND USE_DX9 AND NOT LIBRETRO AND NOT WINDOWS_STORE AND ("x86" IN_LIST ARCHITECTURE OR "x86_64" IN_LIST ARCHITECTURE))
set(REND_DX9_FILES
core/rend/dx9/d3d_overlay.h
core/rend/dx9/d3d_overlay.cpp
core/rend/dx9/d3d_renderer.h
core/rend/dx9/d3d_renderer.cpp
core/rend/dx9/d3d_shaders.h
core/rend/dx9/d3d_shaders.cpp
core/rend/dx9/d3d_texture.h
core/rend/dx9/d3d_texture.cpp
core/rend/dx9/dx9_driver.h
core/rend/dx9/dxcontext.cpp
core/rend/dx9/dxcontext.h
core/deps/imgui/backends/imgui_impl_dx9.h
core/deps/imgui/backends/imgui_impl_dx9.cpp)
target_sources(${PROJECT_NAME} PRIVATE ${REND_DX9_FILES})
if(NOT MINGW)
set_source_files_properties(${REND_DX9_FILES} PROPERTIES INCLUDE_DIRECTORIES "$ENV{DXSDK_DIR}/Include")
endif()
target_compile_definitions(${PROJECT_NAME} PRIVATE USE_DX9)
endif()
if(WIN32 AND USE_DX11)
if(NOT LIBRETRO)
target_sources(${PROJECT_NAME} PRIVATE
core/deps/imgui/backends/imgui_impl_dx11.cpp
core/deps/imgui/backends/imgui_impl_dx11.h)
endif()
target_sources(${PROJECT_NAME} PRIVATE
core/rend/dx11/dx11_driver.h
core/rend/dx11/dx11_overlay.cpp
core/rend/dx11/dx11_overlay.h
core/rend/dx11/dx11_renderer.cpp
core/rend/dx11/dx11_renderer.h
core/rend/dx11/dx11_renderstate.h
core/rend/dx11/dx11_renderstate.cpp
core/rend/dx11/dx11_shaders.cpp
core/rend/dx11/dx11_shaders.h
core/rend/dx11/dx11_texture.cpp
core/rend/dx11/dx11_texture.h
core/rend/dx11/dx11context.cpp
core/rend/dx11/dx11context.h
core/rend/dx11/dx11context_lr.cpp
core/rend/dx11/dx11context_lr.h
core/rend/dx11/dx11_driver.h
core/rend/dx11/dx11_naomi2.cpp
core/rend/dx11/dx11_naomi2.h
core/rend/dx11/oit/dx11_oitbuffers.h
core/rend/dx11/oit/dx11_oitrenderer.cpp
core/rend/dx11/oit/dx11_oitshaders.cpp
core/rend/dx11/oit/dx11_oitshaders.h)
target_compile_definitions(${PROJECT_NAME} PRIVATE HAVE_D3D11)
target_link_libraries(${PROJECT_NAME} PRIVATE d3d11 d3dcompiler dxgi)
endif()
add_subdirectory(core/rend)
if(ENABLE_GDB_SERVER)
@@ -1373,6 +1313,15 @@ if(NOT LIBRETRO)
add_subdirectory(shell/android-studio/flycast/src/main/jni/src)
target_link_libraries(${PROJECT_NAME} PRIVATE android log)
set(GAMESDK_THREAD_CHECKS 0)
add_subdirectory(core/deps/gamesdk/games-frame-pacing EXCLUDE_FROM_ALL)
target_compile_options(swappy_static PRIVATE $<$<COMPILE_LANGUAGE:CXX>:-Wno-vla-cxx-extension>)
target_compile_definitions(swappy_static PRIVATE ANDROIDGAMESDK_NO_BINARY_DEX_LINKAGE)
target_include_directories(${PROJECT_NAME} PRIVATE core/deps/gamesdk/games-frame-pacing/include)
target_link_libraries(${PROJECT_NAME} PRIVATE swappy_static)
target_compile_definitions(${PROJECT_NAME} PRIVATE SWAPPY)
elseif(APPLE)
string(TIMESTAMP YEAR "%Y")
string(REGEX MATCH "^[^-]+" TAG_VERSION ${GIT_VERSION})
@@ -1467,19 +1416,24 @@ if(NOT LIBRETRO)
COMMAND ${CMAKE_COMMAND} -E tar "cfv" "${CMAKE_CURRENT_BINARY_DIR}/$<CONFIG>-${CMAKE_OSX_SYSROOT}/Flycast.ipa" --format=zip ${CMAKE_CURRENT_BINARY_DIR}/Payload)
else()
add_subdirectory(core/deps/Syphon)
target_link_libraries(${PROJECT_NAME} PRIVATE Syphon)
add_custom_command(TARGET ${PROJECT_NAME} POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy ${SYPHON_METALLIB}
$<TARGET_FILE_DIR:flycast>/../Resources/)
target_compile_definitions(${PROJECT_NAME} PRIVATE VIDEO_ROUTING)
target_sources(${PROJECT_NAME} PRIVATE
shell/apple/common/http_client.mm
shell/apple/common/util.mm
shell/apple/emulator-osx/emulator-osx/SDLApplicationDelegate.h
shell/apple/emulator-osx/emulator-osx/SDLApplicationDelegate.mm
shell/apple/emulator-osx/emulator-osx/osx-main.mm)
shell/apple/common/util.mm)
# The app's main() lives in SDLApplicationDelegate.mm; the test binary
# gets its main() from gtest_main and stubs the rest (tests/src/test_stubs.cpp).
if(NOT BUILD_TESTING)
add_subdirectory(core/deps/Syphon)
target_link_libraries(${PROJECT_NAME} PRIVATE Syphon)
add_custom_command(TARGET ${PROJECT_NAME} POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy ${SYPHON_METALLIB}
$<TARGET_FILE_DIR:flycast>/../Resources/)
target_compile_definitions(${PROJECT_NAME} PRIVATE VIDEO_ROUTING)
target_sources(${PROJECT_NAME} PRIVATE
shell/apple/emulator-osx/emulator-osx/SDLApplicationDelegate.h
shell/apple/emulator-osx/emulator-osx/SDLApplicationDelegate.mm
shell/apple/emulator-osx/emulator-osx/osx-main.mm)
endif()
set(ASSETS shell/apple/emulator-osx/emulator-osx/Images.xcassets)
target_sources(${PROJECT_NAME} PRIVATE ${ASSETS})
source_group("Resources" FILES ${ASSETS})
@@ -1522,9 +1476,15 @@ if(NOT LIBRETRO)
${IOSURFACE_LIBRARY}
${MULTITOUCH_SUPPORT_LIBRARY})
add_custom_command(TARGET ${PROJECT_NAME} POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy "$ENV{VULKAN_SDK}/lib/libMoltenVK.dylib"
$<TARGET_FILE_DIR:flycast>/../Frameworks/libvulkan.dylib)
if(USE_VULKAN)
set(MOLTENVK_DYLIB_PATH "${MOLTENVK_DYLIB}")
if(NOT MOLTENVK_DYLIB_PATH OR NOT EXISTS "${MOLTENVK_DYLIB_PATH}")
message(FATAL_ERROR "A macOS MoltenVK dylib is required for macOS Vulkan builds. Run shell/apple/generate_xcode_project.command to download the configured MoltenVK release, or pass -DMOLTENVK_DYLIB=/path/to/libMoltenVK.dylib.")
endif()
add_custom_command(TARGET ${PROJECT_NAME} POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy "${MOLTENVK_DYLIB_PATH}"
$<TARGET_FILE_DIR:flycast>/../Frameworks/libMoltenVK.dylib)
endif()
endif()
elseif(UNIX)
if(NOT BUILD_TESTING)
@@ -1584,16 +1544,38 @@ endif()
if(NINTENDO_SWITCH)
if(LIBRETRO)
# ar names a member after the basename of the object, so extracting every
# archive into one directory loses whatever two of them happen to share:
# zstd carries its own copy of xxhash.c, whose object lands on top of the
# real xxHash one and takes XXH32 with it. A directory per archive keeps
# them apart.
add_custom_target(combined ALL
COMMAND ${CMAKE_AR} -x $<TARGET_FILE:xxHash::xxhash>
COMMAND ${CMAKE_AR} -x $<TARGET_FILE:chdr-static>
COMMAND ${CMAKE_AR} -x $<TARGET_FILE:zip>
COMMAND ${CMAKE_AR} -x $<TARGET_FILE:elf>
COMMAND ${CMAKE_AR} -x $<TARGET_FILE:flycast::res>
COMMAND ${CMAKE_AR} -rs flycast_libretro_libnx.a *.o
COMMAND rm *.o
COMMAND ${CMAKE_COMMAND} -E rm -rf combined-objs
COMMAND ${CMAKE_COMMAND} -E make_directory
combined-objs/xxhash combined-objs/chdr combined-objs/zip
combined-objs/zstd combined-objs/elf combined-objs/tinygettext
combined-objs/res combined-objs/libjuice
COMMAND ${CMAKE_COMMAND} -E chdir combined-objs/xxhash
${CMAKE_AR} -x $<TARGET_FILE:xxHash::xxhash>
COMMAND ${CMAKE_COMMAND} -E chdir combined-objs/chdr
${CMAKE_AR} -x $<TARGET_FILE:chdr-static>
COMMAND ${CMAKE_COMMAND} -E chdir combined-objs/zip
${CMAKE_AR} -x $<TARGET_FILE:zip>
COMMAND ${CMAKE_COMMAND} -E chdir combined-objs/zstd
${CMAKE_AR} -x $<TARGET_FILE:libzstd_static>
COMMAND ${CMAKE_COMMAND} -E chdir combined-objs/elf
${CMAKE_AR} -x $<TARGET_FILE:elf>
COMMAND ${CMAKE_COMMAND} -E chdir combined-objs/tinygettext
${CMAKE_AR} -x $<TARGET_FILE:tinygettext::tinygettext>
COMMAND ${CMAKE_COMMAND} -E chdir combined-objs/res
${CMAKE_AR} -x $<TARGET_FILE:flycast::res>
COMMAND ${CMAKE_COMMAND} -E chdir combined-objs/libjuice
${CMAKE_AR} -x $<TARGET_FILE:LibJuice::LibJuiceStatic>
COMMAND ${CMAKE_AR} -rs flycast_libretro_libnx.a combined-objs/*/*.o
COMMAND ${CMAKE_COMMAND} -E rm -rf combined-objs
WORKING_DIRECTORY ${CMAKE_BINARY_DIR}/${CMAKE_ARCHIVE_OUTPUT_DIRECTORY}
DEPENDS xxHash::xxhash chdr-static zip ${PROJECT_NAME})
DEPENDS xxHash::xxhash chdr-static zip libzstd_static
tinygettext::tinygettext ${PROJECT_NAME})
else()
nx_generate_nacp(flycast.nacp NAME "Flycast" AUTHOR "flyinghead, M4xw" VERSION "${GIT_VERSION}")
nx_create_nro(flycast NACP flycast.nacp ICON "${CMAKE_SOURCE_DIR}/shell/switch/flycast.jpeg")
+59 -14
View File
@@ -12,47 +12,88 @@
Information about configuration and supported features can be found on [**TheArcadeStriker's flycast wiki**](https://github.com/TheArcadeStriker/flycast-wiki/wiki).
Join us on our [**Discord server**](https://discord.gg/X8YWP8w) for a chat.
Join us on our [**Discord server**](https://discord.gg/X8YWP8w) for a chat.
## Downloads ![android](https://flyinghead.github.io/flycast-builds/android.jpg) ![windows](https://flyinghead.github.io/flycast-builds/windows.png) ![linux](https://flyinghead.github.io/flycast-builds/ubuntu.png) ![apple](https://flyinghead.github.io/flycast-builds/apple.png) ![switch](https://flyinghead.github.io/flycast-builds/switch.png) ![xbox](https://flyinghead.github.io/flycast-builds/xbox.png)
Get builds for your system from the [**builds page**](https://flyinghead.github.io/flycast-builds/) or [**GitHub Releases**](https://github.com/flyinghead/flycast/releases).
- **Latest master builds:** regular builds from the `master` branch with recent fixes and updates.
- **Nightly dev builds:** experimental builds with the latest features and changes.
- **Stable tagged releases:** versioned release builds published on GitHub Releases.
Automated test results are available from the builds page as well.
## Install
### Android ![android](https://flyinghead.github.io/flycast-builds/android.jpg)
Install Flycast from [**Google Play**](https://play.google.com/store/apps/details?id=com.flycast.emulator).
&emsp;Install Flycast from [**Google Play**](https://play.google.com/store/apps/details?id=com.flycast.emulator).
### Flatpak (Linux ![ubuntu logo](https://flyinghead.github.io/flycast-builds/ubuntu.png))
1. [Set up Flatpak](https://www.flatpak.org/setup/).
2. Install Flycast from [Flathub](https://flathub.org/apps/details/org.flycast.Flycast):
`flatpak install -y org.flycast.Flycast`
&emsp;`flatpak install -y org.flycast.Flycast`
3. Run Flycast:
`flatpak run org.flycast.Flycast`
&emsp;`flatpak run org.flycast.Flycast`
### Homebrew (MacOS ![apple logo](https://flyinghead.github.io/flycast-builds/apple.png))
### Homebrew (macOS ![apple logo](https://flyinghead.github.io/flycast-builds/apple.png))
1. [Set up Homebrew](https://brew.sh).
1. [Set up Homebrew](https://brew.sh) or run `brew update` if already installed.
2. Install Flycast via Homebrew:
2. Choose one channel:
`brew install --cask flycast`
| Channel | Install command |
| -------------------- | ------------------------------------------------------- |
| Master (recommended) | `brew install --cask flyinghead/flycast/flycast@master` |
| Stable | `brew install --cask flyinghead/flycast/flycast` |
| Nightly dev | `brew install --cask flyinghead/flycast/flycast@dev` |
3. Run Flycast from your Application folder
&emsp;See the <a href="https://github.com/flyinghead/homebrew-flycast#readme">Flycast tap</a> for updating, uninstalling, and switching channels.
### iOS
Due to persistent harassment from an iOS user, support for this platform has been dropped.
&emsp;Due to persistent harassment from an iOS user, support for this platform has been dropped.
### Xbox One/Series ![xbox logo](https://flyinghead.github.io/flycast-builds/xbox.png)
Grab the latest build from [**the builds page**](https://flyinghead.github.io/flycast-builds/), or the [**GitHub Actions**](https://github.com/flyinghead/flycast/actions/workflows/uwp.yml). Then install it using the **Xbox Device Portal**.
&emsp;Grab the latest build from [**the builds page**](https://flyinghead.github.io/flycast-builds/), or the [**GitHub Actions**](https://github.com/flyinghead/flycast/actions/workflows/uwp.yml). Then install it using the **Xbox Device Portal**.
### Binaries ![android](https://flyinghead.github.io/flycast-builds/android.jpg) ![windows](https://flyinghead.github.io/flycast-builds/windows.png) ![linux](https://flyinghead.github.io/flycast-builds/ubuntu.png) ![apple](https://flyinghead.github.io/flycast-builds/apple.png) ![switch](https://flyinghead.github.io/flycast-builds/switch.png) ![xbox](https://flyinghead.github.io/flycast-builds/xbox.png)
## Build from source
Get fresh builds for your system [**on the builds page**](https://flyinghead.github.io/flycast-builds/).
### macOS
**New:** Now automated test results are available as well.
&emsp;Right-click the bootstrap script and choose **Open**:
&emsp;`shell/apple/generate_xcode_project.command`
### Windows
&emsp;Double-click the bootstrap script:
&emsp;`shell\windows\generate_vs_project.bat`
### Linux
#### Dependencies
- **C/C++ compiler toolchain** (e.g. `gcc`/`g++`)
- **CMake**
- **make**
- **libcurl** (development headers)
- **libudev** (development headers)
- **SDL2** (development headers)
- **Graphics API**: Vulkan, OpenGL
#### Build
### Build instructions:
```
$ git clone --recursive https://github.com/flyinghead/flycast.git
$ cd flycast
@@ -60,3 +101,7 @@ $ mkdir build && cd build
$ cmake ..
$ make
```
## Packaging status
[![Packaging status](https://repology.org/badge/vertical-allrepos/flycast.svg)](https://repology.org/project/flycast/versions)
+45 -1
View File
@@ -63,6 +63,7 @@ public:
void deserialize(Deserializer& deser);
static Achievements& Instance();
void setHostOverride(const std::string& host);
private:
bool createClient();
@@ -105,6 +106,7 @@ private:
rc_client_t *rc_client = nullptr;
bool loggedOn = false;
bool hostOverrideActive = false;
std::atomic_bool loadingGame {};
bool active = false;
bool paused = false;
@@ -156,6 +158,10 @@ bool canPause() {
return Achievements::Instance().canPause();
}
void setHostOverride(const std::string& host) {
Achievements::Instance().setHostOverride(host);
}
void serialize(Serializer& ser) {
Achievements::Instance().serialize(ser);
}
@@ -243,9 +249,45 @@ bool Achievements::createClient()
rc_client_set_userdata(rc_client, this);
if (!config::AchievementsHostUrl.get().empty()) {
rc_client_set_host(rc_client, config::AchievementsHostUrl.get().c_str());
hostOverrideActive = true;
}
return true;
}
void Achievements::setHostOverride(const std::string& host)
{
config::AchievementsHostUrl.set(host);
hostOverrideActive = !host.empty();
// rc_client_set_host sets the global URL even when rc_client is null
rc_client_set_host(rc_client, host.empty() ? nullptr : host.c_str());
if (rc_client != nullptr)
{
if (!host.empty())
{
rc_client_set_hardcore_enabled(rc_client, 0);
if (!loggedOn
&& !config::AchievementsUserName.get().empty()
&& !config::AchievementsToken.get().empty())
{
INFO_LOG(COMMON, "RA: Retrying login via proxy host '%s'", host.c_str());
rc_client_begin_login_with_token(rc_client,
config::AchievementsUserName.get().c_str(),
config::AchievementsToken.get().c_str(),
clientLoginWithTokenCallback, nullptr);
}
}
else
{
bool restoredHardcore = config::AchievementsHardcoreMode.get();
rc_client_set_hardcore_enabled(rc_client, (int)restoredHardcore);
settings.raHardcoreMode = restoredHardcore;
}
}
}
void Achievements::loadCache()
{
cachePath = get_writable_data_path("achievements/");
@@ -855,7 +897,9 @@ void Achievements::loadGame()
if (!gameHash.empty())
{
// settings.raHardcoreMode is set before enabling cheats and loading the initial savestate
rc_client_set_hardcore_enabled(rc_client, settings.raHardcoreMode);
// Hardcore is disabled when routing through a custom host (e.g. RAOfflineProxy).
bool effectiveHardcore = settings.raHardcoreMode && !hostOverrideActive;
rc_client_set_hardcore_enabled(rc_client, effectiveHardcore);
rc_client_begin_load_game(rc_client, gameHash.c_str(), [](int result, const char *error_message, rc_client_t *client, void *userdata) {
((Achievements *)userdata)->gameLoaded(result, error_message);
}, this);
+1
View File
@@ -54,6 +54,7 @@ bool isActive();
Game getCurrentGame();
std::vector<Achievement> getAchievementList();
bool canPause();
void setHostOverride(const std::string& host);
#else
+27 -7
View File
@@ -29,20 +29,40 @@
static bool crc_tables_generated;
bool SzArchive::Open(FILE *file)
SRes SzArchive::ArchiveStream::Read(const ISeekInStream *p, void *buf, size_t *size)
{
auto file_archive = CONTAINER_FROM_VTBL(p, ArchiveStream, vt);
*size = file_archive->file->read(buf, 1, *size);
return file_archive->file->error() ? SZ_ERROR_READ : SZ_OK;
}
SRes SzArchive::ArchiveStream::Seek(const ISeekInStream *p, Int64 *pos, ESzSeek origin)
{
auto file_archive = CONTAINER_FROM_VTBL(p, ArchiveStream, vt);
if (file_archive->file->seek(*pos, origin) == 0) {
*pos = file_archive->file->tell();
return SZ_OK;
}
else {
return SZ_ERROR_FAIL;
}
}
bool SzArchive::Open(hostfs::File *file)
{
SzArEx_Init(&szarchive);
File_Close(&archiveStream.file);
File_Construct(&archiveStream.file);
archiveStream.file.file = file;
if (archiveStream.file != nullptr)
delete archiveStream.file;
archiveStream.vt.Read = ArchiveStream::Read;
archiveStream.vt.Seek = ArchiveStream::Seek;
archiveStream.file = file;
FileInStream_CreateVTable(&archiveStream);
LookToRead2_CreateVTable(&lookStream, 0);
lookStream.buf = (Byte *)ISzAlloc_Alloc(&g_Alloc, kInputBufSize);
if (lookStream.buf == NULL)
{
File_Close(&archiveStream.file);
delete archiveStream.file;
return false;
}
lookStream.bufSize = kInputBufSize;
@@ -115,7 +135,7 @@ SzArchive::~SzArchive()
{
if (lookStream.buf != NULL)
{
File_Close(&archiveStream.file);
delete archiveStream.file;
ISzAlloc_Free(&g_Alloc, lookStream.buf);
if (out_buffer != NULL)
ISzAlloc_Free(&g_Alloc, out_buffer);
+11 -3
View File
@@ -22,7 +22,6 @@
#include "archive.h"
#include "lzma/7z.h"
#include "lzma/7zFile.h"
#include <algorithm>
#include <cstring>
@@ -40,14 +39,23 @@ public:
ArchiveFile *OpenFileByCrc(u32 crc) override;
protected:
bool Open(FILE *file) override;
bool Open(hostfs::File *file) override;
private:
struct ArchiveStream
{
static SRes Read(const ISeekInStream *p, void *buf, size_t *size);
static SRes Seek(const ISeekInStream *p, Int64 *pos, ESzSeek origin);
ISeekInStream vt;
hostfs::File *file;
};
CSzArEx szarchive;
UInt32 block_idx; /* it can have any value before first call (if outBuffer = 0) */
Byte *out_buffer; /* it must be 0 before first call for each new archive. */
size_t out_buffer_size; /* it can have any value before first call (if outBuffer = 0) */
CFileInStream archiveStream;
ArchiveStream archiveStream;
CLookToRead2 lookStream;
};
+118 -3
View File
@@ -20,18 +20,133 @@
*/
#include "ZipArchive.h"
#include <array>
ZipArchive::~ZipArchive()
{
zip_close(zip);
}
bool ZipArchive::Open(FILE *file)
static zip_int64_t ZipSourceCallback(void *userdata, void *data, zip_uint64_t len, zip_source_cmd_t cmd)
{
using Error = std::array<int, 2>;
static Error error = {ZIP_ER_OK, 0};
auto file = static_cast<hostfs::File*>(userdata);
switch (cmd)
{
case ZIP_SOURCE_OPEN:
return 0;
case ZIP_SOURCE_READ:
{
const auto bytes_read = file->read(data, 1, len);
if (file->error())
{
error = {ZIP_ER_INTERNAL, 0};
return -1;
}
return bytes_read;
}
case ZIP_SOURCE_CLOSE:
return 0;
case ZIP_SOURCE_STAT:
{
auto size = file->size();
if (size == -1)
{
error = {ZIP_ER_INTERNAL, 0};
return -1;
}
auto stat = static_cast<zip_stat_t*>(data);
zip_stat_init(stat);
stat->valid |= ZIP_STAT_SIZE;
stat->size = size;
return sizeof(*stat);
}
case ZIP_SOURCE_ERROR:
{
auto error_out = static_cast<std::array<int, 2>*>(data);
*error_out = error;
return sizeof(*error_out);
}
case ZIP_SOURCE_FREE:
delete file;
return 0;
case ZIP_SOURCE_SEEK:
{
zip_error_t error;
auto seek_info = ZIP_SOURCE_GET_ARGS(zip_source_args_seek, data, len, &error);
if (seek_info == nullptr)
{
error = {ZIP_ER_INTERNAL, 0};
return -1;
}
if (file->seek(seek_info->offset, seek_info->whence) != 0)
{
error = {ZIP_ER_INTERNAL, 0};
return -1;
}
return 0;
}
case ZIP_SOURCE_TELL:
{
const auto position = file->tell();
if (position == -1)
{
error = {ZIP_ER_INTERNAL, 0};
return -1;
}
return position;
}
case ZIP_SOURCE_SUPPORTS:
return zip_source_make_command_bitmap(
ZIP_SOURCE_OPEN,
ZIP_SOURCE_READ,
ZIP_SOURCE_CLOSE,
ZIP_SOURCE_STAT,
ZIP_SOURCE_ERROR,
ZIP_SOURCE_FREE,
ZIP_SOURCE_SEEK,
ZIP_SOURCE_TELL,
ZIP_SOURCE_SUPPORTS,
-1
);
default:
break;
}
// Unsupported command.
error = {ZIP_ER_OPNOTSUPP, 0};
return -1;
}
bool ZipArchive::Open(hostfs::File *file)
{
zip_error_t error;
zip_source_t *source = zip_source_filep_create(file, 0, -1, &error);
zip_source_t *source = zip_source_function_create(ZipSourceCallback, file, &error);
if (source == nullptr)
{
std::fclose(file);
delete file;
return false;
}
zip = zip_open_from_source(source, 0, nullptr);
+1 -1
View File
@@ -31,7 +31,7 @@ public:
ArchiveFile* OpenFile(const char* name) override;
ArchiveFile* OpenFileByCrc(u32 crc) override;
bool Open(FILE *file) override;
bool Open(hostfs::File *file) override;
bool Open(const void *data, size_t size);
ArchiveFile *OpenFirstFile();
+2 -2
View File
@@ -26,7 +26,7 @@
Archive *OpenArchive(const std::string& path)
{
FILE *file = nullptr;
hostfs::File *file = nullptr;
hostfs::FileInfo fileInfo;
try {
fileInfo = hostfs::storage().getFileInfo(path);
@@ -74,7 +74,7 @@ Archive *OpenArchive(const std::string& path)
bool Archive::Open(const char* path)
{
FILE *file = nowide::fopen(path, "rb");
hostfs::File *file = hostfs::storage().openFile(path, "rb");
if (file == nullptr)
return false;
return Open(file);
+2 -1
View File
@@ -21,6 +21,7 @@
#pragma once
#include "types.h"
#include "oslib/storage.h"
class ArchiveFile
{
@@ -39,7 +40,7 @@ public:
virtual ArchiveFile *OpenFileByCrc(u32 crc) = 0;
protected:
virtual bool Open(FILE *file) = 0;
virtual bool Open(hostfs::File *file) = 0;
private:
bool Open(const char *name);
+19 -19
View File
@@ -23,27 +23,27 @@
const u8 RZipHeader[8] = { '#', 'R', 'Z', 'I', 'P', 'v', 1, '#' };
bool RZipFile::Open(FILE *file, bool write)
bool RZipFile::Open(hostfs::File *file, bool write)
{
verify(this->file == nullptr);
verify(file != nullptr);
startOffset = std::ftell(file);
startOffset = file->tell();
if (!write)
{
u8 header[sizeof(RZipHeader)];
if (std::fread(header, sizeof(header), 1, file) != 1
if (file->read(header, sizeof(header), 1) != 1
|| memcmp(header, RZipHeader, sizeof(header))
|| std::fread(&maxChunkSize, sizeof(maxChunkSize), 1, file) != 1
|| std::fread(&size, sizeof(size), 1, file) != 1)
|| file->read(&maxChunkSize, sizeof(maxChunkSize), 1) != 1
|| file->read(&size, sizeof(size), 1) != 1)
{
std::fseek(file, startOffset, SEEK_SET);
file->seek(startOffset, SEEK_SET);
return false;
}
// savestates created on 32-bit platforms used to have a 32-bit size
if (size >> 32 != 0)
{
size &= 0xffffffff;
std::fseek(file, -4, SEEK_CUR);
file->seek(-4, SEEK_CUR);
}
chunk = new u8[maxChunkSize];
chunkIndex = 0;
@@ -52,11 +52,11 @@ bool RZipFile::Open(FILE *file, bool write)
else
{
maxChunkSize = 1_MB;
if (std::fwrite(RZipHeader, sizeof(RZipHeader), 1, file) != 1
|| std::fwrite(&maxChunkSize, sizeof(maxChunkSize), 1, file) != 1
|| std::fwrite(&size, sizeof(size), 1, file) != 1)
if (file->write(RZipHeader, sizeof(RZipHeader), 1) != 1
|| file->write(&maxChunkSize, sizeof(maxChunkSize), 1) != 1
|| file->write(&size, sizeof(size), 1) != 1)
{
std::fseek(file, startOffset, SEEK_SET);
file->seek(startOffset, SEEK_SET);
return false;
}
}
@@ -67,7 +67,7 @@ bool RZipFile::Open(FILE *file, bool write)
bool RZipFile::Open(const std::string& path, bool write)
{
FILE *f = nowide::fopen(path.c_str(), write ? "wb" : "rb");
hostfs::File *f = hostfs::storage().openFile(path.c_str(), write ? "wb" : "rb");
if (f == nullptr)
return false;
if (!Open(f, write)) {
@@ -83,10 +83,10 @@ void RZipFile::Close()
{
if (write)
{
std::fseek(file, startOffset + sizeof(RZipHeader) + sizeof(maxChunkSize), SEEK_SET);
std::fwrite(&size, sizeof(size), 1, file);
file->seek(startOffset + sizeof(RZipHeader) + sizeof(maxChunkSize), SEEK_SET);
file->write(&size, sizeof(size), 1);
}
std::fclose(file);
delete file;
file = nullptr;
if (chunk != nullptr)
{
@@ -110,12 +110,12 @@ size_t RZipFile::Read(void *data, size_t length)
chunkSize = 0;
chunkIndex = 0;
u32 zippedSize;
if (std::fread(&zippedSize, sizeof(zippedSize), 1, file) != 1)
if (file->read(&zippedSize, sizeof(zippedSize), 1) != 1)
break;
if (zippedSize == 0)
continue;
u8 *zipped = new u8[zippedSize];
if (std::fread(zipped, zippedSize, 1, file) != 1)
if (file->read(zipped, zippedSize, 1) != 1)
{
delete [] zipped;
break;
@@ -161,8 +161,8 @@ size_t RZipFile::Write(const void *data, size_t length)
break;
}
u32 sz = (u32)zippedSize;
if (std::fwrite(&sz, sizeof(sz), 1, file) != 1
|| std::fwrite(zipped, zippedSize, 1, file) != 1)
if (file->write(&sz, sizeof(sz), 1) != 1
|| file->write(zipped, zippedSize, 1) != 1)
{
rv = 0;
break;
+4 -3
View File
@@ -21,6 +21,7 @@
#pragma once
#include "types.h"
#include "oslib/storage.h"
class RZipFile
{
@@ -28,15 +29,15 @@ public:
~RZipFile() { Close(); }
bool Open(const std::string& path, bool write);
bool Open(FILE *file, bool write);
bool Open(hostfs::File *file, bool write);
void Close();
size_t Size() const { return size; }
size_t Read(void *data, size_t length);
size_t Write(const void *data, size_t length);
FILE *rawFile() const { return file; }
hostfs::File *rawFile() const { return file; }
private:
FILE *file = nullptr;
hostfs::File *file = nullptr;
u64 size = 0;
u32 maxChunkSize = 0;
u8 *chunk = nullptr;
+4 -2
View File
@@ -73,8 +73,10 @@ constexpr u32 SAMPLE_COUNT = 512; // AudioBackend::push() is always called with
class RingBuffer
{
std::vector<u8> buffer;
std::atomic_int readCursor { 0 };
std::atomic_int writeCursor { 0 };
// The producer and the audio callback each update one cursor: keep them on
// separate cache lines.
alignas(64) std::atomic_int readCursor { 0 };
alignas(64) std::atomic_int writeCursor { 0 };
u32 readSize() {
return (u32)((writeCursor - readCursor + buffer.size()) % buffer.size());
+8 -4
View File
@@ -12,13 +12,13 @@ static IniFile cfgdb;
static void saveFile()
{
FILE* cfgfile = nowide::fopen(cfgPath.c_str(), "wt");
hostfs::File* cfgfile = hostfs::storage().openFile(cfgPath.c_str(), "wt");
if (!cfgfile) {
WARN_LOG(COMMON, "Error: Unable to open file '%s' for saving", cfgPath.c_str());
}
else {
cfgdb.save(cfgfile);
std::fclose(cfgfile);
delete cfgfile;
}
}
@@ -37,10 +37,10 @@ bool open()
std::string config_path_read = get_readonly_config_path(filename);
cfgPath = get_writable_config_path(filename);
FILE* cfgfile = nowide::fopen(config_path_read.c_str(), "rt");
hostfs::File* cfgfile = hostfs::storage().openFile(config_path_read.c_str(), "rt");
if (cfgfile != nullptr) {
cfgdb.load(cfgfile);
std::fclose(cfgfile);
delete cfgfile;
}
else
{
@@ -128,4 +128,8 @@ float loadFloat(const std::string& section, const std::string& key, float def) {
return cfgdb.getFloat(section, key, def);
}
std::vector<std::string> getEntries(const std::string& section) {
return cfgdb.getEntryNames(section);
}
} // namespace config
+4 -1
View File
@@ -1,5 +1,7 @@
#pragma once
#include "types.h"
#include <cstdint>
#include <string>
#include <vector>
namespace config
{
@@ -25,4 +27,5 @@ bool hasSection(const std::string& section);
void deleteSection(const std::string& section);
void deleteEntry(const std::string& section, const std::string& key);
std::vector<std::string> getEntries(const std::string& section);
}
+132 -21
View File
@@ -35,32 +35,143 @@ static void usage(const char *exe)
static void parseConfigOption(const std::string& str)
{
size_t pos = 0;
while (pos < str.length())
char inQuote = '\0';
std::string section, key, value;
int step = 0; // section, key, value
for (char c : str)
{
size_t comma = str.find(',', pos);
if (comma == str.npos)
comma = str.length();
std::string option = str.substr(pos, comma - pos);
size_t colpos = option.find(':');
size_t eqpos = option.npos;
if (colpos != option.npos)
if (inQuote != '\0' && c == inQuote)
{
std::string section = option.substr(0, colpos);
eqpos = option.find('=', colpos);
if (eqpos != option.npos)
inQuote = false;
step = 0;
setTransient(section, key, value);
DEBUG_LOG(COMMON, "-config [%s] %s = %s", section.c_str(), key.c_str(), value.c_str());
section.clear();
key.clear();
value.clear();
continue;
}
switch (c)
{
case ':':
switch (step)
{
std::string key = option.substr(colpos + 1, eqpos - (colpos + 1));
std::string value = option.substr(eqpos + 1);
setTransient(section, key, value);
DEBUG_LOG(COMMON, "-config [%s] %s = %s", section.c_str(), key.c_str(), value.c_str());
case 0:
section = trim_trailing_ws(section);
if (section.empty()) {
WARN_LOG(COMMON, "Invalid -config option '%s'. Format is: -config section:key=value,...", str.c_str());
return;
}
step = 1;
break;
case 1:
key += c;
break;
case 2:
value += c;
break;
}
break;
case '=':
switch (step)
{
case 0:
WARN_LOG(COMMON, "Invalid -config option '%s'. Format is: -config section:key=value,...", str.c_str());
return;
case 1:
key = trim_trailing_ws(key);
if (key.empty()) {
WARN_LOG(COMMON, "Invalid -config option '%s'. Format is: -config section:key=value,...", str.c_str());
return;
}
step = 2;
break;
case 2:
value += c;
break;
}
break;
case '\'':
case '"':
switch (step)
{
case 0:
section += c;
break;
case 1:
key += c;
break;
case 2:
if (inQuote == '\0') {
inQuote = c;
value.clear();
}
else
value += c;
break;
}
break;
case ',':
switch (step)
{
case 0:
// ignore consecutive commas
break;
case 1:
key += c;
break;
case 2:
if (inQuote != '\0') {
value += c;
}
else
{
step = 0;
setTransient(section, key, value);
DEBUG_LOG(COMMON, "-config [%s] %s = %s", section.c_str(), key.c_str(), value.c_str());
section.clear();
key.clear();
value.clear();
}
break;
}
break;
case ' ':
switch (step)
{
case 0:
// Ignore leading spaces. Inner spaces are kept (game ids such as "INITIAL D Ver.2")
// and trailing ones are trimmed at the ':' and '=' separators
if (!section.empty())
section += c;
break;
case 1:
if (!key.empty())
key += c;
break;
case 2:
value += c;
break;
}
break;
default:
switch (step)
{
case 0:
section += c;
break;
case 1:
key += c;
break;
case 2:
value += c;
break;
}
}
if (colpos == option.npos || eqpos == option.npos) {
WARN_LOG(COMMON, "Invalid -config option '%s'. Format is: -config section:key=value,...", str.c_str());
return;
}
pos = comma + 1;
}
if (step == 2) {
setTransient(section, key, value);
DEBUG_LOG(COMMON, "-config [%s] %s = %s", section.c_str(), key.c_str(), value.c_str());
}
}
+20 -9
View File
@@ -20,6 +20,8 @@
#include "types.h"
#include "stdclass.h"
#include <cctype>
namespace config {
const IniFile::Entry *IniFile::getEntry(const std::string& sectionName, const std::string& name) const
@@ -71,7 +73,7 @@ bool IniFile::getBool(const std::string& section, const std::string& entry, bool
static bool hasHexPrefix(const std::string& s)
{
size_t pos = 0;
while (pos < s.length() - 1 && std::isspace((int8_t)s[pos]))
while (pos + 1 < s.length() && std::isspace(static_cast<unsigned char>(s[pos])))
pos++;
if (pos + 1 >= s.length())
return false;
@@ -89,9 +91,18 @@ int IniFile::getInt(const std::string& section, const std::string& entry, int de
ss.setf(std::ios_base::hex, std::ios_base::basefield);
else
ss.setf(std::ios_base::dec, std::ios_base::basefield);
unsigned value = defaultValue;
unsigned value = static_cast<unsigned>(defaultValue);
ss >> value;
return (int)value;
if (ss)
return static_cast<int>(value);
// Compatibility for options migrated from Option<bool> to Option<int>.
std::string token = *pValue;
string_tolower(token);
if (token == "yes" || token == "true" || token == "on")
return 1;
if (token == "no" || token == "false" || token == "off")
return 0;
return defaultValue;
}
int64_t IniFile::getInt64(const std::string& section, const std::string& entry, int64_t defaultValue) const
@@ -217,9 +228,9 @@ void IniFile::load(const std::string& data, bool cEscape)
}
std::string value = line.substr(pos + 1);
// Space characters before and after the equal sign are for readability, and not part of the entry name or value
if (value[0] == ' ')
if (!value.empty() && value[0] == ' ')
value = value.substr(1);
if (value[0] == '"')
if (!value.empty() && value[0] == '"')
{
// quoted value
size_t end = value.rfind('"');
@@ -236,13 +247,13 @@ void IniFile::load(const std::string& data, bool cEscape)
}
}
void IniFile::load(FILE *file, bool cEscape)
void IniFile::load(hostfs::File *file, bool cEscape)
{
std::string data;
for (;;)
{
char buffer[4096];
int n = fread(buffer, 1, sizeof(buffer), file);
int n = file->read(buffer, 1, sizeof(buffer));
if (n <= 0)
break;
data += std::string(buffer, buffer + n);
@@ -272,11 +283,11 @@ void IniFile::save(std::string& data) const
data = ss.str();
}
void IniFile::save(FILE *file) const
void IniFile::save(hostfs::File *file) const
{
std::string data;
save(data);
fwrite(data.c_str(), 1, data.length(), file);
file->write(data.c_str(), 1, data.length());
}
bool IniFile::hasSection(const std::string& section) const {
+3 -3
View File
@@ -17,10 +17,10 @@
along with Flycast. If not, see <https://www.gnu.org/licenses/>.
*/
#pragma once
#include "oslib/storage.h"
#include <cstdio>
#include <map>
#include <string>
#include <cstdint>
#include <vector>
#include <locale>
#include <sstream>
@@ -30,9 +30,9 @@ namespace config {
class IniFile
{
public:
void load(FILE *file, bool cEscape = false);
void load(hostfs::File *file, bool cEscape = false);
void load(const std::string& data, bool cEscape = false);
void save(FILE *file) const;
void save(hostfs::File *file) const;
void save(std::string& data) const;
bool hasSection(const std::string& section) const;
bool hasEntry(const std::string& section, const std::string& name) const;
+39 -12
View File
@@ -17,7 +17,7 @@
along with Flycast. If not, see <https://www.gnu.org/licenses/>.
*/
#include "option.h"
#include "network/naomi_network.h"
#include "network/net_handshake.h"
#include "debug/gdb_server.h"
namespace config {
@@ -33,6 +33,7 @@ Option<int> Cable("Dreamcast.Cable", 3); // TV Composite
Option<int> Region("Dreamcast.Region", 1); // USA
Option<int> Broadcast("Dreamcast.Broadcast", 0); // NTSC
Option<int> Language("Dreamcast.Language", 1); // English
OptionString UILanguage("UILanguage", "");
Option<bool> AutoLoadState("Dreamcast.AutoLoadState");
Option<bool> AutoSaveState("Dreamcast.AutoSaveState");
Option<int, false> SavestateSlot("Dreamcast.SavestateSlot");
@@ -76,10 +77,12 @@ Option<int> TextureUpscale("rend.TextureUpscale2", 1);
Option<int> MaxFilteredTextureSize("rend.MaxFilteredTextureSize", 256);
Option<float> ExtraDepthScale("rend.ExtraDepthScale", 1.f);
Option<bool> CustomTextures("rend.CustomTextures");
Option<bool> PreloadCustomTextures("rend.PreloadCustomTextures");
Option<int> PreloadCustomTextures("rend.PreloadCustomTextures", 0);
Option<bool> DumpTextures("rend.DumpTextures");
Option<bool> DumpUniqueTextures("rend.DumpUniqueTextures");
Option<bool> DumpReplacedTextures("rend.DumpReplacedTextures");
Option<int> ScreenStretching("rend.ScreenStretching", 100);
Option<bool> StretchToFill("rend.StretchToFill");
Option<bool> Fog("rend.Fog", true);
Option<bool> FloatVMUs("rend.FloatVMUs");
Option<bool> Rotate90("rend.Rotate90");
@@ -118,6 +121,21 @@ Option<bool> NativeDepthInterpolation("rend.NativeDepthInterpolation", false);
Option<bool> EmulateFramebuffer("rend.EmulateFramebuffer", false);
Option<bool> FixUpscaleBleedingEdge("rend.FixUpscaleBleedingEdge", true);
Option<bool> CustomGpuDriver("rend.CustomGpuDriver", false);
Option<bool> FramePacing("rend.FramePacing", true);
CustomTexturePreloadMode customTexturePreloadMode()
{
const auto mode = static_cast<CustomTexturePreloadMode>(PreloadCustomTextures.get());
switch (mode)
{
case CustomTexturePreloadMode::Off:
case CustomTexturePreloadMode::SystemMemory:
case CustomTexturePreloadMode::VideoMemory:
return mode;
default:
return CustomTexturePreloadMode::Off;
}
}
#ifdef VIDEO_ROUTING
Option<bool, false> VideoRouting("rend.VideoRouting", false);
Option<bool, false> VideoRoutingScale("rend.VideoRoutingScale", false);
@@ -165,9 +183,10 @@ Option<float> ProfilerFrameWarningTime("Profiler.FrameWarningTime", 1.0f / 55.0f
Option<bool> NetworkEnable("Enable", false, "network");
Option<bool> ActAsServer("ActAsServer", false, "network");
Option<bool> NaomiSatellite("NaomiSatellite", false, "network");
OptionString DNS("DNS", "dns.flyca.st", "network");
OptionString NetworkServer("server", "", "network");
Option<int> LocalPort("LocalPort", NaomiNetwork::SERVER_PORT, "network");
Option<int> LocalPort("LocalPort", defaultNaomiServerPort(), "network");
Option<bool> EmulateBBA("EmulateBBA", false, "network");
Option<bool> EnableUPnP("EnableUPnP", true, "network");
Option<bool> GGPOEnable("GGPO", false, "network");
@@ -180,7 +199,7 @@ Option<int> GGPOChatTimeout("GGPOChatTimeout", 10, "network");
Option<bool> NetworkOutput("NetworkOutput", false, "network");
Option<int> MultiboardSlaves("MultiboardSlaves", 1, "network");
Option<bool> BattleCableEnable("BattleCable", false, "network");
Option<bool> UseDCNet("DCNet", false, "network");
Option<bool> UseDCNet("DCNet", true, "network");
OptionString ISPUsername("ISPUsername", "flycast1", "network");
#ifdef USE_OMX
@@ -214,18 +233,24 @@ std::array<std::array<Option<MapleDeviceType>, 2>, 4> MapleExpansionDevices {{
Option<MapleDeviceType>("device4.2", MDT_None, "input")}},
}};
std::array<Option<bool>, 4> UseNetworkExpansionDevices{
Option<bool>("device1.UseNetworkExpansionDevices", false, "input"),
Option<bool>("device2.UseNetworkExpansionDevices", false, "input"),
Option<bool>("device3.UseNetworkExpansionDevices", false, "input"),
Option<bool>("device4.UseNetworkExpansionDevices", false, "input"),
};
std::array<std::array<Option<int>, 2>, 4> NetworkExpansionDevices{{
{{Option<int>("device1.1.net", 0, "input"),
Option<int>("device1.2.net", 0, "input")}},
Option<bool> PerGameVmu("PerGameVmu", false, "config");
{{Option<int>("device2.1.net", 0, "input"),
Option<int>("device2.2.net", 0, "input")}},
{{Option<int>("device3.1.net", 0, "input"),
Option<int>("device3.2.net", 0, "input")}},
{{Option<int>("device4.1.net", 0, "input"),
Option<int>("device4.2.net", 0, "input")}},
}};
Option<bool> PerGameVmu("PerGameVmu", true, "config");
#ifdef _WIN32
Option<bool, false> UseRawInput("RawInput", false, "input");
#endif
Option<bool> UsePhysicalVmuMemory("UsePhysicalVmuMemory", true);
Option<bool> UsePhysicalVmuMemory("UsePhysicalVmuMemory", false);
#ifdef USE_LUA
Option<std::string, false> LuaFileName("LuaFileName", "flycast.lua");
@@ -234,8 +259,10 @@ Option<std::string, false> LuaFileName("LuaFileName", "flycast.lua");
// RetroAchievements
Option<bool> EnableAchievements("Enabled", false, "achievements");
Option<bool> ShowAchievementPopups("Show", true, "achievements");
Option<bool> AchievementsHardcoreMode("HardcoreMode", false, "achievements");
OptionString AchievementsUserName("UserName", "", "achievements");
OptionString AchievementsToken("Token", "", "achievements");
OptionString AchievementsHostUrl("HostUrl", "", "achievements");
} // namespace config
+21 -7
View File
@@ -17,16 +17,16 @@
along with Flycast. If not, see <https://www.gnu.org/licenses/>.
*/
#pragma once
#include <string>
#include <vector>
#include <array>
#include <cmath>
#include <type_traits>
#include "cfg.h"
#include "hw/maple/maple_cfg.h"
#ifdef LIBRETRO
#include <libretro.h>
#endif
#include <array>
#include <cmath>
#include <string>
#include <type_traits>
#include <vector>
namespace config {
@@ -360,6 +360,7 @@ extern Option<int> Cable; // 0 -> VGA, 1 -> VGA, 2 -> RGB, 3 -> TV Composite
extern Option<int> Region; // 0 -> JP, 1 -> USA, 2 -> EU, 3 -> default
extern Option<int> Broadcast; // 0 -> NTSC, 1 -> PAL, 2 -> PAL/M, 3 -> PAL/N, 4 -> default
extern Option<int> Language; // 0 -> JP, 1 -> EN, 2 -> DE, 3 -> FR, 4 -> SP, 5 -> IT, 6 -> default
extern OptionString UILanguage;
extern Option<bool> AutoLoadState;
extern Option<bool> AutoSaveState;
extern Option<int, false> SavestateSlot;
@@ -446,10 +447,19 @@ extern Option<int> PerPixelLayers;
#endif
extern Option<float> ExtraDepthScale;
extern Option<bool> CustomTextures;
extern Option<bool> PreloadCustomTextures;
enum class CustomTexturePreloadMode : int
{
Off = 0,
SystemMemory = 1,
VideoMemory = 2,
};
extern Option<int> PreloadCustomTextures;
CustomTexturePreloadMode customTexturePreloadMode();
extern Option<bool> DumpTextures;
extern Option<bool> DumpUniqueTextures;
extern Option<bool> DumpReplacedTextures;
extern Option<int> ScreenStretching; // in percent. 150 means stretch from 4/3 to 6/3
extern Option<bool> StretchToFill;
extern Option<bool> Fog;
extern Option<bool> FloatVMUs;
extern Option<bool> Rotate90;
@@ -474,6 +484,7 @@ extern Option<bool> NativeDepthInterpolation;
extern Option<bool> EmulateFramebuffer;
extern Option<bool> FixUpscaleBleedingEdge;
extern Option<bool> CustomGpuDriver;
extern Option<bool> FramePacing;
#ifdef VIDEO_ROUTING
extern Option<bool, false> VideoRouting;
extern Option<bool, false> VideoRoutingScale;
@@ -521,6 +532,7 @@ extern Option<float> ProfilerFrameWarningTime;
extern Option<bool> NetworkEnable;
extern Option<bool> ActAsServer;
extern Option<bool> NaomiSatellite;
extern OptionString DNS;
extern OptionString NetworkServer;
extern Option<int> LocalPort;
@@ -551,7 +563,7 @@ extern Option<int> VirtualGamepadVibration;
extern Option<int> VirtualGamepadTransparency;
extern std::array<Option<MapleDeviceType>, 4> MapleMainDevices;
extern std::array<std::array<Option<MapleDeviceType>, 2>, 4> MapleExpansionDevices;
extern std::array<Option<bool>, 4> UseNetworkExpansionDevices;
extern std::array<std::array<Option<int>, 2>, 4> NetworkExpansionDevices;
extern Option<bool> PerGameVmu;
#ifdef _WIN32
extern Option<bool, false> UseRawInput;
@@ -567,8 +579,10 @@ extern Option<std::string, false> LuaFileName;
// RetroAchievements
extern Option<bool> EnableAchievements;
extern Option<bool> ShowAchievementPopups;
extern Option<bool> AchievementsHardcoreMode;
extern OptionString AchievementsUserName;
extern OptionString AchievementsToken;
extern OptionString AchievementsHostUrl;
} // namespace config
+54
View File
@@ -0,0 +1,54 @@
#pragma once
#include <utility>
#include <libchdr/chd.h>
#include "oslib/storage.h"
namespace CHD
{
namespace Callbacks
{
// Moved out here to avoid being duplicated by the template.
inline uint64_t fsize(struct chd_core_file *core_file)
{
auto file = static_cast<hostfs::File*>(core_file->argp);
return file->size();
}
inline size_t fread(void* buffer, size_t size, size_t count, struct chd_core_file *core_file)
{
auto file = static_cast<hostfs::File*>(core_file->argp);
return file->read(buffer, size, count);
}
inline int fclose(struct chd_core_file *core_file)
{
auto file = static_cast<hostfs::File*>(core_file->argp);
delete file;
delete core_file;
return 0;
}
inline int fseek(struct chd_core_file *core_file, int64_t offset, int whence)
{
auto file = static_cast<hostfs::File*>(core_file->argp);
return file->seek(offset, whence);
}
}
}
template<typename... Ts>
inline auto chd_open_file(hostfs::File *file, Ts &&...args)
{
auto core_file = new struct chd_core_file();
core_file->argp = file;
core_file->fsize = CHD::Callbacks::fsize;
core_file->fread = CHD::Callbacks::fread;
core_file->fclose = CHD::Callbacks::fclose;
core_file->fseek = CHD::Callbacks::fseek;
return chd_open_core_file(core_file, std::forward<Ts>(args)...);
}
+38 -29
View File
@@ -29,6 +29,10 @@
#include "emulator.h"
#include "oslib/storage.h"
#include "oslib/i18n.h"
#include <string>
#include <vector>
using namespace i18n;
const WidescreenCheat CheatManager::widescreen_cheats[] =
@@ -78,14 +82,15 @@ const WidescreenCheat CheatManager::widescreen_cheats[] =
{ "MK-51136", nullptr, { 0x2BDDD0 }, { 0x43700000 } }, // Crazy Taxi 2 (USA)
// { "HDR-0159", nullptr, { 0x2FBBD0 }, { 0x43700000 } }, // Crazy Taxi 2 (JP) not working
{ "T13004N", nullptr, { 0x016D94 }, { 0x44234E73 } }, // Cyber Troopers - Virtual On - Oratorio Tangram (USA)
{ "HDR-0040", nullptr, { 0x016D94 }, { 0x44234E73 } }, // Cyber Troopers - Virtual On - Oratorio Tangram (JP)
// D2 (USA)
{ "MK-51036", nullptr, { 0x4B5CF4, 0x4B5CC4, 0x3E92A0, 0x3E92A8, 0x3E92C0, 0x3E92C8 },
{ 0x3F400000, 0x43F00000, 0, 0, 0, 0 } },
// D2 (JP)
{ "T30006M", nullptr, { 0x4CF42C, 0x4CF45C, 0x3E1A36, 0x3E1A34, 0x3E1A3C, 0x3E1A54, 0x3E1A5C },
{ 0x43F00000, 0x3F400000, 0x08010000, 0, 0, 0, 0 } },
{ "MK-5103750", nullptr, { 0x1FE270 }, { 0x43700000 } }, // Daytona USA (PAL)
// breaks online connection { "MK-51037", nullptr, { 0x1FC6D0 }, { 0x43700000 } }, // Daytona USA (USA)
{ "MK-5103750", nullptr, { 0x1FE270 }, { 0x43700000 }, { 0x43a00000 } }, // Daytona USA (PAL)
{ "MK-51037", nullptr, { 0x1FC6D0 }, { 0x43700000 }, { 0x43a00000 } }, // Daytona USA (USA)
{ "T9501N-50", nullptr, { 0x9821D4 }, { 0x3F400000 } }, // Deadly Skies (PAL)
{ "T8116D 50", nullptr, { 0x2E5530 }, { 0x43700000 } }, // Dead or Alive 2 (PAL)
{ "T3601N", nullptr, { 0x2F0670 }, { 0x43700000 } }, // Dead or Alive 2 (USA)
@@ -238,10 +243,10 @@ const WidescreenCheat CheatManager::widescreen_cheats[] =
{ "MK-5105950", nullptr, { 0x231EF8, 0x1EF370 }, { 0x43800000, 0x7C1EF400 } }, // Shenmue (PAL) code 1 reduces clipping
{ "MK-51059", nullptr, { 0x230250 }, { 0x43800000 }, { 0x43a00000 } }, // Shenmue (USA) Clipping
{ "HDR-0016", nullptr, { 0x22E8A0, 0x1EBE70 }, { 0x43800000, 0x7C1EBF00 } }, // Shenmue (JP) code 1 reduces clipping
{ "MK-5118450", nullptr, { 0x31186C }, { 0x43800000 } }, // Shenmue II (PAL) 01160FF4 0000E100 for black bars in cutscenes
{ "MK-5118450", nullptr, { 0x31186C }, { 0x43800000 }, { 0x43a00000 } }, // Shenmue II (PAL) 01160FF4 0000E100 for black bars in cutscenes
// Shenmue II (PAL) Alternative code without clipping or black bars. Might be demanding on real hardware.
// 02311880 C3A00000, 0227E198 35AA359E, 01160FF4 0000E100
{ "HDR-0164", nullptr, { 0x30D67C }, { 0x43700000 } }, // Shenmue II (JP) Clipping
{ "HDR-0164", nullptr, { 0x30D67C }, { 0x43700000 }, { 0x43a00000 } }, // Shenmue II (JP) Clipping
{ "T9505D", nullptr, { 0xD1FB14, 0x096A4C }, { 0x3F400000, 0x3FAAAAAB } }, // Silent Scope (PAL) Choose 60Hz in game options
{ "MK-51052", " E ", { 0x502A84 }, { 0x3F400000 } }, // Skies of Arcadia (PAL)
{ "MK-51052", " U ", { 0x599158 }, { 0x3F400000 } }, // Skies of Arcadia (USA)
@@ -391,7 +396,7 @@ void CheatManager::loadCheatFile(const std::string& filename)
return;
}
FILE* cheatfile = hostfs::storage().openFile(filename, "r");
hostfs::File* cheatfile = hostfs::storage().openFile(filename, "r");
if (cheatfile == nullptr)
{
WARN_LOG(COMMON, "Cannot open cheat file '%s'", filename.c_str());
@@ -399,7 +404,7 @@ void CheatManager::loadCheatFile(const std::string& filename)
}
config::IniFile cfg;
cfg.load(cheatfile);
fclose(cheatfile);
delete cheatfile;
int count = cfg.getInt("", "cheats", 0);
cheats.clear();
@@ -450,7 +455,7 @@ void CheatManager::reset(const std::string& gameId)
this->gameId = gameId;
#ifndef LIBRETRO
if (!settings.raHardcoreMode)
if (!settings.raHardcoreMode && !gameId.empty())
{
std::string cheatFile = config::loadStr("cheats", gameId);
if (!cheatFile.empty())
@@ -504,12 +509,6 @@ found_cheats:
else if (gameId == "T8120D 50") { // Dave Mirra BMX (EU)
cheats.emplace_back(Cheat::Type::setValue, "fix main loop time", true, 32, 0x003011cc, 0x42200000, true); // 40.0 ms
}
else if (gameId == "MK-0100") { // F355 US
cheats.emplace_back(Cheat::Type::setValue, "increase datapump timeout", true, 16, 0x00131668, 1000, true);
}
else if (gameId == "T8118D 50") { // F355 EU
cheats.emplace_back(Cheat::Type::setValue, "increase datapump timeout", true, 16, 0x00135588, 1000, true);
}
else if (gameId == "SAMURAI SPIRITS 6" || gameId == "T0002M") {
cheats.emplace_back(Cheat::Type::setValue, "fix depth", true, 16, 0x0003e602, 0x0009, true); // nop (shift by 8 bits instead of 10)
}
@@ -562,10 +561,6 @@ found_cheats:
cheats.emplace_back(Cheat::Type::runNextIfEq, "bypass auth ifeq", true, 32, 0x0013f150, 0x2fd62fe6, true);
cheats.emplace_back(Cheat::Type::setValue, "bypass dricas auth", true, 32, 0x0013f150, 0xe000000b, true);
}
else if (gameId == "HDR-0124") { // Hundred Swords
cheats.emplace_back(Cheat::Type::runNextIfEq, "bypass auth ifeq", true, 32, 0x006558ac, 0x1f414f22, true);
cheats.emplace_back(Cheat::Type::setValue, "bypass dricas auth", true, 32, 0x006558ac, 0xe000000b, true);
}
else if (gameId == "T43903M") { // Culdcept II
cheats.emplace_back(Cheat::Type::runNextIfEq, "bypass auth ifeq", true, 32, 0x00800524, 0x2fd62fe6, true);
cheats.emplace_back(Cheat::Type::setValue, "bypass dricas auth", true, 32, 0x00800524, 0xe000000b, true);
@@ -574,20 +569,34 @@ found_cheats:
cheats.emplace_back(Cheat::Type::runNextIfEq, "bypass auth ifeq", true, 32, 0x00139f54, 0x2fd62fe6, true);
cheats.emplace_back(Cheat::Type::setValue, "bypass dricas auth", true, 32, 0x00139f54, 0xe000000b, true);
}
else if (gameId == "HDR-0113") // Power Smash
else if (gameId == "HDR-0100") // F355 (JP)
{
cheats.emplace_back(Cheat::Type::runNextIfEq, "no dupe SYN ifeq", true, 16, 0x14d258, 0xbbce, true); // bsr SendNormalSYN
cheats.emplace_back(Cheat::Type::setValue, "no dupe SYN set", true, 16, 0x14d258, 0x0009, true); // nop
cheats.emplace_back(Cheat::Type::runNextIfEq, "no dupe ACK ifeq", true, 16, 0x14af42, 0x430b, true); // jsr TCPInternalSendPacket
cheats.emplace_back(Cheat::Type::setValue, "no dupe ACK set", true, 16, 0x14af42, 0x0009, true); // nop
cheats.emplace_back(Cheat::Type::runNextIfEq, "bypass auth ifeq", true, 32, 0x0ba980, 0x2fd62fe6, true);
cheats.emplace_back(Cheat::Type::setValue, "bypass dricas auth", true, 32, 0x0ba980, 0xe000000b, true);
cheats.emplace_back(Cheat::Type::runNextIfEq, "bypass auth check ifeq", true, 32, 0x0a2d3c, 0xed048900, true);
cheats.emplace_back(Cheat::Type::setValue, "bypass dricas auth check", true, 32, 0x0a2d3c, 0xed048b00, true); // BT -> BF
}
else if (gameId == "HDR-0091") // Pro Yakyuu Team de Asobou Net!
else if (gameId == "MK-51140") { // Ooga Booga
cheats.emplace_back(Cheat::Type::runNextIfEq, "disable net check ifeq", true, 16, 0x00085d2c, 0x3630, true);
cheats.emplace_back(Cheat::Type::setValue, "disable net sync check", true, 16, 0x00085d2c, 0x3330, true);
}
else if (gameId == "HDR-0006") { // Nettou Golf
cheats.emplace_back(Cheat::Type::runNextIfEq, "bypass auth ifeq", true, 32, 0x0d5fbc, 0x2fd62fe6, true);
cheats.emplace_back(Cheat::Type::setValue, "bypass dricas auth", true, 32, 0x0d5fbc, 0xe000000b, true);
}
else if (gameId == "SEGA STRIKE FIGHTER IN JPN-SLAVE")
{
cheats.emplace_back(Cheat::Type::runNextIfEq, "no dupe SYN ifeq", true, 16, 0xe13e0c, 0xbbce, true); // bsr SendNormalSYN
cheats.emplace_back(Cheat::Type::setValue, "no dupe SYN set", true, 16, 0xe13e0c, 0x0009, true); // nop
cheats.emplace_back(Cheat::Type::runNextIfEq, "no dupe ACK ifeq", true, 16, 0xe11af6, 0x430b, true); // jsr TCPInternalSendPacket
cheats.emplace_back(Cheat::Type::setValue, "no dupe ACK set", true, 16, 0xe11af6, 0x0009, true); // nop
// hack the slave sync procedure
cheats.emplace_back(Cheat::Type::runNextIfEq, "easy sync ifeq", true, 32, 0x08b66c, 0x40600000, true);
cheats.emplace_back(Cheat::Type::setValue, "easy sync", true, 32, 0x08b66c, 0x4cbebc20, true); // 3.5 -> 100000000
}
else if (gameId == "AIRLINE PILOTS IN JAPAN-SLAVE")
{
// hack the slave sync procedure
cheats.emplace_back(Cheat::Type::runNextIfEq, "easy sync ifeq", true, 32, 0x052ee0, 0x40200000, true);
cheats.emplace_back(Cheat::Type::setValue, "easy sync", true, 32, 0x052ee0, 0x4cbebc20, true); // 2.5 -> 100000000
}
if (cheats.size() > cheatCount)
setActive(true);
@@ -1102,10 +1111,10 @@ void CheatManager::saveCheatFile(const std::string& filename)
i++;
}
cfg.set("", "cheats", i);
FILE *fp = hostfs::storage().openFile(filename.c_str(), "w");
hostfs::File *fp = hostfs::storage().openFile(filename.c_str(), "w");
if (fp == nullptr)
throw FlycastException(Ts("Can't save cheat file"));
cfg.save(fp);
fclose(fp);
delete fp;
#endif
}
+3
View File
@@ -69,6 +69,9 @@ struct Cheat
class CheatManager
{
public:
~CheatManager() {
reset("");
}
void reset(const std::string& gameId);
void apply();
size_t cheatCount() const { return cheats.size(); }
+1 -1
View File
@@ -24,7 +24,7 @@
#include "hw/sh4/dyna/shil.h"
#include "cfg/option.h"
#include <array>
#include <signal.h>
#include <csignal>
#include <map>
#ifndef SIGTRAP
+59 -62
View File
@@ -4,33 +4,29 @@
#include <errno.h>
#include "cdipsr.h"
// Global variables
unsigned long temp_value;
/////////////////////////////////////////////////////////////////////////////
unsigned long ask_type(FILE *fsource, long header_position)
unsigned long ask_type(hostfs::File *fsource, long header_position)
{
unsigned char filename_length;
unsigned long track_mode;
uint32_t temp_value = 0;
fseek(fsource, header_position, SEEK_SET);
fread(&temp_value, 4, 1, fsource);
fsource->seek(header_position, SEEK_SET);
fsource->read(&temp_value, 4, 1);
if (temp_value != 0)
fseek(fsource, 8, SEEK_CUR); // extra data (DJ 3.00.780 and up)
fseek(fsource, 24, SEEK_CUR);
fread(&filename_length, 1, 1, fsource);
fseek(fsource, filename_length, SEEK_CUR);
fseek(fsource, 19, SEEK_CUR);
fread(&temp_value, 4, 1, fsource);
fsource->seek(8, SEEK_CUR); // extra data (DJ 3.00.780 and up)
fsource->seek(24, SEEK_CUR);
fsource->read(&filename_length, 1, 1);
fsource->seek(filename_length, SEEK_CUR);
fsource->seek(19, SEEK_CUR);
fsource->read(&temp_value, 4, 1);
if (temp_value == 0x80000000)
fseek(fsource, 8, SEEK_CUR); // DJ4
fseek(fsource, 16, SEEK_CUR);
fread(&track_mode, 4, 1, fsource);
fseek(fsource, header_position, SEEK_SET);
fsource->seek(8, SEEK_CUR); // DJ4
fsource->seek(16, SEEK_CUR);
fsource->read(&track_mode, 4, 1);
fsource->seek(header_position, SEEK_SET);
return (track_mode);
}
@@ -38,47 +34,48 @@ unsigned long track_mode;
/////////////////////////////////////////////////////////////////////////////
bool CDI_read_track (FILE *fsource, image_s *image, track_s *track)
bool CDI_read_track (hostfs::File *fsource, image_s *image, track_s *track)
{
unsigned char TRACK_START_MARK[10] = { 0, 0, 0x01, 0, 0, 0, 0xFF, 0xFF, 0xFF, 0xFF };
unsigned char current_start_mark[10];
uint32_t temp_value = 0;
fread(&temp_value, 4, 1, fsource);
fsource->read(&temp_value, 4, 1);
if (temp_value != 0)
fseek(fsource, 8, SEEK_CUR); // extra data (DJ 3.00.780 and up)
fsource->seek(8, SEEK_CUR); // extra data (DJ 3.00.780 and up)
fread(&current_start_mark, 10, 1, fsource);
fsource->read(&current_start_mark, 10, 1);
if (memcmp(TRACK_START_MARK, current_start_mark, 10)) {
printf("CDI_read_track: Unsupported format: Could not find the track start mark\n");
return false;
}
fread(&current_start_mark, 10, 1, fsource);
fsource->read(&current_start_mark, 10, 1);
if (memcmp(TRACK_START_MARK, current_start_mark, 10)) {
printf("CDI_read_track: Unsupported format: Could not find the track start mark\n");
return false;
}
fseek(fsource, 4, SEEK_CUR);
fread(&track->filename_length, 1, 1, fsource);
fseek(fsource, track->filename_length, SEEK_CUR);
fseek(fsource, 11, SEEK_CUR);
fseek(fsource, 4, SEEK_CUR);
fseek(fsource, 4, SEEK_CUR);
fread(&temp_value, 4, 1, fsource);
fsource->seek(4, SEEK_CUR);
fsource->read(&track->filename_length, 1, 1);
fsource->seek(track->filename_length, SEEK_CUR);
fsource->seek(11, SEEK_CUR);
fsource->seek(4, SEEK_CUR);
fsource->seek(4, SEEK_CUR);
fsource->read(&temp_value, 4, 1);
if (temp_value == 0x80000000)
fseek(fsource, 8, SEEK_CUR); // DJ4
fseek(fsource, 2, SEEK_CUR);
fread(&track->pregap_length, 4, 1, fsource);
fread(&track->length, 4, 1, fsource);
fseek(fsource, 6, SEEK_CUR);
fread(&track->mode, 4, 1, fsource);
fseek(fsource, 12, SEEK_CUR);
fread(&track->start_lba, 4, 1, fsource);
fread(&track->total_length, 4, 1, fsource);
fseek(fsource, 16, SEEK_CUR);
fread(&track->sector_size_value, 4, 1, fsource);
fsource->seek(8, SEEK_CUR); // DJ4
fsource->seek(2, SEEK_CUR);
fsource->read(&track->pregap_length, 4, 1);
fsource->read(&track->length, 4, 1);
fsource->seek(6, SEEK_CUR);
fsource->read(&track->mode, 4, 1);
fsource->seek(12, SEEK_CUR);
fsource->read(&track->start_lba, 4, 1);
fsource->read(&track->total_length, 4, 1);
fsource->seek(16, SEEK_CUR);
fsource->read(&track->sector_size_value, 4, 1);
switch(track->sector_size_value)
{
@@ -96,34 +93,34 @@ bool CDI_read_track (FILE *fsource, image_s *image, track_s *track)
return false;
}
fseek(fsource, 29, SEEK_CUR);
fsource->seek(29, SEEK_CUR);
if (image->version != CDI_V2)
{
fseek(fsource, 5, SEEK_CUR);
fread(&temp_value, 4, 1, fsource);
fsource->seek(5, SEEK_CUR);
fsource->read(&temp_value, 4, 1);
if (temp_value == 0xffffffff)
fseek(fsource, 78, SEEK_CUR); // extra data (DJ 3.00.780 and up)
fsource->seek(78, SEEK_CUR); // extra data (DJ 3.00.780 and up)
}
return true;
}
void CDI_skip_next_session (FILE *fsource, image_s *image)
void CDI_skip_next_session (hostfs::File *fsource, image_s *image)
{
fseek(fsource, 4, SEEK_CUR);
fseek(fsource, 8, SEEK_CUR);
if (image->version != CDI_V2) fseek(fsource, 1, SEEK_CUR);
fsource->seek(4, SEEK_CUR);
fsource->seek(8, SEEK_CUR);
if (image->version != CDI_V2) fsource->seek(1, SEEK_CUR);
}
bool CDI_get_tracks (FILE *fsource, image_s *image)
bool CDI_get_tracks (hostfs::File *fsource, image_s *image)
{
return fread(&image->tracks, 2, 1, fsource) == 1;
return fsource->read(&image->tracks, 2, 1) == 1;
}
bool CDI_init (FILE *fsource, image_s *image, const char *fsourcename)
bool CDI_init (hostfs::File *fsource, image_s *image, const char *fsourcename)
{
fseek(fsource, 0, SEEK_END);
image->length = ftell(fsource);
fsource->seek(0, SEEK_END);
image->length = fsource->tell();
if (image->length < 8)
{
@@ -131,9 +128,9 @@ bool CDI_init (FILE *fsource, image_s *image, const char *fsourcename)
return false;
}
fseek(fsource, image->length-8, SEEK_SET);
if (fread(&image->version, 4, 1, fsource) != 1
|| fread(&image->header_offset, 4, 1, fsource) != 1)
fsource->seek(image->length-8, SEEK_SET);
if (fsource->read(&image->version, 4, 1) != 1
|| fsource->read(&image->header_offset, 4, 1) != 1)
return false;
if ((image->version != CDI_V2 && image->version != CDI_V3 && image->version != CDI_V35)
@@ -145,17 +142,17 @@ bool CDI_init (FILE *fsource, image_s *image, const char *fsourcename)
return true;
}
bool CDI_get_sessions (FILE *fsource, image_s *image)
bool CDI_get_sessions (hostfs::File *fsource, image_s *image)
{
#ifndef DEBUG_CDI
if (image->version == CDI_V35)
fseek(fsource, (image->length - image->header_offset), SEEK_SET);
fsource->seek((image->length - image->header_offset), SEEK_SET);
else
fseek(fsource, image->header_offset, SEEK_SET);
fsource->seek(image->header_offset, SEEK_SET);
#else
fseek(fsource, 0L, SEEK_SET);
fsource->seek(0L, SEEK_SET);
#endif
return fread(&image->sessions, 2, 1, fsource) == 1;
return fsource->read(&image->sessions, 2, 1) == 1;
}
+8 -6
View File
@@ -1,6 +1,8 @@
#ifndef __CDI_H__
#define __CDI_H__
#include "../../oslib/storage.h"
/* Basic structures */
typedef struct image_s
@@ -36,11 +38,11 @@ typedef struct track_s
#define CDI_V3 0x80000005
#define CDI_V35 0x80000006
unsigned long ask_type(FILE *fsource, long header_position);
bool CDI_init(FILE *fsource, image_s *image, const char *fsourcename);
bool CDI_get_sessions(FILE *fsource, image_s *image);
bool CDI_get_tracks(FILE *fsource, image_s *image);
bool CDI_read_track(FILE *fsource, image_s *image, track_s *track);
void CDI_skip_next_session(FILE *fsource, image_s *image);
unsigned long ask_type(hostfs::File *fsource, long header_position);
bool CDI_init(hostfs::File *fsource, image_s *image, const char *fsourcename);
bool CDI_get_sessions(hostfs::File *fsource, image_s *image);
bool CDI_get_tracks(hostfs::File *fsource, image_s *image);
bool CDI_read_track(hostfs::File *fsource, image_s *image, track_s *track);
void CDI_skip_next_session(hostfs::File *fsource, image_s *image);
#endif
Submodule core/deps/freetype added at 0a0221a134.
+1
Submodule core/deps/gamesdk added at 4ac9952e6a.
@@ -13,6 +13,15 @@
static const int UDP_HEADER_SIZE = 28; /* Size of IP + UDP headers */
static const int NUM_SYNC_PACKETS = 5;
static const int SYNC_RETRY_INTERVAL = 2000;
static bool IsValidAppData(const UdpMsg *msg, int len)
{
const int header_size = (const uint8 *)msg->u.app_data.data - (const uint8 *)msg;
if (len < header_size)
return false;
return msg->u.app_data.size <= MAX_APPDATA_SIZE
&& msg->u.app_data.size <= len - header_size;
}
static const int SYNC_FIRST_RETRY_INTERVAL = 500;
static const int RUNNING_RETRY_INTERVAL = 200;
static const int KEEP_ALIVE_INTERVAL = 200;
@@ -320,6 +329,13 @@ UdpProtocol::HandlesMsg(sockaddr_in &from,
void
UdpProtocol::OnMsg(UdpMsg *msg, int len)
{
if (len < (int)sizeof(msg->hdr)) {
return;
}
if (msg->hdr.type == UdpMsg::AppData && !IsValidAppData(msg, len)) {
return;
}
bool handled = false;
typedef bool (UdpProtocol::*DispatchFn)(UdpMsg *msg, int len);
static const DispatchFn table[] = {
@@ -791,6 +807,8 @@ UdpProtocol::ClearSendQueue()
void UdpProtocol::SendAppData(const void *data, int len, bool spectators)
{
if (len < 0 || len > MAX_APPDATA_SIZE || (data == nullptr && len != 0))
return;
if (_udp == nullptr)
return;
if (_current_state != Synchronzied && _current_state != Running)
@@ -806,6 +824,8 @@ void UdpProtocol::SendAppData(const void *data, int len, bool spectators)
bool UdpProtocol::OnAppData(UdpMsg *msg, int len)
{
if (!IsValidAppData(msg, len))
return false;
UdpProtocol::Event evt(UdpProtocol::Event::AppData);
evt.u.app_data.spectators = msg->u.app_data.spectators != 0;
evt.u.app_data.size = msg->u.app_data.size;
+2 -1
View File
@@ -14,7 +14,8 @@ _input_queues(NULL),
_framecount = 0;
_last_confirmed_frame = -1;
_max_prediction_frames = 0;
memset(&_savedstate, 0, sizeof(_savedstate));
_savedstate = {};
_rollingback = false;
}
Sync::~Sync()
+278 -207
View File
@@ -2,8 +2,10 @@
// This needs to be used along with a Platform Backend (e.g. Win32)
// Implemented features:
// [X] Renderer: User texture binding. Use 'ID3D11ShaderResourceView*' as ImTextureID. Read the FAQ about ImTextureID!
// [X] Renderer: Large meshes support (64k+ vertices) with 16-bit indices.
// [X] Renderer: User texture binding. Use 'ID3D11ShaderResourceView*' as texture identifier. Read the FAQ about ImTextureID/ImTextureRef!
// [X] Renderer: Large meshes support (64k+ vertices) even with 16-bit indices (ImGuiBackendFlags_RendererHasVtxOffset).
// [X] Renderer: Texture updates support for dynamic font atlas (ImGuiBackendFlags_RendererHasTextures).
// [X] Renderer: Expose selected render state for draw callbacks to use. Access in '(ImGui_ImplXXXX_RenderState*)GetPlatformIO().Renderer_RenderState'.
// You can use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this.
// Prefer including the entire imgui/ repository into your project (either as a copy or as a submodule), and only build the backends you need.
@@ -15,12 +17,19 @@
// CHANGELOG
// (minor and older changes stripped away, please see git history for details)
// 2026-01-19: DirectX11: Added 'SamplerNearest' in ImGui_ImplDX11_RenderState. Renamed 'SamplerDefault' to 'SamplerLinear'.
// 2025-09-18: Call platform_io.ClearRendererHandlers() on shutdown.
// 2025-06-11: DirectX11: Added support for ImGuiBackendFlags_RendererHasTextures, for dynamic font atlas.
// 2025-05-07: DirectX11: Honor draw_data->FramebufferScale to allow for custom backends and experiment using it (consistently with other renderer backends, even though in normal condition it is not set under Windows).
// 2025-01-06: DirectX11: Expose VertexConstantBuffer in ImGui_ImplDX11_RenderState. Reset projection matrix in ImDrawCallback_ResetRenderState handler.
// 2024-10-07: DirectX11: Changed default texture sampler to Clamp instead of Repeat/Wrap.
// 2024-10-07: DirectX11: Expose selected render state in ImGui_ImplDX11_RenderState, which you can access in 'void* platform_io.Renderer_RenderState' during draw callbacks.
// 2022-10-11: Using 'nullptr' instead of 'NULL' as per our switch to C++11.
// 2021-06-29: Reorganized backend to pull data from a single structure to facilitate usage with multiple-contexts (all g_XXXX access changed to bd->XXXX).
// 2021-05-19: DirectX11: Replaced direct access to ImDrawCmd::TextureId with a call to ImDrawCmd::GetTexID(). (will become a requirement)
// 2021-02-18: DirectX11: Change blending equation to preserve alpha in output buffer.
// 2019-08-01: DirectX11: Fixed code querying the Geometry Shader state (would generally error with Debug layer enabled).
// 2019-07-21: DirectX11: Backup, clear and restore Geometry Shader is any is bound when calling ImGui_ImplDX10_RenderDrawData. Clearing Hull/Domain/Compute shaders without backup/restore.
// 2019-07-21: DirectX11: Backup, clear and restore Geometry Shader is any is bound when calling ImGui_ImplDX11_RenderDrawData. Clearing Hull/Domain/Compute shaders without backup/restore.
// 2019-05-29: DirectX11: Added support for large mesh (64K+ vertices), enable ImGuiBackendFlags_RendererHasVtxOffset flag.
// 2019-04-30: DirectX11: Added support for special ImDrawCallback_ResetRenderState callback to reset render state.
// 2018-12-03: Misc: Added #pragma comment statement to automatically link with d3dcompiler.lib when using D3DCompile().
@@ -45,7 +54,20 @@
#pragma comment(lib, "d3dcompiler") // Automatically link with d3dcompiler.lib as we are using D3DCompile() below.
#endif
// Clang/GCC warnings with -Weverything
#if defined(__clang__)
#pragma clang diagnostic ignored "-Wold-style-cast" // warning: use of old-style cast // yes, they are more terse.
#pragma clang diagnostic ignored "-Wsign-conversion" // warning: implicit conversion changes signedness
#endif
/* Flycast: Use ImTextureDX11 instead
// DirectX11 data
struct ImGui_ImplDX11_Texture
{
ID3D11Texture2D* pTexture;
ID3D11ShaderResourceView* pTextureView;
};
*/
struct ImGui_ImplDX11_Data
{
ID3D11Device* pd3dDevice;
@@ -57,10 +79,9 @@ struct ImGui_ImplDX11_Data
ID3D11InputLayout* pInputLayout;
ID3D11Buffer* pVertexConstantBuffer;
ID3D11PixelShader* pPixelShader;
ID3D11SamplerState* pFontSampler;
ID3D11SamplerState* pTextureSampler;
ID3D11SamplerState* pPointSampler;
ImTextureDX11 FontTexture;
ID3D11SamplerState* pTexSamplerLinear;
ID3D11SamplerState* pTexSamplerLinearBorder;
ID3D11SamplerState* pTexSamplerNearest;
ID3D11RasterizerState* pRasterizerState;
ID3D11BlendState* pBlendState;
ID3D11DepthStencilState* pDepthStencilState;
@@ -83,106 +104,24 @@ static ImGui_ImplDX11_Data* ImGui_ImplDX11_GetBackendData()
}
// Functions
static void ImGui_ImplDX11_SetupRenderState(ImDrawData* draw_data, ID3D11DeviceContext* ctx)
static void ImGui_ImplDX11_SetupRenderState(const ImDrawData* draw_data, ID3D11DeviceContext* device_ctx)
{
ImGui_ImplDX11_Data* bd = ImGui_ImplDX11_GetBackendData();
// Setup viewport
D3D11_VIEWPORT vp;
memset(&vp, 0, sizeof(D3D11_VIEWPORT));
vp.Width = draw_data->DisplaySize.x;
vp.Height = draw_data->DisplaySize.y;
D3D11_VIEWPORT vp = {};
vp.Width = draw_data->DisplaySize.x * draw_data->FramebufferScale.x;
vp.Height = draw_data->DisplaySize.y * draw_data->FramebufferScale.y;
vp.MinDepth = 0.0f;
vp.MaxDepth = 1.0f;
vp.TopLeftX = vp.TopLeftY = 0;
ctx->RSSetViewports(1, &vp);
// Setup shader and vertex buffers
unsigned int stride = sizeof(ImDrawVert);
unsigned int offset = 0;
ctx->IASetInputLayout(bd->pInputLayout);
ctx->IASetVertexBuffers(0, 1, &bd->pVB, &stride, &offset);
ctx->IASetIndexBuffer(bd->pIB, sizeof(ImDrawIdx) == 2 ? DXGI_FORMAT_R16_UINT : DXGI_FORMAT_R32_UINT, 0);
ctx->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
ctx->VSSetShader(bd->pVertexShader, nullptr, 0);
ctx->VSSetConstantBuffers(0, 1, &bd->pVertexConstantBuffer);
ctx->PSSetShader(bd->pPixelShader, nullptr, 0);
ctx->GSSetShader(nullptr, nullptr, 0);
ctx->HSSetShader(nullptr, nullptr, 0); // In theory we should backup and restore this as well.. very infrequently used..
ctx->DSSetShader(nullptr, nullptr, 0); // In theory we should backup and restore this as well.. very infrequently used..
ctx->CSSetShader(nullptr, nullptr, 0); // In theory we should backup and restore this as well.. very infrequently used..
// Setup blend state
const float blend_factor[4] = { 0.f, 0.f, 0.f, 0.f };
ctx->OMSetBlendState(bd->pBlendState, blend_factor, 0xffffffff);
ctx->OMSetDepthStencilState(bd->pDepthStencilState, 0);
ctx->RSSetState(bd->pRasterizerState);
}
// Render function
void ImGui_ImplDX11_RenderDrawData(ImDrawData* draw_data)
{
// Avoid rendering when minimized
if (draw_data->DisplaySize.x <= 0.0f || draw_data->DisplaySize.y <= 0.0f)
return;
ImGui_ImplDX11_Data* bd = ImGui_ImplDX11_GetBackendData();
ID3D11DeviceContext* ctx = bd->pd3dDeviceContext;
// Create and grow vertex/index buffers if needed
if (!bd->pVB || bd->VertexBufferSize < draw_data->TotalVtxCount)
{
if (bd->pVB) { bd->pVB->Release(); bd->pVB = nullptr; }
bd->VertexBufferSize = draw_data->TotalVtxCount + 5000;
D3D11_BUFFER_DESC desc;
memset(&desc, 0, sizeof(D3D11_BUFFER_DESC));
desc.Usage = D3D11_USAGE_DYNAMIC;
desc.ByteWidth = bd->VertexBufferSize * sizeof(ImDrawVert);
desc.BindFlags = D3D11_BIND_VERTEX_BUFFER;
desc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE;
desc.MiscFlags = 0;
if (bd->pd3dDevice->CreateBuffer(&desc, nullptr, &bd->pVB) < 0)
return;
}
if (!bd->pIB || bd->IndexBufferSize < draw_data->TotalIdxCount)
{
if (bd->pIB) { bd->pIB->Release(); bd->pIB = nullptr; }
bd->IndexBufferSize = draw_data->TotalIdxCount + 10000;
D3D11_BUFFER_DESC desc;
memset(&desc, 0, sizeof(D3D11_BUFFER_DESC));
desc.Usage = D3D11_USAGE_DYNAMIC;
desc.ByteWidth = bd->IndexBufferSize * sizeof(ImDrawIdx);
desc.BindFlags = D3D11_BIND_INDEX_BUFFER;
desc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE;
if (bd->pd3dDevice->CreateBuffer(&desc, nullptr, &bd->pIB) < 0)
return;
}
// Upload vertex/index data into a single contiguous GPU buffer
D3D11_MAPPED_SUBRESOURCE vtx_resource, idx_resource;
if (ctx->Map(bd->pVB, 0, D3D11_MAP_WRITE_DISCARD, 0, &vtx_resource) != S_OK)
return;
if (ctx->Map(bd->pIB, 0, D3D11_MAP_WRITE_DISCARD, 0, &idx_resource) != S_OK)
return;
ImDrawVert* vtx_dst = (ImDrawVert*)vtx_resource.pData;
ImDrawIdx* idx_dst = (ImDrawIdx*)idx_resource.pData;
for (int n = 0; n < draw_data->CmdListsCount; n++)
{
const ImDrawList* cmd_list = draw_data->CmdLists[n];
memcpy(vtx_dst, cmd_list->VtxBuffer.Data, cmd_list->VtxBuffer.Size * sizeof(ImDrawVert));
memcpy(idx_dst, cmd_list->IdxBuffer.Data, cmd_list->IdxBuffer.Size * sizeof(ImDrawIdx));
vtx_dst += cmd_list->VtxBuffer.Size;
idx_dst += cmd_list->IdxBuffer.Size;
}
ctx->Unmap(bd->pVB, 0);
ctx->Unmap(bd->pIB, 0);
device_ctx->RSSetViewports(1, &vp);
// Setup orthographic projection matrix into our constant buffer
// Our visible imgui space lies from draw_data->DisplayPos (top left) to draw_data->DisplayPos+data_data->DisplaySize (bottom right). DisplayPos is (0,0) for single viewport apps.
D3D11_MAPPED_SUBRESOURCE mapped_resource;
if (device_ctx->Map(bd->pVertexConstantBuffer, 0, D3D11_MAP_WRITE_DISCARD, 0, &mapped_resource) == S_OK)
{
D3D11_MAPPED_SUBRESOURCE mapped_resource;
if (ctx->Map(bd->pVertexConstantBuffer, 0, D3D11_MAP_WRITE_DISCARD, 0, &mapped_resource) != S_OK)
return;
VERTEX_CONSTANT_BUFFER_DX11* constant_buffer = (VERTEX_CONSTANT_BUFFER_DX11*)mapped_resource.pData;
float L = draw_data->DisplayPos.x;
float R = draw_data->DisplayPos.x + draw_data->DisplaySize.x;
@@ -196,9 +135,93 @@ void ImGui_ImplDX11_RenderDrawData(ImDrawData* draw_data)
{ (R+L)/(L-R), (T+B)/(B-T), 0.5f, 1.0f },
};
memcpy(&constant_buffer->mvp, mvp, sizeof(mvp));
ctx->Unmap(bd->pVertexConstantBuffer, 0);
device_ctx->Unmap(bd->pVertexConstantBuffer, 0);
}
// Setup shader and vertex buffers
unsigned int stride = sizeof(ImDrawVert);
unsigned int offset = 0;
device_ctx->IASetInputLayout(bd->pInputLayout);
device_ctx->IASetVertexBuffers(0, 1, &bd->pVB, &stride, &offset);
device_ctx->IASetIndexBuffer(bd->pIB, sizeof(ImDrawIdx) == 2 ? DXGI_FORMAT_R16_UINT : DXGI_FORMAT_R32_UINT, 0);
device_ctx->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
device_ctx->VSSetShader(bd->pVertexShader, nullptr, 0);
device_ctx->VSSetConstantBuffers(0, 1, &bd->pVertexConstantBuffer);
device_ctx->PSSetShader(bd->pPixelShader, nullptr, 0);
device_ctx->GSSetShader(nullptr, nullptr, 0);
device_ctx->HSSetShader(nullptr, nullptr, 0); // In theory we should backup and restore this as well.. very infrequently used..
device_ctx->DSSetShader(nullptr, nullptr, 0); // In theory we should backup and restore this as well.. very infrequently used..
device_ctx->CSSetShader(nullptr, nullptr, 0); // In theory we should backup and restore this as well.. very infrequently used..
// Setup render state
const float blend_factor[4] = { 0.f, 0.f, 0.f, 0.f };
device_ctx->OMSetBlendState(bd->pBlendState, blend_factor, 0xffffffff);
device_ctx->OMSetDepthStencilState(bd->pDepthStencilState, 0);
device_ctx->RSSetState(bd->pRasterizerState);
}
// Render function
void ImGui_ImplDX11_RenderDrawData(ImDrawData* draw_data)
{
// Avoid rendering when minimized
if (draw_data->DisplaySize.x <= 0.0f || draw_data->DisplaySize.y <= 0.0f)
return;
ImGui_ImplDX11_Data* bd = ImGui_ImplDX11_GetBackendData();
ID3D11DeviceContext* device = bd->pd3dDeviceContext;
// Catch up with texture updates. Most of the times, the list will have 1 element with an OK status, aka nothing to do.
// (This almost always points to ImGui::GetPlatformIO().Textures[] but is part of ImDrawData to allow overriding or disabling texture updates).
if (draw_data->Textures != nullptr)
for (ImTextureData* tex : *draw_data->Textures)
if (tex->Status != ImTextureStatus_OK)
ImGui_ImplDX11_UpdateTexture(tex);
// Create and grow vertex/index buffers if needed
if (!bd->pVB || bd->VertexBufferSize < draw_data->TotalVtxCount)
{
if (bd->pVB) { bd->pVB->Release(); bd->pVB = nullptr; }
bd->VertexBufferSize = draw_data->TotalVtxCount + 5000;
D3D11_BUFFER_DESC desc = {};
desc.Usage = D3D11_USAGE_DYNAMIC;
desc.ByteWidth = bd->VertexBufferSize * sizeof(ImDrawVert);
desc.BindFlags = D3D11_BIND_VERTEX_BUFFER;
desc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE;
desc.MiscFlags = 0;
if (bd->pd3dDevice->CreateBuffer(&desc, nullptr, &bd->pVB) < 0)
return;
}
if (!bd->pIB || bd->IndexBufferSize < draw_data->TotalIdxCount)
{
if (bd->pIB) { bd->pIB->Release(); bd->pIB = nullptr; }
bd->IndexBufferSize = draw_data->TotalIdxCount + 10000;
D3D11_BUFFER_DESC desc = {};
desc.Usage = D3D11_USAGE_DYNAMIC;
desc.ByteWidth = bd->IndexBufferSize * sizeof(ImDrawIdx);
desc.BindFlags = D3D11_BIND_INDEX_BUFFER;
desc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE;
if (bd->pd3dDevice->CreateBuffer(&desc, nullptr, &bd->pIB) < 0)
return;
}
// Upload vertex/index data into a single contiguous GPU buffer
D3D11_MAPPED_SUBRESOURCE vtx_resource, idx_resource;
if (device->Map(bd->pVB, 0, D3D11_MAP_WRITE_DISCARD, 0, &vtx_resource) != S_OK)
return;
if (device->Map(bd->pIB, 0, D3D11_MAP_WRITE_DISCARD, 0, &idx_resource) != S_OK)
return;
ImDrawVert* vtx_dst = (ImDrawVert*)vtx_resource.pData;
ImDrawIdx* idx_dst = (ImDrawIdx*)idx_resource.pData;
for (const ImDrawList* draw_list : draw_data->CmdLists)
{
memcpy(vtx_dst, draw_list->VtxBuffer.Data, draw_list->VtxBuffer.Size * sizeof(ImDrawVert));
memcpy(idx_dst, draw_list->IdxBuffer.Data, draw_list->IdxBuffer.Size * sizeof(ImDrawIdx));
vtx_dst += draw_list->VtxBuffer.Size;
idx_dst += draw_list->IdxBuffer.Size;
}
device->Unmap(bd->pVB, 0);
device->Unmap(bd->pIB, 0);
// Backup DX state that will be modified to restore it afterwards (unfortunately this is very ugly looking and verbose. Close your eyes!)
struct BACKUP_DX11_STATE
{
@@ -226,114 +249,143 @@ void ImGui_ImplDX11_RenderDrawData(ImDrawData* draw_data)
};
BACKUP_DX11_STATE old = {};
old.ScissorRectsCount = old.ViewportsCount = D3D11_VIEWPORT_AND_SCISSORRECT_OBJECT_COUNT_PER_PIPELINE;
ctx->RSGetScissorRects(&old.ScissorRectsCount, old.ScissorRects);
ctx->RSGetViewports(&old.ViewportsCount, old.Viewports);
ctx->RSGetState(&old.RS);
ctx->OMGetBlendState(&old.BlendState, old.BlendFactor, &old.SampleMask);
ctx->OMGetDepthStencilState(&old.DepthStencilState, &old.StencilRef);
ctx->PSGetShaderResources(0, 1, &old.PSShaderResource);
ctx->PSGetSamplers(0, 1, &old.PSSampler);
device->RSGetScissorRects(&old.ScissorRectsCount, old.ScissorRects);
device->RSGetViewports(&old.ViewportsCount, old.Viewports);
device->RSGetState(&old.RS);
device->OMGetBlendState(&old.BlendState, old.BlendFactor, &old.SampleMask);
device->OMGetDepthStencilState(&old.DepthStencilState, &old.StencilRef);
device->PSGetShaderResources(0, 1, &old.PSShaderResource);
device->PSGetSamplers(0, 1, &old.PSSampler);
old.PSInstancesCount = old.VSInstancesCount = old.GSInstancesCount = 256;
ctx->PSGetShader(&old.PS, old.PSInstances, &old.PSInstancesCount);
ctx->VSGetShader(&old.VS, old.VSInstances, &old.VSInstancesCount);
ctx->VSGetConstantBuffers(0, 1, &old.VSConstantBuffer);
ctx->GSGetShader(&old.GS, old.GSInstances, &old.GSInstancesCount);
device->PSGetShader(&old.PS, old.PSInstances, &old.PSInstancesCount);
device->VSGetShader(&old.VS, old.VSInstances, &old.VSInstancesCount);
device->VSGetConstantBuffers(0, 1, &old.VSConstantBuffer);
device->GSGetShader(&old.GS, old.GSInstances, &old.GSInstancesCount);
ctx->IAGetPrimitiveTopology(&old.PrimitiveTopology);
ctx->IAGetIndexBuffer(&old.IndexBuffer, &old.IndexBufferFormat, &old.IndexBufferOffset);
ctx->IAGetVertexBuffers(0, 1, &old.VertexBuffer, &old.VertexBufferStride, &old.VertexBufferOffset);
ctx->IAGetInputLayout(&old.InputLayout);
device->IAGetPrimitiveTopology(&old.PrimitiveTopology);
device->IAGetIndexBuffer(&old.IndexBuffer, &old.IndexBufferFormat, &old.IndexBufferOffset);
device->IAGetVertexBuffers(0, 1, &old.VertexBuffer, &old.VertexBufferStride, &old.VertexBufferOffset);
device->IAGetInputLayout(&old.InputLayout);
// Setup desired DX state
ImGui_ImplDX11_SetupRenderState(draw_data, ctx);
ImGui_ImplDX11_SetupRenderState(draw_data, device);
// Setup render state structure (for callbacks and custom texture bindings)
ImGuiPlatformIO& platform_io = ImGui::GetPlatformIO();
ImGui_ImplDX11_RenderState render_state;
render_state.Device = bd->pd3dDevice;
render_state.DeviceContext = bd->pd3dDeviceContext;
render_state.SamplerLinear = bd->pTexSamplerLinear;
render_state.SamplerNearest = bd->pTexSamplerNearest;
render_state.VertexConstantBuffer = bd->pVertexConstantBuffer;
platform_io.Renderer_RenderState = &render_state;
// Render command lists
// (Because we merged all buffers into a single one, we maintain our own offset into them)
int global_idx_offset = 0;
int global_vtx_offset = 0;
ImVec2 clip_off = draw_data->DisplayPos;
for (int n = 0; n < draw_data->CmdListsCount; n++)
ImVec2 clip_scale = draw_data->FramebufferScale;
for (const ImDrawList* draw_list : draw_data->CmdLists)
{
const ImDrawList* cmd_list = draw_data->CmdLists[n];
for (int cmd_i = 0; cmd_i < cmd_list->CmdBuffer.Size; cmd_i++)
for (int cmd_i = 0; cmd_i < draw_list->CmdBuffer.Size; cmd_i++)
{
const ImDrawCmd* pcmd = &cmd_list->CmdBuffer[cmd_i];
const ImDrawCmd* pcmd = &draw_list->CmdBuffer[cmd_i];
if (pcmd->UserCallback != nullptr)
{
// User callback, registered via ImDrawList::AddCallback()
// (ImDrawCallback_ResetRenderState is a special callback value used by the user to request the renderer to reset render state.)
if (pcmd->UserCallback == ImDrawCallback_ResetRenderState)
ImGui_ImplDX11_SetupRenderState(draw_data, ctx);
ImGui_ImplDX11_SetupRenderState(draw_data, device);
else
pcmd->UserCallback(cmd_list, pcmd);
pcmd->UserCallback(draw_list, pcmd);
}
else
{
// Project scissor/clipping rectangles into framebuffer space
ImVec2 clip_min(pcmd->ClipRect.x - clip_off.x, pcmd->ClipRect.y - clip_off.y);
ImVec2 clip_max(pcmd->ClipRect.z - clip_off.x, pcmd->ClipRect.w - clip_off.y);
ImVec2 clip_min((pcmd->ClipRect.x - clip_off.x) * clip_scale.x, (pcmd->ClipRect.y - clip_off.y) * clip_scale.y);
ImVec2 clip_max((pcmd->ClipRect.z - clip_off.x) * clip_scale.x, (pcmd->ClipRect.w - clip_off.y) * clip_scale.y);
if (clip_max.x <= clip_min.x || clip_max.y <= clip_min.y)
continue;
// Apply scissor/clipping rectangle
const D3D11_RECT r = { (LONG)clip_min.x, (LONG)clip_min.y, (LONG)clip_max.x, (LONG)clip_max.y };
ctx->RSSetScissorRects(1, &r);
device->RSSetScissorRects(1, &r);
// Bind texture, Draw
ImTextureDX11 *tex = (ImTextureDX11 *)pcmd->GetTexID();
if (tex != &bd->FontTexture)
{
if (tex->pointSampling)
ctx->PSSetSamplers(0, 1, &bd->pPointSampler);
else
ctx->PSSetSamplers(0, 1, &bd->pTextureSampler);
}
else
ctx->PSSetSamplers(0, 1, &bd->pFontSampler);
if((ImTextureID)tex != ImGui::GetIO().Fonts->TexID.GetTexID())
{
if (tex->pointSampling)
device->PSSetSamplers(0, 1, &bd->pTexSamplerNearest);
else
device->PSSetSamplers(0, 1, &bd->pTexSamplerLinearBorder);
}
else
device->PSSetSamplers(0, 1, &bd->pTexSamplerLinear);
ID3D11ShaderResourceView* texture_srv = tex->shaderResourceView;
ctx->PSSetShaderResources(0, 1, &texture_srv);
ctx->DrawIndexed(pcmd->ElemCount, pcmd->IdxOffset + global_idx_offset, pcmd->VtxOffset + global_vtx_offset);
device->PSSetShaderResources(0, 1, &texture_srv);
device->DrawIndexed(pcmd->ElemCount, pcmd->IdxOffset + global_idx_offset, pcmd->VtxOffset + global_vtx_offset);
}
}
global_idx_offset += cmd_list->IdxBuffer.Size;
global_vtx_offset += cmd_list->VtxBuffer.Size;
global_idx_offset += draw_list->IdxBuffer.Size;
global_vtx_offset += draw_list->VtxBuffer.Size;
}
platform_io.Renderer_RenderState = nullptr;
// Restore modified DX state
ctx->RSSetScissorRects(old.ScissorRectsCount, old.ScissorRects);
ctx->RSSetViewports(old.ViewportsCount, old.Viewports);
ctx->RSSetState(old.RS); if (old.RS) old.RS->Release();
ctx->OMSetBlendState(old.BlendState, old.BlendFactor, old.SampleMask); if (old.BlendState) old.BlendState->Release();
ctx->OMSetDepthStencilState(old.DepthStencilState, old.StencilRef); if (old.DepthStencilState) old.DepthStencilState->Release();
ctx->PSSetShaderResources(0, 1, &old.PSShaderResource); if (old.PSShaderResource) old.PSShaderResource->Release();
ctx->PSSetSamplers(0, 1, &old.PSSampler); if (old.PSSampler) old.PSSampler->Release();
ctx->PSSetShader(old.PS, old.PSInstances, old.PSInstancesCount); if (old.PS) old.PS->Release();
device->RSSetScissorRects(old.ScissorRectsCount, old.ScissorRects);
device->RSSetViewports(old.ViewportsCount, old.Viewports);
device->RSSetState(old.RS); if (old.RS) old.RS->Release();
device->OMSetBlendState(old.BlendState, old.BlendFactor, old.SampleMask); if (old.BlendState) old.BlendState->Release();
device->OMSetDepthStencilState(old.DepthStencilState, old.StencilRef); if (old.DepthStencilState) old.DepthStencilState->Release();
device->PSSetShaderResources(0, 1, &old.PSShaderResource); if (old.PSShaderResource) old.PSShaderResource->Release();
device->PSSetSamplers(0, 1, &old.PSSampler); if (old.PSSampler) old.PSSampler->Release();
device->PSSetShader(old.PS, old.PSInstances, old.PSInstancesCount); if (old.PS) old.PS->Release();
for (UINT i = 0; i < old.PSInstancesCount; i++) if (old.PSInstances[i]) old.PSInstances[i]->Release();
ctx->VSSetShader(old.VS, old.VSInstances, old.VSInstancesCount); if (old.VS) old.VS->Release();
ctx->VSSetConstantBuffers(0, 1, &old.VSConstantBuffer); if (old.VSConstantBuffer) old.VSConstantBuffer->Release();
ctx->GSSetShader(old.GS, old.GSInstances, old.GSInstancesCount); if (old.GS) old.GS->Release();
device->VSSetShader(old.VS, old.VSInstances, old.VSInstancesCount); if (old.VS) old.VS->Release();
device->VSSetConstantBuffers(0, 1, &old.VSConstantBuffer); if (old.VSConstantBuffer) old.VSConstantBuffer->Release();
device->GSSetShader(old.GS, old.GSInstances, old.GSInstancesCount); if (old.GS) old.GS->Release();
for (UINT i = 0; i < old.VSInstancesCount; i++) if (old.VSInstances[i]) old.VSInstances[i]->Release();
ctx->IASetPrimitiveTopology(old.PrimitiveTopology);
ctx->IASetIndexBuffer(old.IndexBuffer, old.IndexBufferFormat, old.IndexBufferOffset); if (old.IndexBuffer) old.IndexBuffer->Release();
ctx->IASetVertexBuffers(0, 1, &old.VertexBuffer, &old.VertexBufferStride, &old.VertexBufferOffset); if (old.VertexBuffer) old.VertexBuffer->Release();
ctx->IASetInputLayout(old.InputLayout); if (old.InputLayout) old.InputLayout->Release();
device->IASetPrimitiveTopology(old.PrimitiveTopology);
device->IASetIndexBuffer(old.IndexBuffer, old.IndexBufferFormat, old.IndexBufferOffset); if (old.IndexBuffer) old.IndexBuffer->Release();
device->IASetVertexBuffers(0, 1, &old.VertexBuffer, &old.VertexBufferStride, &old.VertexBufferOffset); if (old.VertexBuffer) old.VertexBuffer->Release();
device->IASetInputLayout(old.InputLayout); if (old.InputLayout) old.InputLayout->Release();
}
static void ImGui_ImplDX11_CreateFontsTexture()
static void ImGui_ImplDX11_DestroyTexture(ImTextureData* tex)
{
// Build texture atlas
ImGuiIO& io = ImGui::GetIO();
ImGui_ImplDX11_Data* bd = ImGui_ImplDX11_GetBackendData();
unsigned char* pixels;
int width, height;
io.Fonts->GetTexDataAsRGBA32(&pixels, &width, &height);
// Upload texture to graphics system
if (ImTextureDX11* backend_tex = (ImTextureDX11*)tex->BackendUserData)
{
IM_ASSERT(backend_tex == (ImTextureDX11*)tex->TexID);
backend_tex->shaderResourceView->Release();
backend_tex->pTexture->Release();
IM_DELETE(backend_tex);
// Clear identifiers and mark as destroyed (in order to allow e.g. calling InvalidateDeviceObjects while running)
tex->SetTexID(ImTextureID_Invalid);
tex->BackendUserData = nullptr;
}
tex->SetStatus(ImTextureStatus_Destroyed);
}
void ImGui_ImplDX11_UpdateTexture(ImTextureData* tex)
{
ImGui_ImplDX11_Data* bd = ImGui_ImplDX11_GetBackendData();
if (tex->Status == ImTextureStatus_WantCreate)
{
// Create and upload new texture to graphics system
//IMGUI_DEBUG_LOG("UpdateTexture #%03d: WantCreate %dx%d\n", tex->UniqueID, tex->Width, tex->Height);
IM_ASSERT(tex->TexID == ImTextureID_Invalid && tex->BackendUserData == nullptr);
IM_ASSERT(tex->Format == ImTextureFormat_RGBA32);
unsigned int* pixels = (unsigned int*)tex->GetPixels();
ImTextureDX11* backend_tex = IM_NEW(ImTextureDX11)();
// Create texture
D3D11_TEXTURE2D_DESC desc;
ZeroMemory(&desc, sizeof(desc));
desc.Width = width;
desc.Height = height;
desc.Width = (UINT)tex->Width;
desc.Height = (UINT)tex->Height;
desc.MipLevels = 1;
desc.ArraySize = 1;
desc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
@@ -341,14 +393,12 @@ static void ImGui_ImplDX11_CreateFontsTexture()
desc.Usage = D3D11_USAGE_DEFAULT;
desc.BindFlags = D3D11_BIND_SHADER_RESOURCE;
desc.CPUAccessFlags = 0;
ID3D11Texture2D* pTexture = nullptr;
D3D11_SUBRESOURCE_DATA subResource;
subResource.pSysMem = pixels;
subResource.SysMemPitch = desc.Width * 4;
subResource.SysMemSlicePitch = 0;
bd->pd3dDevice->CreateTexture2D(&desc, &subResource, &pTexture);
IM_ASSERT(pTexture != nullptr);
bd->pd3dDevice->CreateTexture2D(&desc, &subResource, &backend_tex->pTexture);
IM_ASSERT(backend_tex->pTexture != nullptr && "Backend failed to create texture!");
// Create texture view
D3D11_SHADER_RESOURCE_VIEW_DESC srvDesc;
@@ -357,35 +407,29 @@ static void ImGui_ImplDX11_CreateFontsTexture()
srvDesc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURE2D;
srvDesc.Texture2D.MipLevels = desc.MipLevels;
srvDesc.Texture2D.MostDetailedMip = 0;
bd->pd3dDevice->CreateShaderResourceView(pTexture, &srvDesc, &bd->FontTexture.shaderResourceView);
pTexture->Release();
bd->pd3dDevice->CreateShaderResourceView(backend_tex->pTexture, &srvDesc, &backend_tex->shaderResourceView);
IM_ASSERT(backend_tex->shaderResourceView != nullptr && "Backend failed to create texture!");
// Store identifiers
tex->SetTexID((ImTextureID)backend_tex);
tex->SetStatus(ImTextureStatus_OK);
tex->BackendUserData = backend_tex;
}
// Store our identifier
io.Fonts->SetTexID((ImTextureID)&bd->FontTexture);
// Create texture samplers
// (Bilinear sampling is required by default. Set 'io.Fonts->Flags |= ImFontAtlasFlags_NoBakedLines' or 'style.AntiAliasedLinesUseTex = false' to allow point/nearest sampling)
else if (tex->Status == ImTextureStatus_WantUpdates)
{
D3D11_SAMPLER_DESC desc;
ZeroMemory(&desc, sizeof(desc));
desc.Filter = D3D11_FILTER_MIN_MAG_MIP_LINEAR;
desc.AddressU = D3D11_TEXTURE_ADDRESS_WRAP;
desc.AddressV = D3D11_TEXTURE_ADDRESS_WRAP;
desc.AddressW = D3D11_TEXTURE_ADDRESS_WRAP;
desc.MipLODBias = 0.f;
desc.ComparisonFunc = D3D11_COMPARISON_ALWAYS;
desc.MinLOD = 0.f;
desc.MaxLOD = 0.f;
bd->pd3dDevice->CreateSamplerState(&desc, &bd->pFontSampler);
desc.AddressU = D3D11_TEXTURE_ADDRESS_BORDER;
desc.AddressV = D3D11_TEXTURE_ADDRESS_BORDER;
bd->pd3dDevice->CreateSamplerState(&desc, &bd->pTextureSampler);
desc.Filter = D3D11_FILTER_MIN_MAG_MIP_POINT;
bd->pd3dDevice->CreateSamplerState(&desc, &bd->pPointSampler);
// Update selected blocks. We only ever write to textures regions which have never been used before!
// This backend choose to use tex->Updates[] but you can use tex->UpdateRect to upload a single region.
ImTextureDX11* backend_tex = (ImTextureDX11*)tex->BackendUserData;
IM_ASSERT(backend_tex == (ImTextureDX11*)tex->TexID);
for (ImTextureRect& r : tex->Updates)
{
D3D11_BOX box = { (UINT)r.x, (UINT)r.y, (UINT)0, (UINT)(r.x + r.w), (UINT)(r.y + r .h), (UINT)1 };
bd->pd3dDeviceContext->UpdateSubresource(backend_tex->pTexture, 0, &box, tex->GetPixelsAt(r.x, r.y), (UINT)tex->GetPitch(), 0);
}
tex->SetStatus(ImTextureStatus_OK);
}
if (tex->Status == ImTextureStatus_WantDestroy && tex->UnusedFrames > 0)
ImGui_ImplDX11_DestroyTexture(tex);
}
bool ImGui_ImplDX11_CreateDeviceObjects()
@@ -393,8 +437,7 @@ bool ImGui_ImplDX11_CreateDeviceObjects()
ImGui_ImplDX11_Data* bd = ImGui_ImplDX11_GetBackendData();
if (!bd->pd3dDevice)
return false;
if (bd->pFontSampler)
ImGui_ImplDX11_InvalidateDeviceObjects();
ImGui_ImplDX11_InvalidateDeviceObjects();
// By using D3DCompile() from <d3dcompiler.h> / d3dcompiler.lib, we introduce a dependency to a given version of d3dcompiler_XX.dll (see D3DCOMPILER_DLL_A)
// If you would like to use this DX11 sample code but remove this dependency you can:
@@ -457,7 +500,7 @@ bool ImGui_ImplDX11_CreateDeviceObjects()
// Create the constant buffer
{
D3D11_BUFFER_DESC desc;
D3D11_BUFFER_DESC desc = {};
desc.ByteWidth = sizeof(VERTEX_CONSTANT_BUFFER_DX11);
desc.Usage = D3D11_USAGE_DYNAMIC;
desc.BindFlags = D3D11_BIND_CONSTANT_BUFFER;
@@ -537,7 +580,28 @@ bool ImGui_ImplDX11_CreateDeviceObjects()
bd->pd3dDevice->CreateDepthStencilState(&desc, &bd->pDepthStencilState);
}
ImGui_ImplDX11_CreateFontsTexture();
// Create texture samplers
// (Bilinear sampling is required by default. Set 'io.Fonts->Flags |= ImFontAtlasFlags_NoBakedLines' or 'style.AntiAliasedLinesUseTex = false' to allow point/nearest sampling)
{
D3D11_SAMPLER_DESC desc;
ZeroMemory(&desc, sizeof(desc));
desc.Filter = D3D11_FILTER_MIN_MAG_MIP_LINEAR;
desc.AddressU = D3D11_TEXTURE_ADDRESS_CLAMP;
desc.AddressV = D3D11_TEXTURE_ADDRESS_CLAMP;
desc.AddressW = D3D11_TEXTURE_ADDRESS_CLAMP;
desc.MipLODBias = 0.f;
desc.ComparisonFunc = D3D11_COMPARISON_ALWAYS;
desc.MinLOD = 0.f;
desc.MaxLOD = 0.f;
bd->pd3dDevice->CreateSamplerState(&desc, &bd->pTexSamplerLinear);
desc.AddressU = D3D11_TEXTURE_ADDRESS_BORDER;
desc.AddressV = D3D11_TEXTURE_ADDRESS_BORDER;
bd->pd3dDevice->CreateSamplerState(&desc, &bd->pTexSamplerLinearBorder);
desc.Filter = D3D11_FILTER_MIN_MAG_MIP_POINT;
bd->pd3dDevice->CreateSamplerState(&desc, &bd->pTexSamplerNearest);
}
return true;
}
@@ -548,15 +612,14 @@ void ImGui_ImplDX11_InvalidateDeviceObjects()
if (!bd->pd3dDevice)
return;
if (bd->pFontSampler) { bd->pFontSampler->Release(); bd->pFontSampler = nullptr; }
if (bd->pTextureSampler) { bd->pTextureSampler->Release(); bd->pTextureSampler = nullptr; }
if (bd->pPointSampler) { bd->pPointSampler->Release(); bd->pPointSampler = nullptr; }
if (bd->FontTexture.shaderResourceView) {
bd->FontTexture.shaderResourceView->Release();
bd->FontTexture.shaderResourceView = nullptr;
// We copied data->FontTexture.shaderResourceView to io.Fonts->TexID so let's clear that as well.
ImGui::GetIO().Fonts->SetTexID(0);
}
// Destroy all textures
for (ImTextureData* tex : ImGui::GetPlatformIO().Textures)
if (tex->RefCount == 1)
ImGui_ImplDX11_DestroyTexture(tex);
if (bd->pTexSamplerLinear) { bd->pTexSamplerLinear->Release(); bd->pTexSamplerLinear = nullptr; }
if (bd->pTexSamplerLinearBorder){ bd->pTexSamplerLinearBorder->Release(); bd->pTexSamplerLinearBorder = nullptr; }
if (bd->pTexSamplerNearest) { bd->pTexSamplerNearest->Release(); bd->pTexSamplerNearest = nullptr; }
if (bd->pIB) { bd->pIB->Release(); bd->pIB = nullptr; }
if (bd->pVB) { bd->pVB->Release(); bd->pVB = nullptr; }
if (bd->pBlendState) { bd->pBlendState->Release(); bd->pBlendState = nullptr; }
@@ -579,6 +642,10 @@ bool ImGui_ImplDX11_Init(ID3D11Device* device, ID3D11DeviceContext* device_co
io.BackendRendererUserData = (void*)bd;
io.BackendRendererName = "imgui_impl_dx11";
io.BackendFlags |= ImGuiBackendFlags_RendererHasVtxOffset; // We can honor the ImDrawCmd::VtxOffset field, allowing for large meshes.
io.BackendFlags |= ImGuiBackendFlags_RendererHasTextures; // We can honor ImGuiPlatformIO::Textures[] requests during render.
ImGuiPlatformIO& platform_io = ImGui::GetPlatformIO();
platform_io.Renderer_TextureMaxWidth = platform_io.Renderer_TextureMaxHeight = D3D11_REQ_TEXTURE2D_U_OR_V_DIMENSION;
// Get factory from device
IDXGIDevice* pDXGIDevice = nullptr;
@@ -606,14 +673,17 @@ void ImGui_ImplDX11_Shutdown()
ImGui_ImplDX11_Data* bd = ImGui_ImplDX11_GetBackendData();
IM_ASSERT(bd != nullptr && "No renderer backend to shutdown, or already shutdown?");
ImGuiIO& io = ImGui::GetIO();
ImGuiPlatformIO& platform_io = ImGui::GetPlatformIO();
ImGui_ImplDX11_InvalidateDeviceObjects();
if (bd->pFactory) { bd->pFactory->Release(); }
if (bd->pd3dDevice) { bd->pd3dDevice->Release(); }
if (bd->pd3dDeviceContext) { bd->pd3dDeviceContext->Release(); }
io.BackendRendererName = nullptr;
io.BackendRendererUserData = nullptr;
io.BackendFlags &= ~ImGuiBackendFlags_RendererHasVtxOffset;
io.BackendFlags &= ~(ImGuiBackendFlags_RendererHasVtxOffset | ImGuiBackendFlags_RendererHasTextures);
platform_io.ClearRendererHandlers();
IM_DELETE(bd);
}
@@ -622,8 +692,9 @@ void ImGui_ImplDX11_NewFrame()
ImGui_ImplDX11_Data* bd = ImGui_ImplDX11_GetBackendData();
IM_ASSERT(bd != nullptr && "Context or backend not initialized! Did you call ImGui_ImplDX11_Init()?");
if (!bd->pFontSampler)
ImGui_ImplDX11_CreateDeviceObjects();
if (!bd->pVertexShader)
if (!ImGui_ImplDX11_CreateDeviceObjects())
IM_ASSERT(0 && "ImGui_ImplDX11_CreateDeviceObjects() failed!");
}
//-----------------------------------------------------------------------------
+24 -4
View File
@@ -2,8 +2,10 @@
// This needs to be used along with a Platform Backend (e.g. Win32)
// Implemented features:
// [X] Renderer: User texture binding. Use 'ID3D11ShaderResourceView*' as ImTextureID. Read the FAQ about ImTextureID!
// [X] Renderer: Large meshes support (64k+ vertices) with 16-bit indices.
// [X] Renderer: User texture binding. Use 'ID3D11ShaderResourceView*' as texture identifier. Read the FAQ about ImTextureID/ImTextureRef!
// [X] Renderer: Large meshes support (64k+ vertices) even with 16-bit indices (ImGuiBackendFlags_RendererHasVtxOffset).
// [X] Renderer: Texture updates support for dynamic font atlas (ImGuiBackendFlags_RendererHasTextures).
// [X] Renderer: Expose selected render state for draw callbacks to use. Access in '(ImGui_ImplXXXX_RenderState*)GetPlatformIO().Renderer_RenderState'.
// You can use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this.
// Prefer including the entire imgui/ repository into your project (either as a copy or as a submodule), and only build the backends you need.
@@ -19,20 +21,38 @@
struct ID3D11Device;
struct ID3D11DeviceContext;
struct ID3D11Texture2D;
struct ID3D11ShaderResourceView;
struct ID3D11SamplerState;
struct ID3D11Buffer;
// Follow "Getting Started" link and check examples/ folder to learn about using backends!
IMGUI_IMPL_API bool ImGui_ImplDX11_Init(ID3D11Device* device, ID3D11DeviceContext* device_context);
IMGUI_IMPL_API void ImGui_ImplDX11_Shutdown();
IMGUI_IMPL_API void ImGui_ImplDX11_NewFrame();
IMGUI_IMPL_API void ImGui_ImplDX11_RenderDrawData(ImDrawData* draw_data);
// Use if you want to reset your rendering device without losing Dear ImGui state.
IMGUI_IMPL_API void ImGui_ImplDX11_InvalidateDeviceObjects();
// Use if you want to reset your rendering state without using ImGui_ImplDX11_RenderDrawData.
IMGUI_IMPL_API bool ImGui_ImplDX11_CreateDeviceObjects();
IMGUI_IMPL_API void ImGui_ImplDX11_InvalidateDeviceObjects();
// (Advanced) Use e.g. if you need to precisely control the timing of texture updates (e.g. for staged rendering), by setting ImDrawData::Textures = NULL to handle this manually.
IMGUI_IMPL_API void ImGui_ImplDX11_UpdateTexture(ImTextureData* tex);
// Selected render state exposed to applications and callbacks.
struct ImGui_ImplDX11_RenderState
{
ID3D11Device* Device;
ID3D11DeviceContext* DeviceContext;
ID3D11SamplerState* SamplerLinear;
ID3D11SamplerState* SamplerNearest;
ID3D11Buffer* VertexConstantBuffer;
};
// ImTextureID should be a pointer to this struct
struct ImTextureDX11
{
ID3D11Texture2D *pTexture;
ID3D11ShaderResourceView *shaderResourceView;
bool pointSampling;
};
+259 -165
View File
@@ -2,8 +2,10 @@
// This needs to be used along with a Platform Backend (e.g. Win32)
// Implemented features:
// [X] Renderer: User texture binding. Use 'LPDIRECT3DTEXTURE9' as ImTextureID. Read the FAQ about ImTextureID!
// [X] Renderer: Large meshes support (64k+ vertices) with 16-bit indices.
// [X] Renderer: User texture binding. Use 'LPDIRECT3DTEXTURE9' as texture identifier. Read the FAQ about ImTextureID/ImTextureRef!
// [X] Renderer: Large meshes support (64k+ vertices) even with 16-bit indices (ImGuiBackendFlags_RendererHasVtxOffset).
// [X] Renderer: Texture updates support for dynamic font atlas (ImGuiBackendFlags_RendererHasTextures).
// [X] Renderer: IMGUI_USE_BGRA_PACKED_COLOR support, as this is the optimal color encoding for DirectX9.
// You can use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this.
// Prefer including the entire imgui/ repository into your project (either as a copy or as a submodule), and only build the backends you need.
@@ -15,6 +17,9 @@
// CHANGELOG
// (minor and older changes stripped away, please see git history for details)
// 2025-09-18: Call platform_io.ClearRendererHandlers() on shutdown.
// 2025-06-11: DirectX9: Added support for ImGuiBackendFlags_RendererHasTextures, for dynamic font atlas.
// 2024-10-07: DirectX9: Changed default texture sampler to Clamp instead of Repeat/Wrap.
// 2024-02-12: DirectX9: Using RGBA format when supported by the driver to avoid CPU side conversion. (#6575)
// 2022-10-11: Using 'nullptr' instead of 'NULL' as per our switch to C++11.
// 2021-06-29: Reorganized backend to pull data from a single structure to facilitate usage with multiple-contexts (all g_XXXX access changed to bd->XXXX).
@@ -42,15 +47,21 @@
// DirectX
#include <d3d9.h>
// Clang/GCC warnings with -Weverything
#if defined(__clang__)
#pragma clang diagnostic ignored "-Wold-style-cast" // warning: use of old-style cast // yes, they are more terse.
#pragma clang diagnostic ignored "-Wsign-conversion" // warning: implicit conversion changes signedness
#endif
// DirectX data
struct ImGui_ImplDX9_Data
{
LPDIRECT3DDEVICE9 pd3dDevice;
LPDIRECT3DVERTEXBUFFER9 pVB;
LPDIRECT3DINDEXBUFFER9 pIB;
ImTextureDX9 FontTexture;
int VertexBufferSize;
int IndexBufferSize;
bool HasRgbaSupport;
ImGui_ImplDX9_Data() { memset((void*)this, 0, sizeof(*this)); VertexBufferSize = 5000; IndexBufferSize = 10000; }
};
@@ -88,41 +99,41 @@ static void ImGui_ImplDX9_SetupRenderState(ImDrawData* draw_data)
vp.Height = (DWORD)draw_data->DisplaySize.y;
vp.MinZ = 0.0f;
vp.MaxZ = 1.0f;
bd->pd3dDevice->SetViewport(&vp);
LPDIRECT3DDEVICE9 device = bd->pd3dDevice;
device->SetViewport(&vp);
// Setup render state: fixed-pipeline, alpha-blending, no face culling, no depth testing, shade mode (for gradient), bilinear sampling.
bd->pd3dDevice->SetPixelShader(nullptr);
bd->pd3dDevice->SetVertexShader(nullptr);
bd->pd3dDevice->SetRenderState(D3DRS_FILLMODE, D3DFILL_SOLID);
bd->pd3dDevice->SetRenderState(D3DRS_SHADEMODE, D3DSHADE_GOURAUD);
bd->pd3dDevice->SetRenderState(D3DRS_ZWRITEENABLE, FALSE);
bd->pd3dDevice->SetRenderState(D3DRS_ALPHATESTENABLE, FALSE);
bd->pd3dDevice->SetRenderState(D3DRS_CULLMODE, D3DCULL_NONE);
bd->pd3dDevice->SetRenderState(D3DRS_ZENABLE, FALSE);
bd->pd3dDevice->SetRenderState(D3DRS_ALPHABLENDENABLE, TRUE);
bd->pd3dDevice->SetRenderState(D3DRS_BLENDOP, D3DBLENDOP_ADD);
bd->pd3dDevice->SetRenderState(D3DRS_SRCBLEND, D3DBLEND_SRCALPHA);
bd->pd3dDevice->SetRenderState(D3DRS_DESTBLEND, D3DBLEND_INVSRCALPHA);
bd->pd3dDevice->SetRenderState(D3DRS_SEPARATEALPHABLENDENABLE, TRUE);
bd->pd3dDevice->SetRenderState(D3DRS_SRCBLENDALPHA, D3DBLEND_ONE);
bd->pd3dDevice->SetRenderState(D3DRS_DESTBLENDALPHA, D3DBLEND_INVSRCALPHA);
bd->pd3dDevice->SetRenderState(D3DRS_SCISSORTESTENABLE, TRUE);
bd->pd3dDevice->SetRenderState(D3DRS_FOGENABLE, FALSE);
bd->pd3dDevice->SetRenderState(D3DRS_RANGEFOGENABLE, FALSE);
bd->pd3dDevice->SetRenderState(D3DRS_SPECULARENABLE, FALSE);
bd->pd3dDevice->SetRenderState(D3DRS_STENCILENABLE, FALSE);
bd->pd3dDevice->SetRenderState(D3DRS_CLIPPING, TRUE);
bd->pd3dDevice->SetRenderState(D3DRS_LIGHTING, FALSE);
bd->pd3dDevice->SetTextureStageState(0, D3DTSS_COLOROP, D3DTOP_MODULATE);
bd->pd3dDevice->SetTextureStageState(0, D3DTSS_COLORARG1, D3DTA_TEXTURE);
bd->pd3dDevice->SetTextureStageState(0, D3DTSS_COLORARG2, D3DTA_DIFFUSE);
bd->pd3dDevice->SetTextureStageState(0, D3DTSS_ALPHAOP, D3DTOP_MODULATE);
bd->pd3dDevice->SetTextureStageState(0, D3DTSS_ALPHAARG1, D3DTA_TEXTURE);
bd->pd3dDevice->SetTextureStageState(0, D3DTSS_ALPHAARG2, D3DTA_DIFFUSE);
bd->pd3dDevice->SetTextureStageState(1, D3DTSS_COLOROP, D3DTOP_DISABLE);
bd->pd3dDevice->SetTextureStageState(1, D3DTSS_ALPHAOP, D3DTOP_DISABLE);
bd->pd3dDevice->SetSamplerState(0, D3DSAMP_MINFILTER, D3DTEXF_LINEAR);
bd->pd3dDevice->SetSamplerState(0, D3DSAMP_MAGFILTER, D3DTEXF_LINEAR);
device->SetPixelShader(nullptr);
device->SetVertexShader(nullptr);
device->SetRenderState(D3DRS_FILLMODE, D3DFILL_SOLID);
device->SetRenderState(D3DRS_SHADEMODE, D3DSHADE_GOURAUD);
device->SetRenderState(D3DRS_ZWRITEENABLE, FALSE);
device->SetRenderState(D3DRS_ALPHATESTENABLE, FALSE);
device->SetRenderState(D3DRS_CULLMODE, D3DCULL_NONE);
device->SetRenderState(D3DRS_ZENABLE, FALSE);
device->SetRenderState(D3DRS_ALPHABLENDENABLE, TRUE);
device->SetRenderState(D3DRS_BLENDOP, D3DBLENDOP_ADD);
device->SetRenderState(D3DRS_SRCBLEND, D3DBLEND_SRCALPHA);
device->SetRenderState(D3DRS_DESTBLEND, D3DBLEND_INVSRCALPHA);
device->SetRenderState(D3DRS_SEPARATEALPHABLENDENABLE, TRUE);
device->SetRenderState(D3DRS_SRCBLENDALPHA, D3DBLEND_ONE);
device->SetRenderState(D3DRS_DESTBLENDALPHA, D3DBLEND_INVSRCALPHA);
device->SetRenderState(D3DRS_SCISSORTESTENABLE, TRUE);
device->SetRenderState(D3DRS_FOGENABLE, FALSE);
device->SetRenderState(D3DRS_RANGEFOGENABLE, FALSE);
device->SetRenderState(D3DRS_SPECULARENABLE, FALSE);
device->SetRenderState(D3DRS_STENCILENABLE, FALSE);
device->SetRenderState(D3DRS_CLIPPING, TRUE);
device->SetRenderState(D3DRS_LIGHTING, FALSE);
device->SetTextureStageState(0, D3DTSS_COLOROP, D3DTOP_MODULATE);
device->SetTextureStageState(0, D3DTSS_COLORARG1, D3DTA_TEXTURE);
device->SetTextureStageState(0, D3DTSS_COLORARG2, D3DTA_DIFFUSE);
device->SetTextureStageState(0, D3DTSS_ALPHAOP, D3DTOP_MODULATE);
device->SetTextureStageState(0, D3DTSS_ALPHAARG1, D3DTA_TEXTURE);
device->SetTextureStageState(0, D3DTSS_ALPHAARG2, D3DTA_DIFFUSE);
device->SetTextureStageState(1, D3DTSS_COLOROP, D3DTOP_DISABLE);
device->SetTextureStageState(1, D3DTSS_ALPHAOP, D3DTOP_DISABLE);
// Setup orthographic projection matrix
// Our visible imgui space lies from draw_data->DisplayPos (top left) to draw_data->DisplayPos+data_data->DisplaySize (bottom right). DisplayPos is (0,0) for single viewport apps.
@@ -140,9 +151,9 @@ static void ImGui_ImplDX9_SetupRenderState(ImDrawData* draw_data)
0.0f, 0.0f, 0.5f, 0.0f,
(L+R)/(L-R), (T+B)/(B-T), 0.5f, 1.0f
} } };
bd->pd3dDevice->SetTransform(D3DTS_WORLD, &mat_identity);
bd->pd3dDevice->SetTransform(D3DTS_VIEW, &mat_identity);
bd->pd3dDevice->SetTransform(D3DTS_PROJECTION, &mat_projection);
device->SetTransform(D3DTS_WORLD, &mat_identity);
device->SetTransform(D3DTS_VIEW, &mat_identity);
device->SetTransform(D3DTS_PROJECTION, &mat_projection);
}
}
@@ -153,33 +164,65 @@ void ImGui_ImplDX9_RenderDrawData(ImDrawData* draw_data)
if (draw_data->DisplaySize.x <= 0.0f || draw_data->DisplaySize.y <= 0.0f)
return;
// Create and grow buffers if needed
ImGui_ImplDX9_Data* bd = ImGui_ImplDX9_GetBackendData();
LPDIRECT3DDEVICE9 device = bd->pd3dDevice;
// Catch up with texture updates. Most of the times, the list will have 1 element with an OK status, aka nothing to do.
// (This almost always points to ImGui::GetPlatformIO().Textures[] but is part of ImDrawData to allow overriding or disabling texture updates).
if (draw_data->Textures != nullptr)
for (ImTextureData* tex : *draw_data->Textures)
if (tex->Status != ImTextureStatus_OK)
ImGui_ImplDX9_UpdateTexture(tex);
// Create and grow buffers if needed
if (!bd->pVB || bd->VertexBufferSize < draw_data->TotalVtxCount)
{
if (bd->pVB) { bd->pVB->Release(); bd->pVB = nullptr; }
bd->VertexBufferSize = draw_data->TotalVtxCount + 5000;
if (bd->pd3dDevice->CreateVertexBuffer(bd->VertexBufferSize * sizeof(CUSTOMVERTEX), D3DUSAGE_DYNAMIC | D3DUSAGE_WRITEONLY, D3DFVF_CUSTOMVERTEX, D3DPOOL_DEFAULT, &bd->pVB, nullptr) < 0)
if (device->CreateVertexBuffer(bd->VertexBufferSize * sizeof(CUSTOMVERTEX), D3DUSAGE_DYNAMIC | D3DUSAGE_WRITEONLY, D3DFVF_CUSTOMVERTEX, D3DPOOL_DEFAULT, &bd->pVB, nullptr) < 0)
return;
}
if (!bd->pIB || bd->IndexBufferSize < draw_data->TotalIdxCount)
{
if (bd->pIB) { bd->pIB->Release(); bd->pIB = nullptr; }
bd->IndexBufferSize = draw_data->TotalIdxCount + 10000;
if (bd->pd3dDevice->CreateIndexBuffer(bd->IndexBufferSize * sizeof(ImDrawIdx), D3DUSAGE_DYNAMIC | D3DUSAGE_WRITEONLY, sizeof(ImDrawIdx) == 2 ? D3DFMT_INDEX16 : D3DFMT_INDEX32, D3DPOOL_DEFAULT, &bd->pIB, nullptr) < 0)
if (device->CreateIndexBuffer(bd->IndexBufferSize * sizeof(ImDrawIdx), D3DUSAGE_DYNAMIC | D3DUSAGE_WRITEONLY, sizeof(ImDrawIdx) == 2 ? D3DFMT_INDEX16 : D3DFMT_INDEX32, D3DPOOL_DEFAULT, &bd->pIB, nullptr) < 0)
return;
}
/* Removed in Flycast
// Backup the DX9 state
IDirect3DStateBlock9* state_block = nullptr;
if (device->CreateStateBlock(D3DSBT_ALL, &state_block) < 0)
return;
if (state_block->Capture() < 0)
{
state_block->Release();
return;
}
// Backup the DX9 transform (DX9 documentation suggests that it is included in the StateBlock but it doesn't appear to)
D3DMATRIX last_world, last_view, last_projection;
device->GetTransform(D3DTS_WORLD, &last_world);
device->GetTransform(D3DTS_VIEW, &last_view);
device->GetTransform(D3DTS_PROJECTION, &last_projection);
*/
// Allocate buffers
CUSTOMVERTEX* vtx_dst;
ImDrawIdx* idx_dst;
if (bd->pVB->Lock(0, (UINT)(draw_data->TotalVtxCount * sizeof(CUSTOMVERTEX)), (void**)&vtx_dst, D3DLOCK_DISCARD) < 0)
{
/* Removed in Flycast
state_block->Release();
*/
return;
}
if (bd->pIB->Lock(0, (UINT)(draw_data->TotalIdxCount * sizeof(ImDrawIdx)), (void**)&idx_dst, D3DLOCK_DISCARD) < 0)
{
bd->pVB->Unlock();
/* Removed in Flycast
state_block->Release();
*/
return;
}
@@ -187,11 +230,10 @@ void ImGui_ImplDX9_RenderDrawData(ImDrawData* draw_data)
// FIXME-OPT: This is a minor waste of resource, the ideal is to use imconfig.h and
// 1) to avoid repacking colors: #define IMGUI_USE_BGRA_PACKED_COLOR
// 2) to avoid repacking vertices: #define IMGUI_OVERRIDE_DRAWVERT_STRUCT_LAYOUT struct ImDrawVert { ImVec2 pos; float z; ImU32 col; ImVec2 uv; }
for (int n = 0; n < draw_data->CmdListsCount; n++)
for (const ImDrawList* draw_list : draw_data->CmdLists)
{
const ImDrawList* cmd_list = draw_data->CmdLists[n];
const ImDrawVert* vtx_src = cmd_list->VtxBuffer.Data;
for (int i = 0; i < cmd_list->VtxBuffer.Size; i++)
const ImDrawVert* vtx_src = draw_list->VtxBuffer.Data;
for (int i = 0; i < draw_list->VtxBuffer.Size; i++)
{
vtx_dst->pos[0] = vtx_src->pos.x;
vtx_dst->pos[1] = vtx_src->pos.y;
@@ -202,14 +244,14 @@ void ImGui_ImplDX9_RenderDrawData(ImDrawData* draw_data)
vtx_dst++;
vtx_src++;
}
memcpy(idx_dst, cmd_list->IdxBuffer.Data, cmd_list->IdxBuffer.Size * sizeof(ImDrawIdx));
idx_dst += cmd_list->IdxBuffer.Size;
memcpy(idx_dst, draw_list->IdxBuffer.Data, draw_list->IdxBuffer.Size * sizeof(ImDrawIdx));
idx_dst += draw_list->IdxBuffer.Size;
}
bd->pVB->Unlock();
bd->pIB->Unlock();
bd->pd3dDevice->SetStreamSource(0, bd->pVB, 0, sizeof(CUSTOMVERTEX));
bd->pd3dDevice->SetIndices(bd->pIB);
bd->pd3dDevice->SetFVF(D3DFVF_CUSTOMVERTEX);
device->SetStreamSource(0, bd->pVB, 0, sizeof(CUSTOMVERTEX));
device->SetIndices(bd->pIB);
device->SetFVF(D3DFVF_CUSTOMVERTEX);
// Setup desired DX state
ImGui_ImplDX9_SetupRenderState(draw_data);
@@ -219,12 +261,11 @@ void ImGui_ImplDX9_RenderDrawData(ImDrawData* draw_data)
int global_vtx_offset = 0;
int global_idx_offset = 0;
ImVec2 clip_off = draw_data->DisplayPos;
for (int n = 0; n < draw_data->CmdListsCount; n++)
for (const ImDrawList* draw_list : draw_data->CmdLists)
{
const ImDrawList* cmd_list = draw_data->CmdLists[n];
for (int cmd_i = 0; cmd_i < cmd_list->CmdBuffer.Size; cmd_i++)
for (int cmd_i = 0; cmd_i < draw_list->CmdBuffer.Size; cmd_i++)
{
const ImDrawCmd* pcmd = &cmd_list->CmdBuffer[cmd_i];
const ImDrawCmd* pcmd = &draw_list->CmdBuffer[cmd_i];
if (pcmd->UserCallback != nullptr)
{
// User callback, registered via ImDrawList::AddCallback()
@@ -232,7 +273,7 @@ void ImGui_ImplDX9_RenderDrawData(ImDrawData* draw_data)
if (pcmd->UserCallback == ImDrawCallback_ResetRenderState)
ImGui_ImplDX9_SetupRenderState(draw_data);
else
pcmd->UserCallback(cmd_list, pcmd);
pcmd->UserCallback(draw_list, pcmd);
}
else
{
@@ -246,73 +287,51 @@ void ImGui_ImplDX9_RenderDrawData(ImDrawData* draw_data)
const RECT r = { (LONG)clip_min.x, (LONG)clip_min.y, (LONG)clip_max.x, (LONG)clip_max.y };
const ImTextureDX9 *tex = (const ImTextureDX9 *)pcmd->GetTexID();
const LPDIRECT3DTEXTURE9 texture = tex->d3dTexture;
bd->pd3dDevice->SetTexture(0, texture);
if (tex != &bd->FontTexture)
{
bd->pd3dDevice->SetSamplerState(0, D3DSAMP_ADDRESSU, D3DTADDRESS_BORDER);
bd->pd3dDevice->SetSamplerState(0, D3DSAMP_ADDRESSV, D3DTADDRESS_BORDER);
if (tex->pointSampling)
{
bd->pd3dDevice->SetSamplerState(0, D3DSAMP_MINFILTER, D3DTEXF_POINT);
bd->pd3dDevice->SetSamplerState(0, D3DSAMP_MAGFILTER, D3DTEXF_POINT);
}
else
{
bd->pd3dDevice->SetSamplerState(0, D3DSAMP_MINFILTER, D3DTEXF_LINEAR);
bd->pd3dDevice->SetSamplerState(0, D3DSAMP_MAGFILTER, D3DTEXF_LINEAR);
}
}
else
{
bd->pd3dDevice->SetSamplerState(0, D3DSAMP_ADDRESSU, D3DTADDRESS_WRAP);
bd->pd3dDevice->SetSamplerState(0, D3DSAMP_ADDRESSV, D3DTADDRESS_WRAP);
bd->pd3dDevice->SetSamplerState(0, D3DSAMP_MINFILTER, D3DTEXF_LINEAR);
bd->pd3dDevice->SetSamplerState(0, D3DSAMP_MAGFILTER, D3DTEXF_LINEAR);
}
bd->pd3dDevice->SetScissorRect(&r);
bd->pd3dDevice->DrawIndexedPrimitive(D3DPT_TRIANGLELIST, pcmd->VtxOffset + global_vtx_offset, 0, (UINT)cmd_list->VtxBuffer.Size, pcmd->IdxOffset + global_idx_offset, pcmd->ElemCount / 3);
device->SetTexture(0, texture);
if((ImTextureID)tex != ImGui::GetIO().Fonts->TexID.GetTexID())
{
device->SetSamplerState(0, D3DSAMP_ADDRESSU, D3DTADDRESS_BORDER);
device->SetSamplerState(0, D3DSAMP_ADDRESSV, D3DTADDRESS_BORDER);
if (tex->pointSampling)
{
device->SetSamplerState(0, D3DSAMP_MINFILTER, D3DTEXF_POINT);
device->SetSamplerState(0, D3DSAMP_MAGFILTER, D3DTEXF_POINT);
}
else
{
device->SetSamplerState(0, D3DSAMP_MINFILTER, D3DTEXF_LINEAR);
device->SetSamplerState(0, D3DSAMP_MAGFILTER, D3DTEXF_LINEAR);
}
}
else
{
device->SetSamplerState(0, D3DSAMP_ADDRESSU, D3DTADDRESS_CLAMP);
device->SetSamplerState(0, D3DSAMP_ADDRESSV, D3DTADDRESS_CLAMP);
device->SetSamplerState(0, D3DSAMP_MINFILTER, D3DTEXF_LINEAR);
device->SetSamplerState(0, D3DSAMP_MAGFILTER, D3DTEXF_LINEAR);
}
device->SetScissorRect(&r);
device->DrawIndexedPrimitive(D3DPT_TRIANGLELIST, pcmd->VtxOffset + global_vtx_offset, 0, (UINT)draw_list->VtxBuffer.Size, pcmd->IdxOffset + global_idx_offset, pcmd->ElemCount / 3);
}
}
global_idx_offset += cmd_list->IdxBuffer.Size;
global_vtx_offset += cmd_list->VtxBuffer.Size;
global_idx_offset += draw_list->IdxBuffer.Size;
global_vtx_offset += draw_list->VtxBuffer.Size;
}
/* Removed in Flycast
// Restore the DX9 transform
device->SetTransform(D3DTS_WORLD, &last_world);
device->SetTransform(D3DTS_VIEW, &last_view);
device->SetTransform(D3DTS_PROJECTION, &last_projection);
// Restore the DX9 state
state_block->Apply();
state_block->Release();
*/
}
bool ImGui_ImplDX9_Init(IDirect3DDevice9* device)
static bool ImGui_ImplDX9_CheckFormatSupport(LPDIRECT3DDEVICE9 pDevice, D3DFORMAT format)
{
ImGuiIO& io = ImGui::GetIO();
IMGUI_CHECKVERSION();
IM_ASSERT(io.BackendRendererUserData == nullptr && "Already initialized a renderer backend!");
// Setup backend capabilities flags
ImGui_ImplDX9_Data* bd = IM_NEW(ImGui_ImplDX9_Data)();
io.BackendRendererUserData = (void*)bd;
io.BackendRendererName = "imgui_impl_dx9";
io.BackendFlags |= ImGuiBackendFlags_RendererHasVtxOffset; // We can honor the ImDrawCmd::VtxOffset field, allowing for large meshes.
bd->pd3dDevice = device;
bd->pd3dDevice->AddRef();
return true;
}
void ImGui_ImplDX9_Shutdown()
{
ImGui_ImplDX9_Data* bd = ImGui_ImplDX9_GetBackendData();
IM_ASSERT(bd != nullptr && "No renderer backend to shutdown, or already shutdown?");
ImGuiIO& io = ImGui::GetIO();
ImGui_ImplDX9_InvalidateDeviceObjects();
if (bd->pd3dDevice) { bd->pd3dDevice->Release(); }
io.BackendRendererName = nullptr;
io.BackendRendererUserData = nullptr;
io.BackendFlags &= ~ImGuiBackendFlags_RendererHasVtxOffset;
IM_DELETE(bd);
}
static bool ImGui_ImplDX9_CheckFormatSupport(IDirect3DDevice9* pDevice, D3DFORMAT format)
{
IDirect3D9* pd3d = nullptr;
LPDIRECT3D9 pd3d = nullptr;
if (pDevice->GetDirect3D(&pd3d) != D3D_OK)
return false;
D3DDEVICE_CREATION_PARAMETERS param = {};
@@ -328,58 +347,134 @@ static bool ImGui_ImplDX9_CheckFormatSupport(IDirect3DDevice9* pDevice, D3DFORMA
return support;
}
static bool ImGui_ImplDX9_CreateFontsTexture()
bool ImGui_ImplDX9_Init(IDirect3DDevice9* device)
{
// Build texture atlas
ImGuiIO& io = ImGui::GetIO();
ImGui_ImplDX9_Data* bd = ImGui_ImplDX9_GetBackendData();
unsigned char* pixels;
int width, height, bytes_per_pixel;
io.Fonts->GetTexDataAsRGBA32(&pixels, &width, &height, &bytes_per_pixel);
IMGUI_CHECKVERSION();
IM_ASSERT(io.BackendRendererUserData == nullptr && "Already initialized a renderer backend!");
// Convert RGBA32 to BGRA32 (because RGBA32 is not well supported by DX9 devices)
#ifndef IMGUI_USE_BGRA_PACKED_COLOR
const bool rgba_support = ImGui_ImplDX9_CheckFormatSupport(bd->pd3dDevice, D3DFMT_A8B8G8R8);
if (!rgba_support && io.Fonts->TexPixelsUseColors)
{
ImU32* dst_start = (ImU32*)ImGui::MemAlloc((size_t)width * height * bytes_per_pixel);
for (ImU32* src = (ImU32*)pixels, *dst = dst_start, *dst_end = dst_start + (size_t)width * height; dst < dst_end; src++, dst++)
*dst = IMGUI_COL_TO_DX9_ARGB(*src);
pixels = (unsigned char*)dst_start;
}
#else
const bool rgba_support = false;
#endif
// Setup backend capabilities flags
ImGui_ImplDX9_Data* bd = IM_NEW(ImGui_ImplDX9_Data)();
io.BackendRendererUserData = (void*)bd;
io.BackendRendererName = "imgui_impl_dx9";
io.BackendFlags |= ImGuiBackendFlags_RendererHasVtxOffset; // We can honor the ImDrawCmd::VtxOffset field, allowing for large meshes.
io.BackendFlags |= ImGuiBackendFlags_RendererHasTextures; // We can honor ImGuiPlatformIO::Textures[] requests during render.
// Upload texture to graphics system
bd->FontTexture.d3dTexture = nullptr;
if (bd->pd3dDevice->CreateTexture(width, height, 1, D3DUSAGE_DYNAMIC, rgba_support ? D3DFMT_A8B8G8R8 : D3DFMT_A8R8G8B8, D3DPOOL_DEFAULT, &bd->FontTexture.d3dTexture, nullptr) < 0)
return false;
D3DLOCKED_RECT tex_locked_rect;
if (bd->FontTexture.d3dTexture->LockRect(0, &tex_locked_rect, nullptr, 0) != D3D_OK)
return false;
for (int y = 0; y < height; y++)
memcpy((unsigned char*)tex_locked_rect.pBits + (size_t)tex_locked_rect.Pitch * y, pixels + (size_t)width * bytes_per_pixel * y, (size_t)width * bytes_per_pixel);
bd->FontTexture.d3dTexture->UnlockRect(0);
ImGuiPlatformIO& platform_io = ImGui::GetPlatformIO();
platform_io.Renderer_TextureMaxWidth = platform_io.Renderer_TextureMaxHeight = 4096;
// Store our identifier
io.Fonts->SetTexID((ImTextureID)&bd->FontTexture);
#ifndef IMGUI_USE_BGRA_PACKED_COLOR
if (!rgba_support && io.Fonts->TexPixelsUseColors)
ImGui::MemFree(pixels);
#endif
bd->pd3dDevice = device;
bd->pd3dDevice->AddRef();
bd->HasRgbaSupport = ImGui_ImplDX9_CheckFormatSupport(bd->pd3dDevice, D3DFMT_A8B8G8R8);
return true;
}
void ImGui_ImplDX9_Shutdown()
{
ImGui_ImplDX9_Data* bd = ImGui_ImplDX9_GetBackendData();
IM_ASSERT(bd != nullptr && "No renderer backend to shutdown, or already shutdown?");
ImGuiIO& io = ImGui::GetIO();
ImGuiPlatformIO& platform_io = ImGui::GetPlatformIO();
ImGui_ImplDX9_InvalidateDeviceObjects();
if (bd->pd3dDevice) { bd->pd3dDevice->Release(); }
io.BackendRendererName = nullptr;
io.BackendRendererUserData = nullptr;
io.BackendFlags &= ~(ImGuiBackendFlags_RendererHasVtxOffset | ImGuiBackendFlags_RendererHasTextures);
platform_io.ClearRendererHandlers();
IM_DELETE(bd);
}
// Convert RGBA32 to BGRA32 (because RGBA32 is not well supported by DX9 devices)
static void ImGui_ImplDX9_CopyTextureRegion(bool tex_use_colors, const ImU32* src, int src_pitch, ImU32* dst, int dst_pitch, int w, int h)
{
#ifndef IMGUI_USE_BGRA_PACKED_COLOR
ImGui_ImplDX9_Data* bd = ImGui_ImplDX9_GetBackendData();
const bool convert_rgba_to_bgra = (!bd->HasRgbaSupport && tex_use_colors);
#else
const bool convert_rgba_to_bgra = false;
IM_UNUSED(tex_use_colors);
#endif
for (int y = 0; y < h; y++)
{
const ImU32* src_p = (const ImU32*)(const void*)((const unsigned char*)src + src_pitch * y);
ImU32* dst_p = (ImU32*)(void*)((unsigned char*)dst + dst_pitch * y);
if (convert_rgba_to_bgra)
for (int x = w; x > 0; x--, src_p++, dst_p++) // Convert copy
*dst_p = IMGUI_COL_TO_DX9_ARGB(*src_p);
else
memcpy(dst_p, src_p, w * 4); // Raw copy
}
}
void ImGui_ImplDX9_UpdateTexture(ImTextureData* tex)
{
ImGui_ImplDX9_Data* bd = ImGui_ImplDX9_GetBackendData();
if (tex->Status == ImTextureStatus_WantCreate)
{
// Create and upload new texture to graphics system
//IMGUI_DEBUG_LOG("UpdateTexture #%03d: WantCreate %dx%d\n", tex->UniqueID, tex->Width, tex->Height);
IM_ASSERT(tex->TexID == ImTextureID_Invalid && tex->BackendUserData == nullptr);
IM_ASSERT(tex->Format == ImTextureFormat_RGBA32);
LPDIRECT3DTEXTURE9 dx_tex = nullptr;
HRESULT hr = bd->pd3dDevice->CreateTexture(tex->Width, tex->Height, 1, D3DUSAGE_DYNAMIC, bd->HasRgbaSupport ? D3DFMT_A8B8G8R8 : D3DFMT_A8R8G8B8, D3DPOOL_DEFAULT, &dx_tex, nullptr);
if (hr < 0)
{
IM_ASSERT(hr >= 0 && "Backend failed to create texture!");
return;
}
D3DLOCKED_RECT locked_rect;
if (dx_tex->LockRect(0, &locked_rect, nullptr, 0) == D3D_OK)
{
ImGui_ImplDX9_CopyTextureRegion(tex->UseColors, (ImU32*)tex->GetPixels(), tex->Width * 4, (ImU32*)locked_rect.pBits, (ImU32)locked_rect.Pitch, tex->Width, tex->Height);
dx_tex->UnlockRect(0);
}
// Store identifiers
ImTextureDX9* tex_wrapper = new ImTextureDX9{dx_tex, false};
tex->SetTexID((ImTextureID)tex_wrapper);
tex->SetStatus(ImTextureStatus_OK);
}
else if (tex->Status == ImTextureStatus_WantUpdates)
{
// Update selected blocks. We only ever write to textures regions which have never been used before!
// This backend choose to use tex->Updates[] but you can use tex->UpdateRect to upload a single region.
const ImTextureDX9 *tex_wrapper = (const ImTextureDX9 *)tex->TexID;
const LPDIRECT3DTEXTURE9 backend_tex = tex_wrapper->d3dTexture;
RECT update_rect = { (LONG)tex->UpdateRect.x, (LONG)tex->UpdateRect.y, (LONG)(tex->UpdateRect.x + tex->UpdateRect.w), (LONG)(tex->UpdateRect.y + tex->UpdateRect.h) };
D3DLOCKED_RECT locked_rect;
if (backend_tex->LockRect(0, &locked_rect, &update_rect, 0) == D3D_OK)
for (ImTextureRect& r : tex->Updates)
ImGui_ImplDX9_CopyTextureRegion(tex->UseColors, (ImU32*)tex->GetPixelsAt(r.x, r.y), tex->Width * 4,
(ImU32*)locked_rect.pBits + (r.x - update_rect.left) + (r.y - update_rect.top) * (locked_rect.Pitch / 4), (int)locked_rect.Pitch, r.w, r.h);
backend_tex->UnlockRect(0);
tex->SetStatus(ImTextureStatus_OK);
}
else if (tex->Status == ImTextureStatus_WantDestroy)
{
ImTextureDX9 *tex_wrapper = (ImTextureDX9 *)tex->TexID;
if (tex_wrapper)
{
IM_ASSERT(tex_wrapper == (ImTextureDX9*)tex->TexID);
tex_wrapper->d3dTexture->Release();
tex_wrapper->d3dTexture = nullptr;
// Clear identifiers and mark as destroyed (in order to allow e.g. calling InvalidateDeviceObjects while running)
tex->SetTexID(ImTextureID_Invalid);
}
tex->SetStatus(ImTextureStatus_Destroyed);
}
}
bool ImGui_ImplDX9_CreateDeviceObjects()
{
ImGui_ImplDX9_Data* bd = ImGui_ImplDX9_GetBackendData();
if (!bd || !bd->pd3dDevice)
return false;
if (!ImGui_ImplDX9_CreateFontsTexture())
return false;
return true;
}
@@ -388,24 +483,23 @@ void ImGui_ImplDX9_InvalidateDeviceObjects()
ImGui_ImplDX9_Data* bd = ImGui_ImplDX9_GetBackendData();
if (!bd || !bd->pd3dDevice)
return;
// Destroy all textures
for (ImTextureData* tex : ImGui::GetPlatformIO().Textures)
if (tex->RefCount == 1)
{
tex->SetStatus(ImTextureStatus_WantDestroy);
ImGui_ImplDX9_UpdateTexture(tex);
}
if (bd->pVB) { bd->pVB->Release(); bd->pVB = nullptr; }
if (bd->pIB) { bd->pIB->Release(); bd->pIB = nullptr; }
if (bd->FontTexture.d3dTexture)
{
bd->FontTexture.d3dTexture->Release();
bd->FontTexture.d3dTexture = nullptr;
// We copied bd->FontTexture to io.Fonts->TexID so let's clear that as well.
ImGui::GetIO().Fonts->SetTexID(0);
}
}
void ImGui_ImplDX9_NewFrame()
{
ImGui_ImplDX9_Data* bd = ImGui_ImplDX9_GetBackendData();
IM_ASSERT(bd != nullptr && "Context or backend not initialized! Did you call ImGui_ImplDX9_Init()?");
if (!bd->FontTexture.d3dTexture)
ImGui_ImplDX9_CreateDeviceObjects();
IM_UNUSED(bd);
}
//-----------------------------------------------------------------------------
+8 -3
View File
@@ -2,8 +2,10 @@
// This needs to be used along with a Platform Backend (e.g. Win32)
// Implemented features:
// [X] Renderer: User texture binding. Use 'LPDIRECT3DTEXTURE9' as ImTextureID. Read the FAQ about ImTextureID!
// [X] Renderer: Large meshes support (64k+ vertices) with 16-bit indices.
// [X] Renderer: User texture binding. Use 'LPDIRECT3DTEXTURE9' as texture identifier. Read the FAQ about ImTextureID/ImTextureRef!
// [X] Renderer: Large meshes support (64k+ vertices) even with 16-bit indices (ImGuiBackendFlags_RendererHasVtxOffset).
// [X] Renderer: Texture updates support for dynamic font atlas (ImGuiBackendFlags_RendererHasTextures).
// [X] Renderer: IMGUI_USE_BGRA_PACKED_COLOR support, as this is the optimal color encoding for DirectX9.
// You can use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this.
// Prefer including the entire imgui/ repository into your project (either as a copy or as a submodule), and only build the backends you need.
@@ -20,12 +22,13 @@
struct IDirect3DDevice9;
struct IDirect3DTexture9;
// Follow "Getting Started" link and check examples/ folder to learn about using backends!
IMGUI_IMPL_API bool ImGui_ImplDX9_Init(IDirect3DDevice9* device);
IMGUI_IMPL_API void ImGui_ImplDX9_Shutdown();
IMGUI_IMPL_API void ImGui_ImplDX9_NewFrame();
IMGUI_IMPL_API void ImGui_ImplDX9_RenderDrawData(ImDrawData* draw_data);
// Use if you want to reset your rendering device without losing Dear ImGui state.
// Use if you want to reset your rendering state without using ImGui_ImplDX9_RenderDrawData.
IMGUI_IMPL_API bool ImGui_ImplDX9_CreateDeviceObjects();
IMGUI_IMPL_API void ImGui_ImplDX9_InvalidateDeviceObjects();
@@ -35,5 +38,7 @@ struct ImTextureDX9
IDirect3DTexture9 *d3dTexture;
bool pointSampling;
};
// (Advanced) Use e.g. if you need to precisely control the timing of texture updates (e.g. for staged rendering), by setting ImDrawData::Textures = NULL to handle this manually.
IMGUI_IMPL_API void ImGui_ImplDX9_UpdateTexture(ImTextureData* tex);
#endif // #ifndef IMGUI_DISABLE
+345 -163
View File
@@ -11,9 +11,64 @@
// CHANGELOG
// (minor and older changes stripped away, please see git history for details)
// 2025-12-11: OpenGL: Fixed embedded loader multiple init/shutdown cycles broken on some platforms. (#8792, #9112)
// 2025-09-18: Call platform_io.ClearRendererHandlers() on shutdown.
// 2025-07-22: OpenGL: Add and call embedded loader shutdown during ImGui_ImplOpenGL3_Shutdown() to facilitate multiple init/shutdown cycles in same process. (#8792)
// 2025-07-15: OpenGL: Set GL_UNPACK_ALIGNMENT to 1 before updating textures (#8802) + restore non-WebGL/ES update path that doesn't require a CPU-side copy.
// 2025-06-11: OpenGL: Added support for ImGuiBackendFlags_RendererHasTextures, for dynamic font atlas. Removed ImGui_ImplOpenGL3_CreateFontsTexture() and ImGui_ImplOpenGL3_DestroyFontsTexture().
// 2025-06-04: OpenGL: Made GLES 3.20 contexts not access GL_CONTEXT_PROFILE_MASK nor GL_PRIMITIVE_RESTART. (#8664)
// 2025-02-18: OpenGL: Lazily reinitialize embedded GL loader for when calling backend from e.g. other DLL boundaries. (#8406)
// 2024-10-07: OpenGL: Changed default texture sampler to Clamp instead of Repeat/Wrap.
// 2024-06-28: OpenGL: ImGui_ImplOpenGL3_NewFrame() recreates font texture if it has been destroyed by ImGui_ImplOpenGL3_DestroyFontsTexture(). (#7748)
// 2024-05-07: OpenGL: Update loader for Linux to support EGL/GLVND. (#7562)
// 2024-04-16: OpenGL: Detect ES3 contexts on desktop based on version string, to e.g. avoid calling glPolygonMode() on them. (#7447)
// 2024-01-09: OpenGL: Update GL3W based imgui_impl_opengl3_loader.h to load "libGL.so" and variants, fixing regression on distros missing a symlink.
// 2023-11-08: OpenGL: Update GL3W based imgui_impl_opengl3_loader.h to load "libGL.so" instead of "libGL.so.1", accommodating for NetBSD systems having only "libGL.so.3" available. (#6983)
// 2023-10-05: OpenGL: Rename symbols in our internal loader so that LTO compilation with another copy of gl3w is possible. (#6875, #6668, #4445)
// 2023-06-20: OpenGL: Fixed erroneous use glGetIntegerv(GL_CONTEXT_PROFILE_MASK) on contexts lower than 3.2. (#6539, #6333)
// 2023-05-09: OpenGL: Support for glBindSampler() backup/restore on ES3. (#6375)
// 2023-04-18: OpenGL: Restore front and back polygon mode separately when supported by context. (#6333)
// 2023-03-23: OpenGL: Properly restoring "no shader program bound" if it was the case prior to running the rendering function. (#6267, #6220, #6224)
// 2023-03-15: OpenGL: Fixed GL loader crash when GL_VERSION returns nullptr. (#6154, #4445, #3530)
// 2023-03-06: OpenGL: Fixed restoration of a potentially deleted OpenGL program, by calling glIsProgram(). (#6220, #6224)
// 2022-11-09: OpenGL: Reverted use of glBufferSubData(), too many corruptions issues + old issues seemingly can't be reproed with Intel drivers nowadays (revert 2021-12-15 and 2022-05-23 changes).
// 2022-10-11: Using 'nullptr' instead of 'NULL' as per our switch to C++11.
// 2022-09-27: OpenGL: Added ability to '#define IMGUI_IMPL_OPENGL_DEBUG'.
// 2022-05-23: OpenGL: Reworking 2021-12-15 "Using buffer orphaning" so it only happens on Intel GPU, seems to cause problems otherwise. (#4468, #4825, #4832, #5127).
// 2022-05-13: OpenGL: Fixed state corruption on OpenGL ES 2.0 due to not preserving GL_ELEMENT_ARRAY_BUFFER_BINDING and vertex attribute states.
// 2021-12-15: OpenGL: Using buffer orphaning + glBufferSubData(), seems to fix leaks with multi-viewports with some Intel HD drivers.
// 2021-08-23: OpenGL: Fixed ES 3.0 shader ("#version 300 es") use normal precision floats to avoid wobbly rendering at HD resolutions.
// 2021-08-19: OpenGL: Embed and use our own minimal GL loader (imgui_impl_opengl3_loader.h), removing requirement and support for third-party loader.
// 2021-06-29: Reorganized backend to pull data from a single structure to facilitate usage with multiple-contexts (all g_XXXX access changed to bd->XXXX).
// 2021-06-25: OpenGL: Use OES_vertex_array extension on Emscripten + backup/restore current state.
// 2021-06-21: OpenGL: Destroy individual vertex/fragment shader objects right after they are linked into the main shader.
// 2021-05-24: OpenGL: Access GL_CLIP_ORIGIN when "GL_ARB_clip_control" extension is detected, inside of just OpenGL 4.5 version.
// 2021-05-19: OpenGL: Replaced direct access to ImDrawCmd::TextureId with a call to ImDrawCmd::GetTexID(). (will become a requirement)
// 2021-04-06: OpenGL: Don't try to read GL_CLIP_ORIGIN unless we're OpenGL 4.5 or greater.
// 2021-02-18: OpenGL: Change blending equation to preserve alpha in output buffer.
// 2021-01-03: OpenGL: Backup, setup and restore GL_STENCIL_TEST state.
// 2020-10-23: OpenGL: Backup, setup and restore GL_PRIMITIVE_RESTART state.
// 2020-10-15: OpenGL: Use glGetString(GL_VERSION) instead of glGetIntegerv(GL_MAJOR_VERSION, ...) when the later returns zero (e.g. Desktop GL 2.x)
// 2020-09-17: OpenGL: Fix to avoid compiling/calling glBindSampler() on ES or pre-3.3 context which have the defines set by a loader.
// 2020-07-10: OpenGL: Added support for glad2 OpenGL loader.
// 2020-05-08: OpenGL: Made default GLSL version 150 (instead of 130) on OSX.
// 2020-04-21: OpenGL: Fixed handling of glClipControl(GL_UPPER_LEFT) by inverting projection matrix.
// 2020-04-12: OpenGL: Fixed context version check mistakenly testing for 4.0+ instead of 3.2+ to enable ImGuiBackendFlags_RendererHasVtxOffset.
// 2020-03-24: OpenGL: Added support for glbinding 2.x OpenGL loader.
// 2020-01-07: OpenGL: Added support for glbinding 3.x OpenGL loader.
// 2019-10-25: OpenGL: Using a combination of GL define and runtime GL version to decide whether to use glDrawElementsBaseVertex(). Fix building with pre-3.2 GL loaders.
// 2019-09-22: OpenGL: Detect default GL loader using __has_include compiler facility.
// 2019-09-16: OpenGL: Tweak initialization code to allow application calling ImGui_ImplOpenGL3_CreateFontsTexture() before the first NewFrame() call.
// 2019-05-29: OpenGL: Desktop GL only: Added support for large mesh (64K+ vertices), enable ImGuiBackendFlags_RendererHasVtxOffset flag.
// 2019-04-30: OpenGL: Added support for special ImDrawCallback_ResetRenderState callback to reset render state.
// 2019-03-29: OpenGL: Not calling glBindBuffer more than necessary in the render loop.
// 2019-03-15: OpenGL: Added a GL call + comments in ImGui_ImplOpenGL3_Init() to detect uninitialized GL function loaders early.
// 2019-03-03: OpenGL: Fix support for ES 2.0 (WebGL 1.0).
// 2019-02-20: OpenGL: Fix for OSX not supporting OpenGL 4.5, we don't try to read GL_CLIP_ORIGIN even if defined by the headers/loader.
// 2019-02-11: OpenGL: Projecting clipping rectangles correctly using draw_data->FramebufferScale to allow multi-viewports for retina display.
// 2019-02-01: OpenGL: Using GLSL 410 shaders for any version over 410 (e.g. 430, 450).
// 2018-11-30: Misc: Setting up io.BackendRendererName so it can be displayed in the About Window.
// 2018-11-13: OpenGL: Support for GL 4.5's glClipControl(GL_UPPER_LEFT).
// 2018-11-13: OpenGL: Support for GL 4.5's glClipControl(GL_UPPER_LEFT) / GL_CLIP_ORIGIN.
// 2018-08-29: OpenGL: Added support for more OpenGL loaders: glew and glad, with comments indicative that any loader can be used.
// 2018-08-09: OpenGL: Default to OpenGL ES 3 on iOS and Android. GLSL version default to "#version 300 ES".
// 2018-07-30: OpenGL: Support for GLSL 300 ES and 410 core. Fixes for Emscripten compilation.
@@ -21,7 +76,7 @@
// 2018-06-08: Misc: Extracted imgui_impl_opengl3.cpp/.h away from the old combined GLFW/SDL+OpenGL3 examples.
// 2018-06-08: OpenGL: Use draw_data->DisplayPos and draw_data->DisplaySize to setup projection matrix and clipping rectangle.
// 2018-05-25: OpenGL: Removed unnecessary backup/restore of GL_ELEMENT_ARRAY_BUFFER_BINDING since this is part of the VAO state.
// 2018-05-14: OpenGL: Making the call to glBindSampler() optional so 3.2 context won't fail if the function is a NULL pointer.
// 2018-05-14: OpenGL: Making the call to glBindSampler() optional so 3.2 context won't fail if the function is a nullptr pointer.
// 2018-03-06: OpenGL: Added const char* glsl_version parameter to ImGui_ImplOpenGL3_Init() so user can override the GLSL version e.g. "#version 150".
// 2018-02-23: OpenGL: Create the VAO in the render function so the setup can more easily be used with multiple shared GL context.
// 2018-02-16: Misc: Obsoleted the io.RenderDrawListsFn callback and exposed ImGui_ImplSdlGL3_RenderDrawData() in the .h file so you can call it yourself.
@@ -37,17 +92,17 @@
// version version string
//----------------------------------------
// 2.0 110 "#version 110"
// 2.1 120
// 3.0 130
// 3.1 140
// 2.1 120 "#version 120"
// 3.0 130 "#version 130"
// 3.1 140 "#version 140"
// 3.2 150 "#version 150"
// 3.3 330
// 4.0 400
// 3.3 330 "#version 330 core"
// 4.0 400 "#version 400 core"
// 4.1 410 "#version 410 core"
// 4.2 420
// 4.3 430
// ES 2.0 100 "#version 100"
// ES 3.0 300 "#version 300 es"
// 4.2 420 "#version 410 core"
// 4.3 430 "#version 430 core"
// ES 2.0 100 "#version 100" = WebGL 1.0
// ES 3.0 300 "#version 300 es" = WebGL 2.0
//----------------------------------------
#if defined(_MSC_VER) && !defined(_CRT_SECURE_NO_WARNINGS)
@@ -55,6 +110,7 @@
#endif
#include "imgui.h"
#ifndef IMGUI_DISABLE
#include "imgui_impl_opengl3.h"
#include <stdio.h>
#if defined(_MSC_VER) && _MSC_VER <= 1500 // MSVC 2008 or earlier
@@ -69,76 +125,110 @@
#include "rend/gles/glcache.h"
// OpenGL Data
static char g_GlslVersionString[32] = "";
static GLuint g_FontTexture = 0;
static GLuint g_ShaderHandle = 0;
static int g_AttribLocationTex = 0, g_AttribLocationProjMtx = 0;
static int g_AttribLocationPosition = 0, g_AttribLocationUV = 0, g_AttribLocationColor = 0;
static unsigned int g_VboHandle = 0, g_ElementsHandle = 0;
struct ImGui_ImplOpenGL3_Data
{
GLuint GlVersion;
char GlslVersionString[32];
GLuint ShaderHandle;
GLint AttribLocationTex;
GLint AttribLocationProjMtx;
GLuint AttribLocationVtxPos;
GLuint AttribLocationVtxUV;
GLuint AttribLocationVtxColor;
unsigned int VboHandle, ElementsHandle;
bool HasPolygonMode;
bool HasBindSampler;
bool HasClipOrigin;
ImVector<char> TempBuffer;
ImGui_ImplOpenGL3_Data() { memset((void*)this, 0, sizeof(*this)); }
};
// Backend data stored in io.BackendRendererUserData to allow support for multiple Dear ImGui contexts
static ImGui_ImplOpenGL3_Data* ImGui_ImplOpenGL3_GetBackendData()
{
return ImGui::GetCurrentContext() ? (ImGui_ImplOpenGL3_Data*)ImGui::GetIO().BackendRendererUserData : nullptr;
}
// Functions
static bool ImGui_ImplOpenGL3_CreateDeviceObjects();
static void ImGui_ImplOpenGL3_DestroyDeviceObjects();
bool ImGui_ImplOpenGL3_CreateDeviceObjects();
void ImGui_ImplOpenGL3_DestroyDeviceObjects();
bool ImGui_ImplOpenGL3_Init()
bool ImGui_ImplOpenGL3_Init(const char* glsl_version)
{
ImGuiIO& io = ImGui::GetIO();
IMGUI_CHECKVERSION();
IM_ASSERT(io.BackendRendererUserData == nullptr && "Already initialized a renderer backend!");
// Setup backend capabilities flags
ImGui_ImplOpenGL3_Data* bd = IM_NEW(ImGui_ImplOpenGL3_Data)();
io.BackendRendererUserData = (void*)bd;
io.BackendRendererName = "imgui_impl_opengl3";
io.BackendFlags |= ImGuiBackendFlags_RendererHasTextures;
// Store GLSL version string so we can refer to it later in case we recreate shaders. Note: GLSL version is NOT the same as GL version. Leave this to NULL if unsure.
const char* glsl_version;
if (theGLContext.isGLES())
glsl_version = "#version 100"; // OpenGL ES 2.0
else
// Flycast: Detect version using GL context
if (glsl_version == nullptr)
{
if (GLGraphicsContext::Instance()->isGLES())
glsl_version = "#version 100"; // OpenGL ES 2.0
else
#if defined(__APPLE__)
glsl_version = "#version 140"; // OpenGL 3.1
glsl_version = "#version 140"; // OpenGL 3.1
#else
glsl_version = "#version 130"; // OpenGL 3.0
glsl_version = "#version 130"; // OpenGL 3.0
#endif
}
IM_ASSERT((int)strlen(glsl_version) + 2 < IM_ARRAYSIZE(bd->GlslVersionString));
strcpy(bd->GlslVersionString, glsl_version);
strcat(bd->GlslVersionString, "\n");
// Detect extensions we support
const bool isGLES = GLGraphicsContext::Instance()->isGLES();
const int majorVer = GLGraphicsContext::Instance()->getMajorVersion();
bd->HasPolygonMode = !isGLES;
if (isGLES)
// GLES 3.0+
bd->HasBindSampler = majorVer >= 3;
else
// OpenGL 3.3+
bd->HasBindSampler = majorVer > 3
|| (majorVer == 3 && GLGraphicsContext::Instance()->getMinorVersion() >= 3);
#ifdef GL_CLIP_ORIGIN
bd->HasClipOrigin = (majorVer >= 4 && glClipControl != NULL);
#endif
IM_ASSERT((int)strlen(glsl_version) + 2 < IM_ARRAYSIZE(g_GlslVersionString));
strcpy(g_GlslVersionString, glsl_version);
strcat(g_GlslVersionString, "\n");
return true;
}
void ImGui_ImplOpenGL3_Shutdown()
{
ImGui_ImplOpenGL3_Data* bd = ImGui_ImplOpenGL3_GetBackendData();
IM_ASSERT(bd != nullptr && "No renderer backend to shutdown, or already shutdown?");
ImGuiIO& io = ImGui::GetIO();
ImGuiPlatformIO& platform_io = ImGui::GetPlatformIO();
ImGui_ImplOpenGL3_DestroyDeviceObjects();
io.BackendRendererName = nullptr;
io.BackendRendererUserData = nullptr;
io.BackendFlags &= ~ImGuiBackendFlags_RendererHasTextures;
platform_io.ClearRendererHandlers();
IM_DELETE(bd);
}
void ImGui_ImplOpenGL3_NewFrame()
{
if (!g_FontTexture)
ImGui_ImplOpenGL3_CreateDeviceObjects();
ImGui_ImplOpenGL3_Data* bd = ImGui_ImplOpenGL3_GetBackendData();
IM_ASSERT(bd != nullptr && "Context or backend not initialized! Did you call ImGui_ImplOpenGL3_Init()?");
if (!bd->ShaderHandle)
if (!ImGui_ImplOpenGL3_CreateDeviceObjects())
IM_ASSERT(0 && "ImGui_ImplOpenGL3_CreateDeviceObjects() failed!");
}
// OpenGL3 Render function.
// (this used to be set in io.RenderDrawListsFn and called by ImGui::Render(), but you can now call this directly from your main loop)
// Note that this implementation is little overcomplicated because we are saving/setting up/restoring every OpenGL state explicitly, in order to be able to run within any OpenGL engine that doesn't do so.
void ImGui_ImplOpenGL3_RenderDrawData(ImDrawData* draw_data)
static void ImGui_ImplOpenGL3_SetupRenderState(ImDrawData* draw_data, int fb_width, int fb_height, GLuint vertex_array_object)
{
// Avoid rendering when minimized, scale coordinates for retina displays (screen coordinates != framebuffer coordinates)
ImGuiIO& io = ImGui::GetIO();
int fb_width = (int)(draw_data->DisplaySize.x * io.DisplayFramebufferScale.x);
int fb_height = (int)(draw_data->DisplaySize.y * io.DisplayFramebufferScale.y);
if (fb_width <= 0 || fb_height <= 0)
return;
draw_data->ScaleClipRects(io.DisplayFramebufferScale);
// Backup GL state
glActiveTexture(GL_TEXTURE0);
bool clip_origin_lower_left = true;
#ifdef GL_CLIP_ORIGIN
if (theGLContext.getMajorVersion() >= 4 && glClipControl != NULL)
{
GLenum last_clip_origin = 0; glGetIntegerv(GL_CLIP_ORIGIN, (GLint*)&last_clip_origin); // Support for GL 4.5's glClipControl(GL_UPPER_LEFT)
if (last_clip_origin == GL_UPPER_LEFT)
clip_origin_lower_left = false;
}
#endif
ImGui_ImplOpenGL3_Data* bd = ImGui_ImplOpenGL3_GetBackendData();
// Setup render state: alpha-blending enabled, no face culling, no depth testing, scissor enabled, polygon fill
glcache.Enable(GL_BLEND);
@@ -146,13 +236,24 @@ void ImGui_ImplOpenGL3_RenderDrawData(ImDrawData* draw_data)
glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
glcache.Disable(GL_CULL_FACE);
glcache.Disable(GL_DEPTH_TEST);
glcache.Enable(GL_SCISSOR_TEST);
glcache.Disable(GL_STENCIL_TEST);
glcache.Enable(GL_SCISSOR_TEST);
#ifdef GL_POLYGON_MODE
if (glPolygonMode != NULL)
if (bd->HasPolygonMode && glPolygonMode != NULL)
glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
#endif
// Support for GL 4.5 rarely used glClipControl(GL_UPPER_LEFT)
#if defined(GL_CLIP_ORIGIN)
bool clip_origin_lower_left = true;
if (bd->HasClipOrigin)
{
GLenum current_clip_origin = 0; glGetIntegerv(GL_CLIP_ORIGIN, (GLint*)&current_clip_origin);
if (current_clip_origin == GL_UPPER_LEFT)
clip_origin_lower_left = false;
}
#endif
// Setup viewport, orthographic projection matrix
// Our visible imgui space lies from draw_data->DisplayPos (top left) to draw_data->DisplayPos+data_data->DisplaySize (bottom right). DisplayMin is typically (0,0) for single viewport apps.
glViewport(0, 0, (GLsizei)fb_width, (GLsizei)fb_height);
@@ -160,6 +261,9 @@ void ImGui_ImplOpenGL3_RenderDrawData(ImDrawData* draw_data)
float R = draw_data->DisplayPos.x + draw_data->DisplaySize.x;
float T = draw_data->DisplayPos.y;
float B = draw_data->DisplayPos.y + draw_data->DisplaySize.y;
#if defined(GL_CLIP_ORIGIN)
if (!clip_origin_lower_left) { float tmp = T; T = B; B = tmp; } // Swap top and bottom if origin is upper left
#endif
const float ortho_projection[4][4] =
{
{ 2.0f/(R-L), 0.0f, 0.0f, 0.0f },
@@ -167,64 +271,95 @@ void ImGui_ImplOpenGL3_RenderDrawData(ImDrawData* draw_data)
{ 0.0f, 0.0f, -1.0f, 0.0f },
{ (R+L)/(L-R), (T+B)/(B-T), 0.0f, 1.0f },
};
glcache.UseProgram(g_ShaderHandle);
glUniform1i(g_AttribLocationTex, 0);
glUniformMatrix4fv(g_AttribLocationProjMtx, 1, GL_FALSE, &ortho_projection[0][0]);
glcache.UseProgram(bd->ShaderHandle);
glUniform1i(bd->AttribLocationTex, 0);
glUniformMatrix4fv(bd->AttribLocationProjMtx, 1, GL_FALSE, &ortho_projection[0][0]);
#ifndef GLES2
if (theGLContext.getMajorVersion() >= 3 && glBindSampler != NULL)
if (bd->HasBindSampler && glBindSampler != NULL)
glBindSampler(0, 0); // We use combined texture/sampler state. Applications using GL 3.3 may set that otherwise.
#endif
GLuint vao_handle = 0;
#ifndef GLES2
if (theGLContext.getMajorVersion() >= 3)
{
// Recreate the VAO every time
// (This is to easily allow multiple GL contexts. VAO are not shared among GL contexts, and we don't track creation/deletion of windows so we don't have an obvious key to use to cache them.)
glGenVertexArrays(1, &vao_handle);
glBindVertexArray(vao_handle);
}
if (GLGraphicsContext::Instance()->getMajorVersion() >= 3)
glBindVertexArray(vertex_array_object);
#endif
glBindBuffer(GL_ARRAY_BUFFER, g_VboHandle);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, g_ElementsHandle);
glEnableVertexAttribArray(g_AttribLocationPosition);
glEnableVertexAttribArray(g_AttribLocationUV);
glEnableVertexAttribArray(g_AttribLocationColor);
glVertexAttribPointer(g_AttribLocationPosition, 2, GL_FLOAT, GL_FALSE, sizeof(ImDrawVert), (GLvoid*)offsetof(ImDrawVert, pos));
glVertexAttribPointer(g_AttribLocationUV, 2, GL_FLOAT, GL_FALSE, sizeof(ImDrawVert), (GLvoid*)offsetof(ImDrawVert, uv));
glVertexAttribPointer(g_AttribLocationColor, 4, GL_UNSIGNED_BYTE, GL_TRUE, sizeof(ImDrawVert), (GLvoid*)offsetof(ImDrawVert, col));
glBindBuffer(GL_ARRAY_BUFFER, bd->VboHandle);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, bd->ElementsHandle);
glEnableVertexAttribArray(bd->AttribLocationVtxPos);
glEnableVertexAttribArray(bd->AttribLocationVtxUV);
glEnableVertexAttribArray(bd->AttribLocationVtxColor);
glVertexAttribPointer(bd->AttribLocationVtxPos, 2, GL_FLOAT, GL_FALSE, sizeof(ImDrawVert), (GLvoid*)offsetof(ImDrawVert, pos));
glVertexAttribPointer(bd->AttribLocationVtxUV, 2, GL_FLOAT, GL_FALSE, sizeof(ImDrawVert), (GLvoid*)offsetof(ImDrawVert, uv));
glVertexAttribPointer(bd->AttribLocationVtxColor, 4, GL_UNSIGNED_BYTE, GL_TRUE, sizeof(ImDrawVert), (GLvoid*)offsetof(ImDrawVert, col));
}
// Draw
ImVec2 pos = draw_data->DisplayPos;
for (int n = 0; n < draw_data->CmdListsCount; n++)
// OpenGL3 Render function.
// (this used to be set in io.RenderDrawListsFn and called by ImGui::Render(), but you can now call this directly from your main loop)
// Note that this implementation is little overcomplicated because we are saving/setting up/restoring every OpenGL state explicitly, in order to be able to run within any OpenGL engine that doesn't do so.
void ImGui_ImplOpenGL3_RenderDrawData(ImDrawData* draw_data)
{
// Avoid rendering when minimized, scale coordinates for retina displays (screen coordinates != framebuffer coordinates)
int fb_width = (int)(draw_data->DisplaySize.x * draw_data->FramebufferScale.x);
int fb_height = (int)(draw_data->DisplaySize.y * draw_data->FramebufferScale.y);
if (fb_width <= 0 || fb_height <= 0)
return;
// Catch up with texture updates. Most of the times, the list will have 1 element with an OK status, aka nothing to do.
// (This almost always points to ImGui::GetPlatformIO().Textures[] but is part of ImDrawData to allow overriding or disabling texture updates).
if (draw_data->Textures != nullptr)
for (ImTextureData* tex : *draw_data->Textures)
if (tex->Status != ImTextureStatus_OK)
ImGui_ImplOpenGL3_UpdateTexture(tex);
// Backup GL state
glActiveTexture(GL_TEXTURE0);
// Setup desired GL state
// Recreate the VAO every time (this is to easily allow multiple GL contexts to be rendered to. VAO are not shared among GL contexts)
// The renderer would actually work without any VAO bound, but then our VertexAttrib calls would overwrite the default one currently bound.
GLuint vertex_array_object = 0;
#ifndef GLES2
if (GLGraphicsContext::Instance()->getMajorVersion() >= 3)
glGenVertexArrays(1, &vertex_array_object);
#endif
ImGui_ImplOpenGL3_SetupRenderState(draw_data, fb_width, fb_height, vertex_array_object);
// Will project scissor/clipping rectangles into framebuffer space
ImVec2 clip_off = draw_data->DisplayPos; // (0,0) unless using multi-viewports
ImVec2 clip_scale = draw_data->FramebufferScale; // (1,1) unless using retina display which are often (2,2)
for (const ImDrawList* draw_list : draw_data->CmdLists)
{
const ImDrawList* cmd_list = draw_data->CmdLists[n];
glBufferData(GL_ARRAY_BUFFER, (GLsizeiptr)cmd_list->VtxBuffer.Size * sizeof(ImDrawVert), (const GLvoid*)cmd_list->VtxBuffer.Data, GL_STREAM_DRAW);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, (GLsizeiptr)cmd_list->IdxBuffer.Size * sizeof(ImDrawIdx), (const GLvoid*)cmd_list->IdxBuffer.Data, GL_STREAM_DRAW);
glBufferData(GL_ARRAY_BUFFER, (GLsizeiptr)draw_list->VtxBuffer.Size * sizeof(ImDrawVert), (const GLvoid*)draw_list->VtxBuffer.Data, GL_STREAM_DRAW);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, (GLsizeiptr)draw_list->IdxBuffer.Size * sizeof(ImDrawIdx), (const GLvoid*)draw_list->IdxBuffer.Data, GL_STREAM_DRAW);
for (int cmd_i = 0; cmd_i < cmd_list->CmdBuffer.Size; cmd_i++)
for (int cmd_i = 0; cmd_i < draw_list->CmdBuffer.Size; cmd_i++)
{
const ImDrawCmd* pcmd = &cmd_list->CmdBuffer[cmd_i];
if (pcmd->UserCallback)
const ImDrawCmd* pcmd = &draw_list->CmdBuffer[cmd_i];
if (pcmd->UserCallback != nullptr)
{
// User callback (registered via ImDrawList::AddCallback)
pcmd->UserCallback(cmd_list, pcmd);
// User callback, registered via ImDrawList::AddCallback()
// (ImDrawCallback_ResetRenderState is a special callback value used by the user to request the renderer to reset render state.)
if (pcmd->UserCallback == ImDrawCallback_ResetRenderState)
ImGui_ImplOpenGL3_SetupRenderState(draw_data, fb_width, fb_height, vertex_array_object);
else
pcmd->UserCallback(draw_list, pcmd);
}
else
{
ImVec4 clip_rect = ImVec4(pcmd->ClipRect.x - pos.x, pcmd->ClipRect.y - pos.y, pcmd->ClipRect.z - pos.x, pcmd->ClipRect.w - pos.y);
if (clip_rect.x < fb_width && clip_rect.y < fb_height && clip_rect.z >= 0.0f && clip_rect.w >= 0.0f)
{
// Apply scissor/clipping rectangle
if (clip_origin_lower_left)
glcache.Scissor((int)clip_rect.x, (int)(fb_height - clip_rect.w), (int)(clip_rect.z - clip_rect.x), (int)(clip_rect.w - clip_rect.y));
else
glcache.Scissor((int)clip_rect.x, (int)clip_rect.y, (int)clip_rect.z, (int)clip_rect.w); // Support for GL 4.5's glClipControl(GL_UPPER_LEFT)
// Project scissor/clipping rectangles into framebuffer space
ImVec2 clip_min((pcmd->ClipRect.x - clip_off.x) * clip_scale.x, (pcmd->ClipRect.y - clip_off.y) * clip_scale.y);
ImVec2 clip_max((pcmd->ClipRect.z - clip_off.x) * clip_scale.x, (pcmd->ClipRect.w - clip_off.y) * clip_scale.y);
if (clip_max.x <= clip_min.x || clip_max.y <= clip_min.y)
continue;
// Bind texture, Draw
glcache.BindTexture(GL_TEXTURE_2D, (GLuint)(intptr_t)pcmd->GetTexID());
glDrawElements(GL_TRIANGLES, (GLsizei)pcmd->ElemCount, sizeof(ImDrawIdx) == 2 ? GL_UNSIGNED_SHORT : GL_UNSIGNED_INT, (void*)(intptr_t)(pcmd->IdxOffset * sizeof(ImDrawIdx)));
}
// Apply scissor/clipping rectangle (Y is inverted in OpenGL)
glcache.Scissor((int)clip_min.x, (int)((float)fb_height - clip_max.y), (int)(clip_max.x - clip_min.x), (int)(clip_max.y - clip_min.y));
// Bind texture, Draw
glcache.BindTexture(GL_TEXTURE_2D, (GLuint)(intptr_t)pcmd->GetTexID());
glDrawElements(GL_TRIANGLES, (GLsizei)pcmd->ElemCount, sizeof(ImDrawIdx) == 2 ? GL_UNSIGNED_SHORT : GL_UNSIGNED_INT, (void*)(intptr_t)(pcmd->IdxOffset * sizeof(ImDrawIdx)));
}
}
}
@@ -232,42 +367,77 @@ void ImGui_ImplOpenGL3_RenderDrawData(ImDrawData* draw_data)
glcache.BlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
#ifndef GLES2
if (vao_handle != 0)
glDeleteVertexArrays(1, &vao_handle);
if (vertex_array_object != 0)
glDeleteVertexArrays(1, &vertex_array_object);
#endif
}
static bool ImGui_ImplOpenGL3_CreateFontsTexture()
static void ImGui_ImplOpenGL3_DestroyTexture(ImTextureData* tex)
{
// Build texture atlas
ImGuiIO& io = ImGui::GetIO();
unsigned char* pixels;
int width, height;
io.Fonts->GetTexDataAsRGBA32(&pixels, &width, &height); // Load as RGBA 32-bits (75% of the memory is wasted, but default font is so small) because it is more likely to be compatible with user's existing shaders. If your ImTextureId represent a higher-level concept than just a GL texture id, consider calling GetTexDataAsAlpha8() instead to save on GPU memory.
// Upload texture to graphics system
g_FontTexture = glcache.GenTexture();
glcache.BindTexture(GL_TEXTURE_2D, g_FontTexture);
glcache.TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glcache.TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
if (theGLContext.getMajorVersion() >= 3)
glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, pixels);
// Store our identifier
io.Fonts->SetTexID((ImTextureID)g_FontTexture);
return true;
ImGui_ImplOpenGL3_Data* bd = ImGui_ImplOpenGL3_GetBackendData();
if (tex->BackendUserData != (void*)bd)
return;
GLuint gl_tex_id = (GLuint)(intptr_t)tex->TexID;
if (gl_tex_id != 0)
glcache.DeleteTextures(1, &gl_tex_id);
tex->SetTexID(ImTextureID_Invalid);
tex->SetStatus(ImTextureStatus_Destroyed);
tex->BackendUserData = nullptr;
}
static void ImGui_ImplOpenGL3_DestroyFontsTexture()
void ImGui_ImplOpenGL3_UpdateTexture(ImTextureData* tex)
{
if (g_FontTexture)
if (tex->Status == ImTextureStatus_Destroyed)
tex->SetStatus(ImTextureStatus_WantCreate);
ImGui_ImplOpenGL3_Data* bd = ImGui_ImplOpenGL3_GetBackendData();
if (tex->Status == ImTextureStatus_WantCreate || tex->Status == ImTextureStatus_WantUpdates)
{
ImGuiIO& io = ImGui::GetIO();
glcache.DeleteTextures(1, &g_FontTexture);
g_FontTexture = 0;
if (GLGraphicsContext::Instance()->getMajorVersion() >= 3)
glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
}
if (tex->Status == ImTextureStatus_WantCreate)
{
GLuint gl_texture_id = glcache.GenTexture();
glcache.BindTexture(GL_TEXTURE_2D, gl_texture_id);
glcache.TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glcache.TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, tex->Width, tex->Height, 0, GL_RGBA, GL_UNSIGNED_BYTE, tex->GetPixels());
tex->SetTexID((ImTextureID)(intptr_t)gl_texture_id);
tex->SetStatus(ImTextureStatus_OK);
tex->BackendUserData = (void*)bd;
}
else if (tex->Status == ImTextureStatus_WantUpdates)
{
GLuint gl_tex_id = (GLuint)(intptr_t)tex->TexID;
glcache.BindTexture(GL_TEXTURE_2D, gl_tex_id);
if (GLGraphicsContext::Instance()->getMajorVersion() >= 3)
{
glPixelStorei(GL_UNPACK_ROW_LENGTH, tex->Width);
for (auto& r : tex->Updates)
glTexSubImage2D(GL_TEXTURE_2D, 0, r.x, r.y, r.w, r.h, GL_RGBA, GL_UNSIGNED_BYTE, tex->GetPixelsAt(r.x, r.y));
glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
}
else
{
// Fallback for GLES2 / No unpack row length: use a temporary buffer to upload
for (auto& r : tex->Updates)
{
const int src_pitch = r.w * 4; // RGBA
bd->TempBuffer.resize(r.h * src_pitch);
char* out_p = bd->TempBuffer.Data;
for (int y = 0; y < r.h; y++, out_p += src_pitch)
memcpy(out_p, (const char*)tex->GetPixelsAt(r.x, r.y + y), src_pitch);
glTexSubImage2D(GL_TEXTURE_2D, 0, r.x, r.y, r.w, r.h, GL_RGBA, GL_UNSIGNED_BYTE, bd->TempBuffer.Data);
}
}
tex->SetStatus(ImTextureStatus_OK);
}
else if (tex->Status == ImTextureStatus_WantDestroy && tex->UnusedFrames > 0)
ImGui_ImplOpenGL3_DestroyTexture(tex);
}
// If you get an error please report on github. You may try different GL context version or GLSL version. See GL<>GLSL version table at the top of this file.
@@ -291,11 +461,12 @@ static bool CheckShader(GLuint handle, const char* desc)
// If you get an error please report on GitHub. You may try different GL context version or GLSL version.
static bool CheckProgram(GLuint handle, const char* desc)
{
ImGui_ImplOpenGL3_Data* bd = ImGui_ImplOpenGL3_GetBackendData();
GLint status = 0, log_length = 0;
glGetProgramiv(handle, GL_LINK_STATUS, &status);
glGetProgramiv(handle, GL_INFO_LOG_LENGTH, &log_length);
if ((GLboolean)status == GL_FALSE)
WARN_LOG(RENDERER, "ERROR: ImGui_ImplOpenGL3_CreateDeviceObjects: failed to link %s! (with GLSL '%s')", desc, g_GlslVersionString);
WARN_LOG(RENDERER, "ERROR: ImGui_ImplOpenGL3_CreateDeviceObjects: failed to link %s! (with GLSL '%s')", desc, bd->GlslVersionString);
if (log_length > 0)
{
ImVector<char> buf;
@@ -306,11 +477,13 @@ static bool CheckProgram(GLuint handle, const char* desc)
return (GLboolean)status == GL_TRUE;
}
static bool ImGui_ImplOpenGL3_CreateDeviceObjects()
bool ImGui_ImplOpenGL3_CreateDeviceObjects()
{
ImGui_ImplOpenGL3_Data* bd = ImGui_ImplOpenGL3_GetBackendData();
// Parse GLSL version string
int glsl_version = 130;
sscanf(g_GlslVersionString, "#version %d", &glsl_version);
sscanf(bd->GlslVersionString, "#version %d", &glsl_version);
const GLchar* vertex_shader_glsl_120 =
"uniform mat4 ProjMtx;\n"
@@ -341,7 +514,7 @@ static bool ImGui_ImplOpenGL3_CreateDeviceObjects()
"}\n";
const GLchar* vertex_shader_glsl_300_es =
"precision mediump float;\n"
"precision highp float;\n"
"layout (location = 0) in vec2 Position;\n"
"layout (location = 1) in vec2 UV;\n"
"layout (location = 2) in vec4 Color;\n"
@@ -413,8 +586,8 @@ static bool ImGui_ImplOpenGL3_CreateDeviceObjects()
"}\n";
// Select shaders matching our GLSL versions
const GLchar* vertex_shader = NULL;
const GLchar* fragment_shader = NULL;
const GLchar* vertex_shader = nullptr;
const GLchar* fragment_shader = nullptr;
if (glsl_version < 130)
{
vertex_shader = vertex_shader_glsl_120;
@@ -437,52 +610,61 @@ static bool ImGui_ImplOpenGL3_CreateDeviceObjects()
}
// Create shaders
const GLchar* vertex_shader_with_version[2] = { g_GlslVersionString, vertex_shader };
const GLchar* vertex_shader_with_version[2] = { bd->GlslVersionString, vertex_shader };
GLuint vert_handle = glCreateShader(GL_VERTEX_SHADER);
glShaderSource(vert_handle, 2, vertex_shader_with_version, NULL);
glShaderSource(vert_handle, 2, vertex_shader_with_version, nullptr);
glCompileShader(vert_handle);
CheckShader(vert_handle, "vertex shader");
if (!CheckShader(vert_handle, "vertex shader"))
return false;
const GLchar* fragment_shader_with_version[2] = { g_GlslVersionString, fragment_shader };
const GLchar* fragment_shader_with_version[2] = { bd->GlslVersionString, fragment_shader };
GLuint frag_handle = glCreateShader(GL_FRAGMENT_SHADER);
glShaderSource(frag_handle, 2, fragment_shader_with_version, NULL);
glShaderSource(frag_handle, 2, fragment_shader_with_version, nullptr);
glCompileShader(frag_handle);
CheckShader(frag_handle, "fragment shader");
if (!CheckShader(frag_handle, "fragment shader"))
return false;
g_ShaderHandle = glCreateProgram();
glAttachShader(g_ShaderHandle, vert_handle);
glAttachShader(g_ShaderHandle, frag_handle);
glLinkProgram(g_ShaderHandle);
CheckProgram(g_ShaderHandle, "shader program");
// Link
bd->ShaderHandle = glCreateProgram();
glAttachShader(bd->ShaderHandle, vert_handle);
glAttachShader(bd->ShaderHandle, frag_handle);
glLinkProgram(bd->ShaderHandle);
if (!CheckProgram(bd->ShaderHandle, "shader program"))
return false;
glDetachShader(g_ShaderHandle, vert_handle);
glDetachShader(g_ShaderHandle, frag_handle);
glDetachShader(bd->ShaderHandle, vert_handle);
glDetachShader(bd->ShaderHandle, frag_handle);
glDeleteShader(vert_handle);
glDeleteShader(frag_handle);
g_AttribLocationTex = glGetUniformLocation(g_ShaderHandle, "Texture");
g_AttribLocationProjMtx = glGetUniformLocation(g_ShaderHandle, "ProjMtx");
g_AttribLocationPosition = glGetAttribLocation(g_ShaderHandle, "Position");
g_AttribLocationUV = glGetAttribLocation(g_ShaderHandle, "UV");
g_AttribLocationColor = glGetAttribLocation(g_ShaderHandle, "Color");
bd->AttribLocationTex = glGetUniformLocation(bd->ShaderHandle, "Texture");
bd->AttribLocationProjMtx = glGetUniformLocation(bd->ShaderHandle, "ProjMtx");
bd->AttribLocationVtxPos = (GLuint)glGetAttribLocation(bd->ShaderHandle, "Position");
bd->AttribLocationVtxUV = (GLuint)glGetAttribLocation(bd->ShaderHandle, "UV");
bd->AttribLocationVtxColor = (GLuint)glGetAttribLocation(bd->ShaderHandle, "Color");
// Create buffers
glGenBuffers(1, &g_VboHandle);
glGenBuffers(1, &g_ElementsHandle);
ImGui_ImplOpenGL3_CreateFontsTexture();
glGenBuffers(1, &bd->VboHandle);
glGenBuffers(1, &bd->ElementsHandle);
return true;
}
static void ImGui_ImplOpenGL3_DestroyDeviceObjects()
void ImGui_ImplOpenGL3_DestroyDeviceObjects()
{
if (g_VboHandle) glDeleteBuffers(1, &g_VboHandle);
if (g_ElementsHandle) glDeleteBuffers(1, &g_ElementsHandle);
g_VboHandle = g_ElementsHandle = 0;
ImGui_ImplOpenGL3_Data* bd = ImGui_ImplOpenGL3_GetBackendData();
if (bd == nullptr) {
WARN_LOG(RENDERER, "ImGui_ImplOpenGL3_DestroyDeviceObjects: backend data is null");
return;
}
if (bd->VboHandle) { glDeleteBuffers(1, &bd->VboHandle); bd->VboHandle = 0; }
if (bd->ElementsHandle) { glDeleteBuffers(1, &bd->ElementsHandle); bd->ElementsHandle = 0; }
if (bd->ShaderHandle) { glDeleteProgram(bd->ShaderHandle); bd->ShaderHandle = 0; }
glcache.DeleteProgram(g_ShaderHandle);
g_ShaderHandle = 0;
ImGui_ImplOpenGL3_DestroyFontsTexture();
// Destroy all textures
for (ImTextureData* tex : ImGui::GetPlatformIO().Textures)
if (tex->RefCount == 1)
ImGui_ImplOpenGL3_DestroyTexture(tex);
}
#endif // #ifndef IMGUI_DISABLE
+56 -23
View File
@@ -1,35 +1,68 @@
// dear imgui: Renderer for OpenGL3 / OpenGL ES2 / OpenGL ES3 (modern OpenGL with shaders / programmatic pipeline)
// This needs to be used along with a Platform Binding (e.g. GLFW, SDL, Win32, custom..)
// (Note: We are using GL3W as a helper library to access OpenGL functions since there is no standard header to access modern OpenGL functions easily. Alternatives are GLEW, Glad, etc..)
// dear imgui: Renderer Backend for modern OpenGL with shaders / programmatic pipeline
// - Desktop GL: 2.x 3.x 4.x
// - Embedded GL: ES 2.0 (WebGL 1.0), ES 3.0 (WebGL 2.0)
// This needs to be used along with a Platform Backend (e.g. GLFW, SDL, Win32, custom..)
// Implemented features:
// [X] Renderer: User texture binding. Use 'GLuint' OpenGL texture identifier as void*/ImTextureID. Read the FAQ about ImTextureID in imgui.cpp.
// [X] Renderer: User texture binding. Use 'GLuint' OpenGL texture as texture identifier. Read the FAQ about ImTextureID/ImTextureRef!
// [x] Renderer: Large meshes support (64k+ vertices) even with 16-bit indices (ImGuiBackendFlags_RendererHasVtxOffset) [Desktop OpenGL only!]
// [X] Renderer: Texture updates support for dynamic font atlas (ImGuiBackendFlags_RendererHasTextures).
// You can copy and use unmodified imgui_impl_* files in your project. See main.cpp for an example of using this.
// If you are new to dear imgui, read examples/README.txt and read the documentation at the top of imgui.cpp.
// https://github.com/ocornut/imgui
// About WebGL/ES:
// - You need to '#define IMGUI_IMPL_OPENGL_ES2' or '#define IMGUI_IMPL_OPENGL_ES3' to use WebGL or OpenGL ES.
// - This is done automatically on iOS, Android and Emscripten targets.
// - For other targets, the define needs to be visible from the imgui_impl_opengl3.cpp compilation unit. If unsure, define globally or in imconfig.h.
// About OpenGL function loaders:
// About OpenGL function loaders: modern OpenGL doesn't have a standard header file and requires individual function pointers to be loaded manually.
// Helper libraries are often used for this purpose! Here we are supporting a few common ones: gl3w, glew, glad.
// You may use another loader/header of your choice (glext, glLoadGen, etc.), or chose to manually implement your own.
// You can use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this.
// Prefer including the entire imgui/ repository into your project (either as a copy or as a submodule), and only build the backends you need.
// Learn about Dear ImGui:
// - FAQ https://dearimgui.com/faq
// - Getting Started https://dearimgui.com/getting-started
// - Documentation https://dearimgui.com/docs (same as your local docs/ folder).
// - Introduction, links and more at the top of imgui.cpp
// About GLSL version:
// The 'glsl_version' initialization parameter should be NULL (default) or a "#version XXX" string.
// On computer platform the GLSL version default to "#version 130". On OpenGL ES 3 platform it defaults to "#version 300 es"
// Only override if your GL version doesn't handle this GLSL version. See GLSL version table at the top of imgui_impl_opengl3.cpp.
// The 'glsl_version' initialization parameter should be nullptr (default) or a "#version XXX" string.
// On computer platform the GLSL version default to "#version 130". On OpenGL ES 3 platform it defaults to "#version 300 es"
// Only override if your GL version doesn't handle this GLSL version. See GLSL version table at the top of imgui_impl_opengl3.cpp.
#pragma once
#include "imgui.h" // IMGUI_IMPL_API
#ifndef IMGUI_DISABLE
// Set default OpenGL loader to be gl3w
#if !defined(IMGUI_IMPL_OPENGL_LOADER_GL3W) \
&& !defined(IMGUI_IMPL_OPENGL_LOADER_GLEW) \
&& !defined(IMGUI_IMPL_OPENGL_LOADER_GLAD) \
&& !defined(IMGUI_IMPL_OPENGL_LOADER_CUSTOM)
#define IMGUI_IMPL_OPENGL_LOADER_GL3W
#endif
IMGUI_IMPL_API bool ImGui_ImplOpenGL3_Init();
// Follow "Getting Started" link and check examples/ folder to learn about using backends!
IMGUI_IMPL_API bool ImGui_ImplOpenGL3_Init(const char* glsl_version = nullptr);
IMGUI_IMPL_API void ImGui_ImplOpenGL3_Shutdown();
IMGUI_IMPL_API void ImGui_ImplOpenGL3_NewFrame();
IMGUI_IMPL_API void ImGui_ImplOpenGL3_RenderDrawData(ImDrawData* draw_data);
// (Optional) Called by Init/NewFrame/Shutdown
IMGUI_IMPL_API bool ImGui_ImplOpenGL3_CreateDeviceObjects();
IMGUI_IMPL_API void ImGui_ImplOpenGL3_DestroyDeviceObjects();
// (Advanced) Use e.g. if you need to precisely control the timing of texture updates (e.g. for staged rendering), by setting ImDrawData::Textures = nullptr to handle this manually.
IMGUI_IMPL_API void ImGui_ImplOpenGL3_UpdateTexture(ImTextureData* tex);
// Configuration flags to add in your imconfig file:
//#define IMGUI_IMPL_OPENGL_ES2 // Enable ES 2 (Auto-detected on Emscripten)
//#define IMGUI_IMPL_OPENGL_ES3 // Enable ES 3 (Auto-detected on iOS/Android)
// You can explicitly select GLES2 or GLES3 API by using one of the '#define IMGUI_IMPL_OPENGL_LOADER_XXX' in imconfig.h or compiler command-line.
#if !defined(IMGUI_IMPL_OPENGL_ES2) \
&& !defined(IMGUI_IMPL_OPENGL_ES3)
// Try to detect GLES on matching platforms
#if defined(__APPLE__)
#include <TargetConditionals.h>
#endif
#if (defined(__APPLE__) && (TARGET_OS_IOS || TARGET_OS_TV)) || (defined(__ANDROID__))
#define IMGUI_IMPL_OPENGL_ES3 // iOS, Android -> GL ES 3, "#version 300 es"
#elif defined(__EMSCRIPTEN__) || defined(__amigaos4__)
#define IMGUI_IMPL_OPENGL_ES2 // Emscripten -> GL ES 2, "#version 100"
#else
// Otherwise imgui_impl_opengl3_loader.h will be used.
#endif
#endif
#endif // #ifndef IMGUI_DISABLE
+677 -358
View File
File diff suppressed because it is too large. Load diff
+139 -60
View File
@@ -2,11 +2,10 @@
// This needs to be used along with a Platform Backend (e.g. GLFW, SDL, Win32, custom..)
// Implemented features:
// [!] Renderer: User texture binding. Use 'VkDescriptorSet' as ImTextureID. Read the FAQ about ImTextureID! See https://github.com/ocornut/imgui/pull/914 for discussions.
// [X] Renderer: Large meshes support (64k+ vertices) with 16-bit indices.
// Important: on 32-bit systems, user texture binding is only supported if your imconfig file has '#define ImTextureID ImU64'.
// See imgui_impl_vulkan.cpp file for details.
// [!] Renderer: User texture binding. Use 'VkDescriptorSet' as texture identifier. Call ImGui_ImplVulkan_AddTexture() to register one. Read the FAQ about ImTextureID/ImTextureRef + https://github.com/ocornut/imgui/pull/914 for discussions.
// [X] Renderer: Large meshes support (64k+ vertices) even with 16-bit indices (ImGuiBackendFlags_RendererHasVtxOffset).
// [X] Renderer: Texture updates support for dynamic font atlas (ImGuiBackendFlags_RendererHasTextures).
// [X] Renderer: Expose selected render state for draw callbacks to use. Access in '(ImGui_ImplXXXX_RenderState*)GetPlatformIO().Renderer_RenderState'.
// The aim of imgui_impl_vulkan.h/.cpp is to be usable in your engine without any modification.
// IF YOU FEEL YOU NEED TO MAKE ANY CHANGE TO THIS CODE, please share them and your feedback at https://github.com/ocornut/imgui/
@@ -42,9 +41,20 @@
// If you have no idea what this is, leave it alone!
//#define IMGUI_IMPL_VULKAN_NO_PROTOTYPES
// Convenience support for Volk
// [Configuration] Convenience support for Volk
// (you can also technically use IMGUI_IMPL_VULKAN_NO_PROTOTYPES + wrap Volk via ImGui_ImplVulkan_LoadFunctions().)
// (When using Volk from directory outside your include directories list you can specify full path to the volk.h header,
// for example when using Volk from VulkanSDK and using include_directories(${Vulkan_INCLUDE_DIRS})' from 'find_package(Vulkan REQUIRED)')
//#define IMGUI_IMPL_VULKAN_USE_VOLK
//#define IMGUI_IMPL_VULKAN_VOLK_FILENAME <Volk/volk.h>
//#define IMGUI_IMPL_VULKAN_VOLK_FILENAME <volk.h> // Default
// Reminder: make those changes in your imconfig.h file, not here!
// Clang/GCC warnings with -Weverything
#if defined(__clang__)
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wold-style-cast" // warning: use of old-style cast
#endif
#if defined(IMGUI_IMPL_VULKAN_NO_PROTOTYPES) && !defined(VK_NO_PROTOTYPES)
#define VK_NO_PROTOTYPES
@@ -55,7 +65,11 @@
// Vulkan includes
#ifdef IMGUI_IMPL_VULKAN_USE_VOLK
#include <Volk/volk.h>
#ifdef IMGUI_IMPL_VULKAN_VOLK_FILENAME
#include IMGUI_IMPL_VULKAN_VOLK_FILENAME
#else
#include <volk.h>
#endif
#else
#include <vulkan/vulkan.h>
#endif
@@ -63,84 +77,131 @@
#define IMGUI_IMPL_VULKAN_HAS_DYNAMIC_RENDERING
#endif
// Backend uses a small number of descriptors per font atlas + as many as additional calls done to ImGui_ImplVulkan_AddTexture().
#define IMGUI_IMPL_VULKAN_MINIMUM_IMAGE_SAMPLER_POOL_SIZE (8) // Minimum per atlas
// Specify settings to create pipeline and swapchain
struct ImGui_ImplVulkan_PipelineInfo
{
VkRenderPass RenderPass; // Ignored if using dynamic rendering
uint32_t Subpass; //
VkSampleCountFlagBits MSAASamples = {}; // 0 defaults to VK_SAMPLE_COUNT_1_BIT
#ifdef IMGUI_IMPL_VULKAN_HAS_DYNAMIC_RENDERING
VkPipelineRenderingCreateInfoKHR PipelineRenderingCreateInfo; // Optional, valid if .sType == VK_STRUCTURE_TYPE_PIPELINE_RENDERING_CREATE_INFO_KHR
#endif
};
// Initialization data, for ImGui_ImplVulkan_Init()
// - VkDescriptorPool should be created with VK_DESCRIPTOR_POOL_CREATE_FREE_DESCRIPTOR_SET_BIT,
// and must contain a pool size large enough to hold an ImGui VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER descriptor.
// - When using dynamic rendering, set UseDynamicRendering=true and fill PipelineRenderingCreateInfo structure.
// [Please zero-clear before use!]
// - About descriptor pool:
// - A VkDescriptorPool should be created with VK_DESCRIPTOR_POOL_CREATE_FREE_DESCRIPTOR_SET_BIT,
// and must contain a pool size large enough to hold a small number of VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER descriptors.
// - As an convenience, by setting DescriptorPoolSize > 0 the backend will create one for you.
// - About dynamic rendering:
// - When using dynamic rendering, set UseDynamicRendering=true + fill PipelineInfoMain.PipelineRenderingCreateInfo structure.
struct ImGui_ImplVulkan_InitInfo
{
uint32_t ApiVersion; // Fill with API version of Instance, e.g. VK_API_VERSION_1_3 or your value of VkApplicationInfo::apiVersion. May be lower than header version (VK_HEADER_VERSION_COMPLETE)
VkInstance Instance;
VkPhysicalDevice PhysicalDevice;
VkDevice Device;
uint32_t QueueFamily;
VkQueue Queue;
VkDescriptorPool DescriptorPool; // See requirements in note above
VkRenderPass RenderPass; // Ignored if using dynamic rendering
uint32_t MinImageCount; // >= 2
uint32_t ImageCount; // >= MinImageCount
VkSampleCountFlagBits MSAASamples; // 0 defaults to VK_SAMPLE_COUNT_1_BIT
VkDescriptorPool DescriptorPool; // See requirements in note above; ignored if using DescriptorPoolSize > 0
uint32_t DescriptorPoolSize; // Optional: set to create internal descriptor pool automatically instead of using DescriptorPool.
uint32_t MinImageCount; // >= 2
uint32_t ImageCount; // >= MinImageCount
VkPipelineCache PipelineCache; // Optional
// (Optional)
VkPipelineCache PipelineCache;
uint32_t Subpass;
// Pipeline
ImGui_ImplVulkan_PipelineInfo PipelineInfoMain; // Infos for Main Viewport (created by app/user)
//VkRenderPass RenderPass; // --> Since 2025/09/26: set 'PipelineInfoMain.RenderPass' instead
//uint32_t Subpass; // --> Since 2025/09/26: set 'PipelineInfoMain.Subpass' instead
//VkSampleCountFlagBits MSAASamples; // --> Since 2025/09/26: set 'PipelineInfoMain.MSAASamples' instead
//VkPipelineRenderingCreateInfoKHR PipelineRenderingCreateInfo; // Since 2025/09/26: set 'PipelineInfoMain.PipelineRenderingCreateInfo' instead
// (Optional) Dynamic Rendering
// Need to explicitly enable VK_KHR_dynamic_rendering extension to use this, even for Vulkan 1.3.
// Need to explicitly enable VK_KHR_dynamic_rendering extension to use this, even for Vulkan 1.3 + setup PipelineInfoMain.PipelineRenderingCreateInfo.
bool UseDynamicRendering;
#ifdef IMGUI_IMPL_VULKAN_HAS_DYNAMIC_RENDERING
VkPipelineRenderingCreateInfoKHR PipelineRenderingCreateInfo;
#endif
// (Optional) Allocation, Debugging
const VkAllocationCallbacks* Allocator;
void (*CheckVkResultFn)(VkResult err);
VkDeviceSize MinAllocationSize; // Minimum allocation size. Set to 1024*1024 to satisfy zealous best practices validation layer and waste a little memory.
VkDeviceSize MinAllocationSize; // Minimum allocation size. Set to 1024*1024 to satisfy zealous best practices validation layer and waste a little memory.
// (Optional) Customize default vertex/fragment shaders.
// - if .sType == VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO we use specified structs, otherwise we use defaults.
// - Shader inputs/outputs need to match ours. Code/data pointed to by the structure needs to survive for whole during of backend usage.
VkShaderModuleCreateInfo CustomShaderVertCreateInfo;
VkShaderModuleCreateInfo CustomShaderFragCreateInfo;
};
// Called by user code
IMGUI_IMPL_API bool ImGui_ImplVulkan_Init(ImGui_ImplVulkan_InitInfo* info);
IMGUI_IMPL_API void ImGui_ImplVulkan_Shutdown();
IMGUI_IMPL_API void ImGui_ImplVulkan_NewFrame();
IMGUI_IMPL_API void ImGui_ImplVulkan_RenderDrawData(ImDrawData* draw_data, VkCommandBuffer command_buffer, VkPipeline pipeline = VK_NULL_HANDLE);
IMGUI_IMPL_API bool ImGui_ImplVulkan_CreateFontsTexture();
IMGUI_IMPL_API void ImGui_ImplVulkan_DestroyFontsTexture();
IMGUI_IMPL_API void ImGui_ImplVulkan_SetMinImageCount(uint32_t min_image_count); // To override MinImageCount after initialization (e.g. if swap chain is recreated)
// Follow "Getting Started" link and check examples/ folder to learn about using backends!
IMGUI_IMPL_API bool ImGui_ImplVulkan_Init(ImGui_ImplVulkan_InitInfo* info);
IMGUI_IMPL_API void ImGui_ImplVulkan_Shutdown();
IMGUI_IMPL_API void ImGui_ImplVulkan_NewFrame();
IMGUI_IMPL_API void ImGui_ImplVulkan_RenderDrawData(ImDrawData* draw_data, VkCommandBuffer command_buffer, VkPipeline pipeline = VK_NULL_HANDLE);
IMGUI_IMPL_API void ImGui_ImplVulkan_SetMinImageCount(uint32_t min_image_count); // To override MinImageCount after initialization (e.g. if swap chain is recreated)
// (Advanced) Use e.g. if you need to recreate pipeline without reinitializing the backend (see #8110, #8111)
// The main window pipeline will be created by ImGui_ImplVulkan_Init() if possible (== RenderPass xor (UseDynamicRendering && PipelineRenderingCreateInfo->sType == VK_STRUCTURE_TYPE_PIPELINE_RENDERING_CREATE_INFO_KHR))
// Else, the pipeline can be created, or re-created, using ImGui_ImplVulkan_CreateMainPipeline() before rendering.
IMGUI_IMPL_API void ImGui_ImplVulkan_CreateMainPipeline(const ImGui_ImplVulkan_PipelineInfo* info);
// (Advanced) Use e.g. if you need to precisely control the timing of texture updates (e.g. for staged rendering), by setting ImDrawData::Textures = nullptr to handle this manually.
IMGUI_IMPL_API void ImGui_ImplVulkan_UpdateTexture(ImTextureData* tex);
// Register a texture (VkDescriptorSet == ImTextureID)
// FIXME: This is experimental in the sense that we are unsure how to best design/tackle this problem
// Please post to https://github.com/ocornut/imgui/pull/914 if you have suggestions.
IMGUI_IMPL_API VkDescriptorSet ImGui_ImplVulkan_AddTexture(VkSampler sampler, VkImageView image_view, VkImageLayout image_layout);
IMGUI_IMPL_API void ImGui_ImplVulkan_RemoveTexture(VkDescriptorSet descriptor_set);
IMGUI_IMPL_API VkDescriptorSet ImGui_ImplVulkan_AddTexture(VkSampler sampler, VkImageView image_view, VkImageLayout image_layout);
IMGUI_IMPL_API void ImGui_ImplVulkan_RemoveTexture(VkDescriptorSet descriptor_set);
// Optional: load Vulkan functions with a custom function loader
// This is only useful with IMGUI_IMPL_VULKAN_NO_PROTOTYPES / VK_NO_PROTOTYPES
IMGUI_IMPL_API bool ImGui_ImplVulkan_LoadFunctions(PFN_vkVoidFunction(*loader_func)(const char* function_name, void* user_data), void* user_data = nullptr);
IMGUI_IMPL_API bool ImGui_ImplVulkan_LoadFunctions(uint32_t api_version, PFN_vkVoidFunction(*loader_func)(const char* function_name, void* user_data), void* user_data = nullptr);
// [BETA] Selected render state data shared with callbacks.
// This is temporarily stored in GetPlatformIO().Renderer_RenderState during the ImGui_ImplVulkan_RenderDrawData() call.
// (Please open an issue if you feel you need access to more data)
struct ImGui_ImplVulkan_RenderState
{
VkCommandBuffer CommandBuffer;
VkPipeline Pipeline;
VkPipelineLayout PipelineLayout;
};
//-------------------------------------------------------------------------
// Internal / Miscellaneous Vulkan Helpers
// (Used by example's main.cpp. Used by multi-viewport features. PROBABLY NOT used by your own engine/app.)
//-------------------------------------------------------------------------
// Used by example's main.cpp. Used by multi-viewport features. PROBABLY NOT used by your own engine/app.
//
// You probably do NOT need to use or care about those functions.
// WE DO NOT PROVIDE STRONG GUARANTEES OF BACKWARD/FORWARD COMPATIBILITY.
// Those functions only exist because:
// 1) they facilitate the readability and maintenance of the multiple main.cpp examples files.
// 2) the multi-viewport / platform window implementation needs them internally.
// Generally we avoid exposing any kind of superfluous high-level helpers in the bindings,
// Generally we avoid exposing any kind of superfluous high-level helpers in the backends,
// but it is too much code to duplicate everywhere so we exceptionally expose them.
//
// Your engine/app will likely _already_ have code to setup all that stuff (swap chain, render pass, frame buffers, etc.).
// You may read this code to learn about Vulkan, but it is recommended you use you own custom tailored code to do equivalent work.
// (The ImGui_ImplVulkanH_XXX functions do not interact with any of the state used by the regular ImGui_ImplVulkan_XXX functions)
// Your engine/app will likely _already_ have code to setup all that stuff (swap chain,
// render pass, frame buffers, etc.). You may read this code if you are curious, but
// it is recommended you use your own custom tailored code to do equivalent work.
//
// The ImGui_ImplVulkanH_XXX functions should NOT interact with any of the state used
// by the regular ImGui_ImplVulkan_XXX functions.
//-------------------------------------------------------------------------
struct ImGui_ImplVulkanH_Frame;
struct ImGui_ImplVulkanH_Window;
// Helpers
IMGUI_IMPL_API void ImGui_ImplVulkanH_CreateOrResizeWindow(VkInstance instance, VkPhysicalDevice physical_device, VkDevice device, ImGui_ImplVulkanH_Window* wnd, uint32_t queue_family, const VkAllocationCallbacks* allocator, int w, int h, uint32_t min_image_count);
IMGUI_IMPL_API void ImGui_ImplVulkanH_DestroyWindow(VkInstance instance, VkDevice device, ImGui_ImplVulkanH_Window* wnd, const VkAllocationCallbacks* allocator);
IMGUI_IMPL_API void ImGui_ImplVulkanH_CreateOrResizeWindow(VkInstance instance, VkPhysicalDevice physical_device, VkDevice device, ImGui_ImplVulkanH_Window* wd, uint32_t queue_family, const VkAllocationCallbacks* allocator, int w, int h, uint32_t min_image_count, VkImageUsageFlags image_usage);
IMGUI_IMPL_API void ImGui_ImplVulkanH_DestroyWindow(VkInstance instance, VkDevice device, ImGui_ImplVulkanH_Window* wd, const VkAllocationCallbacks* allocator);
IMGUI_IMPL_API VkSurfaceFormatKHR ImGui_ImplVulkanH_SelectSurfaceFormat(VkPhysicalDevice physical_device, VkSurfaceKHR surface, const VkFormat* request_formats, int request_formats_count, VkColorSpaceKHR request_color_space);
IMGUI_IMPL_API VkPresentModeKHR ImGui_ImplVulkanH_SelectPresentMode(VkPhysicalDevice physical_device, VkSurfaceKHR surface, const VkPresentModeKHR* request_modes, int request_modes_count);
IMGUI_IMPL_API VkPhysicalDevice ImGui_ImplVulkanH_SelectPhysicalDevice(VkInstance instance);
IMGUI_IMPL_API uint32_t ImGui_ImplVulkanH_SelectQueueFamilyIndex(VkPhysicalDevice physical_device);
IMGUI_IMPL_API int ImGui_ImplVulkanH_GetMinImageCountFromPresentMode(VkPresentModeKHR present_mode);
// Helper structure to hold the data needed by one rendering frame
@@ -166,30 +227,48 @@ struct ImGui_ImplVulkanH_FrameSemaphores
// (Used by example's main.cpp. Used by multi-viewport features. Probably NOT used by your own engine/app.)
struct ImGui_ImplVulkanH_Window
{
int Width;
int Height;
VkSwapchainKHR Swapchain;
VkSurfaceKHR Surface;
VkSurfaceFormatKHR SurfaceFormat;
VkPresentModeKHR PresentMode;
VkRenderPass RenderPass;
VkPipeline Pipeline; // The window pipeline may uses a different VkRenderPass than the one passed in ImGui_ImplVulkan_InitInfo
bool UseDynamicRendering;
bool ClearEnable;
VkClearValue ClearValue;
uint32_t FrameIndex; // Current frame being rendered to (0 <= FrameIndex < FrameInFlightCount)
uint32_t ImageCount; // Number of simultaneous in-flight frames (returned by vkGetSwapchainImagesKHR, usually derived from min_image_count)
uint32_t SemaphoreCount; // Number of simultaneous in-flight frames + 1, to be able to use it in vkAcquireNextImageKHR
uint32_t SemaphoreIndex; // Current set of swapchain wait semaphores we're using (needs to be distinct from per frame data)
ImGui_ImplVulkanH_Frame* Frames;
ImGui_ImplVulkanH_FrameSemaphores* FrameSemaphores;
// Input
bool UseDynamicRendering;
VkSurfaceKHR Surface; // Surface created and destroyed by caller.
VkSurfaceFormatKHR SurfaceFormat;
VkPresentModeKHR PresentMode;
VkAttachmentDescription AttachmentDesc; // RenderPass creation: main attachment description.
VkClearValue ClearValue; // RenderPass creation: clear value when using VK_ATTACHMENT_LOAD_OP_CLEAR.
// Internal
int Width; // Generally same as passed to ImGui_ImplVulkanH_CreateOrResizeWindow()
int Height;
VkSwapchainKHR Swapchain;
VkRenderPass RenderPass;
VkPipeline Pipeline; // The window pipeline may uses a different VkRenderPass than the one passed in ImGui_ImplVulkan_InitInfo
uint32_t FrameIndex; // Current frame being rendered to (0 <= FrameIndex < FrameInFlightCount)
uint32_t ImageCount; // Number of simultaneous in-flight frames (returned by vkGetSwapchainImagesKHR, usually derived from min_image_count)
uint32_t SemaphoreCount; // Number of simultaneous in-flight frames + 1, to be able to use it in vkAcquireNextImageKHR
uint32_t SemaphoreIndex; // Current set of swapchain wait semaphores we're using (needs to be distinct from per frame data)
ImVector<ImGui_ImplVulkanH_Frame> Frames;
ImVector<ImGui_ImplVulkanH_FrameSemaphores> FrameSemaphores;
ImGui_ImplVulkanH_Window()
{
memset((void*)this, 0, sizeof(*this));
PresentMode = (VkPresentModeKHR)~0; // Ensure we get an error if user doesn't set this.
ClearEnable = true;
// Parameters to create SwapChain
PresentMode = (VkPresentModeKHR)~0; // Ensure we get an error if user doesn't set this.
// Parameters to create RenderPass
AttachmentDesc.format = VK_FORMAT_UNDEFINED; // Will automatically use wd->SurfaceFormat.format.
AttachmentDesc.samples = VK_SAMPLE_COUNT_1_BIT;
AttachmentDesc.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
AttachmentDesc.storeOp = VK_ATTACHMENT_STORE_OP_STORE;
AttachmentDesc.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
AttachmentDesc.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
AttachmentDesc.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
AttachmentDesc.finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;
}
};
#if defined(__clang__)
#pragma clang diagnostic pop
#endif
#endif // #ifndef IMGUI_DISABLE
+3 -3
View File
@@ -49,7 +49,7 @@
//#define IMGUI_DISABLE_FILE_FUNCTIONS // Don't implement ImFileOpen/ImFileClose/ImFileRead/ImFileWrite and ImFileHandle at all (replace them with dummies)
//#define IMGUI_DISABLE_DEFAULT_FILE_FUNCTIONS // Don't implement ImFileOpen/ImFileClose/ImFileRead/ImFileWrite and ImFileHandle so you can implement them yourself if you don't want to link with fopen/fclose/fread/fwrite. This will also disable the LogToTTY() function.
//#define IMGUI_DISABLE_DEFAULT_ALLOCATORS // Don't implement default allocators calling malloc()/free() to avoid linking with them. You will need to call ImGui::SetAllocatorFunctions().
//#define IMGUI_DISABLE_DEFAULT_FONT // Disable default embedded font (ProggyClean.ttf), remove ~9.5 KB from output binary. AddFontDefault() will assert.
//#define IMGUI_DISABLE_DEFAULT_FONT // Disable default embedded fonts (ProggyClean/ProggyForever), remove ~9 KB + ~14 KB from output binary. AddFontDefaultXXX() functions will assert.
//#define IMGUI_DISABLE_SSE // Disable use of SSE intrinsics even if available
//---- Enable Test Engine / Automation features.
@@ -67,7 +67,7 @@
//#define IMGUI_USE_LEGACY_CRC32_ADLER
//---- Use 32-bit for ImWchar (default is 16-bit) to support Unicode planes 1-16. (e.g. point beyond 0xFFFF like emoticons, dingbats, symbols, shapes, ancient languages, etc...)
//#define IMGUI_USE_WCHAR32
#define IMGUI_USE_WCHAR32
//---- Avoid multiple STB libraries implementations, or redefine path/filenames to prioritize another version
// By default the embedded implementations are declared static and not available outside of Dear ImGui sources files.
@@ -86,7 +86,7 @@
// Requires FreeType headers to be available in the include path. Requires program to be compiled with 'misc/freetype/imgui_freetype.cpp' (in this repository) + the FreeType library (not provided).
// Note that imgui_freetype.cpp may be used _without_ this define, if you manually call ImFontAtlas::SetFontLoader(). The define is simply a convenience.
// On Windows you may use vcpkg with 'vcpkg install freetype --triplet=x64-windows' + 'vcpkg integrate install'.
//#define IMGUI_ENABLE_FREETYPE
#define IMGUI_ENABLE_FREETYPE
//---- Use FreeType + plutosvg or lunasvg to render OpenType SVG fonts (SVGinOT)
// Only works in combination with IMGUI_ENABLE_FREETYPE.
Regular → Executable
+316 -244
View File
File diff suppressed because it is too large. Load diff
Regular → Executable
+104 -79
View File
@@ -1,4 +1,4 @@
// dear imgui, v1.92.5
// dear imgui, v1.92.6
// (headers)
// Help:
@@ -29,8 +29,8 @@
// Library Version
// (Integer encoded as XYYZZ for use in #if preprocessor conditionals, e.g. '#if IMGUI_VERSION_NUM >= 12345')
#define IMGUI_VERSION "1.92.5"
#define IMGUI_VERSION_NUM 19250
#define IMGUI_VERSION "1.92.6"
#define IMGUI_VERSION_NUM 19261
#define IMGUI_HAS_TABLE // Added BeginTable() - from IMGUI_VERSION_NUM >= 18000
#define IMGUI_HAS_TEXTURES // Added ImGuiBackendFlags_RendererHasTextures - from IMGUI_VERSION_NUM >= 19198
@@ -95,7 +95,7 @@ Index of this file:
#include <assert.h>
#define IM_ASSERT(_EXPR) assert(_EXPR) // You can override the default assert handler by editing imconfig.h
#endif
#define IM_ARRAYSIZE(_ARR) ((int)(sizeof(_ARR) / sizeof(*(_ARR)))) // Size of a static C-style array. Don't use on pointers!
#define IM_COUNTOF(_ARR) ((int)(sizeof(_ARR) / sizeof(*(_ARR)))) // Size of a static C-style array. Don't use on pointers!
#define IM_UNUSED(_VAR) ((void)(_VAR)) // Used to silence "unused variable warnings". Often useful as asserts may be stripped out from final builds.
#define IM_STRINGIFY_HELPER(_EXPR) #_EXPR
#define IM_STRINGIFY(_EXPR) IM_STRINGIFY_HELPER(_EXPR) // Preprocessor idiom to stringify e.g. an integer or a macro.
@@ -349,7 +349,7 @@ typedef ImU64 ImTextureID; // Default: store up to 64-bits (any pointer or
// The identifier is valid even before the texture has been uploaded to the GPU/graphics system.
// This is what gets passed to functions such as `ImGui::Image()`, `ImDrawList::AddImage()`.
// This is what gets stored in draw commands (`ImDrawCmd`) to identify a texture during rendering.
// - When a texture is created by user code (e.g. custom images), we directly stores the low-level ImTextureID.
// - When a texture is created by user code (e.g. custom images), we directly store the low-level ImTextureID.
// Because of this, when displaying your own texture you are likely to ever only manage ImTextureID values on your side.
// - When a texture is created by the backend, we stores a ImTextureData* which becomes an indirection
// to extract the ImTextureID value during rendering, after texture upload has happened.
@@ -853,20 +853,23 @@ namespace ImGui
// - CloseCurrentPopup() is called by default by Selectable()/MenuItem() when activated (FIXME: need some options).
// - Use ImGuiPopupFlags_NoOpenOverExistingPopup to avoid opening a popup if there's already one at the same level. This is equivalent to e.g. testing for !IsAnyPopupOpen() prior to OpenPopup().
// - Use IsWindowAppearing() after BeginPopup() to tell if a window just opened.
// - IMPORTANT: Notice that for OpenPopupOnItemClick() we exceptionally default flags to 1 (== ImGuiPopupFlags_MouseButtonRight) for backward compatibility with older API taking 'int mouse_button = 1' parameter
IMGUI_API void OpenPopup(const char* str_id, ImGuiPopupFlags popup_flags = 0); // call to mark popup as open (don't call every frame!).
IMGUI_API void OpenPopup(ImGuiID id, ImGuiPopupFlags popup_flags = 0); // id overload to facilitate calling from nested stacks
IMGUI_API void OpenPopupOnItemClick(const char* str_id = NULL, ImGuiPopupFlags popup_flags = 1); // helper to open popup when clicked on last item. Default to ImGuiPopupFlags_MouseButtonRight == 1. (note: actually triggers on the mouse _released_ event to be consistent with popup behaviors)
IMGUI_API void OpenPopupOnItemClick(const char* str_id = NULL, ImGuiPopupFlags popup_flags = 0); // helper to open popup when clicked on last item. Default to ImGuiPopupFlags_MouseButtonRight == 1. (note: actually triggers on the mouse _released_ event to be consistent with popup behaviors)
IMGUI_API void CloseCurrentPopup(); // manually close the popup we have begin-ed into.
// Popups: open+begin combined functions helpers
// Popups: Open+Begin popup combined functions helpers to create context menus.
// - Helpers to do OpenPopup+BeginPopup where the Open action is triggered by e.g. hovering an item and right-clicking.
// - They are convenient to easily create context menus, hence the name.
// - IMPORTANT: Notice that BeginPopupContextXXX takes ImGuiPopupFlags just like OpenPopup() and unlike BeginPopup(). For full consistency, we may add ImGuiWindowFlags to the BeginPopupContextXXX functions in the future.
// - IMPORTANT: Notice that we exceptionally default their flags to 1 (== ImGuiPopupFlags_MouseButtonRight) for backward compatibility with older API taking 'int mouse_button = 1' parameter, so if you add other flags remember to re-add the ImGuiPopupFlags_MouseButtonRight.
IMGUI_API bool BeginPopupContextItem(const char* str_id = NULL, ImGuiPopupFlags popup_flags = 1); // open+begin popup when clicked on last item. Use str_id==NULL to associate the popup to previous item. If you want to use that on a non-interactive item such as Text() you need to pass in an explicit ID here. read comments in .cpp!
IMGUI_API bool BeginPopupContextWindow(const char* str_id = NULL, ImGuiPopupFlags popup_flags = 1);// open+begin popup when clicked on current window.
IMGUI_API bool BeginPopupContextVoid(const char* str_id = NULL, ImGuiPopupFlags popup_flags = 1); // open+begin popup when clicked in void (where there are no windows).
// - IMPORTANT: If you ever used the left mouse button with BeginPopupContextXXX() helpers before 1.92.6:
// - Before this version, OpenPopupOnItemClick(), BeginPopupContextItem(), BeginPopupContextWindow(), BeginPopupContextVoid() had 'a ImGuiPopupFlags popup_flags = 1' default value in their function signature.
// - Before: Explicitly passing a literal 0 meant ImGuiPopupFlags_MouseButtonLeft. The default = 1 meant ImGuiPopupFlags_MouseButtonRight.
// - After: The default = 0 means ImGuiPopupFlags_MouseButtonRight. Explicitly passing a literal 1 also means ImGuiPopupFlags_MouseButtonRight (if legacy behavior are enabled) or will assert (if legacy behavior are disabled).
// - TL;DR: if you don't want to use right mouse button for popups, always specify it explicitly using a named ImGuiPopupFlags_MouseButtonXXXX value.
// - Read "API BREAKING CHANGES" 2026/01/07 (1.92.6) entry in imgui.cpp or GitHub topic #9157 for all details.
IMGUI_API bool BeginPopupContextItem(const char* str_id = NULL, ImGuiPopupFlags popup_flags = 0); // open+begin popup when clicked on last item. Use str_id==NULL to associate the popup to previous item. If you want to use that on a non-interactive item such as Text() you need to pass in an explicit ID here. read comments in .cpp!
IMGUI_API bool BeginPopupContextWindow(const char* str_id = NULL, ImGuiPopupFlags popup_flags = 0);// open+begin popup when clicked on current window.
IMGUI_API bool BeginPopupContextVoid(const char* str_id = NULL, ImGuiPopupFlags popup_flags = 0); // open+begin popup when clicked in void (where there are no windows).
// Popups: query functions
// - IsPopupOpen(): return true if the popup is open at the current BeginPopup() level of the popup stack.
@@ -897,7 +900,7 @@ namespace ImGui
// - 5. Call EndTable()
IMGUI_API bool BeginTable(const char* str_id, int columns, ImGuiTableFlags flags = 0, const ImVec2& outer_size = ImVec2(0.0f, 0.0f), float inner_width = 0.0f);
IMGUI_API void EndTable(); // only call EndTable() if BeginTable() returns true!
IMGUI_API void TableNextRow(ImGuiTableRowFlags row_flags = 0, float min_row_height = 0.0f); // append into the first cell of a new row.
IMGUI_API void TableNextRow(ImGuiTableRowFlags row_flags = 0, float min_row_height = 0.0f); // append into the first cell of a new row. 'min_row_height' include the minimum top and bottom padding aka CellPadding.y * 2.0f.
IMGUI_API bool TableNextColumn(); // append into the next column (or first column of next row if currently in last column). Return true when column is visible.
IMGUI_API bool TableSetColumnIndex(int column_n); // append into the specified column. Return true when column is visible.
@@ -977,7 +980,7 @@ namespace ImGui
// Disabling [BETA API]
// - Disable all user interactions and dim items visuals (applying style.DisabledAlpha over current colors)
// - Those can be nested but it cannot be used to enable an already disabled section (a single BeginDisabled(true) in the stack is enough to keep everything disabled)
// - Tooltips windows are automatically opted out of disabling. Note that IsItemHovered() by default returns false on disabled items, unless using ImGuiHoveredFlags_AllowWhenDisabled.
// - Tooltips windows are automatically opted out of disabling. Note that IsItemHovered() by default returns false on disabled items, unless using ImGuiHoveredFlags_AllowWhenDisabled.
// - BeginDisabled(false)/EndDisabled() essentially does nothing but is provided to facilitate use of boolean expressions (as a micro-optimization: if you have tens of thousands of BeginDisabled(false)/EndDisabled() pairs, you might want to reformulate your code to avoid making those calls)
IMGUI_API void BeginDisabled(bool disabled = true);
IMGUI_API void EndDisabled();
@@ -1017,6 +1020,7 @@ namespace ImGui
IMGUI_API ImVec2 GetItemRectMin(); // get upper-left bounding rectangle of the last item (screen space)
IMGUI_API ImVec2 GetItemRectMax(); // get lower-right bounding rectangle of the last item (screen space)
IMGUI_API ImVec2 GetItemRectSize(); // get size of last item
IMGUI_API ImGuiItemFlags GetItemFlags(); // get generic flags of last item
// Viewports
// - Currently represents the Platform Window created by the application which is hosting our Dear ImGui windows.
@@ -1047,19 +1051,22 @@ namespace ImGui
IMGUI_API void ColorConvertRGBtoHSV(float r, float g, float b, float& out_h, float& out_s, float& out_v);
IMGUI_API void ColorConvertHSVtoRGB(float h, float s, float v, float& out_r, float& out_g, float& out_b);
// Inputs Utilities: Keyboard/Mouse/Gamepad
// Inputs Utilities: Raw Keyboard/Mouse/Gamepad Access
// - Consider using the Shortcut() function instead of IsKeyPressed()/IsKeyChordPressed()! Shortcut() is easier to use and better featured (can do focus routing check).
// - the ImGuiKey enum contains all possible keyboard, mouse and gamepad inputs (e.g. ImGuiKey_A, ImGuiKey_MouseLeft, ImGuiKey_GamepadDpadUp...).
// - (legacy: before v1.87, we used ImGuiKey to carry native/user indices as defined by each backends. This was obsoleted in 1.87 (2022-02) and completely removed in 1.91.5 (2024-11). See https://github.com/ocornut/imgui/issues/4921)
// - (legacy: any use of ImGuiKey will assert when key < 512 to detect passing legacy native/user indices)
// - (legacy: before v1.87 (2022-02), we used ImGuiKey < 512 values to carry native/user indices as defined by each backends. This was obsoleted in 1.87 (2022-02) and completely removed in 1.91.5 (2024-11). See https://github.com/ocornut/imgui/issues/4921)
IMGUI_API bool IsKeyDown(ImGuiKey key); // is key being held.
IMGUI_API bool IsKeyPressed(ImGuiKey key, bool repeat = true); // was key pressed (went from !Down to Down)? if repeat=true, uses io.KeyRepeatDelay / KeyRepeatRate
IMGUI_API bool IsKeyPressed(ImGuiKey key, bool repeat = true); // was key pressed (went from !Down to Down)? Repeat rate uses io.KeyRepeatDelay / KeyRepeatRate.
IMGUI_API bool IsKeyReleased(ImGuiKey key); // was key released (went from Down to !Down)?
IMGUI_API bool IsKeyChordPressed(ImGuiKeyChord key_chord); // was key chord (mods + key) pressed, e.g. you can pass 'ImGuiMod_Ctrl | ImGuiKey_S' as a key-chord. This doesn't do any routing or focus check, please consider using Shortcut() function instead.
IMGUI_API int GetKeyPressedAmount(ImGuiKey key, float repeat_delay, float rate); // uses provided repeat rate/delay. return a count, most often 0 or 1 but might be >1 if RepeatRate is small enough that DeltaTime > RepeatRate
IMGUI_API const char* GetKeyName(ImGuiKey key); // [DEBUG] returns English name of the key. Those names are provided for debugging purpose and are not meant to be saved persistently nor compared.
IMGUI_API void SetNextFrameWantCaptureKeyboard(bool want_capture_keyboard); // Override io.WantCaptureKeyboard flag next frame (said flag is left for your application to handle, typically when true it instructs your app to ignore inputs). e.g. force capture keyboard when your widget is being hovered. This is equivalent to setting "io.WantCaptureKeyboard = want_capture_keyboard"; after the next NewFrame() call.
// Inputs Utilities: Shortcut Testing & Routing [BETA]
// Inputs Utilities: Shortcut Testing & Routing
// - Typical use is e.g.: 'if (ImGui::Shortcut(ImGuiMod_Ctrl | ImGuiKey_S)) { ... }'.
// - Flags: Default route use ImGuiInputFlags_RouteFocused, but see ImGuiInputFlags_RouteGlobal and other options in ImGuiInputFlags_!
// - Flags: Use ImGuiInputFlags_Repeat to support repeat.
// - ImGuiKeyChord = a ImGuiKey + optional ImGuiMod_Alt/ImGuiMod_Ctrl/ImGuiMod_Shift/ImGuiMod_Super.
// ImGuiKey_C // Accepted by functions taking ImGuiKey or ImGuiKeyChord arguments
// ImGuiMod_Ctrl | ImGuiKey_C // Accepted by functions taking ImGuiKeyChord arguments
@@ -1071,8 +1078,10 @@ namespace ImGui
// The whole system is order independent, so if Child1 makes its calls before Parent, results will be identical.
// This is an important property as it facilitate working with foreign code or larger codebase.
// - To understand the difference:
// - IsKeyChordPressed() compares mods and call IsKeyPressed() -> function has no side-effect.
// - Shortcut() submits a route, routes are resolved, if it currently can be routed it calls IsKeyChordPressed() -> function has (desirable) side-effects as it can prevents another call from getting the route.
// - IsKeyChordPressed() compares mods and call IsKeyPressed()
// -> the function has no side-effect.
// - Shortcut() submits a route, routes are resolved, if it currently can be routed it calls IsKeyChordPressed()
// -> the function has (desirable) side-effects as it can prevents another call from getting the route.
// - Visualize registered routes in 'Metrics/Debugger->Inputs'.
IMGUI_API bool Shortcut(ImGuiKeyChord key_chord, ImGuiInputFlags flags = 0);
IMGUI_API void SetNextItemShortcut(ImGuiKeyChord key_chord, ImGuiInputFlags flags = 0);
@@ -1122,7 +1131,9 @@ namespace ImGui
IMGUI_API const char* SaveIniSettingsToMemory(size_t* out_ini_size = NULL); // return a zero-terminated string with the .ini data which you can save by your own mean. call when io.WantSaveIniSettings is set, then save data by your own mean and clear io.WantSaveIniSettings.
// Debug Utilities
// - Your main debugging friend is the ShowMetricsWindow() function, which is also accessible from Demo->Tools->Metrics Debugger
// - Your main debugging friend is the ShowMetricsWindow() function.
// - Interactive tools are all accessible from the 'Dear ImGui Demo->Tools' menu.
// - Read https://github.com/ocornut/imgui/wiki/Debug-Tools for a description of all available debug tools.
IMGUI_API void DebugTextEncoding(const char* text);
IMGUI_API void DebugFlashStyleColor(ImGuiCol idx);
IMGUI_API void DebugStartItemPicker();
@@ -1219,7 +1230,7 @@ enum ImGuiChildFlags_
};
// Flags for ImGui::PushItemFlag()
// (Those are shared by all items)
// (Those are shared by all submitted items)
enum ImGuiItemFlags_
{
ImGuiItemFlags_None = 0, // (Default)
@@ -1229,6 +1240,7 @@ enum ImGuiItemFlags_
ImGuiItemFlags_ButtonRepeat = 1 << 3, // false // Any button-like behavior will have repeat mode enabled (based on io.KeyRepeatDelay and io.KeyRepeatRate values). Note that you can also call IsItemActive() after any button to tell if it is being held.
ImGuiItemFlags_AutoClosePopups = 1 << 4, // true // MenuItem()/Selectable() automatically close their parent popup window.
ImGuiItemFlags_AllowDuplicateId = 1 << 5, // false // Allow submitting an item with the same identifier as an item already submitted this frame without triggering a warning tooltip if io.ConfigDebugHighlightIdConflicts is set.
ImGuiItemFlags_Disabled = 1 << 6, // false // [Internal] Disable interactions. DOES NOT affect visuals. This is used by BeginDisabled()/EndDisabled() and only provided here so you can read back via GetItemFlags().
};
// Flags for ImGui::InputText()
@@ -1321,21 +1333,14 @@ enum ImGuiTreeNodeFlags_
};
// Flags for OpenPopup*(), BeginPopupContext*(), IsPopupOpen() functions.
// - To be backward compatible with older API which took an 'int mouse_button = 1' argument instead of 'ImGuiPopupFlags flags',
// we need to treat small flags values as a mouse button index, so we encode the mouse button in the first few bits of the flags.
// It is therefore guaranteed to be legal to pass a mouse button index in ImGuiPopupFlags.
// - For the same reason, we exceptionally default the ImGuiPopupFlags argument of BeginPopupContextXXX functions to 1 instead of 0.
// IMPORTANT: because the default parameter is 1 (==ImGuiPopupFlags_MouseButtonRight), if you rely on the default parameter
// and want to use another flag, you need to pass in the ImGuiPopupFlags_MouseButtonRight flag explicitly.
// - IMPORTANT: If you ever used the left mouse button with BeginPopupContextXXX() helpers before 1.92.6: Read "API BREAKING CHANGES" 2026/01/07 (1.92.6) entry in imgui.cpp or GitHub topic #9157.
// - Multiple buttons currently cannot be combined/or-ed in those functions (we could allow it later).
enum ImGuiPopupFlags_
{
ImGuiPopupFlags_None = 0,
ImGuiPopupFlags_MouseButtonLeft = 0, // For BeginPopupContext*(): open on Left Mouse release. Guaranteed to always be == 0 (same as ImGuiMouseButton_Left)
ImGuiPopupFlags_MouseButtonRight = 1, // For BeginPopupContext*(): open on Right Mouse release. Guaranteed to always be == 1 (same as ImGuiMouseButton_Right)
ImGuiPopupFlags_MouseButtonMiddle = 2, // For BeginPopupContext*(): open on Middle Mouse release. Guaranteed to always be == 2 (same as ImGuiMouseButton_Middle)
ImGuiPopupFlags_MouseButtonMask_ = 0x1F,
ImGuiPopupFlags_MouseButtonDefault_ = 1,
ImGuiPopupFlags_MouseButtonLeft = 1 << 2, // For BeginPopupContext*(): open on Left Mouse release. Only one button allowed!
ImGuiPopupFlags_MouseButtonRight = 2 << 2, // For BeginPopupContext*(): open on Right Mouse release. Only one button allowed! (default)
ImGuiPopupFlags_MouseButtonMiddle = 3 << 2, // For BeginPopupContext*(): open on Middle Mouse release. Only one button allowed!
ImGuiPopupFlags_NoReopen = 1 << 5, // For OpenPopup*(), BeginPopupContext*(): don't reopen same popup if already open (won't reposition, won't reinitialize navigation)
//ImGuiPopupFlags_NoReopenAlwaysNavInit = 1 << 6, // For OpenPopup*(), BeginPopupContext*(): focus and initialize navigation even when not reopening.
ImGuiPopupFlags_NoOpenOverExistingPopup = 1 << 7, // For OpenPopup*(), BeginPopupContext*(): don't open if there's already a popup at the same level of the popup stack
@@ -1343,6 +1348,9 @@ enum ImGuiPopupFlags_
ImGuiPopupFlags_AnyPopupId = 1 << 10, // For IsPopupOpen(): ignore the ImGuiID parameter and test for any popup.
ImGuiPopupFlags_AnyPopupLevel = 1 << 11, // For IsPopupOpen(): search/test at any level of the popup stack (default test in the current level)
ImGuiPopupFlags_AnyPopup = ImGuiPopupFlags_AnyPopupId | ImGuiPopupFlags_AnyPopupLevel,
ImGuiPopupFlags_MouseButtonShift_ = 2, // [Internal]
ImGuiPopupFlags_MouseButtonMask_ = 0x0C, // [Internal]
ImGuiPopupFlags_InvalidMask_ = 0x03, // [Internal] Reserve legacy bits 0-1 to detect incorrectly passing 1 or 2 to the function.
};
// Flags for ImGui::Selectable()
@@ -1539,7 +1547,7 @@ enum ImGuiSortDirection : ImU8
// All our named keys are >= 512. Keys value 0 to 511 are left unused and were legacy native/opaque key values (< 1.87).
// Support for legacy keys was completely removed in 1.91.5.
// Read details about the 1.87+ transition : https://github.com/ocornut/imgui/issues/4921
// Note that "Keys" related to physical keys and are not the same concept as input "Characters", the later are submitted via io.AddInputCharacter().
// Note that "Keys" related to physical keys and are not the same concept as input "Characters", the latter are submitted via io.AddInputCharacter().
// The keyboard key enum values are named after the keys on a standard US keyboard, and on other keyboard types the keys reported may not match the keycaps.
enum ImGuiKey : int
{
@@ -1696,7 +1704,7 @@ enum ImGuiInputFlags_
enum ImGuiConfigFlags_
{
ImGuiConfigFlags_None = 0,
ImGuiConfigFlags_NavEnableKeyboard = 1 << 0, // Master keyboard navigation enable flag. Enable full Tabbing + directional arrows + space/enter to activate.
ImGuiConfigFlags_NavEnableKeyboard = 1 << 0, // Master keyboard navigation enable flag. Enable full Tabbing + directional arrows + Space/Enter to activate. Note: some features such as basic Tabbing and CtrL+Tab are enabled by regardless of this flag (and may be disabled via other means, see #4828, #9218).
ImGuiConfigFlags_NavEnableGamepad = 1 << 1, // Master gamepad navigation enable flag. Backend also needs to set ImGuiBackendFlags_HasGamepad.
ImGuiConfigFlags_NoMouse = 1 << 4, // Instruct dear imgui to disable mouse inputs and interactions.
ImGuiConfigFlags_NoMouseCursorChange = 1 << 5, // Instruct backend to not alter mouse cursor shape and visibility. Use if the backend cursor changes are interfering with yours and you don't want to use SetMouseCursor() to change mouse cursor. You may want to honor requests from imgui by reading GetMouseCursor() yourself instead.
@@ -1830,6 +1838,7 @@ enum ImGuiStyleVar_
ImGuiStyleVar_ScrollbarPadding, // float ScrollbarPadding
ImGuiStyleVar_GrabMinSize, // float GrabMinSize
ImGuiStyleVar_GrabRounding, // float GrabRounding
ImGuiStyleVar_ImageRounding, // float ImageRounding
ImGuiStyleVar_ImageBorderSize, // float ImageBorderSize
ImGuiStyleVar_TabRounding, // float TabRounding
ImGuiStyleVar_TabBorderSize, // float TabBorderSize
@@ -1872,19 +1881,20 @@ enum ImGuiColorEditFlags_
ImGuiColorEditFlags_NoTooltip = 1 << 6, // // ColorEdit, ColorPicker, ColorButton: disable tooltip when hovering the preview.
ImGuiColorEditFlags_NoLabel = 1 << 7, // // ColorEdit, ColorPicker: disable display of inline text label (the label is still forwarded to the tooltip and picker).
ImGuiColorEditFlags_NoSidePreview = 1 << 8, // // ColorPicker: disable bigger color preview on right side of the picker, use small color square preview instead.
ImGuiColorEditFlags_NoDragDrop = 1 << 9, // // ColorEdit: disable drag and drop target. ColorButton: disable drag and drop source.
ImGuiColorEditFlags_NoDragDrop = 1 << 9, // // ColorEdit: disable drag and drop target/source. ColorButton: disable drag and drop source.
ImGuiColorEditFlags_NoBorder = 1 << 10, // // ColorButton: disable border (which is enforced by default)
ImGuiColorEditFlags_NoColorMarkers = 1 << 11, // // ColorEdit: disable rendering R/G/B/A color marker. May also be disabled globally by setting style.ColorMarkerSize = 0.
// Alpha preview
// - Prior to 1.91.8 (2025/01/21): alpha was made opaque in the preview by default using old name ImGuiColorEditFlags_AlphaPreview.
// - We now display the preview as transparent by default. You can use ImGuiColorEditFlags_AlphaOpaque to use old behavior.
// - The new flags may be combined better and allow finer controls.
ImGuiColorEditFlags_AlphaOpaque = 1 << 11, // // ColorEdit, ColorPicker, ColorButton: disable alpha in the preview,. Contrary to _NoAlpha it may still be edited when calling ColorEdit4()/ColorPicker4(). For ColorButton() this does the same as _NoAlpha.
ImGuiColorEditFlags_AlphaNoBg = 1 << 12, // // ColorEdit, ColorPicker, ColorButton: disable rendering a checkerboard background behind transparent color.
ImGuiColorEditFlags_AlphaPreviewHalf= 1 << 13, // // ColorEdit, ColorPicker, ColorButton: display half opaque / half transparent preview.
ImGuiColorEditFlags_AlphaOpaque = 1 << 12, // // ColorEdit, ColorPicker, ColorButton: disable alpha in the preview,. Contrary to _NoAlpha it may still be edited when calling ColorEdit4()/ColorPicker4(). For ColorButton() this does the same as _NoAlpha.
ImGuiColorEditFlags_AlphaNoBg = 1 << 13, // // ColorEdit, ColorPicker, ColorButton: disable rendering a checkerboard background behind transparent color.
ImGuiColorEditFlags_AlphaPreviewHalf= 1 << 14, // // ColorEdit, ColorPicker, ColorButton: display half opaque / half transparent preview.
// User Options (right-click on widget to change some of them).
ImGuiColorEditFlags_AlphaBar = 1 << 16, // // ColorEdit, ColorPicker: show vertical alpha bar/gradient in picker.
ImGuiColorEditFlags_AlphaBar = 1 << 18, // // ColorEdit, ColorPicker: show vertical alpha bar/gradient in picker.
ImGuiColorEditFlags_HDR = 1 << 19, // // (WIP) ColorEdit: Currently only disable 0.0f..1.0f limits in RGBA edition (note: you probably want to use ImGuiColorEditFlags_Float flag as well).
ImGuiColorEditFlags_DisplayRGB = 1 << 20, // [Display] // ColorEdit: override _display_ type among RGB/HSV/Hex. ColorPicker: select any combination using one or more of RGB/HSV/Hex.
ImGuiColorEditFlags_DisplayHSV = 1 << 21, // [Display] // "
@@ -1927,8 +1937,9 @@ enum ImGuiSliderFlags_
ImGuiSliderFlags_ClampOnInput = 1 << 9, // Clamp value to min/max bounds when input manually with Ctrl+Click. By default Ctrl+Click allows going out of bounds.
ImGuiSliderFlags_ClampZeroRange = 1 << 10, // Clamp even if min==max==0.0f. Otherwise due to legacy reason DragXXX functions don't clamp with those values. When your clamping limits are dynamic you almost always want to use it.
ImGuiSliderFlags_NoSpeedTweaks = 1 << 11, // Disable keyboard modifiers altering tweak speed. Useful if you want to alter tweak speed yourself based on your own logic.
ImGuiSliderFlags_ColorMarkers = 1 << 12, // DragScalarN(), SliderScalarN(): Draw R/G/B/A color markers on each component.
ImGuiSliderFlags_AlwaysClamp = ImGuiSliderFlags_ClampOnInput | ImGuiSliderFlags_ClampZeroRange,
ImGuiSliderFlags_InvalidMask_ = 0x7000000F, // [Internal] We treat using those bits as being potentially a 'float power' argument from the previous API that has got miscast to this enum, and will trigger an assert if needed.
ImGuiSliderFlags_InvalidMask_ = 0x7000000F, // [Internal] We treat using those bits as being potentially a 'float power' argument from legacy API (obsoleted 2020-08) that has got miscast to this enum, and will trigger an assert if needed.
};
// Identify a mouse button.
@@ -2018,7 +2029,7 @@ enum ImGuiTableFlags_
ImGuiTableFlags_Reorderable = 1 << 1, // Enable reordering columns in header row (need calling TableSetupColumn() + TableHeadersRow() to display headers)
ImGuiTableFlags_Hideable = 1 << 2, // Enable hiding/disabling columns in context menu.
ImGuiTableFlags_Sortable = 1 << 3, // Enable sorting. Call TableGetSortSpecs() to obtain sort specs. Also see ImGuiTableFlags_SortMulti and ImGuiTableFlags_SortTristate.
ImGuiTableFlags_NoSavedSettings = 1 << 4, // Disable persisting columns order, width and sort settings in the .ini file.
ImGuiTableFlags_NoSavedSettings = 1 << 4, // Disable persisting columns order, width, visibility and sort settings in the .ini file.
ImGuiTableFlags_ContextMenuInBody = 1 << 5, // Right-click on columns body/contents will display table context menu. By default it is available in TableHeadersRow().
// Decorations
ImGuiTableFlags_RowBg = 1 << 6, // Set each RowBg color with ImGuiCol_TableRowBg or ImGuiCol_TableRowBgAlt (equivalent of calling TableSetBgColor with ImGuiTableBgFlags_RowBg0 on each row manually)
@@ -2134,7 +2145,7 @@ struct ImGuiTableSortSpecs
int SpecsCount; // Sort spec count. Most often 1. May be > 1 when ImGuiTableFlags_SortMulti is enabled. May be == 0 when ImGuiTableFlags_SortTristate is enabled.
bool SpecsDirty; // Set to true when specs have changed since last time! Use this to sort again, then clear the flag.
ImGuiTableSortSpecs() { memset(this, 0, sizeof(*this)); }
ImGuiTableSortSpecs() { memset((void*)this, 0, sizeof(*this)); }
};
// Sorting specification for one column of a table (sizeof == 12 bytes)
@@ -2145,7 +2156,7 @@ struct ImGuiTableColumnSortSpecs
ImS16 SortOrder; // Index within parent ImGuiTableSortSpecs (always stored in order starting from 0, tables sorted on a single criteria will always have a 0 here)
ImGuiSortDirection SortDirection; // ImGuiSortDirection_Ascending or ImGuiSortDirection_Descending
ImGuiTableColumnSortSpecs() { memset(this, 0, sizeof(*this)); }
ImGuiTableColumnSortSpecs() { memset((void*)this, 0, sizeof(*this)); }
};
//-----------------------------------------------------------------------------
@@ -2268,7 +2279,7 @@ struct ImGuiStyle
// - recap: ImGui::GetFontSize() == FontSizeBase * (FontScaleMain * FontScaleDpi * other_scaling_factors)
float FontSizeBase; // Current base font size before external global factors are applied. Use PushFont(NULL, size) to modify. Use ImGui::GetFontSize() to obtain scaled value.
float FontScaleMain; // Main global scale factor. May be set by application once, or exposed to end-user.
float FontScaleDpi; // Additional global scale factor from viewport/monitor contents scale. When io.ConfigDpiScaleFonts is enabled, this is automatically overwritten when changing monitor DPI.
float FontScaleDpi; // Additional global scale factor from viewport/monitor contents scale. In docking branch: when io.ConfigDpiScaleFonts is enabled, this is automatically overwritten when changing monitor DPI.
float Alpha; // Global alpha applies to everything in Dear ImGui.
float DisabledAlpha; // Additional alpha multiplier applied by BeginDisabled(). Multiply over current value of Alpha.
@@ -2298,6 +2309,7 @@ struct ImGuiStyle
float GrabMinSize; // Minimum width/height of a grab box for slider/scrollbar.
float GrabRounding; // Radius of grabs corners rounding. Set to 0.0f to have rectangular slider grabs.
float LogSliderDeadzone; // The size in pixels of the dead-zone around zero on logarithmic sliders that cross zero.
float ImageRounding; // Rounding of Image() calls.
float ImageBorderSize; // Thickness of border around Image() calls.
float TabRounding; // Radius of upper corners of a tab. Set to 0.0f to have rectangular tabs.
float TabBorderSize; // Thickness of border around tabs.
@@ -2315,6 +2327,7 @@ struct ImGuiStyle
float DragDropTargetRounding; // Radius of the drag and drop target frame.
float DragDropTargetBorderSize; // Thickness of the drag and drop target border.
float DragDropTargetPadding; // Size to expand the drag and drop target from actual target item size.
float ColorMarkerSize; // Size of R/G/B/A color markers for ColorEdit4() and for Drags/Sliders when using ImGuiSliderFlags_ColorMarkers.
ImGuiDir ColorButtonPosition; // Side of the color button in the ColorEdit4 widget (left/right). Defaults to ImGuiDir_Right.
ImVec2 ButtonTextAlign; // Alignment of button text when button is larger than text. Defaults to (0.5f, 0.5f) (centered).
ImVec2 SelectableTextAlign; // Alignment of selectable text. Defaults to (0.0f, 0.0f) (top-left aligned). It's generally important to keep this left-aligned if you want to lay multiple items on a same line.
@@ -2435,7 +2448,7 @@ struct ImGuiIO
// Options to configure Error Handling and how we handle recoverable errors [EXPERIMENTAL]
// - Error recovery is provided as a way to facilitate:
// - Recovery after a programming error (native code or scripting language - the later tends to facilitate iterating on code while running).
// - Recovery after a programming error (native code or scripting language - the latter tends to facilitate iterating on code while running).
// - Recovery after running an exception handler or any error processing which may skip code after an error has been detected.
// - Error recovery is not perfect nor guaranteed! It is a feature to ease development.
// You not are not supposed to rely on it in the course of a normal application run.
@@ -2624,18 +2637,20 @@ struct ImGuiInputTextCallbackData
ImGuiInputTextFlags EventFlag; // One ImGuiInputTextFlags_Callback* // Read-only
ImGuiInputTextFlags Flags; // What user passed to InputText() // Read-only
void* UserData; // What user passed to InputText() // Read-only
ImGuiID ID; // Widget ID // Read-only
// Arguments for the different callback events
// - During Resize callback, Buf will be same as your input buffer.
// - However, during Completion/History/Always callback, Buf always points to our own internal data (it is not the same as your buffer)! Changes to it will be reflected into your own buffer shortly after the callback.
// - To modify the text buffer in a callback, prefer using the InsertChars() / DeleteChars() function. InsertChars() will take care of calling the resize callback if necessary.
// - If you know your edits are not going to resize the underlying buffer allocation, you may modify the contents of 'Buf[]' directly. You need to update 'BufTextLen' accordingly (0 <= BufTextLen < BufSize) and set 'BufDirty'' to true so InputText can update its internal state.
ImWchar EventChar; // Character input // Read-write // [CharFilter] Replace character with another one, or set to zero to drop. return 1 is equivalent to setting EventChar=0;
ImGuiKey EventKey; // Key pressed (Up/Down/TAB) // Read-only // [Completion,History]
ImWchar EventChar; // Character input // Read-write // [CharFilter] Replace character with another one, or set to zero to drop. return 1 is equivalent to setting EventChar=0;
bool EventActivated; // Input field just got activated // Read-only // [Always]
bool BufDirty; // Set if you modify Buf/BufTextLen! // Write // [Completion,History,Always]
char* Buf; // Text buffer // Read-write // [Resize] Can replace pointer / [Completion,History,Always] Only write to pointed data, don't replace the actual pointer!
int BufTextLen; // Text length (in bytes) // Read-write // [Resize,Completion,History,Always] Exclude zero-terminator storage. In C land: == strlen(some_text), in C++ land: string.length()
int BufSize; // Buffer size (in bytes) = capacity+1 // Read-only // [Resize,Completion,History,Always] Include zero-terminator storage. In C land: == ARRAYSIZE(my_char_array), in C++ land: string.capacity()+1
bool BufDirty; // Set if you modify Buf/BufTextLen! // Write // [Completion,History,Always]
int CursorPos; // // Read-write // [Completion,History,Always]
int SelectionStart; // // Read-write // [Completion,History,Always] == to SelectionEnd when no selection
int SelectionEnd; // // Read-write // [Completion,History,Always]
@@ -2645,9 +2660,10 @@ struct ImGuiInputTextCallbackData
IMGUI_API ImGuiInputTextCallbackData();
IMGUI_API void DeleteChars(int pos, int bytes_count);
IMGUI_API void InsertChars(int pos, const char* text, const char* text_end = NULL);
void SelectAll() { SelectionStart = 0; SelectionEnd = BufTextLen; }
void ClearSelection() { SelectionStart = SelectionEnd = BufTextLen; }
bool HasSelection() const { return SelectionStart != SelectionEnd; }
void SelectAll() { SelectionStart = 0; CursorPos = SelectionEnd = BufTextLen; }
void SetSelection(int s, int e) { IM_ASSERT(s >= 0 && s <= BufTextLen); IM_ASSERT(e >= 0 && e <= BufTextLen); SelectionStart = s; CursorPos = SelectionEnd = e; }
void ClearSelection() { SelectionStart = SelectionEnd = BufTextLen; }
bool HasSelection() const { return SelectionStart != SelectionEnd; }
};
// Resizing callback data to apply custom constraint. As enabled by SetNextWindowSizeConstraints(). Callback is called during the next Begin().
@@ -2866,7 +2882,7 @@ struct ImGuiListClipper
#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS
//inline void IncludeRangeByIndices(int item_begin, int item_end) { IncludeItemsByIndex(item_begin, item_end); } // [renamed in 1.89.9]
//inline void ForceDisplayRangeByIndices(int item_begin, int item_end) { IncludeItemsByIndex(item_begin, item_end); } // [renamed in 1.89.6]
//inline ImGuiListClipper(int items_count, float items_height = -1.0f) { memset(this, 0, sizeof(*this)); ItemsCount = -1; Begin(items_count, items_height); } // [removed in 1.79]
//inline ImGuiListClipper(int items_count, float items_height = -1.0f) { memset((void*)this, 0, sizeof(*this)); ItemsCount = -1; Begin(items_count, items_height); } // [removed in 1.79]
#endif
};
@@ -3151,7 +3167,7 @@ struct ImDrawCmd
int UserCallbackDataSize; // 4 // Size of callback user data when using storage, otherwise 0.
int UserCallbackDataOffset;// 4 // [Internal] Offset of callback user data when using storage, otherwise -1.
ImDrawCmd() { memset(this, 0, sizeof(*this)); } // Also ensure our padding fields are zeroed
ImDrawCmd() { memset((void*)this, 0, sizeof(*this)); } // Also ensure our padding fields are zeroed
// Since 1.83: returns ImTextureID associated with this draw call. Warning: DO NOT assume this is always same as 'TextureId' (we will change this function for an upcoming feature)
// Since 1.92: removed ImDrawCmd::TextureId field, the getter function must be used!
@@ -3197,7 +3213,7 @@ struct ImDrawListSplitter
int _Count; // Number of active channels (1+)
ImVector<ImDrawChannel> _Channels; // Draw channels (not resized down so _Count might be < Channels.Size)
inline ImDrawListSplitter() { memset(this, 0, sizeof(*this)); }
inline ImDrawListSplitter() { memset((void*)this, 0, sizeof(*this)); }
inline ~ImDrawListSplitter() { ClearFreeMemory(); }
inline void Clear() { _Current = 0; _Count = 1; } // Do not clear Channels[] so our allocations are reused next frame
IMGUI_API void ClearFreeMemory();
@@ -3490,7 +3506,7 @@ struct ImTextureData
bool WantDestroyNextFrame; // rw - // [Internal] Queued to set ImTextureStatus_WantDestroy next frame. May still be used in the current frame.
// Functions
ImTextureData() { memset(this, 0, sizeof(*this)); Status = ImTextureStatus_Destroyed; TexID = ImTextureID_Invalid; }
ImTextureData() { memset((void*)this, 0, sizeof(*this)); Status = ImTextureStatus_Destroyed; TexID = ImTextureID_Invalid; }
~ImTextureData() { DestroyPixels(); }
IMGUI_API void Create(ImTextureFormat format, int w, int h);
IMGUI_API void DestroyPixels();
@@ -3505,7 +3521,7 @@ struct ImTextureData
// - Call SetTexID() and SetStatus() after honoring texture requests. Never modify TexID and Status directly!
// - A backend may decide to destroy a texture that we did not request to destroy, which is fine (e.g. freeing resources), but we immediately set the texture back in _WantCreate mode.
void SetTexID(ImTextureID tex_id) { TexID = tex_id; }
void SetStatus(ImTextureStatus status) { Status = status; if (status == ImTextureStatus_Destroyed && !WantDestroyNextFrame) Status = ImTextureStatus_WantCreate; }
void SetStatus(ImTextureStatus status) { Status = status; if (status == ImTextureStatus_Destroyed && !WantDestroyNextFrame && Pixels != nullptr) Status = ImTextureStatus_WantCreate; }
};
//-----------------------------------------------------------------------------
@@ -3519,16 +3535,15 @@ struct ImFontConfig
char Name[40]; // <auto> // Name (strictly to ease debugging, hence limited size buffer)
void* FontData; // // TTF/OTF data
int FontDataSize; // // TTF/OTF data size
bool FontDataOwnedByAtlas; // true // TTF/OTF data ownership taken by the owner ImFontAtlas (will delete memory itself).
bool FontDataOwnedByAtlas; // true // TTF/OTF data ownership taken by the owner ImFontAtlas (will delete memory itself). SINCE 1.92, THE DATA NEEDS TO PERSIST FOR WHOLE DURATION OF ATLAS.
// Options
bool MergeMode; // false // Merge into previous ImFont, so you can combine multiple inputs font into one ImFont (e.g. ASCII font + icons + Japanese glyphs). You may want to use GlyphOffset.y when merge font of different heights.
bool PixelSnapH; // false // Align every glyph AdvanceX to pixel boundaries. Useful e.g. if you are merging a non-pixel aligned font with the default font. If enabled, you can set OversampleH/V to 1.
bool PixelSnapV; // true // Align Scaled GlyphOffset.y to pixel boundaries.
bool PixelSnapH; // false // Align every glyph AdvanceX to pixel boundaries. Prevents fractional font size from working correctly! Useful e.g. if you are merging a non-pixel aligned font with the default font. If enabled, OversampleH/V will default to 1.
ImS8 OversampleH; // 0 (2) // Rasterize at higher quality for sub-pixel positioning. 0 == auto == 1 or 2 depending on size. Note the difference between 2 and 3 is minimal. You can reduce this to 1 for large glyphs save memory. Read https://github.com/nothings/stb/blob/master/tests/oversample/README.md for details.
ImS8 OversampleV; // 0 (1) // Rasterize at higher quality for sub-pixel positioning. 0 == auto == 1. This is not really useful as we don't use sub-pixel positions on the Y axis.
ImWchar EllipsisChar; // 0 // Explicitly specify Unicode codepoint of ellipsis character. When fonts are being merged first specified ellipsis will be used.
float SizePixels; // // Size in pixels for rasterizer (more or less maps to the resulting font height).
float SizePixels; // // Output size in pixels for rasterizer (more or less maps to the resulting font height).
const ImWchar* GlyphRanges; // NULL // *LEGACY* THE ARRAY DATA NEEDS TO PERSIST AS LONG AS THE FONT IS ALIVE. Pointer to a user-provided list of Unicode range (2 value per range, values are inclusive, zero-terminated list).
const ImWchar* GlyphExcludeRanges; // NULL // Pointer to a small user-provided list of Unicode ranges (2 value per range, values are inclusive, zero-terminated list). This is very close to GlyphRanges[] but designed to exclude ranges from a font source, when merging fonts with overlapping glyphs. Use "Input Glyphs Overlap Detection Tool" to find about your overlapping ranges.
//ImVec2 GlyphExtraSpacing; // 0, 0 // (REMOVED AT IT SEEMS LARGELY OBSOLETE. PLEASE REPORT IF YOU WERE USING THIS). Extra spacing (in pixels) between glyphs when rendered: essentially add to glyph->AdvanceX. Only X axis is supported for now.
@@ -3538,9 +3553,10 @@ struct ImFontConfig
float GlyphExtraAdvanceX; // 0 // Extra spacing (in pixels) between glyphs. Please contact us if you are using this. // FIXME-NEWATLAS: Intentionally unscaled
ImU32 FontNo; // 0 // Index of font within TTF/OTF file
unsigned int FontLoaderFlags; // 0 // Settings for custom font builder. THIS IS BUILDER IMPLEMENTATION DEPENDENT. Leave as zero if unsure.
//unsigned int FontBuilderFlags; // -- // [Renamed in 1.92] Ue FontLoaderFlags.
//unsigned int FontBuilderFlags; // -- // [Renamed in 1.92] Use FontLoaderFlags.
float RasterizerMultiply; // 1.0f // Linearly brighten (>1.0f) or darken (<1.0f) font output. Brightening small fonts may be a good workaround to make them more readable. This is a silly thing we may remove in the future.
float RasterizerDensity; // 1.0f // [LEGACY: this only makes sense when ImGuiBackendFlags_RendererHasTextures is not supported] DPI scale multiplier for rasterization. Not altering other font metrics: makes it easy to swap between e.g. a 100% and a 400% fonts for a zooming display, or handle Retina screen. IMPORTANT: If you change this it is expected that you increase/decrease font scale roughly to the inverse of this, otherwise quality may look lowered.
float ExtraSizeScale; // 1.0f // Extra rasterizer scale over SizePixels.
// [Internal]
ImFontFlags Flags; // Font flags (don't use just yet, will be exposed in upcoming 1.92.X updates)
@@ -3548,6 +3564,9 @@ struct ImFontConfig
const ImFontLoader* FontLoader; // Custom font backend for this source (default source is the one stored in ImFontAtlas)
void* FontLoaderData; // Font loader opaque storage (per font config)
#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS
bool PixelSnapV; // true // [Obsoleted in 1.91.6] Align Scaled GlyphOffset.y to pixel boundaries.
#endif
IMGUI_API ImFontConfig();
};
@@ -3564,7 +3583,7 @@ struct ImFontGlyph
float U0, V0, U1, V1; // Texture coordinates for the current value of ImFontAtlas->TexRef. Cached equivalent of calling GetCustomRect() with PackId.
int PackId; // [Internal] ImFontAtlasRectId value (FIXME: Cold data, could be moved elsewhere?)
ImFontGlyph() { memset(this, 0, sizeof(*this)); PackId = -1; }
ImFontGlyph() { memset((void*)this, 0, sizeof(*this)); PackId = -1; }
};
// Helper to build glyph ranges from text/string data. Feed your application strings/characters to it then call BuildRanges().
@@ -3597,7 +3616,7 @@ struct ImFontAtlasRect
unsigned short w, h; // Size
ImVec2 uv0, uv1; // UV coordinates (in current texture)
ImFontAtlasRect() { memset(this, 0, sizeof(*this)); }
ImFontAtlasRect() { memset((void*)this, 0, sizeof(*this)); }
};
// Flags for ImFontAtlas build
@@ -3633,7 +3652,9 @@ struct ImFontAtlas
IMGUI_API ImFontAtlas();
IMGUI_API ~ImFontAtlas();
IMGUI_API ImFont* AddFont(const ImFontConfig* font_cfg);
IMGUI_API ImFont* AddFontDefault(const ImFontConfig* font_cfg = NULL);
IMGUI_API ImFont* AddFontDefault(const ImFontConfig* font_cfg = NULL); // Selects between AddFontDefaultVector() and AddFontDefaultBitmap().
IMGUI_API ImFont* AddFontDefaultVector(const ImFontConfig* font_cfg = NULL); // Embedded scalable font. Recommended at any higher size.
IMGUI_API ImFont* AddFontDefaultBitmap(const ImFontConfig* font_cfg = NULL); // Embedded classic pixel-clean font. Recommended at Size 13px with no scaling.
IMGUI_API ImFont* AddFontFromFileTTF(const char* filename, float size_pixels = 0.0f, const ImFontConfig* font_cfg = NULL, const ImWchar* glyph_ranges = NULL);
IMGUI_API ImFont* AddFontFromMemoryTTF(void* font_data, int font_data_size, float size_pixels = 0.0f, const ImFontConfig* font_cfg = NULL, const ImWchar* glyph_ranges = NULL); // Note: Transfer ownership of 'ttf_data' to ImFontAtlas! Will be deleted after destruction of the atlas. Set font_cfg->FontDataOwnedByAtlas=false to keep ownership of your data and it won't be freed.
IMGUI_API ImFont* AddFontFromMemoryCompressedTTF(const void* compressed_font_data, int compressed_font_data_size, float size_pixels = 0.0f, const ImFontConfig* font_cfg = NULL, const ImWchar* glyph_ranges = NULL); // 'compressed_font_data' still owned by caller. Compress with binary_to_compressed_c.cpp.
@@ -3795,7 +3816,7 @@ struct ImFontBaked
unsigned int MetricsTotalSurface:26;// 3 // out // Total surface in pixels to get an idea of the font rasterization/texture cost (not exact, we approximate the cost of padding between glyphs)
unsigned int WantDestroy:1; // 0 // // Queued for destroy
unsigned int LoadNoFallback:1; // 0 // // Disable loading fallback in lower-level calls.
unsigned int LoadNoRenderOnLayout:1;// 0 // // Enable a two-steps mode where CalcTextSize() calls will load AdvanceX *without* rendering/packing glyphs. Only advantagous if you know that the glyph is unlikely to actually be rendered, otherwise it is slower because we'd do one query on the first CalcTextSize and one query on the first Draw.
unsigned int LoadNoRenderOnLayout:1;// 0 // // Enable a two-steps mode where CalcTextSize() calls will load AdvanceX *without* rendering/packing glyphs. Only advantageous if you know that the glyph is unlikely to actually be rendered, otherwise it is slower because we'd do one query on the first CalcTextSize and one query on the first Draw.
int LastUsedFrame; // 4 // // Record of that time this was bounds
ImGuiID BakedId; // 4 // // Unique ID for this baked storage
ImFont* OwnerFont; // 4-8 // in // Parent font
@@ -3838,10 +3859,10 @@ struct ImFont
ImGuiID FontId; // Unique identifier for the font
float LegacySize; // 4 // in // Font size passed to AddFont(). Use for old code calling PushFont() expecting to use that size. (use ImGui::GetFontBaked() to get font baked at current bound size).
ImVector<ImFontConfig*> Sources; // 16 // in // List of sources. Pointers within OwnerAtlas->Sources[]
ImWchar EllipsisChar; // 2-4 // out // Character used for ellipsis rendering ('...').
ImWchar EllipsisChar; // 2-4 // out // Character used for ellipsis rendering ('...'). If you ever want to temporarily swap this for an alternative/dummy char, make sure to clear EllipsisAutoBake.
ImWchar FallbackChar; // 2-4 // out // Character used if a glyph isn't found (U+FFFD, '?')
ImU8 Used8kPagesMap[(IM_UNICODE_CODEPOINT_MAX+1)/8192/8]; // 1 bytes if ImWchar=ImWchar16, 16 bytes if ImWchar==ImWchar32. Store 1-bit for each block of 4K codepoints that has one active glyph. This is mainly used to facilitate iterations across all used codepoints.
bool EllipsisAutoBake; // 1 // // Mark when the "..." glyph needs to be generated.
ImU8 Used8kPagesMap[(IM_UNICODE_CODEPOINT_MAX+1)/8192/8]; // 1 bytes if ImWchar=ImWchar16, 17 bytes if ImWchar==ImWchar32. Store 1-bit for each block of 8K codepoints that has one active glyph. This is mainly used to facilitate iterations across all used codepoints.
bool EllipsisAutoBake; // 1 // // Mark when the "..." glyph (== EllipsisChar) needs to be generated by combining multiple '.'.
ImGuiStorage RemapPairs; // 16 // // Remapping pairs when using AddRemapChar(), otherwise empty.
#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS
float Scale; // 4 // in // Legacy base font scale (~1.0f), multiplied by the per-window font scale which you can adjust with SetWindowFontScale()
@@ -3924,7 +3945,7 @@ struct ImGuiViewport
void* PlatformHandle; // void* to hold higher-level, platform window handle (e.g. HWND, GLFWWindow*, SDL_Window*)
void* PlatformHandleRaw; // void* to hold lower-level, platform-native window handle (under Win32 this is expected to be a HWND, unused for other platforms)
ImGuiViewport() { memset(this, 0, sizeof(*this)); }
ImGuiViewport() { memset((void*)this, 0, sizeof(*this)); }
// Helpers
ImVec2 GetCenter() const { return ImVec2(Pos.x + Size.x * 0.5f, Pos.y + Size.y * 0.5f); }
@@ -3946,7 +3967,7 @@ struct ImGuiPlatformIO
// Optional: Access OS clipboard
// (default to use native Win32 clipboard on Windows, otherwise uses a private clipboard. Override to access OS clipboard on other architectures)
const char* (*Platform_GetClipboardTextFn)(ImGuiContext* ctx);
const char* (*Platform_GetClipboardTextFn)(ImGuiContext* ctx); // Should return NULL on failure (e.g. clipboard data is not text).
void (*Platform_SetClipboardTextFn)(ImGuiContext* ctx, const char* text);
void* Platform_ClipboardUserData;
@@ -4001,7 +4022,7 @@ struct ImGuiPlatformImeData
float InputLineHeight; // Line height (for IME).
ImGuiID ViewportId; // ID of platform window/viewport.
ImGuiPlatformImeData() { memset(this, 0, sizeof(*this)); }
ImGuiPlatformImeData() { memset((void*)this, 0, sizeof(*this)); }
};
//-----------------------------------------------------------------------------
@@ -4023,19 +4044,21 @@ namespace ImGui
inline void PopButtonRepeat() { PopItemFlag(); }
inline void PushTabStop(bool tab_stop) { PushItemFlag(ImGuiItemFlags_NoTabStop, !tab_stop); }
inline void PopTabStop() { PopItemFlag(); }
// You do not need those functions! See #7838 on GitHub for more info.
IMGUI_API ImVec2 GetContentRegionMax(); // Content boundaries max (e.g. window boundaries including scrolling, or current column boundaries). You should never need this. Always use GetCursorScreenPos() and GetContentRegionAvail()!
IMGUI_API ImVec2 GetWindowContentRegionMin(); // Content boundaries min for the window (roughly (0,0)-Scroll), in window-local coordinates. You should never need this. Always use GetCursorScreenPos() and GetContentRegionAvail()!
IMGUI_API ImVec2 GetWindowContentRegionMax(); // Content boundaries max for the window (roughly (0,0)+Size-Scroll), in window-local coordinates. You should never need this. Always use GetCursorScreenPos() and GetContentRegionAvail()!
// OBSOLETED in 1.90.0 (from September 2023)
inline bool BeginChildFrame(ImGuiID id, const ImVec2& size, ImGuiWindowFlags window_flags = 0) { return BeginChild(id, size, ImGuiChildFlags_FrameStyle, window_flags); }
inline void EndChildFrame() { EndChild(); }
//inline bool BeginChild(const char* str_id, const ImVec2& size_arg, bool borders, ImGuiWindowFlags window_flags){ return BeginChild(str_id, size_arg, borders ? ImGuiChildFlags_Borders : ImGuiChildFlags_None, window_flags); } // Unnecessary as true == ImGuiChildFlags_Borders
//inline bool BeginChild(ImGuiID id, const ImVec2& size_arg, bool borders, ImGuiWindowFlags window_flags) { return BeginChild(id, size_arg, borders ? ImGuiChildFlags_Borders : ImGuiChildFlags_None, window_flags); } // Unnecessary as true == ImGuiChildFlags_Borders
inline void ShowStackToolWindow(bool* p_open = NULL) { ShowIDStackToolWindow(p_open); }
IMGUI_API bool Combo(const char* label, int* current_item, bool (*old_callback)(void* user_data, int idx, const char** out_text), void* user_data, int items_count, int popup_max_height_in_items = -1);
IMGUI_API bool ListBox(const char* label, int* current_item, bool (*old_callback)(void* user_data, int idx, const char** out_text), void* user_data, int items_count, int height_in_items = -1);
// Some of the older obsolete names along with their replacement (commented out so they are not reported in IDE)
// OBSOLETED in 1.90.0 (from September 2023)
//inline bool BeginChild(const char* str_id, const ImVec2& size_arg, bool borders, ImGuiWindowFlags window_flags) { return BeginChild(str_id, size_arg, borders ? ImGuiChildFlags_Borders : ImGuiChildFlags_None, window_flags); } // Unnecessary as true == ImGuiChildFlags_Borders
//inline bool BeginChild(ImGuiID id, const ImVec2& size_arg, bool borders, ImGuiWindowFlags window_flags) { return BeginChild(id, size_arg, borders ? ImGuiChildFlags_Borders : ImGuiChildFlags_None, window_flags); } // Unnecessary as true == ImGuiChildFlags_Borders
//inline bool BeginChildFrame(ImGuiID id, const ImVec2& size, ImGuiWindowFlags flags = 0) { return BeginChild(id, size, ImGuiChildFlags_FrameStyle, flags); }
//inline void EndChildFrame() { EndChild(); }
//inline void ShowStackToolWindow(bool* p_open = NULL) { ShowIDStackToolWindow(p_open); }
// OBSOLETED in 1.89.7 (from June 2023)
//IMGUI_API void SetItemAllowOverlap(); // Use SetNextItemAllowOverlap() _before_ item.
//-- OBSOLETED in 1.89.4 (from March 2023)
@@ -4145,10 +4168,12 @@ typedef ImFontAtlasRect ImFontAtlasCustomRect;
//typedef ImGuiKeyChord ImGuiKeyModFlags; // == int
//enum ImGuiKeyModFlags_ { ImGuiKeyModFlags_None = 0, ImGuiKeyModFlags_Ctrl = ImGuiMod_Ctrl, ImGuiKeyModFlags_Shift = ImGuiMod_Shift, ImGuiKeyModFlags_Alt = ImGuiMod_Alt, ImGuiKeyModFlags_Super = ImGuiMod_Super };
#define IM_OFFSETOF(_TYPE,_MEMBER) offsetof(_TYPE, _MEMBER) // OBSOLETED IN 1.90 (now using C++11 standard version)
//#define IM_OFFSETOF(_TYPE,_MEMBER) offsetof(_TYPE, _MEMBER) // OBSOLETED IN 1.90 (now using C++11 standard version)
#endif // #ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS
#define IM_ARRAYSIZE IM_COUNTOF // RENAMED IN 1.92.6: IM_ARRAYSIZE -> IM_COUNTOF
// RENAMED IMGUI_DISABLE_METRICS_WINDOW > IMGUI_DISABLE_DEBUG_TOOLS in 1.88 (from June 2022)
#ifdef IMGUI_DISABLE_METRICS_WINDOW
#error IMGUI_DISABLE_METRICS_WINDOW was renamed to IMGUI_DISABLE_DEBUG_TOOLS, please use new name.
+284
View File
@@ -0,0 +1,284 @@
// Solution adapted from https://github.com/ocornut/imgui/issues/9066
#include "imgui_cjk.h"
#include "imgui_internal.h"
#include <array>
#include <vector>
#include <string>
#include <unordered_set>
#include <algorithm>
#include <cstdint>
// From imgui_draw.cpp
extern float BuildLoadGlyphGetAdvanceOrFallback(ImFontBaked* baked, unsigned int codepoint);
namespace {
// --- Bitmap helpers ---
template <size_t N>
struct Bitmap {
std::array<uint8_t, N> bits{};
void set(size_t idx) {
bits[idx] = 1;
}
bool test(size_t idx) const {
return bits[idx] != 0;
}
};
// --- Build bitmaps for dense ranges ---
static constexpr size_t RANGE_3000_30FF_SIZE = 0x100; // 256 code points
static Bitmap<RANGE_3000_30FF_SIZE> bitmap_3000_30FF{};
static constexpr size_t RANGE_31F0_31FF_SIZE = 0x10; // 16 code points
static Bitmap<RANGE_31F0_31FF_SIZE> bitmap_31F0_31FF{};
static constexpr size_t RANGE_FF61_FF70_SIZE = 0x10; // 16 code points
static Bitmap<RANGE_FF61_FF70_SIZE> bitmap_FF61_FF70{};
// --- Hash set for scattered values ---
static const std::unordered_set<unsigned int> HeadProhibitedScatter = {
0x00A2, 0x00B0, 0x2032, 0x2033, 0x2030, 0x2103,
0x201D, 0x3009, 0x300B, 0x300D, 0x300F, 0x3011, 0x3015,
0xFF09, 0xFF3D, 0xFF5D, 0xFF63
// add other scattered ones here
};
// --- Initialization (fill bitmaps) ---
struct InitBitmaps {
InitBitmaps() {
// Fill 0x3000–0x30FF bitmap
unsigned int dense_3000_30FF[] = {
0x3001, 0x3002, 0x3005, 0x303B, 0x30FB,
0x309D, 0x309E, 0x30FD, 0x30FE, 0x30FC,
0x30A1, 0x30A3, 0x30A5, 0x30A7, 0x30A9,
0x30C3, 0x30E3, 0x30E5, 0x30E7, 0x30EE,
0x30F5, 0x30F6
};
for (auto c : dense_3000_30FF) {
bitmap_3000_30FF.set(c - 0x3000);
}
// Fill 0x31F0–0x31FF bitmap
for (unsigned int c = 0x31F0; c <= 0x31FF; ++c) {
bitmap_31F0_31FF.set(c - 0x31F0);
}
// Fill 0xFF61–0xFF70 bitmap
for (unsigned int c = 0xFF61; c <= 0xFF70; ++c) {
bitmap_FF61_FF70.set(c - 0xFF61);
}
}
};
// Static initializer
static InitBitmaps initBitmaps;
// --- Lookup function ---
inline bool ImCharIsHeadProhibitedW(unsigned int c)
{
if (c >= 0x3000 && c <= 0x30FF) {
return bitmap_3000_30FF.test(c - 0x3000);
}
if (c >= 0x31F0 && c <= 0x31FF) {
return bitmap_31F0_31FF.test(c - 0x31F0);
}
if (c >= 0xFF61 && c <= 0xFF70) {
return bitmap_FF61_FF70.test(c - 0xFF61);
}
return HeadProhibitedScatter.find(c) != HeadProhibitedScatter.end();
}
inline bool ImCharIsHeadProhibitedA(char c) { return c == ' ' || c == '\t' || c == '}' || c == ')' || c == ']' || c == '?' || c == '!' || c == '|' || c == '/' || c == '&' || c == '.' || c == ',' || c == ':' || c == ';';}
inline bool ImCharIsHeadProhibited(unsigned int c) { return (c < 128 && ImCharIsHeadProhibitedA((char)c)) || ImCharIsHeadProhibitedW(c); }
inline bool ImCharIsTailProhibitedA(unsigned int c) { return c == '(' || c == '[' || c == '{' || c == '+'; }
const unsigned int TailProhibitedW[] = { 0x2018, 0x201c, 0x3008, 0x300a, 0x300c, 0x300e, 0x3010, 0x3014, 0xff08, 0xff3b, 0xff5b, 0xff62, 0xa3, 0xa5, 0xff04, 0xffe1, 0xffe5, 0xff0b };
inline bool ImCharIsTailProhibitedW(unsigned int c) { for (int i = 0; i < IM_ARRAYSIZE(TailProhibitedW); i++) if (c == TailProhibitedW[i]) return true; return false; }
inline bool ImCharIsTailProhibited(unsigned int c) { return (c < 128 && ImCharIsTailProhibitedA(c)) || ImCharIsTailProhibitedW(c); }
inline bool ImCharIsLineBreakableW(unsigned int c)
{
return
(unsigned)(c - 0x3040) <= (0x9FFF - 0x3040) ||
(unsigned)(c - 0x20000) <= (0xDFFFF - 0x20000) ||
(unsigned)(c - 0xAC00) <= (0xD7FF - 0xAC00) ||
(unsigned)(c - 0xF900) <= (0xFAFF - 0xF900) ||
(unsigned)(c - 0x1100) <= (0x11FF - 0x1100) ||
(unsigned)(c - 0x2E80) <= (0x2FFF - 0x2E80);
}
const char *CalcWordWrapPositionCJK(ImFont *font, float size, const char *text, const char *text_end, float wrap_width)
{
if (wrap_width <= 0.0f)
return text_end;
ImFontBaked *baked = font->GetFontBaked(size);
const float scale = size / baked->Size;
float line_width = 0.0f;
float word_width = 0.0f;
float blank_width = 0.0f;
wrap_width /= scale;
const char *word_end = text;
const char *prev_s = NULL;
const char *s = NULL;
const char *next_s = text;
unsigned int prev_c = 0;
unsigned int c = 0;
unsigned int next_c = 0;
bool next_char_is_line_break_able = false;
bool char_line_is_break_able = false;
bool next_char_is_head_prohibited = false;
bool char_is_head_prohibited = false;
#define IM_ADVANCE_WORD() \
do \
{ \
word_end = s; \
line_width += word_width + blank_width; \
word_width = blank_width = 0.0f; \
} while (0)
IM_ASSERT(text_end != NULL);
while (s < text_end)
{
// prev_s is the END of prev_c, which actually points to c
// same for s and next_s.
prev_s = s;
s = next_s;
prev_c = c;
c = next_c;
char_line_is_break_able = next_char_is_line_break_able;
char_is_head_prohibited = next_char_is_head_prohibited;
next_c = (unsigned int) *next_s;
if (next_c < 0x80)
next_s = next_s + 1;
else
next_s = next_s + ImTextCharFromUtf8(&next_c, next_s, text_end);
if (next_s > text_end)
next_c = 0;
if (prev_s == NULL)
{
continue;
}
if (c < 0x20)
{
if (c == '\n')
return prev_s;
if (c == '\r')
continue;
}
// Optimized inline version of 'float char_width = GetCharAdvance((ImWchar)c);'
float char_width = (c < (unsigned int) baked->IndexAdvanceX.Size) ? baked->IndexAdvanceX.Data[c] : -1.0f;
if (char_width < 0.0f)
char_width = BuildLoadGlyphGetAdvanceOrFallback(baked, c);
if (ImCharIsBlankW(c))
blank_width += char_width;
else
{
word_width += char_width + blank_width;
blank_width = 0.0f;
}
// We ignore blank width at the end of the line (they can be skipped)
if (line_width + word_width > wrap_width)
{
// Words that cannot possibly fit within an entire line will be cut anywhere.
if (word_width < wrap_width)
s = word_end;
else
s = prev_s;
break;
}
if (!next_c)
{
IM_ADVANCE_WORD();
}
else if (c)
{
next_char_is_line_break_able = ImCharIsLineBreakableW(next_c);
next_char_is_head_prohibited = ImCharIsHeadProhibited(next_c);
if (prev_c >= '0' && prev_c <= '9' && next_c >= '0' && next_c <= '9' && !next_char_is_line_break_able)
continue;
if (next_char_is_line_break_able && !next_char_is_head_prohibited && !ImCharIsTailProhibited(c))
IM_ADVANCE_WORD();
if ((char_is_head_prohibited || char_line_is_break_able) && !next_char_is_head_prohibited)
IM_ADVANCE_WORD();
}
}
#undef IM_ADVANCE_WORD
if (s == text && text < text_end)
return s + ImTextCountUtf8BytesFromChar(s, text_end);
return (s > text_end) ? text_end : s;
}
} // namespace
void ImGui::RenderTextWrappedCJK(ImVec2 pos, const char* text, const char* text_end, float wrap_width)
{
ImGuiContext& g = *GImGui;
ImGuiWindow* window = g.CurrentWindow;
if (!text_end)
text_end = text + strlen(text);
if (text == text_end)
return;
ImFont* font = g.Font;
float fontSize = g.FontSize;
const char* s = text;
ImVec2 current_pos = pos;
while (s < text_end)
{
const char* line_end = CalcWordWrapPositionCJK(font, fontSize, s, text_end, wrap_width);
if (s != line_end)
{
window->DrawList->AddText(font, fontSize, current_pos, GetColorU32(ImGuiCol_Text), s, line_end, 0.0f);
}
current_pos.y += fontSize;
s = ImTextCalcWordWrapNextLineStart(line_end, text_end);
}
if (g.LogEnabled)
LogRenderedText(&pos, text, text_end);
}
ImVec2 ImGui::CalcTextSizeCJK(ImFont* font, float size, float max_width, float wrap_width, const char* text_begin, const char* text_end, const char** out_remaining)
{
if (!text_end)
text_end = text_begin + strlen(text_begin);
if (text_begin == text_end)
return ImVec2(0.0f, size);
ImVec2 text_size(0.0f, 0.0f);
const char* s = text_begin;
while (s < text_end)
{
const char* line_end = CalcWordWrapPositionCJK(font, size, s, text_end, wrap_width);
ImVec2 line_size = font->CalcTextSizeA(size, FLT_MAX, 0.0f, s, line_end);
text_size.x = ImMax(text_size.x, line_size.x);
text_size.y += size;
s = ImTextCalcWordWrapNextLineStart(line_end, text_end);
}
if (out_remaining)
*out_remaining = s;
return text_size;
}
+9
View File
@@ -0,0 +1,9 @@
#pragma once
#include "imgui.h"
namespace ImGui {
// CJK-aware text wrapping function.
// Use this instead of ImGui::RenderTextWrapped for better CJK support.
void RenderTextWrappedCJK(ImVec2 pos, const char* text, const char* text_end, float wrap_width);
ImVec2 CalcTextSizeCJK(ImFont* font, float size, float max_width, float wrap_width, const char* text_begin, const char* text_end, const char** out_remaining = nullptr);
}
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
Regular → Executable
+113 -82
View File
@@ -1,4 +1,4 @@
// dear imgui, v1.92.5
// dear imgui, v1.92.6
// (internal structures/api)
// You may use this file to debug, understand or extend Dear ImGui features but we don't provide any guarantee of forward compatibility.
@@ -60,7 +60,7 @@ Index of this file:
#include <limits.h> // INT_MIN, INT_MAX
// Enable SSE intrinsics if available
#if (defined __SSE__ || defined __x86_64__ || defined _M_X64 || (defined(_M_IX86_FP) && (_M_IX86_FP >= 1))) && !defined(IMGUI_DISABLE_SSE)
#if (defined __SSE__ || defined __x86_64__ || defined _M_X64 || (defined(_M_IX86_FP) && (_M_IX86_FP >= 1))) && !defined(IMGUI_DISABLE_SSE) && !defined(_M_ARM64) && !defined(_M_ARM64EC)
#define IMGUI_ENABLE_SSE
#include <immintrin.h>
#if (defined __AVX__ || defined __SSE4_2__)
@@ -106,6 +106,7 @@ Index of this file:
#pragma GCC diagnostic ignored "-Wfloat-equal" // warning: comparing floating-point with '==' or '!=' is unsafe
#pragma GCC diagnostic ignored "-Wclass-memaccess" // [__GNUC__ >= 8] warning: 'memset/memcpy' clearing/writing an object of type 'xxxx' with no trivial copy-assignment; use assignment or value-initialization instead
#pragma GCC diagnostic ignored "-Wdeprecated-enum-enum-conversion" // warning: bitwise operation between different enumeration types ('XXXFlags_' and 'XXXFlagsPrivate_') is deprecated
#pragma GCC diagnostic ignored "-Wsign-conversion" // warning: conversion to 'xxxx' from 'xxxx' may change the sign of the result
#endif
// In 1.89.4, we moved the implementation of "courtesy maths operators" from imgui_internal.h in imgui.h
@@ -276,11 +277,9 @@ extern IMGUI_API ImGuiContext* GImGui; // Current implicit context pointer
#define IM_MEMALIGN(_OFF,_ALIGN) (((_OFF) + ((_ALIGN) - 1)) & ~((_ALIGN) - 1)) // Memory align e.g. IM_ALIGN(0,4)=0, IM_ALIGN(1,4)=4, IM_ALIGN(4,4)=4, IM_ALIGN(5,4)=8
#define IM_F32_TO_INT8_UNBOUND(_VAL) ((int)((_VAL) * 255.0f + ((_VAL)>=0 ? 0.5f : -0.5f))) // Unsaturated, for display purpose
#define IM_F32_TO_INT8_SAT(_VAL) ((int)(ImSaturate(_VAL) * 255.0f + 0.5f)) // Saturated, always output 0..255
#define IM_TRUNC(_VAL) ((float)(int)(_VAL)) // ImTrunc() is not inlined in MSVC debug builds
#define IM_ROUND(_VAL) ((float)(int)((_VAL) + 0.5f)) //
#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS
#define IM_FLOOR IM_TRUNC // [OBSOLETE] Renamed in 1.90.0 (Sept 2023)
#endif
#define IM_TRUNC(_VAL) ((float)(int)(_VAL)) // Positive values only! ImTrunc() is not inlined in MSVC debug builds
#define IM_ROUND(_VAL) ((float)(int)((_VAL) + 0.5f)) // Positive values only!
//#define IM_FLOOR IM_TRUNC // [OBSOLETE] Renamed in 1.90.0 (Sept 2023)
// Hint for branch prediction
#if (defined(__cplusplus) && (__cplusplus >= 202002L)) || (defined(_MSVC_LANG) && (_MSVC_LANG >= 202002L))
@@ -335,7 +334,6 @@ extern IMGUI_API ImGuiContext* GImGui; // Current implicit context pointer
#define IM_PRIu64 "llu"
#define IM_PRIX64 "llX"
#endif
#define IM_TEXTUREID_TO_U64(_TEXID) ((ImU64)(intptr_t)(_TEXID))
//-----------------------------------------------------------------------------
// [SECTION] Generic helpers
@@ -442,6 +440,16 @@ IMGUI_API ImVec2 ImFontCalcTextSizeEx(ImFont* font, float size, float max
IMGUI_API const char* ImFontCalcWordWrapPositionEx(ImFont* font, float size, const char* text, const char* text_end, float wrap_width, ImDrawTextFlags flags = 0);
IMGUI_API const char* ImTextCalcWordWrapNextLineStart(const char* text, const char* text_end, ImDrawTextFlags flags = 0); // trim trailing space and find beginning of next line
// Character classification for word-wrapping logic
enum ImWcharClass
{
ImWcharClass_Blank, ImWcharClass_Punct, ImWcharClass_Other
};
IMGUI_API void ImTextInitClassifiers();
IMGUI_API void ImTextClassifierClear(ImU32* bits, unsigned int codepoint_min, unsigned int codepoint_end, ImWcharClass char_class);
IMGUI_API void ImTextClassifierSetCharClass(ImU32* bits, unsigned int codepoint_min, unsigned int codepoint_end, ImWcharClass char_class, unsigned int c);
IMGUI_API void ImTextClassifierSetCharClassFromStr(ImU32* bits, unsigned int codepoint_min, unsigned int codepoint_end, ImWcharClass char_class, const char* s);
// Helpers: File System
#ifdef IMGUI_DISABLE_FILE_FUNCTIONS
#define IMGUI_DISABLE_DEFAULT_FILE_FUNCTIONS
@@ -518,7 +526,7 @@ inline ImVec2 ImTrunc(const ImVec2& v) { return
inline float ImFloor(float f) { return (float)((f >= 0 || (float)(int)f == f) ? (int)f : (int)f - 1); } // Decent replacement for floorf()
inline ImVec2 ImFloor(const ImVec2& v) { return ImVec2(ImFloor(v.x), ImFloor(v.y)); }
inline float ImTrunc64(float f) { return (float)(ImS64)(f); }
inline float ImRound64(float f) { return (float)(ImS64)(f + 0.5f); }
inline float ImRound64(float f) { return (float)(ImS64)(f + 0.5f); } // FIXME: Positive values only.
inline int ImModPositive(int a, int b) { return (a + b) % b; }
inline float ImDot(const ImVec2& a, const ImVec2& b) { return a.x * b.x + a.y * b.y; }
inline ImVec2 ImRotate(const ImVec2& v, float cos_a, float sin_a) { return ImVec2(v.x * cos_a - v.y * sin_a, v.x * sin_a + v.y * cos_a); }
@@ -602,7 +610,6 @@ struct IMGUI_API ImRect
void TranslateY(float dy) { Min.y += dy; Max.y += dy; }
void ClipWith(const ImRect& r) { Min = ImMax(Min, r.Min); Max = ImMin(Max, r.Max); } // Simple version, may lead to an inverted rectangle, which is fine for Contains/Overlaps test but not for display.
void ClipWithFull(const ImRect& r) { Min = ImClamp(Min, r.Min, r.Max); Max = ImClamp(Max, r.Min, r.Max); } // Full version, ensure both points are fully clipped.
void Floor() { Min.x = IM_TRUNC(Min.x); Min.y = IM_TRUNC(Min.y); Max.x = IM_TRUNC(Max.x); Max.y = IM_TRUNC(Max.y); }
bool IsInverted() const { return Min.x > Max.x || Min.y > Max.y; }
ImVec4 ToVec4() const { return ImVec4(Min.x, Min.y, Max.x, Max.y); }
const ImVec4& AsVec4() const { return *(const ImVec4*)&Min.x; }
@@ -636,15 +643,15 @@ typedef ImU32* ImBitArrayPtr; // Name for use in structs
template<int BITCOUNT, int OFFSET = 0>
struct ImBitArray
{
ImU32 Storage[(BITCOUNT + 31) >> 5];
ImU32 Data[(BITCOUNT + 31) >> 5];
ImBitArray() { ClearAllBits(); }
void ClearAllBits() { memset(Storage, 0, sizeof(Storage)); }
void SetAllBits() { memset(Storage, 255, sizeof(Storage)); }
bool TestBit(int n) const { n += OFFSET; IM_ASSERT(n >= 0 && n < BITCOUNT); return IM_BITARRAY_TESTBIT(Storage, n); }
void SetBit(int n) { n += OFFSET; IM_ASSERT(n >= 0 && n < BITCOUNT); ImBitArraySetBit(Storage, n); }
void ClearBit(int n) { n += OFFSET; IM_ASSERT(n >= 0 && n < BITCOUNT); ImBitArrayClearBit(Storage, n); }
void SetBitRange(int n, int n2) { n += OFFSET; n2 += OFFSET; IM_ASSERT(n >= 0 && n < BITCOUNT && n2 > n && n2 <= BITCOUNT); ImBitArraySetBitRange(Storage, n, n2); } // Works on range [n..n2)
bool operator[](int n) const { n += OFFSET; IM_ASSERT(n >= 0 && n < BITCOUNT); return IM_BITARRAY_TESTBIT(Storage, n); }
void ClearAllBits() { memset(Data, 0, sizeof(Data)); }
void SetAllBits() { memset(Data, 255, sizeof(Data)); }
bool TestBit(int n) const { n += OFFSET; IM_ASSERT(n >= 0 && n < BITCOUNT); return IM_BITARRAY_TESTBIT(Data, n); }
void SetBit(int n) { n += OFFSET; IM_ASSERT(n >= 0 && n < BITCOUNT); ImBitArraySetBit(Data, n); }
void ClearBit(int n) { n += OFFSET; IM_ASSERT(n >= 0 && n < BITCOUNT); ImBitArrayClearBit(Data, n); }
void SetBitRange(int n, int n2) { n += OFFSET; n2 += OFFSET; IM_ASSERT(n >= 0 && n < BITCOUNT && n2 > n && n2 <= BITCOUNT); ImBitArraySetBitRange(Data, n, n2); } // Works on range [n..n2)
bool operator[](int n) const { n += OFFSET; IM_ASSERT(n >= 0 && n < BITCOUNT); return IM_BITARRAY_TESTBIT(Data, n); }
};
// Helper: ImBitVector
@@ -701,7 +708,7 @@ struct ImSpanAllocator
int Offsets[CHUNKS];
int Sizes[CHUNKS];
ImSpanAllocator() { memset(this, 0, sizeof(*this)); }
ImSpanAllocator() { memset((void*)this, 0, sizeof(*this)); }
inline void Reserve(int n, size_t sz, int a=4) { IM_ASSERT(n == CurrIdx && n < CHUNKS); CurrOff = IM_MEMALIGN(CurrOff, a); Offsets[n] = CurrOff; Sizes[n] = (int)sz; CurrIdx++; CurrOff += (int)sz; }
inline int GetArenaSizeInBytes() { return CurrOff; }
inline void SetArenaBasePtr(void* base_ptr) { BasePtr = (char*)base_ptr; }
@@ -712,10 +719,10 @@ struct ImSpanAllocator
};
// Helper: ImStableVector<>
// Allocating chunks of BLOCK_SIZE items. Objects pointers are never invalidated when growing, only by clear().
// Allocating chunks of BLOCKSIZE items. Objects pointers are never invalidated when growing, only by clear().
// Important: does not destruct anything!
// Implemented only the minimum set of functions we need for it.
template<typename T, int BLOCK_SIZE>
template<typename T, int BLOCKSIZE>
struct ImStableVector
{
int Size = 0;
@@ -729,19 +736,19 @@ struct ImStableVector
inline void resize(int new_size) { if (new_size > Capacity) reserve(new_size); Size = new_size; }
inline void reserve(int new_cap)
{
new_cap = IM_MEMALIGN(new_cap, BLOCK_SIZE);
int old_count = Capacity / BLOCK_SIZE;
int new_count = new_cap / BLOCK_SIZE;
new_cap = IM_MEMALIGN(new_cap, BLOCKSIZE);
int old_count = Capacity / BLOCKSIZE;
int new_count = new_cap / BLOCKSIZE;
if (new_count <= old_count)
return;
Blocks.resize(new_count);
for (int n = old_count; n < new_count; n++)
Blocks[n] = (T*)IM_ALLOC(sizeof(T) * BLOCK_SIZE);
Blocks[n] = (T*)IM_ALLOC(sizeof(T) * BLOCKSIZE);
Capacity = new_cap;
}
inline T& operator[](int i) { IM_ASSERT(i >= 0 && i < Size); return Blocks[i / BLOCK_SIZE][i % BLOCK_SIZE]; }
inline const T& operator[](int i) const { IM_ASSERT(i >= 0 && i < Size); return Blocks[i / BLOCK_SIZE][i % BLOCK_SIZE]; }
inline T* push_back(const T& v) { int i = Size; IM_ASSERT(i >= 0); if (Size == Capacity) reserve(Capacity + BLOCK_SIZE); void* ptr = &Blocks[i / BLOCK_SIZE][i % BLOCK_SIZE]; memcpy(ptr, &v, sizeof(v)); Size++; return (T*)ptr; }
inline T& operator[](int i) { IM_ASSERT(i >= 0 && i < Size); return Blocks[i / BLOCKSIZE][i % BLOCKSIZE]; }
inline const T& operator[](int i) const { IM_ASSERT(i >= 0 && i < Size); return Blocks[i / BLOCKSIZE][i % BLOCKSIZE]; }
inline T* push_back(const T& v) { int i = Size; IM_ASSERT(i >= 0); if (Size == Capacity) reserve(Capacity + BLOCKSIZE); void* ptr = &Blocks[i / BLOCKSIZE][i % BLOCKSIZE]; memcpy(ptr, &v, sizeof(v)); Size++; return (T*)ptr; }
};
// Helper: ImPool<>
@@ -882,7 +889,7 @@ struct ImDrawDataBuilder
ImVector<ImDrawList*>* Layers[2]; // Pointers to global layers for: regular, tooltip. LayersP[0] is owned by DrawData.
ImVector<ImDrawList*> LayerData1;
ImDrawDataBuilder() { memset(this, 0, sizeof(*this)); }
ImDrawDataBuilder() { memset((void*)this, 0, sizeof(*this)); }
};
struct ImFontStackData
@@ -956,7 +963,6 @@ enum ImGuiDataTypePrivate_
enum ImGuiItemFlagsPrivate_
{
// Controlled by user
ImGuiItemFlags_Disabled = 1 << 10, // false // Disable interactions (DOES NOT affect visuals. DO NOT mix direct use of this with BeginDisabled(). See BeginDisabled()/EndDisabled() for full disable feature, and github #211).
ImGuiItemFlags_ReadOnly = 1 << 11, // false // [ALPHA] Allow hovering interactions but underlying value is not changed.
ImGuiItemFlags_MixedValue = 1 << 12, // false // [BETA] Represent a mixed/indeterminate value, generally multi-selection where values differ. Currently only supported by Checkbox() (later should support all sorts of widgets)
ImGuiItemFlags_NoWindowHoverableCheck = 1 << 13, // false // Disable hoverable check in ItemHoverable()
@@ -1158,7 +1164,7 @@ struct IMGUI_API ImGuiComboPreviewData
float BackupPrevLineTextBaseOffset;
ImGuiLayoutType BackupLayout;
ImGuiComboPreviewData() { memset(this, 0, sizeof(*this)); }
ImGuiComboPreviewData() { memset((void*)this, 0, sizeof(*this)); }
};
// Stacked storage data for BeginGroup()/EndGroup()
@@ -1192,7 +1198,7 @@ struct IMGUI_API ImGuiMenuColumns
ImU16 OffsetMark;
ImU16 Widths[4]; // Width of: Icon, Label, Shortcut, Mark (accumulators for current frame)
ImGuiMenuColumns() { memset(this, 0, sizeof(*this)); }
ImGuiMenuColumns() { memset((void*)this, 0, sizeof(*this)); }
void Update(float spacing, bool window_reappearing);
float DeclColumns(float w_icon, float w_label, float w_shortcut, float w_mark);
void CalcNextTotalWidth(bool update_offsets);
@@ -1204,7 +1210,7 @@ struct IMGUI_API ImGuiInputTextDeactivatedState
ImGuiID ID; // widget id owning the text state (which just got deactivated)
ImVector<char> TextA; // text buffer
ImGuiInputTextDeactivatedState() { memset(this, 0, sizeof(*this)); }
ImGuiInputTextDeactivatedState() { memset((void*)this, 0, sizeof(*this)); }
void ClearFreeMemory() { ID = 0; TextA.clear(); }
};
@@ -1228,7 +1234,7 @@ struct IMGUI_API ImGuiInputTextState
ImGuiInputTextFlags Flags; // copy of InputText() flags. may be used to check if e.g. ImGuiInputTextFlags_Password is set.
ImGuiID ID; // widget id owning the text state
int TextLen; // UTF-8 length of the string in TextA (in bytes)
const char* TextSrc; // == TextA.Data unless read-only, in which case == buf passed to InputText(). Field only set and valid _inside_ the call InputText() call.
const char* TextSrc; // == TextA.Data unless read-only, in which case == buf passed to InputText(). For _ReadOnly fields, pointer will be null outside the InputText() call.
ImVector<char> TextA; // main UTF8 buffer. TextA.Size is a buffer size! Should always be >= buf_size passed by user (and of course >= CurLenA + 1).
ImVector<char> TextToRevertTo; // value to revert to when pressing Escape = backup of end-user buffer at the time of focus (in UTF-8, unaltered)
ImVector<char> CallbackTextBackup; // temporary storage for callback to support automatic reconcile of undo-stack
@@ -1262,6 +1268,7 @@ struct IMGUI_API ImGuiInputTextState
int GetCursorPos() const;
int GetSelectionStart() const;
int GetSelectionEnd() const;
void SetSelection(int start, int end);
void SelectAll();
// Reload user buf (WIP #2890)
@@ -1329,18 +1336,19 @@ struct ImGuiNextWindowData
ImVec2 MenuBarOffsetMinVal; // (Always on) This is not exposed publicly, so we don't clear it and it doesn't have a corresponding flag (could we? for consistency?)
ImGuiWindowRefreshFlags RefreshFlagsVal;
ImGuiNextWindowData() { memset(this, 0, sizeof(*this)); }
ImGuiNextWindowData() { memset((void*)this, 0, sizeof(*this)); }
inline void ClearFlags() { HasFlags = ImGuiNextWindowDataFlags_None; }
};
enum ImGuiNextItemDataFlags_
{
ImGuiNextItemDataFlags_None = 0,
ImGuiNextItemDataFlags_HasWidth = 1 << 0,
ImGuiNextItemDataFlags_HasOpen = 1 << 1,
ImGuiNextItemDataFlags_HasShortcut = 1 << 2,
ImGuiNextItemDataFlags_HasRefVal = 1 << 3,
ImGuiNextItemDataFlags_HasStorageID = 1 << 4,
ImGuiNextItemDataFlags_None = 0,
ImGuiNextItemDataFlags_HasWidth = 1 << 0,
ImGuiNextItemDataFlags_HasOpen = 1 << 1,
ImGuiNextItemDataFlags_HasShortcut = 1 << 2,
ImGuiNextItemDataFlags_HasRefVal = 1 << 3,
ImGuiNextItemDataFlags_HasStorageID = 1 << 4,
ImGuiNextItemDataFlags_HasColorMarker = 1 << 5,
};
struct ImGuiNextItemData
@@ -1358,8 +1366,9 @@ struct ImGuiNextItemData
ImU8 OpenCond; // Set by SetNextItemOpen()
ImGuiDataTypeStorage RefVal; // Not exposed yet, for ImGuiInputTextFlags_ParseEmptyAsRefVal
ImGuiID StorageId; // Set by SetNextItemStorageID()
ImU32 ColorMarker; // Set by SetNextItemColorMarker(). Not exposed yet, supported by DragScalar,SliderScalar and for ImGuiSliderFlags_ColorMarkers.
ImGuiNextItemData() { memset(this, 0, sizeof(*this)); SelectionUserData = -1; }
ImGuiNextItemData() { memset((void*)this, 0, sizeof(*this)); SelectionUserData = -1; }
inline void ClearFlags() { HasFlags = ImGuiNextItemDataFlags_None; ItemFlags = ImGuiItemFlags_None; } // Also cleared manually by ItemAdd()!
};
@@ -1376,7 +1385,7 @@ struct ImGuiLastItemData
ImRect ClipRect; // Clip rectangle at the time of submitting item. ONLY VALID IF (StatusFlags & ImGuiItemStatusFlags_HasClipRect) is set..
ImGuiKeyChord Shortcut; // Shortcut at the time of submitting item. ONLY VALID IF (StatusFlags & ImGuiItemStatusFlags_HasShortcut) is set..
ImGuiLastItemData() { memset(this, 0, sizeof(*this)); }
ImGuiLastItemData() { memset((void*)this, 0, sizeof(*this)); }
};
// Store data emitted by TreeNode() for usage by TreePop()
@@ -1409,7 +1418,7 @@ struct IMGUI_API ImGuiErrorRecoveryState
short SizeOfBeginPopupStack;
short SizeOfDisabledStack;
ImGuiErrorRecoveryState() { memset(this, 0, sizeof(*this)); }
ImGuiErrorRecoveryState() { memset((void*)this, 0, sizeof(*this)); }
};
// Data saved for each window pushed into the stack
@@ -1470,7 +1479,7 @@ struct ImGuiPopupData
ImVec2 OpenPopupPos; // Set on OpenPopup(), preferred popup position (typically == OpenMousePos when using mouse)
ImVec2 OpenMousePos; // Set on OpenPopup(), copy of mouse position at the time of opening popup
ImGuiPopupData() { memset(this, 0, sizeof(*this)); ParentNavLayer = OpenFrameCount = -1; }
ImGuiPopupData() { memset((void*)this, 0, sizeof(*this)); ParentNavLayer = OpenFrameCount = -1; }
};
//-----------------------------------------------------------------------------
@@ -1548,7 +1557,7 @@ struct ImGuiInputEvent
};
bool AddedByTestEngine;
ImGuiInputEvent() { memset(this, 0, sizeof(*this)); }
ImGuiInputEvent() { memset((void*)this, 0, sizeof(*this)); }
};
// Input function taking an 'ImGuiID owner_id' argument defaults to (ImGuiKeyOwner_Any == 0) aka don't test ownership, which matches legacy behavior.
@@ -1580,7 +1589,7 @@ struct ImGuiKeyRoutingTable
ImVector<ImGuiKeyRoutingData> EntriesNext; // Double-buffer to avoid reallocation (could use a shared buffer)
ImGuiKeyRoutingTable() { Clear(); }
void Clear() { for (int n = 0; n < IM_ARRAYSIZE(Index); n++) Index[n] = -1; Entries.clear(); EntriesNext.clear(); }
void Clear() { for (int n = 0; n < IM_COUNTOF(Index); n++) Index[n] = -1; Entries.clear(); EntriesNext.clear(); }
};
// This extends ImGuiKeyData but only for named keys (legacy keys don't support the new features)
@@ -1663,7 +1672,7 @@ struct ImGuiListClipperData
int ItemsFrozen;
ImVector<ImGuiListClipperRange> Ranges;
ImGuiListClipperData() { memset(this, 0, sizeof(*this)); }
ImGuiListClipperData() { memset((void*)this, 0, sizeof(*this)); }
void Reset(ImGuiListClipper* clipper) { ListClipper = clipper; StepNo = ItemsFrozen = 0; Ranges.resize(0); }
};
@@ -1798,7 +1807,7 @@ struct IMGUI_API ImGuiTypingSelectState
float LastRequestTime = 0.0f;
bool SingleCharModeLock = false; // After a certain single char repeat count we lock into SingleCharMode. Two benefits: 1) buffer never fill, 2) we can provide an immediate SingleChar mode without timer elapsing.
ImGuiTypingSelectState() { memset(this, 0, sizeof(*this)); }
ImGuiTypingSelectState() { memset((void*)this, 0, sizeof(*this)); }
void Clear() { SearchBuffer[0] = 0; SingleCharModeLock = false; } // We preserve remaining data for easier debugging
};
@@ -1834,7 +1843,7 @@ struct ImGuiOldColumnData
ImGuiOldColumnFlags Flags; // Not exposed
ImRect ClipRect;
ImGuiOldColumnData() { memset(this, 0, sizeof(*this)); }
ImGuiOldColumnData() { memset((void*)this, 0, sizeof(*this)); }
};
struct ImGuiOldColumns
@@ -1855,7 +1864,7 @@ struct ImGuiOldColumns
ImVector<ImGuiOldColumnData> Columns;
ImDrawListSplitter Splitter;
ImGuiOldColumns() { memset(this, 0, sizeof(*this)); }
ImGuiOldColumns() { memset((void*)this, 0, sizeof(*this)); }
};
//-----------------------------------------------------------------------------
@@ -1883,7 +1892,7 @@ struct ImGuiBoxSelectState
ImRect BoxSelectRectPrev; // Selection rectangle in absolute coordinates (derived every frame from BoxSelectStartPosRel and MousePos)
ImRect BoxSelectRectCurr;
ImGuiBoxSelectState() { memset(this, 0, sizeof(*this)); }
ImGuiBoxSelectState() { memset((void*)this, 0, sizeof(*this)); }
};
//-----------------------------------------------------------------------------
@@ -1994,7 +2003,7 @@ struct ImGuiWindowSettings
bool WantApply; // Set when loaded from .ini data (to enable merging/loading .ini data into an already running context)
bool WantDelete; // Set to invalidate/delete the settings entry
ImGuiWindowSettings() { memset(this, 0, sizeof(*this)); }
ImGuiWindowSettings() { memset((void*)this, 0, sizeof(*this)); }
char* GetName() { return (char*)(this + 1); }
};
@@ -2010,7 +2019,7 @@ struct ImGuiSettingsHandler
void (*WriteAllFn)(ImGuiContext* ctx, ImGuiSettingsHandler* handler, ImGuiTextBuffer* out_buf); // Write: Output every entries into 'out_buf'
void* UserData;
ImGuiSettingsHandler() { memset(this, 0, sizeof(*this)); }
ImGuiSettingsHandler() { memset((void*)this, 0, sizeof(*this)); }
};
//-----------------------------------------------------------------------------
@@ -2049,7 +2058,9 @@ struct ImGuiLocEntry
// - See 'Demo->Configuration->Error Handling' and ImGuiIO definitions for details on error handling.
// - Read https://github.com/ocornut/imgui/wiki/Error-Handling for details on error handling.
#ifndef IM_ASSERT_USER_ERROR
#define IM_ASSERT_USER_ERROR(_EXPR,_MSG) do { if (!(_EXPR) && ImGui::ErrorLog(_MSG)) { IM_ASSERT((_EXPR) && _MSG); } } while (0) // Recoverable User Error
#define IM_ASSERT_USER_ERROR(_EXPR,_MSG) do { if (!(_EXPR)) { if (ImGui::ErrorLog(_MSG)) { IM_ASSERT((_EXPR) && _MSG); } } } while (0) // Recoverable User Error
#define IM_ASSERT_USER_ERROR_RET(_EXPR,_MSG) do { if (!(_EXPR)) { if (ImGui::ErrorLog(_MSG)) { IM_ASSERT((_EXPR) && _MSG); } return; } } while (0) // Recoverable User Error
#define IM_ASSERT_USER_ERROR_RETV(_EXPR,_RETV,_MSG) do { if (!(_EXPR)) { if (ImGui::ErrorLog(_MSG)) { IM_ASSERT((_EXPR) && _MSG); } return _RETV; } } while (0) // Recoverable User Error
#endif
// The error callback is currently not public, as it is expected that only advanced users will rely on it.
@@ -2079,7 +2090,8 @@ enum ImGuiDebugLogFlags_
ImGuiDebugLogFlags_EventMask_ = ImGuiDebugLogFlags_EventError | ImGuiDebugLogFlags_EventActiveId | ImGuiDebugLogFlags_EventFocus | ImGuiDebugLogFlags_EventPopup | ImGuiDebugLogFlags_EventNav | ImGuiDebugLogFlags_EventClipper | ImGuiDebugLogFlags_EventSelection | ImGuiDebugLogFlags_EventIO | ImGuiDebugLogFlags_EventFont | ImGuiDebugLogFlags_EventInputRouting | ImGuiDebugLogFlags_EventDocking | ImGuiDebugLogFlags_EventViewport,
ImGuiDebugLogFlags_OutputToTTY = 1 << 20, // Also send output to TTY
ImGuiDebugLogFlags_OutputToTestEngine = 1 << 21, // Also send output to Test Engine
ImGuiDebugLogFlags_OutputToDebugger = 1 << 21, // Also send output to Debugger Console [Windows only]
ImGuiDebugLogFlags_OutputToTestEngine = 1 << 22, // Also send output to Dear ImGui Test Engine
};
struct ImGuiDebugAllocEntry
@@ -2096,7 +2108,7 @@ struct ImGuiDebugAllocInfo
ImS16 LastEntriesIdx; // Current index in buffer
ImGuiDebugAllocEntry LastEntriesBuf[6]; // Track last 6 frames that had allocations
ImGuiDebugAllocInfo() { memset(this, 0, sizeof(*this)); }
ImGuiDebugAllocInfo() { memset((void*)this, 0, sizeof(*this)); }
};
struct ImGuiMetricsConfig
@@ -2125,7 +2137,7 @@ struct ImGuiStackLevelInfo
ImS8 DataType; // ImGuiDataType
int DescOffset; // -1 or offset into parent's ResultsPathsBuf
ImGuiStackLevelInfo() { memset(this, 0, sizeof(*this)); DataType = -1; DescOffset = -1; }
ImGuiStackLevelInfo() { memset((void*)this, 0, sizeof(*this)); DataType = -1; DescOffset = -1; }
};
struct ImGuiDebugItemPathQuery
@@ -2138,7 +2150,7 @@ struct ImGuiDebugItemPathQuery
ImGuiTextBuffer ResultsDescBuf;
ImGuiTextBuffer ResultPathBuf;
ImGuiDebugItemPathQuery() { memset(this, 0, sizeof(*this)); }
ImGuiDebugItemPathQuery() { memset((void*)this, 0, sizeof(*this)); }
};
// State for ID Stack tool queries
@@ -2149,7 +2161,7 @@ struct ImGuiIDStackTool
int LastActiveFrame;
float CopyToClipboardLastTime;
ImGuiIDStackTool() { memset(this, 0, sizeof(*this)); LastActiveFrame = -1; OptHexEncodeNonAsciiChars = true; CopyToClipboardLastTime = -FLT_MAX; }
ImGuiIDStackTool() { memset((void*)this, 0, sizeof(*this)); LastActiveFrame = -1; OptHexEncodeNonAsciiChars = true; CopyToClipboardLastTime = -FLT_MAX; }
};
//-----------------------------------------------------------------------------
@@ -2167,7 +2179,7 @@ struct ImGuiContextHook
ImGuiContextHookCallback Callback;
void* UserData;
ImGuiContextHook() { memset(this, 0, sizeof(*this)); }
ImGuiContextHook() { memset((void*)this, 0, sizeof(*this)); }
};
//-----------------------------------------------------------------------------
@@ -2340,7 +2352,7 @@ struct ImGuiContext
ImGuiDir NavMoveDirForDebug;
ImGuiDir NavMoveClipDir; // FIXME-NAV: Describe the purpose of this better. Might want to rename?
ImRect NavScoringRect; // Rectangle used for scoring, in screen space. Based of window->NavRectRel[], modified for directional navigation scoring.
ImRect NavScoringNoClipRect; // Some nav operations (such as PageUp/PageDown) enforce a region which clipper will attempt to always keep submitted
ImRect NavScoringNoClipRect; // Some nav operations (such as PageUp/PageDown) enforce a region which clipper will attempt to always keep submitted. Unset/invalid if inverted.
int NavScoringDebugCount; // Metrics for debugging
int NavTabbingDir; // Generally -1 or +1, 0 when tabbing without a nav id
int NavTabbingCounter; // >0 when counting items for tabbing
@@ -2357,10 +2369,15 @@ struct ImGuiContext
bool NavJustMovedToIsTabbing; // Copy of ImGuiNavMoveFlags_IsTabbing. Maybe we should store whole flags.
bool NavJustMovedToHasSelectionData; // Copy of move result's ItemFlags & ImGuiItemFlags_HasSelectionUserData). Maybe we should just store ImGuiNavItemData.
// Navigation: Windowing (Ctrl+Tab for list, or Menu button + keys or directional pads to move/resize)
// Navigation: extra config options (will be made public eventually)
// - Tabbing (Tab, Shift+Tab) and Windowing (Ctrl+Tab, Ctrl+Shift+Tab) are enabled REGARDLESS of ImGuiConfigFlags_NavEnableKeyboard being set.
// - Ctrl+Tab is reconfigurable because it is the only shortcut that may be polled when no window are focused. It also doesn't work e.g. Web platforms.
bool ConfigNavEnableTabbing; // = true. Enable tabbing (Tab, Shift+Tab). PLEASE LET ME KNOW IF YOU USE THIS.
bool ConfigNavWindowingWithGamepad; // = true. Enable Ctrl+Tab by holding ImGuiKey_GamepadFaceLeft (== ImGuiKey_NavGamepadMenu). When false, the button may still be used to toggle Menu layer.
ImGuiKeyChord ConfigNavWindowingKeyNext; // = ImGuiMod_Ctrl | ImGuiKey_Tab (or ImGuiMod_Super | ImGuiKey_Tab on OS X). For reconfiguration (see #4828)
ImGuiKeyChord ConfigNavWindowingKeyNext; // = ImGuiMod_Ctrl | ImGuiKey_Tab (or ImGuiMod_Super | ImGuiKey_Tab on OS X). Set to 0 to disable. For reconfiguration (see #4828)
ImGuiKeyChord ConfigNavWindowingKeyPrev; // = ImGuiMod_Ctrl | ImGuiMod_Shift | ImGuiKey_Tab (or ImGuiMod_Super | ImGuiMod_Shift | ImGuiKey_Tab on OS X)
// Navigation: Windowing (Ctrl+Tab for list, or Menu button + keys or directional pads to move/resize)
ImGuiWindow* NavWindowingTarget; // Target window when doing Ctrl+Tab (or Pad Menu + FocusPrev/Next), this window is temporarily displayed top-most!
ImGuiWindow* NavWindowingTargetAnim; // Record of last valid NavWindowingTarget until DimBgRatio and NavWindowingHighlightAlpha becomes 0.0f, so the fade-out can stay on it.
ImGuiWindow* NavWindowingListWindow; // Internal window actually listing the Ctrl+Tab contents
@@ -2562,6 +2579,8 @@ struct ImGuiContext
// [SECTION] ImGuiWindowTempData, ImGuiWindow
//-----------------------------------------------------------------------------
#define IMGUI_WINDOW_HARD_MIN_SIZE 4.0f
// Transient per-window data, reset at the beginning of the frame. This used to be called ImGuiDrawContext, hence the DC variable name in ImGuiWindow.
// (That's theory, in practice the delimitation between ImGuiWindow and ImGuiWindowTempData is quite tenuous and could be reconsidered..)
// (This doesn't need a constructor because we zero-clear it as part of ImGuiWindow and all frame-temporary data are setup on Begin)
@@ -2612,6 +2631,7 @@ struct IMGUI_API ImGuiWindowTempData
// Local parameters stacks
// We store the current settings outside of the vectors to increase memory locality (reduce cache misses). The vectors are rarely modified. Also it allows us to not heap allocate for short-lived windows which are not using those settings.
float ItemWidth; // Current item width (>0.0: width in pixels, <0.0: align xx pixels to the right of window).
float ItemWidthDefault;
float TextWrapPos; // Current text wrap pos.
ImVector<float> ItemWidthStack; // Store item widths to restore (attention: .back() is not == ItemWidth)
ImVector<float> TextWrapPosStack; // Store text wrap pos to restore (attention: .back() is not == TextWrapPos)
@@ -2704,7 +2724,6 @@ struct IMGUI_API ImGuiWindow
int LastFrameActive; // Last frame number the window was Active.
float LastTimeActive; // Last timestamp the window was Active (using float as we don't need high precision there)
float ItemWidthDefault;
ImGuiStorage StateStorage;
ImVector<ImGuiOldColumns> ColumnsStorage;
float FontWindowScale; // User scale multiplier per-window, via SetWindowFontScale()
@@ -2780,7 +2799,7 @@ struct ImGuiTabItem
ImGuiTabItemFlags Flags;
int LastFrameVisible;
int LastFrameSelected; // This allows us to infer an ordered list of the last activated tabs with little maintenance
float Offset; // Position relative to beginning of tab
float Offset; // Position relative to beginning of tab bar
float Width; // Width currently displayed
float ContentWidth; // Width of label + padding, stored during BeginTabItem() call (misnamed as "Content" would normally imply width of label only)
float RequestedWidth; // Width optionally requested by caller, -1.0f is unused
@@ -2789,7 +2808,7 @@ struct ImGuiTabItem
ImS16 IndexDuringLayout; // Index only used during TabBarLayout(). Tabs gets reordered so 'Tabs[n].IndexDuringLayout == n' but may mismatch during additions.
bool WantClose; // Marked as closed by SetTabItemClosed()
ImGuiTabItem() { memset(this, 0, sizeof(*this)); LastFrameVisible = LastFrameSelected = -1; RequestedWidth = -1.0f; NameOffset = -1; BeginOrder = IndexDuringLayout = -1; }
ImGuiTabItem() { memset((void*)this, 0, sizeof(*this)); LastFrameVisible = LastFrameSelected = -1; RequestedWidth = -1.0f; NameOffset = -1; BeginOrder = IndexDuringLayout = -1; }
};
// Storage for a tab bar (sizeof() 160 bytes)
@@ -2801,6 +2820,7 @@ struct IMGUI_API ImGuiTabBar
ImGuiID ID; // Zero for tab-bars used by docking
ImGuiID SelectedTabId; // Selected tab/window
ImGuiID NextSelectedTabId; // Next selected tab/window. Will also trigger a scrolling animation
ImGuiID NextScrollToTabId;
ImGuiID VisibleTabId; // Can occasionally be != SelectedTabId (e.g. when previewing contents for Ctrl+Tab preview)
int CurrFrameVisible;
int PrevFrameVisible;
@@ -2893,7 +2913,7 @@ struct ImGuiTableColumn
ImGuiTableColumn()
{
memset(this, 0, sizeof(*this));
memset((void*)this, 0, sizeof(*this));
StretchWeight = WidthRequest = -1.0f;
NameOffset = -1;
DisplayOrder = IndexWithinEnabledSet = -1;
@@ -3054,7 +3074,7 @@ struct IMGUI_API ImGuiTable
bool MemoryCompacted;
bool HostSkipItems; // Backup of InnerWindow->SkipItem at the end of BeginTable(), because we will overwrite InnerWindow->SkipItem on a per-column basis
ImGuiTable() { memset(this, 0, sizeof(*this)); LastFrameActive = -1; }
ImGuiTable() { memset((void*)this, 0, sizeof(*this)); LastFrameActive = -1; }
~ImGuiTable() { IM_FREE(RawData); }
};
@@ -3083,7 +3103,7 @@ struct IMGUI_API ImGuiTableTempData
float HostBackupItemWidth; // Backup of OuterWindow->DC.ItemWidth at the end of BeginTable()
int HostBackupItemWidthStackSize;//Backup of OuterWindow->DC.ItemWidthStack.Size at the end of BeginTable()
ImGuiTableTempData() { memset(this, 0, sizeof(*this)); LastTimeActive = -1.0f; }
ImGuiTableTempData() { memset((void*)this, 0, sizeof(*this)); LastTimeActive = -1.0f; }
};
// sizeof() ~ 16
@@ -3120,7 +3140,7 @@ struct ImGuiTableSettings
ImGuiTableColumnIdx ColumnsCountMax; // Maximum number of columns this settings instance can store, we can recycle a settings instance with lower number of columns but not higher
bool WantApply; // Set when loaded from .ini data (to enable merging/loading .ini data into an already running context)
ImGuiTableSettings() { memset(this, 0, sizeof(*this)); }
ImGuiTableSettings() { memset((void*)this, 0, sizeof(*this)); }
ImGuiTableColumnSettings* GetColumnSettings() { return (ImGuiTableColumnSettings*)(this + 1); }
};
@@ -3138,6 +3158,7 @@ namespace ImGui
// - You are calling ImGui functions after ImGui::EndFrame()/ImGui::Render() and before the next ImGui::NewFrame(), which is also illegal.
IMGUI_API ImGuiIO& GetIO(ImGuiContext* ctx);
IMGUI_API ImGuiPlatformIO& GetPlatformIO(ImGuiContext* ctx);
inline float GetScale() { ImGuiContext& g = *GImGui; return g.Style._MainScale; } // FIXME-DPI: I don't want to formalize this just yet. Because reasons. Please don't use.
inline ImGuiWindow* GetCurrentWindowRead() { ImGuiContext& g = *GImGui; return g.CurrentWindow; }
inline ImGuiWindow* GetCurrentWindow() { ImGuiContext& g = *GImGui; g.CurrentWindow->WriteAccessed = true; return g.CurrentWindow; }
IMGUI_API ImGuiWindow* FindWindowByID(ImGuiID id);
@@ -3196,6 +3217,12 @@ namespace ImGui
IMGUI_API void Initialize();
IMGUI_API void Shutdown(); // Since 1.60 this is a _private_ function. You can call DestroyContext() to destroy the context created by CreateContext().
// Context name & generic context hooks
IMGUI_API void SetContextName(ImGuiContext* ctx, const char* name);
IMGUI_API ImGuiID AddContextHook(ImGuiContext* ctx, const ImGuiContextHook* hook);
IMGUI_API void RemoveContextHook(ImGuiContext* ctx, ImGuiID hook_to_remove);
IMGUI_API void CallContextHooks(ImGuiContext* ctx, ImGuiContextHookType type);
// NewFrame
IMGUI_API void UpdateInputEvents(bool trickle_fast_inputs);
IMGUI_API void UpdateHoveredWindowAndCaptureFlags(const ImVec2& mouse_pos);
@@ -3205,12 +3232,8 @@ namespace ImGui
IMGUI_API void UpdateMouseMovingWindowNewFrame();
IMGUI_API void UpdateMouseMovingWindowEndFrame();
// Generic context hooks
IMGUI_API ImGuiID AddContextHook(ImGuiContext* context, const ImGuiContextHook* hook);
IMGUI_API void RemoveContextHook(ImGuiContext* context, ImGuiID hook_to_remove);
IMGUI_API void CallContextHooks(ImGuiContext* context, ImGuiContextHookType type);
// Viewports
inline ImGuiViewport* GetWindowViewport() { return GetMainViewport(); } // For code consistency. This is public API in docking branch.
IMGUI_API void ScaleWindowsInViewport(ImGuiViewportP* viewport, float scale);
IMGUI_API void SetWindowViewport(ImGuiWindow* window, ImGuiViewportP* viewport);
@@ -3248,7 +3271,6 @@ namespace ImGui
// Basic Accessors
inline ImGuiItemStatusFlags GetItemStatusFlags() { ImGuiContext& g = *GImGui; return g.LastItemData.StatusFlags; }
inline ImGuiItemFlags GetItemFlags() { ImGuiContext& g = *GImGui; return g.LastItemData.ItemFlags; }
inline ImGuiID GetActiveID() { ImGuiContext& g = *GImGui; return g.ActiveId; }
inline ImGuiID GetFocusID() { ImGuiContext& g = *GImGui; return g.NavId; }
IMGUI_API void SetActiveID(ImGuiID id, ImGuiWindow* window);
@@ -3304,6 +3326,7 @@ namespace ImGui
IMGUI_API ImGuiWindow* FindBlockingModal(ImGuiWindow* window);
IMGUI_API ImVec2 FindBestWindowPosForPopup(ImGuiWindow* window);
IMGUI_API ImVec2 FindBestWindowPosForPopupEx(const ImVec2& ref_pos, const ImVec2& size, ImGuiDir* last_dir, const ImRect& r_outer, const ImRect& r_avoid, ImGuiPopupPositionPolicy policy);
IMGUI_API ImGuiMouseButton GetMouseButtonFromPopupFlags(ImGuiPopupFlags flags);
// Tooltips
IMGUI_API bool BeginTooltipEx(ImGuiTooltipFlags tooltip_flags, ImGuiWindowFlags extra_window_flags);
@@ -3511,6 +3534,7 @@ namespace ImGui
IMGUI_API void TableMergeDrawChannels(ImGuiTable* table);
inline ImGuiTableInstanceData* TableGetInstanceData(ImGuiTable* table, int instance_no) { if (instance_no == 0) return &table->InstanceDataFirst; return &table->InstanceDataExtra[instance_no - 1]; }
inline ImGuiID TableGetInstanceID(ImGuiTable* table, int instance_no) { return TableGetInstanceData(table, instance_no)->TableInstanceID; }
IMGUI_API void TableFixDisplayOrder(ImGuiTable* table);
IMGUI_API void TableSortSpecsSanitize(ImGuiTable* table);
IMGUI_API void TableSortSpecsBuild(ImGuiTable* table);
IMGUI_API ImGuiSortDirection TableGetColumnNextSortDirection(ImGuiTableColumn* column);
@@ -3526,6 +3550,7 @@ namespace ImGui
IMGUI_API float TableCalcMaxColumnWidth(const ImGuiTable* table, int column_n);
IMGUI_API void TableSetColumnWidthAutoSingle(ImGuiTable* table, int column_n);
IMGUI_API void TableSetColumnWidthAutoAll(ImGuiTable* table);
IMGUI_API void TableSetColumnDisplayOrder(ImGuiTable* table, int column_n, int dst_order);
IMGUI_API void TableRemove(ImGuiTable* table);
IMGUI_API void TableGcCompactTransientBuffers(ImGuiTable* table);
IMGUI_API void TableGcCompactTransientBuffers(ImGuiTableTempData* table);
@@ -3574,6 +3599,7 @@ namespace ImGui
IMGUI_API void RenderTextEllipsis(ImDrawList* draw_list, const ImVec2& pos_min, const ImVec2& pos_max, float ellipsis_max_x, const char* text, const char* text_end, const ImVec2* text_size_if_known);
IMGUI_API void RenderFrame(ImVec2 p_min, ImVec2 p_max, ImU32 fill_col, bool borders = true, float rounding = 0.0f);
IMGUI_API void RenderFrameBorder(ImVec2 p_min, ImVec2 p_max, float rounding = 0.0f);
IMGUI_API void RenderColorComponentMarker(const ImRect& bb, ImU32 col, float rounding);
IMGUI_API void RenderColorRectWithAlphaCheckerboard(ImDrawList* draw_list, ImVec2 p_min, ImVec2 p_max, ImU32 fill_col, float grid_step, ImVec2 grid_off, float rounding = 0.0f, ImDrawFlags flags = 0);
IMGUI_API void RenderNavCursor(const ImRect& bb, ImGuiID id, ImGuiNavRenderCursorFlags flags = ImGuiNavRenderCursorFlags_None); // Navigation highlight
#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS
@@ -3587,8 +3613,9 @@ namespace ImGui
IMGUI_API void RenderBullet(ImDrawList* draw_list, ImVec2 pos, ImU32 col);
IMGUI_API void RenderCheckMark(ImDrawList* draw_list, ImVec2 pos, ImU32 col, float sz);
IMGUI_API void RenderArrowPointingAt(ImDrawList* draw_list, ImVec2 pos, ImVec2 half_sz, ImGuiDir direction, ImU32 col);
IMGUI_API void RenderRectFilledRangeH(ImDrawList* draw_list, const ImRect& rect, ImU32 col, float x_start_norm, float x_end_norm, float rounding);
IMGUI_API void RenderRectFilledInRangeH(ImDrawList* draw_list, const ImRect& rect, ImU32 col, float fill_x0, float fill_x1, float rounding);
IMGUI_API void RenderRectFilledWithHole(ImDrawList* draw_list, const ImRect& outer, const ImRect& inner, ImU32 col, float rounding);
IMGUI_API ImDrawFlags CalcRoundingFlagsForRectInRect(const ImRect& r_in, const ImRect& r_outer, float threshold);
// Widgets: Text
IMGUI_API void TextEx(const char* text, const char* text_end = NULL, ImGuiTextFlags flags = 0);
@@ -3653,7 +3680,7 @@ namespace ImGui
IMGUI_API void InputTextDeactivateHook(ImGuiID id);
IMGUI_API bool TempInputText(const ImRect& bb, ImGuiID id, const char* label, char* buf, int buf_size, ImGuiInputTextFlags flags);
IMGUI_API bool TempInputScalar(const ImRect& bb, ImGuiID id, const char* label, ImGuiDataType data_type, void* p_data, const char* format, const void* p_clamp_min = NULL, const void* p_clamp_max = NULL);
inline bool TempInputIsActive(ImGuiID id) { ImGuiContext& g = *GImGui; return (g.ActiveId == id && g.TempInputId == id); }
inline bool TempInputIsActive(ImGuiID id) { ImGuiContext& g = *GImGui; return g.ActiveId == id && g.TempInputId == id; }
inline ImGuiInputTextState* GetInputTextState(ImGuiID id) { ImGuiContext& g = *GImGui; return (id != 0 && g.InputTextState.ID == id) ? &g.InputTextState : NULL; } // Get input text state if active
IMGUI_API void SetNextItemRefVal(ImGuiDataType data_type, void* p_data);
inline bool IsItemActiveAsInputText() { ImGuiContext& g = *GImGui; return g.ActiveId != 0 && g.ActiveId == g.LastItemData.ID && g.InputTextState.ID == g.LastItemData.ID; } // This may be useful to apply workaround that a based on distinguish whenever an item is active as a text input field.
@@ -3662,6 +3689,7 @@ namespace ImGui
IMGUI_API void ColorTooltip(const char* text, const float* col, ImGuiColorEditFlags flags);
IMGUI_API void ColorEditOptionsPopup(const float* col, ImGuiColorEditFlags flags);
IMGUI_API void ColorPickerOptionsPopup(const float* ref_col, ImGuiColorEditFlags flags);
inline void SetNextItemColorMarker(ImU32 col) { ImGuiContext& g = *GImGui; g.NextItemData.HasFlags |= ImGuiNextItemDataFlags_HasColorMarker; g.NextItemData.ColorMarker = col; }
// Plot
IMGUI_API int PlotEx(ImGuiPlotType plot_type, const char* label, float (*values_getter)(void* data, int idx), void* data, int values_count, int values_offset, const char* overlay_text, float scale_min, float scale_max, const ImVec2& size_arg);
@@ -3699,6 +3727,7 @@ namespace ImGui
IMGUI_API bool DebugBreakButton(const char* label, const char* description_of_location);
IMGUI_API void DebugBreakButtonTooltip(bool keyboard_only, const char* description_of_location);
IMGUI_API void ShowFontAtlas(ImFontAtlas* atlas);
IMGUI_API ImU64 DebugTextureIDToU64(ImTextureID tex_id);
IMGUI_API void DebugHookIdInfo(ImGuiID id, ImGuiDataType data_type, const void* data_id, const void* data_id_end);
IMGUI_API void DebugNodeColumns(ImGuiOldColumns* columns);
IMGUI_API void DebugNodeDrawList(ImGuiWindow* window, ImGuiViewportP* viewport, const ImDrawList* draw_list, const char* label);
@@ -3762,14 +3791,14 @@ struct ImFontLoader
// FIXME: At this point the two other types of buffers may be managed by core to be consistent?
size_t FontBakedSrcLoaderDataSize;
ImFontLoader() { memset(this, 0, sizeof(*this)); }
ImFontLoader() { memset((void*)this, 0, sizeof(*this)); }
};
#ifdef IMGUI_ENABLE_STB_TRUETYPE
IMGUI_API const ImFontLoader* ImFontAtlasGetFontLoaderForStbTruetype();
#endif
#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS
typedef ImFontLoader ImFontBuilderIO; // [renamed/changed in 1.92] The types are not actually compatible but we provide this as a compile-time error report helper.
typedef ImFontLoader ImFontBuilderIO; // [renamed/changed in 1.92.0] The types are not actually compatible but we provide this as a compile-time error report helper.
#endif
//-----------------------------------------------------------------------------
@@ -3859,7 +3888,7 @@ struct ImFontAtlasBuilder
ImFontAtlasRectId PackIdMouseCursors; // White pixel + mouse cursors. Also happen to be fallback in case of packing failure.
ImFontAtlasRectId PackIdLinesTexData;
ImFontAtlasBuilder() { memset(this, 0, sizeof(*this)); FrameCount = -1; RectsIndexFreeListStart = -1; PackIdMouseCursors = PackIdLinesTexData = -1; }
ImFontAtlasBuilder() { memset((void*)this, 0, sizeof(*this)); FrameCount = -1; RectsIndexFreeListStart = -1; PackIdMouseCursors = PackIdLinesTexData = -1; }
};
IMGUI_API void ImFontAtlasBuildInit(ImFontAtlas* atlas);
@@ -3888,6 +3917,7 @@ IMGUI_API void ImFontAtlasFontSourceAddToFont(ImFontAtlas* atlas, I
IMGUI_API void ImFontAtlasFontDestroySourceData(ImFontAtlas* atlas, ImFontConfig* src);
IMGUI_API bool ImFontAtlasFontInitOutput(ImFontAtlas* atlas, ImFont* font); // Using FontDestroyOutput/FontInitOutput sequence useful notably if font loader params have changed
IMGUI_API void ImFontAtlasFontDestroyOutput(ImFontAtlas* atlas, ImFont* font);
IMGUI_API void ImFontAtlasFontRebuildOutput(ImFontAtlas* atlas, ImFont* font);
IMGUI_API void ImFontAtlasFontDiscardBakes(ImFontAtlas* atlas, ImFont* font, int unused_frames);
IMGUI_API ImGuiID ImFontAtlasBakedGetId(ImGuiID font_id, float baked_size, float rasterizer_density);
@@ -3913,6 +3943,7 @@ IMGUI_API void ImFontAtlasUpdateDrawListsTextures(ImFontAtlas* atla
IMGUI_API void ImFontAtlasUpdateDrawListsSharedData(ImFontAtlas* atlas);
IMGUI_API void ImFontAtlasTextureBlockConvert(const unsigned char* src_pixels, ImTextureFormat src_fmt, int src_pitch, unsigned char* dst_pixels, ImTextureFormat dst_fmt, int dst_pitch, int w, int h);
IMGUI_API void ImFontAtlasTextureBlockResize(const ImU32* src_pixels, const int src_w, const int src_h, ImU32* dst_pixels, const int dst_w, const int dst_h);
IMGUI_API void ImFontAtlasTextureBlockPostProcess(ImFontAtlasPostProcessData* data);
IMGUI_API void ImFontAtlasTextureBlockPostProcessMultiply(ImFontAtlasPostProcessData* data, float multiply_factor);
IMGUI_API void ImFontAtlasTextureBlockFill(ImTextureData* dst_tex, int dst_x, int dst_y, int w, int h, ImU32 col);
+95 -91
View File
@@ -1,4 +1,4 @@
// dear imgui, v1.92.5
// dear imgui, v1.92.6
// (tables and columns code)
/*
@@ -240,6 +240,7 @@ Index of this file:
#pragma GCC diagnostic ignored "-Wformat" // warning: format '%p' expects argument of type 'int'/'void*', but argument X has type 'unsigned int'/'ImGuiWindow*'
#pragma GCC diagnostic ignored "-Wstrict-overflow"
#pragma GCC diagnostic ignored "-Wclass-memaccess" // [__GNUC__ >= 8] warning: 'memset/memcpy' clearing/writing an object of type 'xxxx' with no trivial copy-assignment; use assignment or value-initialization instead
#pragma GCC diagnostic ignored "-Wsign-conversion" // warning: conversion to 'xxxx' from 'xxxx' may change the sign of the result
#endif
//-----------------------------------------------------------------------------
@@ -335,7 +336,7 @@ bool ImGui::BeginTableEx(const char* name, ImGuiID id, int columns_count, ImG
// - always performing the GetOrAddByKey() O(log N) query in g.Tables.Map[].
const bool use_child_window = (flags & (ImGuiTableFlags_ScrollX | ImGuiTableFlags_ScrollY)) != 0;
const ImVec2 avail_size = GetContentRegionAvail();
const ImVec2 actual_outer_size = ImTrunc(CalcItemSize(outer_size, ImMax(avail_size.x, 1.0f), use_child_window ? ImMax(avail_size.y, 1.0f) : 0.0f));
const ImVec2 actual_outer_size = ImTrunc(CalcItemSize(outer_size, ImMax(avail_size.x, IMGUI_WINDOW_HARD_MIN_SIZE), use_child_window ? ImMax(avail_size.y, IMGUI_WINDOW_HARD_MIN_SIZE) : 0.0f));
const ImRect outer_rect(outer_window->DC.CursorPos, outer_window->DC.CursorPos + actual_outer_size);
const bool outer_window_is_measuring_size = (outer_window->AutoFitFramesX > 0) || (outer_window->AutoFitFramesY > 0); // Doesn't apply to AlwaysAutoResize windows!
if (use_child_window && IsClippedEx(outer_rect, 0) && !outer_window_is_measuring_size)
@@ -564,9 +565,9 @@ bool ImGui::BeginTableEx(const char* name, ImGuiID id, int columns_count, ImG
const int old_columns_count = table->Columns.size();
if (old_columns_count != 0 && old_columns_count != columns_count)
{
// Attempt to preserve width on column count change (#4046)
// Attempt to preserve width and other settings on column count/specs change (#4046)
old_columns_to_preserve = table->Columns.Data;
old_columns_raw_data = table->RawData;
old_columns_raw_data = table->RawData; // Free at end of function
table->RawData = NULL;
}
if (table->RawData == NULL)
@@ -592,7 +593,6 @@ bool ImGui::BeginTableEx(const char* name, ImGuiID id, int columns_count, ImG
ImGuiTableColumn* column = &table->Columns[n];
if (old_columns_to_preserve && n < old_columns_count)
{
// FIXME: We don't attempt to preserve column order in this path.
*column = old_columns_to_preserve[n];
}
else
@@ -602,8 +602,9 @@ bool ImGui::BeginTableEx(const char* name, ImGuiID id, int columns_count, ImG
column->WidthAuto = width_auto;
column->IsPreserveWidthAuto = true; // Preserve WidthAuto when reinitializing a live table: not technically necessary but remove a visible flicker
column->IsEnabled = column->IsUserEnabled = column->IsUserEnabledNextFrame = true;
column->DisplayOrder = (ImGuiTableColumnIdx)n;
}
column->DisplayOrder = table->DisplayOrderToIndex[n] = (ImGuiTableColumnIdx)n;
table->DisplayOrderToIndex[n] = column->DisplayOrder;
}
}
if (old_columns_raw_data)
@@ -698,7 +699,7 @@ void ImGui::TableBeginApplyRequests(ImGuiTable* table)
}
// Handle reordering request
// Note: we don't clear ReorderColumn after handling the request.
// Note: we don't clear ReorderColumn after handling the request (FIXME: clarify why or add a test).
if (table->InstanceCurrent == 0)
{
if (table->HeldHeaderColumn == -1 && table->ReorderColumn != -1)
@@ -710,24 +711,12 @@ void ImGui::TableBeginApplyRequests(ImGuiTable* table)
// In the configuration below, moving C to the right of E will lead to:
// ... C [D] E ---> ... [D] E C (Column name/index)
// ... 2 3 4 ... 2 3 4 (Display order)
const int reorder_dir = table->ReorderColumnDir;
IM_ASSERT(reorder_dir == -1 || reorder_dir == +1);
IM_ASSERT(table->ReorderColumnDir == -1 || table->ReorderColumnDir == +1);
IM_ASSERT(table->Flags & ImGuiTableFlags_Reorderable);
ImGuiTableColumn* src_column = &table->Columns[table->ReorderColumn];
ImGuiTableColumn* dst_column = &table->Columns[(reorder_dir == -1) ? src_column->PrevEnabledColumn : src_column->NextEnabledColumn];
IM_UNUSED(dst_column);
const int src_order = src_column->DisplayOrder;
const int dst_order = dst_column->DisplayOrder;
src_column->DisplayOrder = (ImGuiTableColumnIdx)dst_order;
for (int order_n = src_order + reorder_dir; order_n != dst_order + reorder_dir; order_n += reorder_dir)
table->Columns[table->DisplayOrderToIndex[order_n]].DisplayOrder -= (ImGuiTableColumnIdx)reorder_dir;
IM_ASSERT(dst_column->DisplayOrder == dst_order - reorder_dir);
// Display order is stored in both columns->IndexDisplayOrder and table->DisplayOrder[]. Rebuild later from the former.
for (int column_n = 0; column_n < table->ColumnsCount; column_n++)
table->DisplayOrderToIndex[table->Columns[column_n].DisplayOrder] = (ImGuiTableColumnIdx)column_n;
ImGuiTableColumn* dst_column = &table->Columns[(table->ReorderColumnDir < 0) ? src_column->PrevEnabledColumn : src_column->NextEnabledColumn];
TableSetColumnDisplayOrder(table, table->ReorderColumn, dst_column->DisplayOrder);
table->ReorderColumnDir = 0;
table->IsSettingsDirty = true;
}
}
@@ -741,6 +730,31 @@ void ImGui::TableBeginApplyRequests(ImGuiTable* table)
}
}
// Note that TableSetupScrollFreeze() enforce a display order range for frozen columns.
// So reordering a column across the frozen column barrier is illegal and will be undone.
void ImGui::TableSetColumnDisplayOrder(ImGuiTable* table, int column_n, int dst_order)
{
IM_ASSERT(column_n >= 0 && column_n < table->ColumnsCount);
IM_ASSERT(dst_order >= 0 && dst_order < table->ColumnsCount);
ImGuiTableColumn* src_column = &table->Columns[column_n];
const int src_order = src_column->DisplayOrder;
if (src_order == dst_order)
return;
const int reorder_dir = (dst_order < src_order) ? -1 : +1;
src_column->DisplayOrder = (ImGuiTableColumnIdx)dst_order;
for (int order_n = src_order + reorder_dir; order_n != dst_order + reorder_dir; order_n += reorder_dir)
table->Columns[table->DisplayOrderToIndex[order_n]].DisplayOrder -= (ImGuiTableColumnIdx)reorder_dir;
//IM_ASSERT(dst_column->DisplayOrder == dst_order - reorder_dir);
// Display order is stored in both columns->IndexDisplayOrder and table->DisplayOrder[]. Rebuild later from the former.
// FIXME-OPT: If this is called multiple times we'd effectively have a O(N^2) thing going on.
for (int n = 0; n < table->ColumnsCount; n++)
table->DisplayOrderToIndex[table->Columns[n].DisplayOrder] = (ImGuiTableColumnIdx)n;
table->IsSettingsDirty = true;
}
// Adjust flags: default width mode + stretch columns are not allowed when auto extending
static void TableSetupColumnFlags(ImGuiTable* table, ImGuiTableColumn* column, ImGuiTableColumnFlags flags_in)
{
@@ -1348,11 +1362,7 @@ void ImGui::EndTable()
{
ImGuiContext& g = *GImGui;
ImGuiTable* table = g.CurrentTable;
if (table == NULL)
{
IM_ASSERT_USER_ERROR(table != NULL, "EndTable() call should only be done while in BeginTable() scope!");
return;
}
IM_ASSERT_USER_ERROR_RET(table != NULL, "EndTable() call should only be done while in BeginTable() scope!");
// This assert would be very useful to catch a common error... unfortunately it would probably trigger in some
// cases, and for consistency user may sometimes output empty tables (and still benefit from e.g. outer border)
@@ -1383,7 +1393,7 @@ void ImGui::EndTable()
inner_window->DC.PrevLineSize = temp_data->HostBackupPrevLineSize;
inner_window->DC.CurrLineSize = temp_data->HostBackupCurrLineSize;
inner_window->DC.CursorMaxPos = temp_data->HostBackupCursorMaxPos;
const float inner_content_max_y = table->RowPosY2;
const float inner_content_max_y = ImCeil(table->RowPosY2); // Rounding final position is important as we currently don't round row height ('Demo->Tables->Outer Size' demo uses non-integer heights)
IM_ASSERT(table->RowPosY2 == inner_window->DC.CursorPos.y);
if (inner_window != outer_window)
inner_window->DC.CursorMaxPos.y = inner_content_max_y;
@@ -1601,18 +1611,10 @@ void ImGui::TableSetupColumn(const char* label, ImGuiTableColumnFlags flags, flo
{
ImGuiContext& g = *GImGui;
ImGuiTable* table = g.CurrentTable;
if (table == NULL)
{
IM_ASSERT_USER_ERROR(table != NULL, "Call should only be done while in BeginTable() scope!");
return;
}
IM_ASSERT(table->IsLayoutLocked == false && "Need to call TableSetupColumn() before first row!");
IM_ASSERT_USER_ERROR_RET(table != NULL, "Call should only be done while in BeginTable() scope!");
IM_ASSERT_USER_ERROR_RET(table->DeclColumnsCount < table->ColumnsCount, "TableSetupColumn(): called too many times!");
IM_ASSERT_USER_ERROR_RET(table->IsLayoutLocked == false, "TableSetupColumn(): need to call before first row!");
IM_ASSERT((flags & ImGuiTableColumnFlags_StatusMask_) == 0 && "Illegal to pass StatusMask values to TableSetupColumn()");
if (table->DeclColumnsCount >= table->ColumnsCount)
{
IM_ASSERT_USER_ERROR(table->DeclColumnsCount < table->ColumnsCount, "Called TableSetupColumn() too many times!");
return;
}
ImGuiTableColumn* column = &table->Columns[table->DeclColumnsCount];
table->DeclColumnsCount++;
@@ -1620,7 +1622,7 @@ void ImGui::TableSetupColumn(const char* label, ImGuiTableColumnFlags flags, flo
// Assert when passing a width or weight if policy is entirely left to default, to avoid storing width into weight and vice-versa.
// Give a grace to users of ImGuiTableFlags_ScrollX.
if (table->IsDefaultSizingPolicy && (flags & ImGuiTableColumnFlags_WidthMask_) == 0 && (flags & ImGuiTableFlags_ScrollX) == 0)
IM_ASSERT(init_width_or_weight <= 0.0f && "Can only specify width/weight if sizing policy is set explicitly in either Table or Column.");
IM_ASSERT_USER_ERROR_RET(init_width_or_weight <= 0.0f, "TableSetupColumn(): can only specify width/weight if sizing policy is set explicitly in either Table or Column.");
// When passing a width automatically enforce WidthFixed policy
// (whereas TableSetupColumnFlags would default to WidthAuto if table is not resizable)
@@ -1662,12 +1664,8 @@ void ImGui::TableSetupScrollFreeze(int columns, int rows)
{
ImGuiContext& g = *GImGui;
ImGuiTable* table = g.CurrentTable;
if (table == NULL)
{
IM_ASSERT_USER_ERROR(table != NULL, "Call should only be done while in BeginTable() scope!");
return;
}
IM_ASSERT(table->IsLayoutLocked == false && "Need to call TableSetupColumn() before first row!");
IM_ASSERT_USER_ERROR_RET(table != NULL, "Call should only be done while in BeginTable() scope!");
IM_ASSERT(table->IsLayoutLocked == false && "TableSetupColumn(): need to call before first row!");
IM_ASSERT(columns >= 0 && columns < IMGUI_TABLE_MAX_COLUMNS);
IM_ASSERT(rows >= 0 && rows < 128); // Arbitrary limit
@@ -1743,11 +1741,7 @@ void ImGui::TableSetColumnEnabled(int column_n, bool enabled)
{
ImGuiContext& g = *GImGui;
ImGuiTable* table = g.CurrentTable;
if (table == NULL)
{
IM_ASSERT_USER_ERROR(table != NULL, "Call should only be done while in BeginTable() scope!");
return;
}
IM_ASSERT_USER_ERROR_RET(table != NULL, "Call should only be done while in BeginTable() scope!");
IM_ASSERT(table->Flags & ImGuiTableFlags_Hideable); // See comments above
if (column_n < 0)
column_n = table->CurrentColumn;
@@ -1825,12 +1819,8 @@ void ImGui::TableSetBgColor(ImGuiTableBgTarget target, ImU32 color, int column_n
{
ImGuiContext& g = *GImGui;
ImGuiTable* table = g.CurrentTable;
IM_ASSERT_USER_ERROR_RET(table != NULL, "Call should only be done while in BeginTable() scope!");
IM_ASSERT(target != ImGuiTableBgTarget_None);
if (table == NULL)
{
IM_ASSERT_USER_ERROR(table != NULL, "Call should only be done while in BeginTable() scope!");
return;
}
if (color == IM_COL32_DISABLE)
color = 0;
@@ -2128,11 +2118,7 @@ bool ImGui::TableSetColumnIndex(int column_n)
{
if (table->CurrentColumn != -1)
TableEndCell(table);
if ((column_n >= 0 && column_n < table->ColumnsCount) == false)
{
IM_ASSERT_USER_ERROR(column_n >= 0 && column_n < table->ColumnsCount, "TableSetColumnIndex() invalid column index!");
return false;
}
IM_ASSERT_USER_ERROR_RETV(column_n >= 0 && column_n < table->ColumnsCount, false, "TableSetColumnIndex() invalid column index!");
TableBeginCell(table, column_n);
}
@@ -2458,6 +2444,11 @@ void ImGui::TableUpdateColumnsWeightFromWidth(ImGuiTable* table)
// - TableDrawBorders() [Internal]
//-------------------------------------------------------------------------
// FIXME: This could be abstracted and merged with PushColumnsBackground(), by creating a generic struct
// with storage for backup cliprect + backup channel + storage for splitter pointer, new clip rect.
// This would slightly simplify caller code.
// Bg2 is used by Selectable (and possibly other widgets) to render to the background.
// Unlike our Bg0/1 channel which we uses for RowBg/CellBg/Borders and where we guarantee all shapes to be CPU-clipped, the Bg2 channel being widgets-facing will rely on regular ClipRect.
void ImGui::TablePushBackgroundChannel()
@@ -2619,7 +2610,7 @@ void ImGui::TableMergeDrawChannels(ImGuiTable* table)
const int size_for_masks_bitarrays_one = (int)ImBitArrayGetStorageSizeInBytes(max_draw_channels);
g.TempBuffer.reserve(size_for_masks_bitarrays_one * 5);
memset(g.TempBuffer.Data, 0, size_for_masks_bitarrays_one * 5);
for (int n = 0; n < IM_ARRAYSIZE(merge_groups); n++)
for (int n = 0; n < IM_COUNTOF(merge_groups); n++)
merge_groups[n].ChannelsMask = (ImBitArrayPtr)(void*)(g.TempBuffer.Data + (size_for_masks_bitarrays_one * n));
ImBitArrayPtr remaining_mask = (ImBitArrayPtr)(void*)(g.TempBuffer.Data + (size_for_masks_bitarrays_one * 4));
@@ -2676,7 +2667,7 @@ void ImGui::TableMergeDrawChannels(ImGuiTable* table)
// [DEBUG] Display merge groups
#if 0
if (g.IO.KeyShift)
for (int merge_group_n = 0; merge_group_n < IM_ARRAYSIZE(merge_groups); merge_group_n++)
for (int merge_group_n = 0; merge_group_n < IM_COUNTOF(merge_groups); merge_group_n++)
{
MergeGroup* merge_group = &merge_groups[merge_group_n];
if (merge_group->ChannelsCount == 0)
@@ -2704,7 +2695,7 @@ void ImGui::TableMergeDrawChannels(ImGuiTable* table)
int remaining_count = splitter->_Count - (has_freeze_v ? LEADING_DRAW_CHANNELS + 1 : LEADING_DRAW_CHANNELS);
//ImRect host_rect = (table->InnerWindow == table->OuterWindow) ? table->InnerClipRect : table->HostClipRect;
ImRect host_rect = table->HostClipRect;
for (int merge_group_n = 0; merge_group_n < IM_ARRAYSIZE(merge_groups); merge_group_n++)
for (int merge_group_n = 0; merge_group_n < IM_COUNTOF(merge_groups); merge_group_n++)
{
if (int merge_channels_count = merge_groups[merge_group_n].ChannelsCount)
{
@@ -3113,11 +3104,7 @@ void ImGui::TableHeadersRow()
{
ImGuiContext& g = *GImGui;
ImGuiTable* table = g.CurrentTable;
if (table == NULL)
{
IM_ASSERT_USER_ERROR(table != NULL, "Call should only be done while in BeginTable() scope!");
return;
}
IM_ASSERT_USER_ERROR_RET(table != NULL, "Call should only be done while in BeginTable() scope!");
// Call layout if not already done. This is automatically done by TableNextRow: we do it here _only_ to make
// it easier to debug-step in TableUpdateLayout(). Your own version of this function doesn't need this.
@@ -3162,12 +3149,7 @@ void ImGui::TableHeader(const char* label)
return;
ImGuiTable* table = g.CurrentTable;
if (table == NULL)
{
IM_ASSERT_USER_ERROR(table != NULL, "Call should only be done while in BeginTable() scope!");
return;
}
IM_ASSERT_USER_ERROR_RET(table != NULL, "Call should only be done while in BeginTable() scope!");
IM_ASSERT(table->CurrentColumn != -1);
const int column_n = table->CurrentColumn;
ImGuiTableColumn* column = &table->Columns[column_n];
@@ -3197,7 +3179,7 @@ void ImGui::TableHeader(const char* label)
sort_arrow = true;
if (column->SortOrder > 0)
{
ImFormatString(sort_order_suf, IM_ARRAYSIZE(sort_order_suf), "%d", column->SortOrder + 1);
ImFormatString(sort_order_suf, IM_COUNTOF(sort_order_suf), "%d", column->SortOrder + 1);
w_sort_text = g.Style.ItemInnerSpacing.x + CalcTextSize(sort_order_suf).x;
}
}
@@ -3342,11 +3324,7 @@ void ImGui::TableAngledHeadersRowEx(ImGuiID row_id, float angle, float max_label
ImGuiTable* table = g.CurrentTable;
ImGuiWindow* window = g.CurrentWindow;
ImDrawList* draw_list = window->DrawList;
if (table == NULL)
{
IM_ASSERT_USER_ERROR(table != NULL, "Call should only be done while in BeginTable() scope!");
return;
}
IM_ASSERT_USER_ERROR_RET(table != NULL, "Call should only be done while in BeginTable() scope!");
IM_ASSERT(table->CurrentRow == -1 && "Must be first row");
if (max_label_width == 0.0f)
@@ -3787,7 +3765,6 @@ void ImGui::TableLoadSettings(ImGuiTable* table)
// Serialize ImGuiTableSettings/ImGuiTableColumnSettings into ImGuiTable/ImGuiTableColumn
ImGuiTableColumnSettings* column_settings = settings->GetColumnSettings();
ImU64 display_order_mask = 0;
for (int data_n = 0; data_n < settings->ColumnsCount; data_n++, column_settings++)
{
int column_n = column_settings->Index;
@@ -3804,24 +3781,51 @@ void ImGui::TableLoadSettings(ImGuiTable* table)
}
if (settings->SaveFlags & ImGuiTableFlags_Reorderable)
column->DisplayOrder = column_settings->DisplayOrder;
display_order_mask |= (ImU64)1 << column->DisplayOrder;
if ((settings->SaveFlags & ImGuiTableFlags_Hideable) && column_settings->IsEnabled != -1)
column->IsUserEnabled = column->IsUserEnabledNextFrame = column_settings->IsEnabled == 1;
column->SortOrder = column_settings->SortOrder;
column->SortDirection = column_settings->SortDirection;
}
// Validate and fix invalid display order data
const ImU64 expected_display_order_mask = (settings->ColumnsCount == 64) ? ~0 : ((ImU64)1 << settings->ColumnsCount) - 1;
if (display_order_mask != expected_display_order_mask)
for (int column_n = 0; column_n < table->ColumnsCount; column_n++)
table->Columns[column_n].DisplayOrder = (ImGuiTableColumnIdx)column_n;
// Rebuild index
// Fix display order and build index
if (settings->SaveFlags & ImGuiTableFlags_Reorderable)
TableFixDisplayOrder(table);
for (int column_n = 0; column_n < table->ColumnsCount; column_n++)
table->DisplayOrderToIndex[table->Columns[column_n].DisplayOrder] = (ImGuiTableColumnIdx)column_n;
}
struct ImGuiTableFixDisplayOrderColumnData
{
ImGuiTableColumnIdx Idx;
ImGuiTable* Table; // This is unfortunate but we don't have userdata in qsort api.
};
// Sort by DisplayOrder and then Index
static int IMGUI_CDECL TableFixDisplayOrderComparer(const void* lhs, const void* rhs)
{
const ImGuiTable* table = ((const ImGuiTableFixDisplayOrderColumnData*)lhs)->Table;
const ImGuiTableColumnIdx lhs_idx = ((const ImGuiTableFixDisplayOrderColumnData*)lhs)->Idx;
const ImGuiTableColumnIdx rhs_idx = ((const ImGuiTableFixDisplayOrderColumnData*)rhs)->Idx;
const int order_delta = (table->Columns[lhs_idx].DisplayOrder - table->Columns[rhs_idx].DisplayOrder);
return (order_delta > 0) ? +1 : (order_delta < 0) ? -1 : (lhs_idx > rhs_idx) ? +1 : -1;
}
// Fix invalid display order data: compact values (0,1,3 -> 0,1,2); preserve relative order (0,3,1 -> 0,2,1); deduplicate (0,4,1,1 -> 0,3,1,2)
void ImGui::TableFixDisplayOrder(ImGuiTable* table)
{
ImGuiContext& g = *GImGui;
g.TempBuffer.reserve((int)(sizeof(ImGuiTableFixDisplayOrderColumnData) * table->ColumnsCount)); // FIXME: Maybe wrap those two lines as a helper.
ImGuiTableFixDisplayOrderColumnData* fdo_columns = (ImGuiTableFixDisplayOrderColumnData*)(void*)g.TempBuffer.Data;
for (int n = 0; n < table->ColumnsCount; n++)
{
fdo_columns[n].Idx = (ImGuiTableColumnIdx)n;
fdo_columns[n].Table = table;
}
ImQsort(fdo_columns, (size_t)table->ColumnsCount, sizeof(ImGuiTableFixDisplayOrderColumnData), TableFixDisplayOrderComparer);
for (int n = 0; n < table->ColumnsCount; n++)
table->Columns[fdo_columns[n].Idx].DisplayOrder = (ImGuiTableColumnIdx)n;
}
static void TableSettingsHandler_ClearAll(ImGuiContext* ctx, ImGuiSettingsHandler*)
{
ImGuiContext& g = *ctx;
@@ -4063,7 +4067,7 @@ void ImGui::DebugNodeTable(ImGuiTable* table)
ImGuiTableColumn* column = &table->Columns[n];
const char* name = TableGetColumnName(table, n);
char buf[512];
ImFormatString(buf, IM_ARRAYSIZE(buf),
ImFormatString(buf, IM_COUNTOF(buf),
"Column %d order %d '%s': offset %+.2f to %+.2f%s\n"
"Enabled: %d, VisibleX/Y: %d/%d, RequestOutput: %d, SkipItems: %d, DrawChannels: %d,%d\n"
"WidthGiven: %.1f, Request/Auto: %.1f/%.1f, StretchWeight: %.3f (%.1f%%)\n"
+164 -136
View File
@@ -1,4 +1,4 @@
// dear imgui, v1.92.5
// dear imgui, v1.92.6
// (widgets code)
/*
@@ -43,6 +43,7 @@ Index of this file:
#include "imgui.h"
#ifndef IMGUI_DISABLE
#include "imgui_internal.h"
#include "imgui_cjk.h"
// System includes
#include <stdint.h> // intptr_t
@@ -92,6 +93,7 @@ Index of this file:
#pragma GCC diagnostic ignored "-Wstrict-overflow" // warning: assuming signed overflow does not occur when simplifying division / ..when changing X +- C1 cmp C2 to X cmp C2 -+ C1
#pragma GCC diagnostic ignored "-Wclass-memaccess" // [__GNUC__ >= 8] warning: 'memset/memcpy' clearing/writing an object of type 'xxxx' with no trivial copy-assignment; use assignment or value-initialization instead
#pragma GCC diagnostic ignored "-Wcast-qual" // warning: cast from type 'const xxxx *' to type 'xxxx *' casts away qualifiers
#pragma GCC diagnostic ignored "-Wsign-conversion" // warning: conversion to 'xxxx' from 'xxxx' may change the sign of the result
#endif
//-------------------------------------------------------------------------
@@ -134,7 +136,7 @@ static const ImU64 IM_U64_MAX = (2ULL * 9223372036854775807LL + 1);
//-------------------------------------------------------------------------
// For InputTextEx()
static bool InputTextFilterCharacter(ImGuiContext* ctx, unsigned int* p_char, ImGuiInputTextFlags flags, ImGuiInputTextCallback callback, void* user_data, bool input_source_is_clipboard = false);
static bool InputTextFilterCharacter(ImGuiContext* ctx, ImGuiInputTextState* state, unsigned int* p_char, ImGuiInputTextCallback callback, void* user_data, bool input_source_is_clipboard = false);
static ImVec2 InputTextCalcTextSize(ImGuiContext* ctx, const char* text_begin, const char* text_end_display, const char* text_end, const char** out_remaining = NULL, ImVec2* out_offset = NULL, ImDrawTextFlags flags = 0);
//-------------------------------------------------------------------------
@@ -179,7 +181,7 @@ void ImGui::TextEx(const char* text, const char* text_end, ImGuiTextFlags flags)
{
// Common case
const float wrap_width = wrap_enabled ? CalcWrapWidthForPos(window->DC.CursorPos, wrap_pos_x) : 0.0f;
const ImVec2 text_size = CalcTextSize(text_begin, text_end, false, wrap_width);
const ImVec2 text_size = CalcTextSizeCJK(g.Font, g.FontSize, FLT_MAX, wrap_width, text_begin, text_end);
ImRect bb(text_pos, text_pos + text_size);
ItemSize(text_size, 0.0f);
@@ -187,7 +189,7 @@ void ImGui::TextEx(const char* text, const char* text_end, ImGuiTextFlags flags)
return;
// Render (we don't hide text after ## in this end-user function)
RenderTextWrapped(bb.Min, text_begin, text_end, wrap_width);
RenderTextWrappedCJK(bb.Min, text_begin, text_end, wrap_width);
}
else
{
@@ -861,11 +863,10 @@ bool ImGui::InvisibleButton(const char* str_id, const ImVec2& size_arg, ImGuiBut
if (window->SkipItems)
return false;
// Cannot use zero-size for InvisibleButton(). Unlike Button() there is not way to fallback using the label size.
IM_ASSERT(size_arg.x != 0.0f && size_arg.y != 0.0f);
// Ensure zero-size fits to contents
ImVec2 size = CalcItemSize(ImVec2(size_arg.x != 0.0f ? size_arg.x : -FLT_MIN, size_arg.y != 0.0f ? size_arg.y : -FLT_MIN), 0.0f, 0.0f);
const ImGuiID id = window->GetID(str_id);
ImVec2 size = CalcItemSize(size_arg, 0.0f, 0.0f);
const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + size);
ItemSize(size);
if (!ItemAdd(bb, id, NULL, (flags & ImGuiButtonFlags_EnableNav) ? ImGuiItemFlags_None : ImGuiItemFlags_NoNav))
@@ -1007,20 +1008,7 @@ void ImGui::Scrollbar(ImGuiAxis axis)
// Calculate scrollbar bounding box
ImRect bb = GetWindowScrollbarRect(window, axis);
ImDrawFlags rounding_corners = ImDrawFlags_RoundCornersNone;
if (axis == ImGuiAxis_X)
{
rounding_corners |= ImDrawFlags_RoundCornersBottomLeft;
if (!window->ScrollbarY)
rounding_corners |= ImDrawFlags_RoundCornersBottomRight;
}
else
{
if ((window->Flags & ImGuiWindowFlags_NoTitleBar) && !(window->Flags & ImGuiWindowFlags_MenuBar))
rounding_corners |= ImDrawFlags_RoundCornersTopRight;
if (!window->ScrollbarX)
rounding_corners |= ImDrawFlags_RoundCornersBottomRight;
}
ImDrawFlags rounding_corners = CalcRoundingFlagsForRectInRect(bb, window->Rect(), g.Style.WindowBorderSize);
float size_visible = window->InnerRect.Max[axis] - window->InnerRect.Min[axis];
float size_contents = window->ContentSize[axis] + window->WindowPadding[axis] * 2.0f;
ImS64 scroll = (ImS64)window->Scroll[axis];
@@ -1062,6 +1050,8 @@ bool ImGui::ScrollbarEx(const ImRect& bb_frame, ImGuiID id, ImGuiAxis axis, ImS6
// V denote the main, longer axis of the scrollbar (= height for a vertical scrollbar)
const float scrollbar_size_v = (axis == ImGuiAxis_X) ? bb.GetWidth() : bb.GetHeight();
if (scrollbar_size_v < 1.0f)
return false;
// Calculate the height of our grabbable box. It generally represent the amount visible (vs the total scrollable amount)
// But we maintain a minimum size in pixel to allow for the user to still aim inside.
@@ -1154,11 +1144,15 @@ void ImGui::ImageWithBg(ImTextureRef tex_ref, const ImVec2& image_size, const Im
return;
// Render
if (g.Style.ImageBorderSize > 0.0f)
window->DrawList->AddRect(bb.Min, bb.Max, GetColorU32(ImGuiCol_Border), 0.0f, ImDrawFlags_None, g.Style.ImageBorderSize);
float rounding = g.Style.ImageRounding;
if (bg_col.w > 0.0f)
window->DrawList->AddRectFilled(bb.Min + padding, bb.Max - padding, GetColorU32(bg_col));
window->DrawList->AddImage(tex_ref, bb.Min + padding, bb.Max - padding, uv0, uv1, GetColorU32(tint_col));
window->DrawList->AddRectFilled(bb.Min + padding, bb.Max - padding, GetColorU32(bg_col), rounding);
if (rounding > 0.0f)
window->DrawList->AddImageRounded(tex_ref, bb.Min + padding, bb.Max - padding, uv0, uv1, GetColorU32(tint_col), rounding);
else
window->DrawList->AddImage(tex_ref, bb.Min + padding, bb.Max - padding, uv0, uv1, GetColorU32(tint_col));
if (g.Style.ImageBorderSize > 0.0f)
window->DrawList->AddRect(bb.Min, bb.Max, GetColorU32(ImGuiCol_Border), rounding, ImDrawFlags_None, g.Style.ImageBorderSize);
}
void ImGui::Image(ImTextureRef tex_ref, const ImVec2& image_size, const ImVec2& uv0, const ImVec2& uv1)
@@ -1198,10 +1192,14 @@ bool ImGui::ImageButtonEx(ImGuiID id, ImTextureRef tex_ref, const ImVec2& image_
// Render
const ImU32 col = GetColorU32((held && hovered) ? ImGuiCol_ButtonActive : hovered ? ImGuiCol_ButtonHovered : ImGuiCol_Button);
RenderNavCursor(bb, id);
RenderFrame(bb.Min, bb.Max, col, true, ImClamp((float)ImMin(padding.x, padding.y), 0.0f, g.Style.FrameRounding));
RenderFrame(bb.Min, bb.Max, col, true, g.Style.FrameRounding);
if (bg_col.w > 0.0f)
window->DrawList->AddRectFilled(bb.Min + padding, bb.Max - padding, GetColorU32(bg_col));
window->DrawList->AddImage(tex_ref, bb.Min + padding, bb.Max - padding, uv0, uv1, GetColorU32(tint_col));
float image_rounding = ImMax(g.Style.FrameRounding - ImMax(padding.x, padding.y), g.Style.ImageRounding);
if (image_rounding > 0.0f)
window->DrawList->AddImageRounded(tex_ref, bb.Min + padding, bb.Max - padding, uv0, uv1, GetColorU32(tint_col), image_rounding);
else
window->DrawList->AddImage(tex_ref, bb.Min + padding, bb.Max - padding, uv0, uv1, GetColorU32(tint_col));
return pressed;
}
@@ -1463,7 +1461,10 @@ void ImGui::ProgressBar(float fraction, const ImVec2& size_arg, const char* over
// Render
RenderFrame(bb.Min, bb.Max, GetColorU32(ImGuiCol_FrameBg), true, style.FrameRounding);
bb.Expand(ImVec2(-style.FrameBorderSize, -style.FrameBorderSize));
RenderRectFilledRangeH(window->DrawList, bb, GetColorU32(ImGuiCol_PlotHistogram), fill_n0, fill_n1, style.FrameRounding);
float fill_x0 = ImLerp(bb.Min.x, bb.Max.x, fill_n0);
float fill_x1 = ImLerp(bb.Min.x, bb.Max.x, fill_n1);
if (fill_x0 < fill_x1)
RenderRectFilledInRangeH(window->DrawList, bb, GetColorU32(ImGuiCol_PlotHistogram), fill_x0, fill_x1, style.FrameRounding);
// Default displaying the fraction as percentage string, but user can override it
// Don't display text for indeterminate bars by default
@@ -1472,14 +1473,14 @@ void ImGui::ProgressBar(float fraction, const ImVec2& size_arg, const char* over
{
if (!overlay)
{
ImFormatString(overlay_buf, IM_ARRAYSIZE(overlay_buf), "%.0f%%", fraction * 100 + 0.01f);
ImFormatString(overlay_buf, IM_COUNTOF(overlay_buf), "%.0f%%", fraction * 100 + 0.01f);
overlay = overlay_buf;
}
ImVec2 overlay_size = CalcTextSize(overlay, NULL);
if (overlay_size.x > 0.0f)
{
float text_x = is_indeterminate ? (bb.Min.x + bb.Max.x - overlay_size.x) * 0.5f : ImLerp(bb.Min.x, bb.Max.x, fill_n1) + style.ItemSpacing.x;
float text_x = is_indeterminate ? (bb.Min.x + bb.Max.x - overlay_size.x) * 0.5f : fill_x1 + style.ItemSpacing.x;
RenderTextClipped(ImVec2(ImClamp(text_x, bb.Min.x, bb.Max.x - overlay_size.x - style.ItemInnerSpacing.x), bb.Min.y), bb.Max, overlay, NULL, &overlay_size, ImVec2(0.0f, 0.5f), &bb);
}
}
@@ -1740,7 +1741,7 @@ void ImGui::SeparatorTextEx(ImGuiID id, const char* label, const char* label_end
const float separator_thickness = style.SeparatorTextBorderSize;
const ImVec2 min_size(label_size.x + extra_w + padding.x * 2.0f, ImMax(label_size.y + padding.y * 2.0f, separator_thickness));
const ImRect bb(pos, ImVec2(window->WorkRect.Max.x, pos.y + min_size.y));
const float text_baseline_y = ImTrunc((bb.GetHeight() - label_size.y) * style.SeparatorTextAlign.y + 0.99999f); //ImMax(padding.y, ImFloor((style.SeparatorTextSize - label_size.y) * 0.5f));
const float text_baseline_y = ImTrunc((bb.GetHeight() - label_size.y) * style.SeparatorTextAlign.y + 0.99999f); //ImMax(padding.y, ImTrunc((style.SeparatorTextSize - label_size.y) * 0.5f));
ItemSize(min_size, text_baseline_y);
if (!ItemAdd(bb, id))
return;
@@ -1857,7 +1858,7 @@ static int IMGUI_CDECL ShrinkWidthItemComparer(const void* lhs, const void* rhs)
const ImGuiShrinkWidthItem* b = (const ImGuiShrinkWidthItem*)rhs;
if (int d = (int)(b->Width - a->Width))
return d;
return (b->Index - a->Index);
return b->Index - a->Index;
}
// Shrink excess width from a set of item, by removing width from the larger items first.
@@ -2039,7 +2040,7 @@ bool ImGui::BeginComboPopup(ImGuiID popup_id, const ImRect& bb, ImGuiComboFlags
// This is essentially a specialized version of BeginPopupEx()
char name[16];
ImFormatString(name, IM_ARRAYSIZE(name), "##Combo_%02d", g.BeginComboDepth); // Recycle windows based on depth
ImFormatString(name, IM_COUNTOF(name), "##Combo_%02d", g.BeginComboDepth); // Recycle windows based on depth
// Set position given a custom constraint (peak into expected window size so we can position it)
// FIXME: This might be easier to express with an hypothetical SetNextWindowPosConstraints() function?
@@ -2074,8 +2075,12 @@ bool ImGui::BeginComboPopup(ImGuiID popup_id, const ImRect& bb, ImGuiComboFlags
void ImGui::EndCombo()
{
ImGuiContext& g = *GImGui;
EndPopup();
g.BeginComboDepth--;
char name[16];
ImFormatString(name, IM_COUNTOF(name), "##Combo_%02d", g.BeginComboDepth); // FIXME: Move those to helpers?
if (strcmp(g.CurrentWindow->Name, name) != 0)
IM_ASSERT_USER_ERROR_RET(0, "Calling EndCombo() in wrong window!");
EndPopup();
}
// Call directly after the BeginCombo/EndCombo block. The preview is designed to only host non-interactive elements
@@ -2260,6 +2265,11 @@ bool ImGui::Combo(const char* label, int* current_item, bool (*old_getter)(void*
// - RoundScalarWithFormat<>()
//-------------------------------------------------------------------------
static const ImU32 GDefaultRgbaColorMarkers[4] =
{
IM_COL32(240,20,20,255), IM_COL32(20,240,20,255), IM_COL32(20,20,240,255), IM_COL32(140,140,140,255)
};
static const ImGuiDataTypeInfo GDataTypeInfo[] =
{
{ sizeof(char), "S8", "%d", "%d" }, // ImGuiDataType_S8
@@ -2280,7 +2290,7 @@ static const ImGuiDataTypeInfo GDataTypeInfo[] =
{ sizeof(bool), "bool", "%d", "%d" }, // ImGuiDataType_Bool
{ 0, "char*","%s", "%s" }, // ImGuiDataType_String
};
IM_STATIC_ASSERT(IM_ARRAYSIZE(GDataTypeInfo) == ImGuiDataType_COUNT);
IM_STATIC_ASSERT(IM_COUNTOF(GDataTypeInfo) == ImGuiDataType_COUNT);
const ImGuiDataTypeInfo* ImGui::DataTypeGetInfo(ImGuiDataType data_type)
{
@@ -2389,7 +2399,7 @@ bool ImGui::DataTypeApplyFromText(const char* buf, ImGuiDataType data_type, void
if (data_type == ImGuiDataType_Float || data_type == ImGuiDataType_Double)
format = type_info->ScanFmt;
else
format = ImParseFormatSanitizeForScanning(format, format_sanitized, IM_ARRAYSIZE(format_sanitized));
format = ImParseFormatSanitizeForScanning(format, format_sanitized, IM_COUNTOF(format_sanitized));
// Small types need a 32-bit buffer to receive the result from scanf()
int v32 = 0;
@@ -2480,7 +2490,7 @@ static float GetMinimumStepAtDecimalPrecision(int decimal_precision)
static const float min_steps[10] = { 1.0f, 0.1f, 0.01f, 0.001f, 0.0001f, 0.00001f, 0.000001f, 0.0000001f, 0.00000001f, 0.000000001f };
if (decimal_precision < 0)
return FLT_MIN;
return (decimal_precision < IM_ARRAYSIZE(min_steps)) ? min_steps[decimal_precision] : ImPow(10.0f, (float)-decimal_precision);
return (decimal_precision < IM_COUNTOF(min_steps)) ? min_steps[decimal_precision] : ImPow(10.0f, (float)-decimal_precision);
}
template<typename TYPE>
@@ -2494,12 +2504,12 @@ TYPE ImGui::RoundScalarWithFormatT(const char* format, ImGuiDataType data_type,
// Sanitize format
char fmt_sanitized[32];
ImParseFormatSanitizeForPrinting(fmt_start, fmt_sanitized, IM_ARRAYSIZE(fmt_sanitized));
ImParseFormatSanitizeForPrinting(fmt_start, fmt_sanitized, IM_COUNTOF(fmt_sanitized));
fmt_start = fmt_sanitized;
// Format value with our rounding, and read back
char v_str[64];
ImFormatString(v_str, IM_ARRAYSIZE(v_str), fmt_start, v);
ImFormatString(v_str, IM_COUNTOF(v_str), fmt_start, v);
const char* p = v_str;
while (*p == ' ')
p++;
@@ -2708,6 +2718,7 @@ bool ImGui::DragScalar(const char* label, ImGuiDataType data_type, void* p_data,
const ImGuiStyle& style = g.Style;
const ImGuiID id = window->GetID(label);
const float w = CalcItemWidth();
const ImU32 color_marker = (g.NextItemData.HasFlags & ImGuiNextItemDataFlags_HasColorMarker) ? g.NextItemData.ColorMarker : 0;
const ImVec2 label_size = CalcTextSize(label, NULL, true);
const ImRect frame_bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(w, label_size.y + style.FramePadding.y * 2.0f));
@@ -2776,7 +2787,10 @@ bool ImGui::DragScalar(const char* label, ImGuiDataType data_type, void* p_data,
// Draw frame
const ImU32 frame_col = GetColorU32(g.ActiveId == id ? ImGuiCol_FrameBgActive : hovered ? ImGuiCol_FrameBgHovered : ImGuiCol_FrameBg);
RenderNavCursor(frame_bb, id);
RenderFrame(frame_bb.Min, frame_bb.Max, frame_col, true, style.FrameRounding);
RenderFrame(frame_bb.Min, frame_bb.Max, frame_col, false, style.FrameRounding);
if (color_marker != 0 && style.ColorMarkerSize > 0.0f)
RenderColorComponentMarker(frame_bb, GetColorU32(color_marker), style.FrameRounding);
RenderFrameBorder(frame_bb.Min, frame_bb.Max, g.Style.FrameRounding);
// Drag behavior
const bool value_changed = DragBehavior(id, data_type, p_data, v_speed, p_min, p_max, format, flags);
@@ -2785,7 +2799,7 @@ bool ImGui::DragScalar(const char* label, ImGuiDataType data_type, void* p_data,
// Display value using user-provided display format so user can add prefix/suffix/decorations to the value.
char value_buf[64];
const char* value_buf_end = value_buf + DataTypeFormatString(value_buf, IM_ARRAYSIZE(value_buf), data_type, p_data, format);
const char* value_buf_end = value_buf + DataTypeFormatString(value_buf, IM_COUNTOF(value_buf), data_type, p_data, format);
if (g.LogEnabled)
LogSetNextTextDecoration("{", "}");
RenderTextClipped(frame_bb.Min, frame_bb.Max, value_buf, value_buf_end, NULL, ImVec2(0.5f, 0.5f));
@@ -2814,6 +2828,8 @@ bool ImGui::DragScalarN(const char* label, ImGuiDataType data_type, void* p_data
PushID(i);
if (i > 0)
SameLine(0, g.Style.ItemInnerSpacing.x);
if (flags & ImGuiSliderFlags_ColorMarkers)
SetNextItemColorMarker(GDefaultRgbaColorMarkers[i]);
value_changed |= DragScalar("", data_type, p_data, v_speed, p_min, p_max, format, flags);
PopID();
PopItemWidth();
@@ -3312,6 +3328,7 @@ bool ImGui::SliderScalar(const char* label, ImGuiDataType data_type, void* p_dat
const ImGuiStyle& style = g.Style;
const ImGuiID id = window->GetID(label);
const float w = CalcItemWidth();
const ImU32 color_marker = (g.NextItemData.HasFlags & ImGuiNextItemDataFlags_HasColorMarker) ? g.NextItemData.ColorMarker : 0;
const ImVec2 label_size = CalcTextSize(label, NULL, true);
const ImRect frame_bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(w, label_size.y + style.FramePadding.y * 2.0f));
@@ -3362,7 +3379,10 @@ bool ImGui::SliderScalar(const char* label, ImGuiDataType data_type, void* p_dat
// Draw frame
const ImU32 frame_col = GetColorU32(g.ActiveId == id ? ImGuiCol_FrameBgActive : hovered ? ImGuiCol_FrameBgHovered : ImGuiCol_FrameBg);
RenderNavCursor(frame_bb, id);
RenderFrame(frame_bb.Min, frame_bb.Max, frame_col, true, g.Style.FrameRounding);
RenderFrame(frame_bb.Min, frame_bb.Max, frame_col, false, style.FrameRounding);
if (color_marker != 0 && style.ColorMarkerSize > 0.0f)
RenderColorComponentMarker(frame_bb, GetColorU32(color_marker), style.FrameRounding);
RenderFrameBorder(frame_bb.Min, frame_bb.Max, g.Style.FrameRounding);
// Slider behavior
ImRect grab_bb;
@@ -3376,7 +3396,7 @@ bool ImGui::SliderScalar(const char* label, ImGuiDataType data_type, void* p_dat
// Display value using user-provided display format so user can add prefix/suffix/decorations to the value.
char value_buf[64];
const char* value_buf_end = value_buf + DataTypeFormatString(value_buf, IM_ARRAYSIZE(value_buf), data_type, p_data, format);
const char* value_buf_end = value_buf + DataTypeFormatString(value_buf, IM_COUNTOF(value_buf), data_type, p_data, format);
if (g.LogEnabled)
LogSetNextTextDecoration("{", "}");
RenderTextClipped(frame_bb.Min, frame_bb.Max, value_buf, value_buf_end, NULL, ImVec2(0.5f, 0.5f));
@@ -3406,6 +3426,8 @@ bool ImGui::SliderScalarN(const char* label, ImGuiDataType data_type, void* v, i
PushID(i);
if (i > 0)
SameLine(0, g.Style.ItemInnerSpacing.x);
if (flags & ImGuiSliderFlags_ColorMarkers)
SetNextItemColorMarker(GDefaultRgbaColorMarkers[i]);
value_changed |= SliderScalar("", data_type, v, v_min, v_max, format, flags);
PopID();
PopItemWidth();
@@ -3527,7 +3549,7 @@ bool ImGui::VSliderScalar(const char* label, const ImVec2& size, ImGuiDataType d
// Display value using user-provided display format so user can add prefix/suffix/decorations to the value.
// For the vertical slider we allow centered text to overlap the frame padding
char value_buf[64];
const char* value_buf_end = value_buf + DataTypeFormatString(value_buf, IM_ARRAYSIZE(value_buf), data_type, p_data, format);
const char* value_buf_end = value_buf + DataTypeFormatString(value_buf, IM_COUNTOF(value_buf), data_type, p_data, format);
RenderTextClipped(ImVec2(frame_bb.Min.x, frame_bb.Min.y + style.FramePadding.y), frame_bb.Max, value_buf, value_buf_end, NULL, ImVec2(0.5f, 0.0f));
if (label_size.x > 0.0f)
RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, frame_bb.Min.y + style.FramePadding.y), label);
@@ -3727,16 +3749,16 @@ bool ImGui::TempInputScalar(const ImRect& bb, ImGuiID id, const char* label, ImG
const ImGuiDataTypeInfo* type_info = DataTypeGetInfo(data_type);
char fmt_buf[32];
char data_buf[32];
format = ImParseFormatTrimDecorations(format, fmt_buf, IM_ARRAYSIZE(fmt_buf));
format = ImParseFormatTrimDecorations(format, fmt_buf, IM_COUNTOF(fmt_buf));
if (format[0] == 0)
format = type_info->PrintFmt;
DataTypeFormatString(data_buf, IM_ARRAYSIZE(data_buf), data_type, p_data, format);
DataTypeFormatString(data_buf, IM_COUNTOF(data_buf), data_type, p_data, format);
ImStrTrimBlanks(data_buf);
ImGuiInputTextFlags flags = ImGuiInputTextFlags_AutoSelectAll | (ImGuiInputTextFlags)ImGuiInputTextFlags_LocalizeDecimalPoint;
g.LastItemData.ItemFlags |= ImGuiItemFlags_NoMarkEdited; // Because TempInputText() uses ImGuiInputTextFlags_MergedItem it doesn't submit a new item, so we poke LastItemData.
bool value_changed = false;
if (TempInputText(bb, id, label, data_buf, IM_ARRAYSIZE(data_buf), flags))
if (TempInputText(bb, id, label, data_buf, IM_COUNTOF(data_buf), flags))
{
// Backup old value
size_t data_type_size = type_info->Size;
@@ -3789,7 +3811,7 @@ bool ImGui::InputScalar(const char* label, ImGuiDataType data_type, void* p_data
if ((flags & ImGuiInputTextFlags_DisplayEmptyRefVal) && DataTypeCompare(data_type, p_data, p_data_default) == 0)
buf[0] = 0;
else
DataTypeFormatString(buf, IM_ARRAYSIZE(buf), data_type, p_data, format);
DataTypeFormatString(buf, IM_COUNTOF(buf), data_type, p_data, format);
// Disable the MarkItemEdited() call in InputText but keep ImGuiItemStatusFlags_Edited.
// We call MarkItemEdited() ourselves by comparing the actual data rather than the string.
@@ -3799,7 +3821,7 @@ bool ImGui::InputScalar(const char* label, ImGuiDataType data_type, void* p_data
bool value_changed = false;
if (p_step == NULL)
{
if (InputText(label, buf, IM_ARRAYSIZE(buf), flags))
if (InputText(label, buf, IM_COUNTOF(buf), flags))
value_changed = DataTypeApplyFromText(buf, data_type, p_data, format, (flags & ImGuiInputTextFlags_ParseEmptyRefVal) ? p_data_default : NULL);
}
else
@@ -3809,7 +3831,7 @@ bool ImGui::InputScalar(const char* label, ImGuiDataType data_type, void* p_data
BeginGroup(); // The only purpose of the group here is to allow the caller to query item data e.g. IsItemActive()
PushID(label);
SetNextItemWidth(ImMax(1.0f, CalcItemWidth() - (button_size + style.ItemInnerSpacing.x) * 2));
if (InputText("", buf, IM_ARRAYSIZE(buf), flags)) // PushId(label) + "" gives us the expected ID from outside point of view
if (InputText("", buf, IM_COUNTOF(buf), flags)) // PushId(label) + "" gives us the expected ID from outside point of view
value_changed = DataTypeApplyFromText(buf, data_type, p_data, format, (flags & ImGuiInputTextFlags_ParseEmptyRefVal) ? p_data_default : NULL);
IMGUI_TEST_ENGINE_ITEM_INFO(g.LastItemData.ID, label, g.LastItemData.StatusFlags | ImGuiItemStatusFlags_Inputable);
@@ -4255,7 +4277,7 @@ static void stb_textedit_replace(ImGuiInputTextState* str, STB_TexteditState* st
// We added an extra indirection where 'Stb' is heap-allocated, in order facilitate the work of bindings generators.
ImGuiInputTextState::ImGuiInputTextState()
{
memset(this, 0, sizeof(*this));
memset((void*)this, 0, sizeof(*this));
Stb = IM_NEW(ImStbTexteditState);
memset(Stb, 0, sizeof(*Stb));
}
@@ -4296,6 +4318,7 @@ void ImGuiInputTextState::ClearSelection() { Stb->select_start
int ImGuiInputTextState::GetCursorPos() const { return Stb->cursor; }
int ImGuiInputTextState::GetSelectionStart() const { return Stb->select_start; }
int ImGuiInputTextState::GetSelectionEnd() const { return Stb->select_end; }
void ImGuiInputTextState::SetSelection(int start, int end) { Stb->select_start = start; Stb->cursor = Stb->select_end = end; }
float ImGuiInputTextState::GetPreferredOffsetX() const { return Stb->has_preferred_x ? Stb->preferred_x : -1; }
void ImGuiInputTextState::SelectAll() { Stb->select_start = 0; Stb->cursor = Stb->select_end = TextLen; Stb->has_preferred_x = 0; }
void ImGuiInputTextState::ReloadUserBufAndSelectAll() { WantReloadUserBuf = true; ReloadSelectionStart = 0; ReloadSelectionEnd = INT_MAX; }
@@ -4304,7 +4327,7 @@ void ImGuiInputTextState::ReloadUserBufAndMoveToEnd() { WantReloadUserBuf
ImGuiInputTextCallbackData::ImGuiInputTextCallbackData()
{
memset(this, 0, sizeof(*this));
memset((void*)this, 0, sizeof(*this));
}
// Public API to manipulate UTF-8 text from within a callback.
@@ -4400,9 +4423,10 @@ void ImGui::PopPasswordFont()
}
// Return false to discard a character.
static bool InputTextFilterCharacter(ImGuiContext* ctx, unsigned int* p_char, ImGuiInputTextFlags flags, ImGuiInputTextCallback callback, void* user_data, bool input_source_is_clipboard)
static bool InputTextFilterCharacter(ImGuiContext* ctx, ImGuiInputTextState* state, unsigned int* p_char, ImGuiInputTextCallback callback, void* user_data, bool input_source_is_clipboard)
{
unsigned int c = *p_char;
ImGuiInputTextFlags flags = state->Flags;
// Filter non-printable (NB: isprint is unreliable! see #2467)
bool apply_named_filters = true;
@@ -4492,9 +4516,11 @@ static bool InputTextFilterCharacter(ImGuiContext* ctx, unsigned int* p_char, Im
ImGuiContext& g = *GImGui;
ImGuiInputTextCallbackData callback_data;
callback_data.Ctx = &g;
callback_data.ID = state->ID;
callback_data.Flags = flags;
callback_data.EventFlag = ImGuiInputTextFlags_CallbackCharFilter;
callback_data.EventChar = (ImWchar)c;
callback_data.Flags = flags;
callback_data.EventActivated = (g.ActiveId == state->ID && g.ActiveIdIsJustActivated);
callback_data.UserData = user_data;
if (callback(&callback_data) != 0)
return false;
@@ -4851,6 +4877,8 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_
SetActiveID(id, window);
SetFocusID(id, window);
FocusWindow(window);
if (input_requested_by_nav)
SetNavCursorVisibleAfterMove();
}
if (g.ActiveId == id)
{
@@ -5009,7 +5037,7 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_
if (Shortcut(ImGuiKey_Tab, ImGuiInputFlags_Repeat, id))
{
unsigned int c = '\t'; // Insert TAB
if (InputTextFilterCharacter(&g, &c, flags, callback, callback_user_data))
if (InputTextFilterCharacter(&g, state, &c, callback, callback_user_data))
state->OnCharPressed(c);
}
// FIXME: Implement Shift+Tab
@@ -5032,7 +5060,7 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_
unsigned int c = (unsigned int)io.InputQueueCharacters[n];
if (c == '\t') // Skip Tab, see above.
continue;
if (InputTextFilterCharacter(&g, &c, flags, callback, callback_user_data))
if (InputTextFilterCharacter(&g, state, &c, callback, callback_user_data))
state->OnCharPressed(c);
}
@@ -5118,7 +5146,7 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_
{
// Insert new line
unsigned int c = '\n';
if (InputTextFilterCharacter(&g, &c, flags, callback, callback_user_data))
if (InputTextFilterCharacter(&g, state, &c, callback, callback_user_data))
state->OnCharPressed(c);
}
}
@@ -5187,7 +5215,7 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_
unsigned int c;
int in_len = ImTextCharFromUtf8(&c, s, clipboard_end);
s += in_len;
if (!InputTextFilterCharacter(&g, &c, flags, callback, callback_user_data, true))
if (!InputTextFilterCharacter(&g, state, &c, callback, callback_user_data, true))
continue;
char c_utf8[5];
ImTextCharToUtf8(c_utf8, c);
@@ -5286,8 +5314,10 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_
{
ImGuiInputTextCallbackData callback_data;
callback_data.Ctx = &g;
callback_data.EventFlag = event_flag;
callback_data.ID = id;
callback_data.Flags = flags;
callback_data.EventFlag = event_flag;
callback_data.EventActivated = (g.ActiveId == state->ID && g.ActiveIdIsJustActivated);
callback_data.UserData = callback_user_data;
// FIXME-OPT: Undo stack reconcile needs a backup of the data until we rework API, see #7925
@@ -5363,8 +5393,10 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_
{
ImGuiInputTextCallbackData callback_data;
callback_data.Ctx = &g;
callback_data.EventFlag = ImGuiInputTextFlags_CallbackResize;
callback_data.ID = id;
callback_data.Flags = flags;
callback_data.EventFlag = ImGuiInputTextFlags_CallbackResize;
callback_data.EventActivated = (g.ActiveId == state->ID && g.ActiveIdIsJustActivated);
callback_data.Buf = buf;
callback_data.BufTextLen = apply_new_text_length;
callback_data.BufSize = ImMax(buf_size, apply_new_text_length + 1);
@@ -5394,7 +5426,6 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_
}
ImVec2 draw_pos = is_multiline ? draw_window->DC.CursorPos : frame_bb.Min + style.FramePadding;
ImVec2 text_size(0.0f, 0.0f);
ImRect clip_rect(frame_bb.Min.x, frame_bb.Min.y, frame_bb.Min.x + inner_size.x, frame_bb.Min.y + inner_size.y); // Not using frame_bb.Max because we have adjusted size
if (is_multiline)
clip_rect.ClipWith(draw_window->ClipRect);
@@ -5452,7 +5483,7 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_
line_visible_n1 = ImMin(line_visible_n1, line_count);
// Store text height (we don't need width)
text_size = ImVec2(inner_size.x, line_count * g.FontSize);
float text_size_y = line_count * g.FontSize;
//GetForegroundDrawList()->AddRect(draw_pos + ImVec2(0, line_visible_n0 * g.FontSize), draw_pos + ImVec2(frame_size.x, line_visible_n1 * g.FontSize), IM_COL32(255, 0, 0, 255));
// Calculate blinking cursor position
@@ -5512,7 +5543,7 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_
}
if (new_scroll_y != scroll_y)
{
const float scroll_max_y = ImMax((text_size.y + style.FramePadding.y * 2.0f) - inner_size.y, 0.0f);
const float scroll_max_y = ImMax((text_size_y + style.FramePadding.y * 2.0f) - inner_size.y, 0.0f);
scroll_y = ImClamp(new_scroll_y, 0.0f, scroll_max_y);
draw_pos.y += (draw_window->Scroll.y - scroll_y); // Manipulate cursor pos immediately avoid a frame of lag
draw_window->Scroll.y = scroll_y;
@@ -5605,7 +5636,7 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_
if (is_multiline)
{
// For focus requests to work on our multiline we need to ensure our child ItemAdd() call specifies the ImGuiItemFlags_Inputable (see #4761, #7870)...
Dummy(ImVec2(text_size.x, text_size.y + style.FramePadding.y));
Dummy(ImVec2(0.0f, text_size_y + style.FramePadding.y));
g.NextItemData.ItemFlags |= (ImGuiItemFlags)ImGuiItemFlags_Inputable | ImGuiItemFlags_NoTabStop;
EndChild();
item_data_backup.StatusFlags |= (g.LastItemData.StatusFlags & ImGuiItemStatusFlags_HoveredWindow);
@@ -5620,7 +5651,7 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_
g.LastItemData.StatusFlags = item_data_backup.StatusFlags;
}
}
if (state)
if (state && is_readonly)
state->TextSrc = NULL;
// Log as text
@@ -5804,8 +5835,10 @@ bool ImGui::ColorEdit4(const char* label, float col[4], ImGuiColorEditFlags flag
{
// RGB/HSV 0..255 Sliders
const float w_items = w_inputs - style.ItemInnerSpacing.x * (components - 1);
const float w_per_component = IM_TRUNC(w_items / components);
const bool draw_color_marker = (flags & (ImGuiColorEditFlags_DisplayHSV | ImGuiColorEditFlags_NoColorMarkers)) == 0;
const bool hide_prefix = (IM_TRUNC(w_items / components) <= CalcTextSize((flags & ImGuiColorEditFlags_Float) ? "M:0.000" : "M:000").x);
const bool hide_prefix = draw_color_marker || (w_per_component <= CalcTextSize((flags & ImGuiColorEditFlags_Float) ? "M:0.000" : "M:000").x);
static const char* ids[4] = { "##X", "##Y", "##Z", "##W" };
static const char* fmt_table_int[3][4] =
{
@@ -5820,6 +5853,7 @@ bool ImGui::ColorEdit4(const char* label, float col[4], ImGuiColorEditFlags flag
{ "H:%0.3f", "S:%0.3f", "V:%0.3f", "A:%0.3f" } // Long display for HSVA
};
const int fmt_idx = hide_prefix ? 0 : (flags & ImGuiColorEditFlags_DisplayHSV) ? 2 : 1;
const ImGuiSliderFlags drag_flags = draw_color_marker ? ImGuiSliderFlags_ColorMarkers : ImGuiSliderFlags_None;
float prev_split = 0.0f;
for (int n = 0; n < components; n++)
@@ -5829,16 +5863,18 @@ bool ImGui::ColorEdit4(const char* label, float col[4], ImGuiColorEditFlags flag
float next_split = IM_TRUNC(w_items * (n + 1) / components);
SetNextItemWidth(ImMax(next_split - prev_split, 1.0f));
prev_split = next_split;
if (draw_color_marker)
SetNextItemColorMarker(GDefaultRgbaColorMarkers[n]);
// FIXME: When ImGuiColorEditFlags_HDR flag is passed HS values snap in weird ways when SV values go below 0.
if (flags & ImGuiColorEditFlags_Float)
{
value_changed |= DragFloat(ids[n], &f[n], 1.0f / 255.0f, 0.0f, hdr ? 0.0f : 1.0f, fmt_table_float[fmt_idx][n]);
value_changed |= DragFloat(ids[n], &f[n], 1.0f / 255.0f, 0.0f, hdr ? 0.0f : 1.0f, fmt_table_float[fmt_idx][n], drag_flags);
value_changed_as_float |= value_changed;
}
else
{
value_changed |= DragInt(ids[n], &i[n], 1.0f, 0, hdr ? 0 : 255, fmt_table_int[fmt_idx][n]);
value_changed |= DragInt(ids[n], &i[n], 1.0f, 0, hdr ? 0 : 255, fmt_table_int[fmt_idx][n], drag_flags);
}
if (!(flags & ImGuiColorEditFlags_NoOptions))
OpenPopupOnItemClick("context", ImGuiPopupFlags_MouseButtonRight);
@@ -5849,11 +5885,11 @@ bool ImGui::ColorEdit4(const char* label, float col[4], ImGuiColorEditFlags flag
// RGB Hexadecimal Input
char buf[64];
if (alpha)
ImFormatString(buf, IM_ARRAYSIZE(buf), "#%02X%02X%02X%02X", ImClamp(i[0], 0, 255), ImClamp(i[1], 0, 255), ImClamp(i[2], 0, 255), ImClamp(i[3], 0, 255));
ImFormatString(buf, IM_COUNTOF(buf), "#%02X%02X%02X%02X", ImClamp(i[0], 0, 255), ImClamp(i[1], 0, 255), ImClamp(i[2], 0, 255), ImClamp(i[3], 0, 255));
else
ImFormatString(buf, IM_ARRAYSIZE(buf), "#%02X%02X%02X", ImClamp(i[0], 0, 255), ImClamp(i[1], 0, 255), ImClamp(i[2], 0, 255));
ImFormatString(buf, IM_COUNTOF(buf), "#%02X%02X%02X", ImClamp(i[0], 0, 255), ImClamp(i[1], 0, 255), ImClamp(i[2], 0, 255));
SetNextItemWidth(w_inputs);
if (InputText("##Text", buf, IM_ARRAYSIZE(buf), ImGuiInputTextFlags_CharsUppercase))
if (InputText("##Text", buf, IM_COUNTOF(buf), ImGuiInputTextFlags_CharsUppercase))
{
value_changed = true;
char* p = buf;
@@ -6461,6 +6497,7 @@ bool ImGui::ColorButton(const char* desc_id, const ImVec4& col, ImGuiColorEditFl
}
// Initialize/override default color options
// FIXME: Could be moved to a simple IO field.
void ImGui::SetColorEditOptions(ImGuiColorEditFlags flags)
{
ImGuiContext& g = *GImGui;
@@ -6547,18 +6584,18 @@ void ImGui::ColorEditOptionsPopup(const float* col, ImGuiColorEditFlags flags)
{
int cr = IM_F32_TO_INT8_SAT(col[0]), cg = IM_F32_TO_INT8_SAT(col[1]), cb = IM_F32_TO_INT8_SAT(col[2]), ca = (flags & ImGuiColorEditFlags_NoAlpha) ? 255 : IM_F32_TO_INT8_SAT(col[3]);
char buf[64];
ImFormatString(buf, IM_ARRAYSIZE(buf), "(%.3ff, %.3ff, %.3ff, %.3ff)", col[0], col[1], col[2], (flags & ImGuiColorEditFlags_NoAlpha) ? 1.0f : col[3]);
ImFormatString(buf, IM_COUNTOF(buf), "(%.3ff, %.3ff, %.3ff, %.3ff)", col[0], col[1], col[2], (flags & ImGuiColorEditFlags_NoAlpha) ? 1.0f : col[3]);
if (Selectable(buf))
SetClipboardText(buf);
ImFormatString(buf, IM_ARRAYSIZE(buf), "(%d,%d,%d,%d)", cr, cg, cb, ca);
ImFormatString(buf, IM_COUNTOF(buf), "(%d,%d,%d,%d)", cr, cg, cb, ca);
if (Selectable(buf))
SetClipboardText(buf);
ImFormatString(buf, IM_ARRAYSIZE(buf), "#%02X%02X%02X", cr, cg, cb);
ImFormatString(buf, IM_COUNTOF(buf), "#%02X%02X%02X", cr, cg, cb);
if (Selectable(buf))
SetClipboardText(buf);
if (!(flags & ImGuiColorEditFlags_NoAlpha))
{
ImFormatString(buf, IM_ARRAYSIZE(buf), "#%02X%02X%02X%02X", cr, cg, cb, ca);
ImFormatString(buf, IM_COUNTOF(buf), "#%02X%02X%02X%02X", cr, cg, cb, ca);
if (Selectable(buf))
SetClipboardText(buf);
}
@@ -6799,7 +6836,6 @@ static void TreeNodeStoreStackData(ImGuiTreeNodeFlags flags, float x1)
window->DC.TreeRecordsClippedNodesY2Mask |= (1 << window->DC.TreeDepth);
}
// When using public API, currently 'id == storage_id' is always true, but we separate the values to facilitate advanced user code doing storage queries outside of UI loop.
bool ImGui::TreeNodeBehavior(ImGuiID id, ImGuiTreeNodeFlags flags, const char* label, const char* label_end)
{
ImGuiWindow* window = GetCurrentWindow();
@@ -6808,26 +6844,28 @@ bool ImGui::TreeNodeBehavior(ImGuiID id, ImGuiTreeNodeFlags flags, const char* l
ImGuiContext& g = *GImGui;
const ImGuiStyle& style = g.Style;
// When not framed, we vertically increase height up to typical framed widget height
const bool display_frame = (flags & ImGuiTreeNodeFlags_Framed) != 0;
const ImVec2 padding = (display_frame || (flags & ImGuiTreeNodeFlags_FramePadding)) ? style.FramePadding : ImVec2(style.FramePadding.x, ImMin(window->DC.CurrLineTextBaseOffset, style.FramePadding.y));
const bool use_frame_padding = (display_frame || (flags & ImGuiTreeNodeFlags_FramePadding));
const ImVec2 padding = use_frame_padding ? style.FramePadding : ImVec2(style.FramePadding.x, ImMin(window->DC.CurrLineTextBaseOffset, style.FramePadding.y));
if (!label_end)
label_end = FindRenderedTextEnd(label);
const ImVec2 label_size = CalcTextSize(label, label_end, false);
const float text_offset_x = g.FontSize + (display_frame ? padding.x * 3 : padding.x * 2); // Collapsing arrow width + Spacing
const float text_offset_y = ImMax(padding.y, window->DC.CurrLineTextBaseOffset); // Latch before ItemSize changes it
const float text_offset_y = use_frame_padding ? ImMax(style.FramePadding.y, window->DC.CurrLineTextBaseOffset) : window->DC.CurrLineTextBaseOffset; // Latch before ItemSize changes it
const float text_width = g.FontSize + label_size.x + padding.x * 2; // Include collapsing arrow
// We vertically grow up to current line height up the typical widget height.
const float frame_height = ImMax(ImMin(window->DC.CurrLineSize.y, g.FontSize + style.FramePadding.y * 2), label_size.y + padding.y * 2);
const float frame_height = label_size.y + padding.y * 2;
const bool span_all_columns = (flags & ImGuiTreeNodeFlags_SpanAllColumns) != 0 && (g.CurrentTable != NULL);
const bool span_all_columns_label = (flags & ImGuiTreeNodeFlags_LabelSpanAllColumns) != 0 && (g.CurrentTable != NULL);
ImRect frame_bb;
frame_bb.Min.x = span_all_columns ? window->ParentWorkRect.Min.x : (flags & ImGuiTreeNodeFlags_SpanFullWidth) ? window->WorkRect.Min.x : window->DC.CursorPos.x;
frame_bb.Min.y = window->DC.CursorPos.y;
frame_bb.Min.y = window->DC.CursorPos.y + (text_offset_y - padding.y);
frame_bb.Max.x = span_all_columns ? window->ParentWorkRect.Max.x : (flags & ImGuiTreeNodeFlags_SpanLabelWidth) ? window->DC.CursorPos.x + text_width + padding.x : window->WorkRect.Max.x;
frame_bb.Max.y = window->DC.CursorPos.y + frame_height;
frame_bb.Max.y = window->DC.CursorPos.y + (text_offset_y - padding.y) + frame_height;
if (display_frame)
{
const float outer_extend = IM_TRUNC(window->WindowPadding.x * 0.5f); // Framed header expand a little outside of current limits
@@ -7516,7 +7554,7 @@ ImGuiTypingSelectRequest* ImGui::GetTypingSelectRequest(ImGuiTypingSelectFlags f
}
// Append to buffer
const int buffer_max_len = IM_ARRAYSIZE(data->SearchBuffer) - 1;
const int buffer_max_len = IM_COUNTOF(data->SearchBuffer) - 1;
int buffer_len = (int)ImStrlen(data->SearchBuffer);
bool select_request = false;
for (ImWchar w : g.IO.InputQueueCharacters)
@@ -8916,7 +8954,7 @@ void ImGui::Value(const char* prefix, float v, const char* float_format)
if (float_format)
{
char fmt[64];
ImFormatString(fmt, IM_ARRAYSIZE(fmt), "%%s: %s", float_format);
ImFormatString(fmt, IM_COUNTOF(fmt), "%%s: %s", float_format);
Text(fmt, prefix, v);
}
else
@@ -8955,7 +8993,7 @@ void ImGuiMenuColumns::CalcNextTotalWidth(bool update_offsets)
{
ImU16 offset = 0;
bool want_spacing = false;
for (int i = 0; i < IM_ARRAYSIZE(Widths); i++)
for (int i = 0; i < IM_COUNTOF(Widths); i++)
{
ImU16 width = Widths[i];
if (want_spacing && width > 0)
@@ -9052,6 +9090,10 @@ void ImGui::EndMenuBar()
NavMoveRequestForward(g.NavMoveDir, g.NavMoveClipDir, g.NavMoveFlags, g.NavMoveScrollFlags); // Repeat
}
}
else
{
NavMoveRequestTryWrapping(window, ImGuiNavMoveFlags_WrapX);
}
PopClipRect();
PopID();
@@ -9137,11 +9179,7 @@ bool ImGui::BeginMainMenuBar()
void ImGui::EndMainMenuBar()
{
ImGuiContext& g = *GImGui;
if (!g.CurrentWindow->DC.MenuBarAppending)
{
IM_ASSERT_USER_ERROR(0, "Calling EndMainMenuBar() not from a menu-bar!"); // Not technically testing that it is the main menu bar
return;
}
IM_ASSERT_USER_ERROR_RET(g.CurrentWindow->DC.MenuBarAppending, "Calling EndMainMenuBar() not from a menu-bar!"); // Not technically testing that it is the main menu bar
EndMenuBar();
g.CurrentWindow->Flags |= ImGuiWindowFlags_NoSavedSettings; // Restore _NoSavedSettings (#8356)
@@ -9221,7 +9259,8 @@ bool ImGui::BeginMenuEx(const char* label, const char* icon, bool enabled)
// The reference position stored in popup_pos will be used by Begin() to find a suitable position for the child menu,
// However the final position is going to be different! It is chosen by FindBestWindowPosForPopup().
// e.g. Menus tend to overlap each other horizontally to amplify relative Z-ordering.
ImVec2 popup_pos, pos = window->DC.CursorPos;
ImVec2 popup_pos;
ImVec2 pos = window->DC.CursorPos;
PushID(label);
if (!enabled)
BeginDisabled();
@@ -9235,34 +9274,34 @@ bool ImGui::BeginMenuEx(const char* label, const char* icon, bool enabled)
// Menu inside a horizontal menu bar
// Selectable extend their highlight by half ItemSpacing in each direction.
// For ChildMenu, the popup position will be overwritten by the call to FindBestWindowPosForPopup() in Begin()
popup_pos = ImVec2(pos.x - 1.0f - IM_TRUNC(style.ItemSpacing.x * 0.5f), pos.y - style.FramePadding.y + window->MenuBarHeight);
window->DC.CursorPos.x += IM_TRUNC(style.ItemSpacing.x * 0.5f);
PushStyleVarX(ImGuiStyleVar_ItemSpacing, style.ItemSpacing.x * 2.0f);
float w = label_size.x;
ImVec2 text_pos(window->DC.CursorPos.x + offsets->OffsetLabel, window->DC.CursorPos.y + window->DC.CurrLineTextBaseOffset);
ImVec2 text_pos(window->DC.CursorPos.x + offsets->OffsetLabel, pos.y + window->DC.CurrLineTextBaseOffset);
pressed = Selectable("", menu_is_open, selectable_flags, ImVec2(w, label_size.y));
LogSetNextTextDecoration("[", "]");
RenderText(text_pos, label);
PopStyleVar();
window->DC.CursorPos.x += IM_TRUNC(style.ItemSpacing.x * (-1.0f + 0.5f)); // -1 spacing to compensate the spacing added when Selectable() did a SameLine(). It would also work to call SameLine() ourselves after the PopStyleVar().
popup_pos = ImVec2(pos.x - 1.0f - IM_TRUNC(style.ItemSpacing.x * 0.5f), text_pos.y - style.FramePadding.y + window->MenuBarHeight);
}
else
{
// Menu inside a regular/vertical menu
// (In a typical menu window where all items are BeginMenu() or MenuItem() calls, extra_w will always be 0.0f.
// Only when they are other items sticking out we're going to add spacing, yet only register minimum width into the layout system.)
popup_pos = ImVec2(pos.x, pos.y - style.WindowPadding.y);
float icon_w = (icon && icon[0]) ? CalcTextSize(icon, NULL).x : 0.0f;
float checkmark_w = IM_TRUNC(g.FontSize * 1.20f);
float min_w = window->DC.MenuColumns.DeclColumns(icon_w, label_size.x, 0.0f, checkmark_w); // Feedback to next frame
float extra_w = ImMax(0.0f, GetContentRegionAvail().x - min_w);
ImVec2 text_pos(window->DC.CursorPos.x + offsets->OffsetLabel, window->DC.CursorPos.y + window->DC.CurrLineTextBaseOffset);
ImVec2 text_pos(window->DC.CursorPos.x, pos.y + window->DC.CurrLineTextBaseOffset);
pressed = Selectable("", menu_is_open, selectable_flags | ImGuiSelectableFlags_SpanAvailWidth, ImVec2(min_w, label_size.y));
LogSetNextTextDecoration("", ">");
RenderText(text_pos, label);
RenderText(ImVec2(text_pos.x + offsets->OffsetLabel, text_pos.y), label);
if (icon_w > 0.0f)
RenderText(pos + ImVec2(offsets->OffsetIcon, 0.0f), icon);
RenderArrow(window->DrawList, pos + ImVec2(offsets->OffsetMark + extra_w + g.FontSize * 0.30f, 0.0f), GetColorU32(ImGuiCol_Text), ImGuiDir_Right);
RenderText(ImVec2(text_pos.x + offsets->OffsetIcon, text_pos.y), icon);
RenderArrow(window->DrawList, ImVec2(text_pos.x + offsets->OffsetMark + extra_w + g.FontSize * 0.30f, text_pos.y), GetColorU32(ImGuiCol_Text), ImGuiDir_Right);
popup_pos = ImVec2(pos.x, text_pos.y - style.WindowPadding.y);
}
if (!enabled)
EndDisabled();
@@ -9400,7 +9439,8 @@ void ImGui::EndMenu()
// Nav: When a left move request our menu failed, close ourselves.
ImGuiContext& g = *GImGui;
ImGuiWindow* window = g.CurrentWindow;
IM_ASSERT(window->Flags & ImGuiWindowFlags_Popup); // Mismatched BeginMenu()/EndMenu() calls
IM_ASSERT_USER_ERROR_RET((window->Flags & (ImGuiWindowFlags_Popup | ImGuiWindowFlags_ChildMenu)) == (ImGuiWindowFlags_Popup | ImGuiWindowFlags_ChildMenu), "Calling EndMenu() in wrong window!");
ImGuiWindow* parent_window = window->ParentWindow; // Should always be != NULL is we passed assert.
if (window->BeginCount == window->BeginCountPreviousFrame)
if (g.NavMoveDir == ImGuiDir_Left && NavMoveRequestButNoResultYet())
@@ -9463,21 +9503,22 @@ bool ImGui::MenuItemEx(const char* label, const char* icon, const char* shortcut
float checkmark_w = IM_TRUNC(g.FontSize * 1.20f);
float min_w = window->DC.MenuColumns.DeclColumns(icon_w, label_size.x, shortcut_w, checkmark_w); // Feedback for next frame
float stretch_w = ImMax(0.0f, GetContentRegionAvail().x - min_w);
ImVec2 text_pos(pos.x, pos.y + window->DC.CurrLineTextBaseOffset);
pressed = Selectable("", false, selectable_flags | ImGuiSelectableFlags_SpanAvailWidth, ImVec2(min_w, label_size.y));
if (g.LastItemData.StatusFlags & ImGuiItemStatusFlags_Visible)
{
RenderText(pos + ImVec2(offsets->OffsetLabel, 0.0f), label);
RenderText(text_pos + ImVec2(offsets->OffsetLabel, 0.0f), label);
if (icon_w > 0.0f)
RenderText(pos + ImVec2(offsets->OffsetIcon, 0.0f), icon);
RenderText(text_pos + ImVec2(offsets->OffsetIcon, 0.0f), icon);
if (shortcut_w > 0.0f)
{
PushStyleColor(ImGuiCol_Text, style.Colors[ImGuiCol_TextDisabled]);
LogSetNextTextDecoration("(", ")");
RenderText(pos + ImVec2(offsets->OffsetShortcut + stretch_w, 0.0f), shortcut, NULL, false);
RenderText(text_pos + ImVec2(offsets->OffsetShortcut + stretch_w, 0.0f), shortcut, NULL, false);
PopStyleColor();
}
if (selected)
RenderCheckMark(window->DrawList, pos + ImVec2(offsets->OffsetMark + stretch_w + g.FontSize * 0.40f, g.FontSize * 0.134f * 0.5f), GetColorU32(ImGuiCol_Text), g.FontSize * 0.866f);
RenderCheckMark(window->DrawList, text_pos + ImVec2(offsets->OffsetMark + stretch_w + g.FontSize * 0.40f, g.FontSize * 0.134f * 0.5f), GetColorU32(ImGuiCol_Text), g.FontSize * 0.866f);
}
}
IMGUI_TEST_ENGINE_ITEM_INFO(g.LastItemData.ID, label, g.LastItemData.StatusFlags | ImGuiItemStatusFlags_Checkable | (selected ? ImGuiItemStatusFlags_Checked : 0));
@@ -9538,7 +9579,7 @@ struct ImGuiTabBarSection
float WidthAfterShrinkMinWidth;
float Spacing; // Horizontal spacing at the end of the section.
ImGuiTabBarSection() { memset(this, 0, sizeof(*this)); }
ImGuiTabBarSection() { memset((void*)this, 0, sizeof(*this)); }
};
namespace ImGui
@@ -9554,7 +9595,7 @@ namespace ImGui
ImGuiTabBar::ImGuiTabBar()
{
memset(this, 0, sizeof(*this));
memset((void*)this, 0, sizeof(*this));
CurrFrameVisible = PrevFrameVisible = -1;
LastTabItemIdx = -1;
}
@@ -9696,11 +9737,7 @@ void ImGui::EndTabBar()
return;
ImGuiTabBar* tab_bar = g.CurrentTabBar;
if (tab_bar == NULL)
{
IM_ASSERT_USER_ERROR(tab_bar != NULL, "Mismatched BeginTabBar()/EndTabBar()!");
return;
}
IM_ASSERT_USER_ERROR_RET(tab_bar != NULL, "Mismatched BeginTabBar()/EndTabBar()!");
// Fallback in case no TabItem have been submitted
if (tab_bar->WantLayout)
@@ -9789,11 +9826,17 @@ static void ImGui::TabBarLayout(ImGuiTabBar* tab_bar)
ImQsort(tab_bar->Tabs.Data, tab_bar->Tabs.Size, sizeof(ImGuiTabItem), TabItemComparerBySection);
// Calculate spacing between sections
sections[0].Spacing = sections[0].TabCount > 0 && (sections[1].TabCount + sections[2].TabCount) > 0 ? g.Style.ItemInnerSpacing.x : 0.0f;
sections[1].Spacing = sections[1].TabCount > 0 && sections[2].TabCount > 0 ? g.Style.ItemInnerSpacing.x : 0.0f;
const float tab_spacing = g.Style.ItemInnerSpacing.x;
sections[0].Spacing = sections[0].TabCount > 0 && (sections[1].TabCount + sections[2].TabCount) > 0 ? tab_spacing : 0.0f;
sections[1].Spacing = sections[1].TabCount > 0 && sections[2].TabCount > 0 ? tab_spacing : 0.0f;
// Setup next selected tab
ImGuiID scroll_to_tab_id = 0;
if (tab_bar->NextScrollToTabId)
{
scroll_to_tab_id = tab_bar->NextScrollToTabId;
tab_bar->NextScrollToTabId = 0;
}
if (tab_bar->NextSelectedTabId)
{
tab_bar->SelectedTabId = tab_bar->NextSelectedTabId;
@@ -9851,8 +9894,8 @@ static void ImGui::TabBarLayout(ImGuiTabBar* tab_bar)
int section_n = TabItemGetSectionIdx(tab);
ImGuiTabBarSection* section = &sections[section_n];
section->Width += tab->ContentWidth + (section_n == curr_section_n ? g.Style.ItemInnerSpacing.x : 0.0f);
section->WidthAfterShrinkMinWidth += ImMin(tab->ContentWidth, shrink_min_width) + (section_n == curr_section_n ? g.Style.ItemInnerSpacing.x : 0.0f);
section->Width += tab->ContentWidth + (section_n == curr_section_n ? tab_spacing : 0.0f);
section->WidthAfterShrinkMinWidth += ImMin(tab->ContentWidth, shrink_min_width) + (section_n == curr_section_n ? tab_spacing : 0.0f);
curr_section_n = section_n;
// Store data so we can build an array sorted by width if we need to shrink tabs down
@@ -10157,6 +10200,7 @@ void ImGui::TabBarQueueReorderFromMousePos(ImGuiTabBar* tab_bar, ImGuiTabItem* s
if ((tab_bar->Flags & ImGuiTabBarFlags_Reorderable) == 0)
return;
const float tab_spacing = g.Style.ItemInnerSpacing.x;
const bool is_central_section = (src_tab->Flags & ImGuiTabItemFlags_SectionMask_) == 0;
const float bar_offset = tab_bar->BarRect.Min.x - (is_central_section ? tab_bar->ScrollingTarget : 0);
@@ -10175,8 +10219,8 @@ void ImGui::TabBarQueueReorderFromMousePos(ImGuiTabBar* tab_bar, ImGuiTabItem* s
dst_idx = i;
// Include spacing after tab, so when mouse cursor is between tabs we would not continue checking further tabs that are not hovered.
const float x1 = bar_offset + dst_tab->Offset - g.Style.ItemInnerSpacing.x;
const float x2 = bar_offset + dst_tab->Offset + dst_tab->Width + g.Style.ItemInnerSpacing.x;
const float x1 = bar_offset + dst_tab->Offset - tab_spacing;
const float x2 = bar_offset + dst_tab->Offset + dst_tab->Width + tab_spacing;
//GetForegroundDrawList()->AddRect(ImVec2(x1, tab_bar->BarRect.Min.y), ImVec2(x2, tab_bar->BarRect.Max.y), IM_COL32(255, 0, 0, 255));
if ((dir < 0 && mouse_pos.x > x1) || (dir > 0 && mouse_pos.x < x2))
break;
@@ -10339,11 +10383,7 @@ bool ImGui::BeginTabItem(const char* label, bool* p_open, ImGuiTabItemFlags f
return false;
ImGuiTabBar* tab_bar = g.CurrentTabBar;
if (tab_bar == NULL)
{
IM_ASSERT_USER_ERROR(tab_bar, "Needs to be called between BeginTabBar() and EndTabBar()!");
return false;
}
IM_ASSERT_USER_ERROR_RETV(tab_bar != NULL, false, "Needs to be called between BeginTabBar() and EndTabBar()!");
IM_ASSERT((flags & ImGuiTabItemFlags_Button) == 0); // BeginTabItem() Can't be used with button flags, use TabItemButton() instead!
bool ret = TabItemEx(tab_bar, label, p_open, flags, NULL);
@@ -10363,11 +10403,7 @@ void ImGui::EndTabItem()
return;
ImGuiTabBar* tab_bar = g.CurrentTabBar;
if (tab_bar == NULL)
{
IM_ASSERT_USER_ERROR(tab_bar != NULL, "Needs to be called between BeginTabBar() and EndTabBar()!");
return;
}
IM_ASSERT_USER_ERROR_RET(tab_bar != NULL, "Needs to be called between BeginTabBar() and EndTabBar()!");
IM_ASSERT(tab_bar->LastTabItemIdx >= 0);
ImGuiTabItem* tab = &tab_bar->Tabs[tab_bar->LastTabItemIdx];
if (!(tab->Flags & ImGuiTabItemFlags_NoPushId))
@@ -10382,11 +10418,7 @@ bool ImGui::TabItemButton(const char* label, ImGuiTabItemFlags flags)
return false;
ImGuiTabBar* tab_bar = g.CurrentTabBar;
if (tab_bar == NULL)
{
IM_ASSERT_USER_ERROR(tab_bar != NULL, "Needs to be called between BeginTabBar() and EndTabBar()!");
return false;
}
IM_ASSERT_USER_ERROR_RETV(tab_bar != NULL, false, "Needs to be called between BeginTabBar() and EndTabBar()!");
return TabItemEx(tab_bar, label, NULL, flags | ImGuiTabItemFlags_Button | ImGuiTabItemFlags_NoReorder, NULL);
}
@@ -10398,11 +10430,7 @@ void ImGui::TabItemSpacing(const char* str_id, ImGuiTabItemFlags flags, float
return;
ImGuiTabBar* tab_bar = g.CurrentTabBar;
if (tab_bar == NULL)
{
IM_ASSERT_USER_ERROR(tab_bar != NULL, "Needs to be called between BeginTabBar() and EndTabBar()!");
return;
}
IM_ASSERT_USER_ERROR_RET(tab_bar != NULL, "Needs to be called between BeginTabBar() and EndTabBar()!");
SetNextItemWidth(width);
TabItemEx(tab_bar, str_id, NULL, flags | ImGuiTabItemFlags_Button | ImGuiTabItemFlags_NoReorder | ImGuiTabItemFlags_Invisible, NULL);
}
+844
View File
@@ -0,0 +1,844 @@
// dear imgui: FreeType font builder (used as a replacement for the stb_truetype builder)
// (code)
// Get the latest version at https://github.com/ocornut/imgui/tree/master/misc/freetype
// Original code by @vuhdo (Aleksei Skriabin) in 2017, with improvements by @mikesart.
// Maintained since 2019 by @ocornut.
// CHANGELOG
// (minor and older changes stripped away, please see git history for details)
// 2025/06/11: refactored for the new ImFontLoader architecture, and ImGuiBackendFlags_RendererHasTextures support.
// 2024/10/17: added plutosvg support for SVG Fonts (seems faster/better than lunasvg). Enable by using '#define IMGUI_ENABLE_FREETYPE_PLUTOSVG'. (#7927)
// 2023/11/13: added support for ImFontConfig::RasterizationDensity field for scaling render density without scaling metrics.
// 2023/08/01: added support for SVG fonts, enable by using '#define IMGUI_ENABLE_FREETYPE_LUNASVG'. (#6591)
// 2023/01/04: fixed a packing issue which in some occurrences would prevent large amount of glyphs from being packed correctly.
// 2021/08/23: fixed crash when FT_Render_Glyph() fails to render a glyph and returns nullptr.
// 2021/03/05: added ImGuiFreeTypeBuilderFlags_Bitmap to load bitmap glyphs.
// 2021/03/02: set 'atlas->TexPixelsUseColors = true' to help some backends with deciding of a preferred texture format.
// 2021/01/28: added support for color-layered glyphs via ImGuiFreeTypeBuilderFlags_LoadColor (require Freetype 2.10+).
// 2021/01/26: simplified integration by using '#define IMGUI_ENABLE_FREETYPE'. renamed ImGuiFreeType::XXX flags to ImGuiFreeTypeBuilderFlags_XXX for consistency with other API. removed ImGuiFreeType::BuildFontAtlas().
// 2020/06/04: fix for rare case where FT_Get_Char_Index() succeed but FT_Load_Glyph() fails.
// 2019/02/09: added RasterizerFlags::Monochrome flag to disable font anti-aliasing (combine with ::MonoHinting for best results!)
// 2019/01/15: added support for imgui allocators + added FreeType only override function SetAllocatorFunctions().
// 2019/01/10: re-factored to match big update in STB builder. fixed texture height waste. fixed redundant glyphs when merging. support for glyph padding.
// 2018/06/08: added support for ImFontConfig::GlyphMinAdvanceX, GlyphMaxAdvanceX.
// 2018/02/04: moved to main imgui repository (away from http://www.github.com/ocornut/imgui_club)
// 2018/01/22: fix for addition of ImFontAtlas::TexUvscale member.
// 2017/10/22: minor inconsequential change to match change in master (removed an unnecessary statement).
// 2017/09/26: fixes for imgui internal changes.
// 2017/08/26: cleanup, optimizations, support for ImFontConfig::RasterizerFlags, ImFontConfig::RasterizerMultiply.
// 2017/08/16: imported from https://github.com/Vuhdo/imgui_freetype into http://www.github.com/ocornut/imgui_club, updated for latest changes in ImFontAtlas, minor tweaks.
// About Gamma Correct Blending:
// - FreeType assumes blending in linear space rather than gamma space.
// - See https://www.freetype.org/freetype2/docs/reference/ft2-base_interface.html#FT_Render_Glyph
// - For correct results you need to be using sRGB and convert to linear space in the pixel shader output.
// - The default dear imgui styles will be impacted by this change (alpha values will need tweaking).
// FIXME: cfg.OversampleH, OversampleV are not supported, but generally not necessary with this rasterizer because Hinting makes everything look better.
#include "imgui.h"
#ifndef IMGUI_DISABLE
#include "imgui_freetype.h"
#include "imgui_internal.h" // ImMin,ImMax,ImFontAtlasBuild*,
#include <stdint.h>
#ifdef _MSC_VER
#pragma push_macro("generic")
#undef generic
#define generic __generic
#endif
#include <ft2build.h>
#include FT_FREETYPE_H // <freetype/freetype.h>
#include FT_MODULE_H // <freetype/ftmodapi.h>
#include FT_GLYPH_H // <freetype/ftglyph.h>
#include FT_SIZES_H // <freetype/ftsizes.h>
#include FT_SYNTHESIS_H // <freetype/ftsynth.h>
#include FT_TRUETYPE_TABLES_H // <freetype/tttables.h>
#include FT_TRUETYPE_TAGS_H // <freetype/tttags.h>
#ifdef _MSC_VER
#pragma pop_macro("generic")
#endif
// Handle LunaSVG and PlutoSVG
#if defined(IMGUI_ENABLE_FREETYPE_LUNASVG) && defined(IMGUI_ENABLE_FREETYPE_PLUTOSVG)
#error "Cannot enable both IMGUI_ENABLE_FREETYPE_LUNASVG and IMGUI_ENABLE_FREETYPE_PLUTOSVG"
#endif
#ifdef IMGUI_ENABLE_FREETYPE_LUNASVG
#include FT_OTSVG_H // <freetype/otsvg.h>
#include FT_BBOX_H // <freetype/ftbbox.h>
#include <lunasvg.h>
#endif
#ifdef IMGUI_ENABLE_FREETYPE_PLUTOSVG
#include <plutosvg.h>
#endif
#if defined(IMGUI_ENABLE_FREETYPE_LUNASVG) || defined (IMGUI_ENABLE_FREETYPE_PLUTOSVG)
#if !((FREETYPE_MAJOR >= 2) && (FREETYPE_MINOR >= 12))
#error IMGUI_ENABLE_FREETYPE_PLUTOSVG or IMGUI_ENABLE_FREETYPE_LUNASVG requires FreeType version >= 2.12
#endif
#endif
#ifdef _MSC_VER
#pragma warning (push)
#pragma warning (disable: 4505) // unreferenced local function has been removed (stb stuff)
#pragma warning (disable: 26812) // [Static Analyzer] The enum type 'xxx' is unscoped. Prefer 'enum class' over 'enum' (Enum.3).
#endif
#ifdef __GNUC__
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wpragmas" // warning: unknown option after '#pragma GCC diagnostic' kind
#pragma GCC diagnostic ignored "-Wunused-function" // warning: 'xxxx' defined but not used
#ifndef __clang__
#pragma GCC diagnostic ignored "-Wsubobject-linkage" // warning: 'xxxx' has a field 'xxxx' whose type uses the anonymous namespace
#endif
#endif
//-------------------------------------------------------------------------
// Data
//-------------------------------------------------------------------------
// Default memory allocators
static void* ImGuiFreeTypeDefaultAllocFunc(size_t size, void* user_data) { IM_UNUSED(user_data); return IM_ALLOC(size); }
static void ImGuiFreeTypeDefaultFreeFunc(void* ptr, void* user_data) { IM_UNUSED(user_data); IM_FREE(ptr); }
// Current memory allocators
static void* (*GImGuiFreeTypeAllocFunc)(size_t size, void* user_data) = ImGuiFreeTypeDefaultAllocFunc;
static void (*GImGuiFreeTypeFreeFunc)(void* ptr, void* user_data) = ImGuiFreeTypeDefaultFreeFunc;
static void* GImGuiFreeTypeAllocatorUserData = nullptr;
// Lunasvg support
#ifdef IMGUI_ENABLE_FREETYPE_LUNASVG
static FT_Error ImGuiLunasvgPortInit(FT_Pointer* state);
static void ImGuiLunasvgPortFree(FT_Pointer* state);
static FT_Error ImGuiLunasvgPortRender(FT_GlyphSlot slot, FT_Pointer* _state);
static FT_Error ImGuiLunasvgPortPresetSlot(FT_GlyphSlot slot, FT_Bool cache, FT_Pointer* _state);
#endif
//-------------------------------------------------------------------------
// Code
//-------------------------------------------------------------------------
#define FT_CEIL(X) (((X + 63) & -64) / 64) // From SDL_ttf: Handy routines for converting from fixed point
#define FT_SCALEFACTOR 64.0f // For converting from/to 26.6 factionals
// Glyph metrics:
// --------------
//
// xmin xmax
// | |
// |<-------- width -------->|
// | |
// | +-------------------------+----------------- ymax
// | | ggggggggg ggggg | ^ ^
// | | g:::::::::ggg::::g | | |
// | | g:::::::::::::::::g | | |
// | | g::::::ggggg::::::gg | | |
// | | g:::::g g:::::g | | |
// offsetX -|-------->| g:::::g g:::::g | offsetY |
// | | g:::::g g:::::g | | |
// | | g::::::g g:::::g | | |
// | | g:::::::ggggg:::::g | | |
// | | g::::::::::::::::g | | height
// | | gg::::::::::::::g | | |
// baseline ---*---------|---- gggggggg::::::g-----*-------- |
// / | | g:::::g | |
// origin | | gggggg g:::::g | |
// | | g:::::gg gg:::::g | |
// | | g::::::ggg:::::::g | |
// | | gg:::::::::::::g | |
// | | ggg::::::ggg | |
// | | gggggg | v
// | +-------------------------+----------------- ymin
// | |
// |------------- advanceX ----------->|
// Stored in ImFontAtlas::FontLoaderData. ALLOCATED BY US.
struct ImGui_ImplFreeType_Data
{
FT_Library Library;
FT_MemoryRec_ MemoryManager;
ImGui_ImplFreeType_Data() { memset((void*)this, 0, sizeof(*this)); }
};
// Stored in ImFontConfig::FontLoaderData. ALLOCATED BY US.
struct ImGui_ImplFreeType_FontSrcData
{
// Initialize from an external data buffer. Doesn't copy data, and you must ensure it stays valid up to this object lifetime.
bool InitFont(FT_Library ft_library, const ImFontConfig* src, ImGuiFreeTypeLoaderFlags extra_user_flags);
void CloseFont();
ImGui_ImplFreeType_FontSrcData() { memset((void*)this, 0, sizeof(*this)); }
~ImGui_ImplFreeType_FontSrcData() { CloseFont(); }
// Members
FT_Face FtFace;
ImGuiFreeTypeLoaderFlags UserFlags; // = ImFontConfig::FontLoaderFlags
FT_Int32 LoadFlags;
ImFontBaked* BakedLastActivated;
};
// Stored in ImFontBaked::FontLoaderDatas: pointer to SourcesCount instances of this. ALLOCATED BY CORE.
struct ImGui_ImplFreeType_FontSrcBakedData
{
FT_Size FtSize; // This represent a FT_Face with a given size.
ImGui_ImplFreeType_FontSrcBakedData() { memset((void*)this, 0, sizeof(*this)); }
};
bool ImGui_ImplFreeType_FontSrcData::InitFont(FT_Library ft_library, const ImFontConfig* src, ImGuiFreeTypeLoaderFlags extra_font_loader_flags)
{
FT_Error error = FT_New_Memory_Face(ft_library, (const FT_Byte*)src->FontData, (FT_Long)src->FontDataSize, (FT_Long)src->FontNo, &FtFace);
if (error != 0)
return false;
// Reject COLRv1 fonts as they require a dedicated rendering engine (not supported)
if (FT_IS_SFNT(FtFace))
{
FT_ULong len = 0;
if (FT_Load_Sfnt_Table(FtFace, TTAG_COLR, 0, nullptr, &len) == 0 && len >= 2)
{
FT_Byte header[2];
FT_ULong headerLen = sizeof(header);
if (FT_Load_Sfnt_Table(FtFace, TTAG_COLR, 0, header, &headerLen) == 0 && headerLen >= 2)
{
int version = (header[0] << 8) | header[1];
if (version == 1)
{
FT_Done_Face(FtFace);
FtFace = nullptr;
return false;
}
}
}
}
error = FT_Select_Charmap(FtFace, FT_ENCODING_UNICODE);
if (error != 0)
return false;
// Convert to FreeType flags (NB: Bold and Oblique are processed separately)
UserFlags = (ImGuiFreeTypeLoaderFlags)(src->FontLoaderFlags | extra_font_loader_flags);
LoadFlags = 0;
if ((UserFlags & ImGuiFreeTypeLoaderFlags_Bitmap) == 0)
LoadFlags |= FT_LOAD_NO_BITMAP;
if (UserFlags & ImGuiFreeTypeLoaderFlags_NoHinting)
LoadFlags |= FT_LOAD_NO_HINTING;
if (UserFlags & ImGuiFreeTypeLoaderFlags_NoAutoHint)
LoadFlags |= FT_LOAD_NO_AUTOHINT;
if (UserFlags & ImGuiFreeTypeLoaderFlags_ForceAutoHint)
LoadFlags |= FT_LOAD_FORCE_AUTOHINT;
if (UserFlags & ImGuiFreeTypeLoaderFlags_LightHinting)
LoadFlags |= FT_LOAD_TARGET_LIGHT;
else if (UserFlags & ImGuiFreeTypeLoaderFlags_MonoHinting)
LoadFlags |= FT_LOAD_TARGET_MONO;
else
LoadFlags |= FT_LOAD_TARGET_NORMAL;
if (UserFlags & ImGuiFreeTypeLoaderFlags_LoadColor)
LoadFlags |= FT_LOAD_COLOR;
return true;
}
void ImGui_ImplFreeType_FontSrcData::CloseFont()
{
if (FtFace)
{
FT_Done_Face(FtFace);
FtFace = nullptr;
}
}
static const FT_Glyph_Metrics* ImGui_ImplFreeType_LoadGlyph(ImGui_ImplFreeType_FontSrcData* src_data, uint32_t codepoint)
{
uint32_t glyph_index = FT_Get_Char_Index(src_data->FtFace, codepoint);
if (glyph_index == 0)
return nullptr;
// If this crash for you: FreeType 2.11.0 has a crash bug on some bitmap/colored fonts.
// - https://gitlab.freedesktop.org/freetype/freetype/-/issues/1076
// - https://github.com/ocornut/imgui/issues/4567
// - https://github.com/ocornut/imgui/issues/4566
// You can use FreeType 2.10, or the patched version of 2.11.0 in VcPkg, or probably any upcoming FreeType version.
FT_Error error = FT_Load_Glyph(src_data->FtFace, glyph_index, src_data->LoadFlags);
if (error)
return nullptr;
// Need an outline for this to work
FT_GlyphSlot slot = src_data->FtFace->glyph;
#if defined(IMGUI_ENABLE_FREETYPE_LUNASVG) || defined(IMGUI_ENABLE_FREETYPE_PLUTOSVG)
IM_ASSERT(slot->format == FT_GLYPH_FORMAT_OUTLINE || slot->format == FT_GLYPH_FORMAT_BITMAP || slot->format == FT_GLYPH_FORMAT_SVG);
#else
#if ((FREETYPE_MAJOR >= 2) && (FREETYPE_MINOR >= 12))
IM_ASSERT(slot->format != FT_GLYPH_FORMAT_SVG && "The font contains SVG glyphs, you'll need to enable IMGUI_ENABLE_FREETYPE_PLUTOSVG or IMGUI_ENABLE_FREETYPE_LUNASVG in imconfig.h and install required libraries in order to use this font");
#endif
IM_ASSERT(slot->format == FT_GLYPH_FORMAT_OUTLINE || slot->format == FT_GLYPH_FORMAT_BITMAP);
#endif // IMGUI_ENABLE_FREETYPE_LUNASVG
// Apply convenience transform (this is not picking from real "Bold"/"Italic" fonts! Merely applying FreeType helper transform. Oblique == Slanting)
if (src_data->UserFlags & ImGuiFreeTypeLoaderFlags_Bold)
FT_GlyphSlot_Embolden(slot);
if (src_data->UserFlags & ImGuiFreeTypeLoaderFlags_Oblique)
{
FT_GlyphSlot_Oblique(slot);
//FT_BBox bbox;
//FT_Outline_Get_BBox(&slot->outline, &bbox);
//slot->metrics.width = bbox.xMax - bbox.xMin;
//slot->metrics.height = bbox.yMax - bbox.yMin;
}
return &slot->metrics;
}
static void ImGui_ImplFreeType_BlitGlyph(const FT_Bitmap* ft_bitmap, uint32_t* dst, uint32_t dst_pitch)
{
IM_ASSERT(ft_bitmap != nullptr);
const uint32_t w = ft_bitmap->width;
const uint32_t h = ft_bitmap->rows;
const uint8_t* src = ft_bitmap->buffer;
const uint32_t src_pitch = ft_bitmap->pitch;
switch (ft_bitmap->pixel_mode)
{
case FT_PIXEL_MODE_GRAY: // Grayscale image, 1 byte per pixel.
{
for (uint32_t y = 0; y < h; y++, src += src_pitch, dst += dst_pitch)
for (uint32_t x = 0; x < w; x++)
dst[x] = IM_COL32(255, 255, 255, src[x]);
break;
}
case FT_PIXEL_MODE_MONO: // Monochrome image, 1 bit per pixel. The bits in each byte are ordered from MSB to LSB.
{
for (uint32_t y = 0; y < h; y++, src += src_pitch, dst += dst_pitch)
{
uint8_t bits = 0;
const uint8_t* bits_ptr = src;
for (uint32_t x = 0; x < w; x++, bits <<= 1)
{
if ((x & 7) == 0)
bits = *bits_ptr++;
dst[x] = IM_COL32(255, 255, 255, (bits & 0x80) ? 255 : 0);
}
}
break;
}
case FT_PIXEL_MODE_BGRA:
{
// FIXME: Converting pre-multiplied alpha to straight. Doesn't smell good.
#define DE_MULTIPLY(color, alpha) ImMin((ImU32)(255.0f * (float)color / (float)(alpha + FLT_MIN) + 0.5f), 255u)
for (uint32_t y = 0; y < h; y++, src += src_pitch, dst += dst_pitch)
for (uint32_t x = 0; x < w; x++)
{
uint8_t r = src[x * 4 + 2], g = src[x * 4 + 1], b = src[x * 4], a = src[x * 4 + 3];
dst[x] = IM_COL32(DE_MULTIPLY(r, a), DE_MULTIPLY(g, a), DE_MULTIPLY(b, a), a);
}
#undef DE_MULTIPLY
break;
}
default:
IM_ASSERT(0 && "FreeTypeFont::BlitGlyph(): Unknown bitmap pixel mode!");
}
}
// FreeType memory allocation callbacks
static void* FreeType_Alloc(FT_Memory /*memory*/, long size)
{
return GImGuiFreeTypeAllocFunc((size_t)size, GImGuiFreeTypeAllocatorUserData);
}
static void FreeType_Free(FT_Memory /*memory*/, void* block)
{
GImGuiFreeTypeFreeFunc(block, GImGuiFreeTypeAllocatorUserData);
}
static void* FreeType_Realloc(FT_Memory /*memory*/, long cur_size, long new_size, void* block)
{
// Implement realloc() as we don't ask user to provide it.
if (block == nullptr)
return GImGuiFreeTypeAllocFunc((size_t)new_size, GImGuiFreeTypeAllocatorUserData);
if (new_size == 0)
{
GImGuiFreeTypeFreeFunc(block, GImGuiFreeTypeAllocatorUserData);
return nullptr;
}
if (new_size > cur_size)
{
void* new_block = GImGuiFreeTypeAllocFunc((size_t)new_size, GImGuiFreeTypeAllocatorUserData);
memcpy(new_block, block, (size_t)cur_size);
GImGuiFreeTypeFreeFunc(block, GImGuiFreeTypeAllocatorUserData);
return new_block;
}
return block;
}
static bool ImGui_ImplFreeType_LoaderInit(ImFontAtlas* atlas)
{
IM_ASSERT(atlas->FontLoaderData == nullptr);
ImGui_ImplFreeType_Data* bd = IM_NEW(ImGui_ImplFreeType_Data)();
// FreeType memory management: https://www.freetype.org/freetype2/docs/design/design-4.html
bd->MemoryManager.user = nullptr;
bd->MemoryManager.alloc = &FreeType_Alloc;
bd->MemoryManager.free = &FreeType_Free;
bd->MemoryManager.realloc = &FreeType_Realloc;
// https://www.freetype.org/freetype2/docs/reference/ft2-module_management.html#FT_New_Library
FT_Error error = FT_New_Library(&bd->MemoryManager, &bd->Library);
if (error != 0)
{
IM_DELETE(bd);
return false;
}
// If you don't call FT_Add_Default_Modules() the rest of code may work, but FreeType won't use our custom allocator.
FT_Add_Default_Modules(bd->Library);
#ifdef IMGUI_ENABLE_FREETYPE_LUNASVG
// Install svg hooks for FreeType
// https://freetype.org/freetype2/docs/reference/ft2-properties.html#svg-hooks
// https://freetype.org/freetype2/docs/reference/ft2-svg_fonts.html#svg_fonts
SVG_RendererHooks hooks = { ImGuiLunasvgPortInit, ImGuiLunasvgPortFree, ImGuiLunasvgPortRender, ImGuiLunasvgPortPresetSlot };
FT_Property_Set(bd->Library, "ot-svg", "svg-hooks", &hooks);
#endif // IMGUI_ENABLE_FREETYPE_LUNASVG
#ifdef IMGUI_ENABLE_FREETYPE_PLUTOSVG
// With plutosvg, use provided hooks
FT_Property_Set(bd->Library, "ot-svg", "svg-hooks", plutosvg_ft_svg_hooks());
#endif // IMGUI_ENABLE_FREETYPE_PLUTOSVG
// Store our data
atlas->FontLoaderData = (void*)bd;
return true;
}
static void ImGui_ImplFreeType_LoaderShutdown(ImFontAtlas* atlas)
{
ImGui_ImplFreeType_Data* bd = (ImGui_ImplFreeType_Data*)atlas->FontLoaderData;
IM_ASSERT(bd != nullptr);
FT_Done_Library(bd->Library);
IM_DELETE(bd);
atlas->FontLoaderData = nullptr;
}
static bool ImGui_ImplFreeType_FontSrcInit(ImFontAtlas* atlas, ImFontConfig* src)
{
ImGui_ImplFreeType_Data* bd = (ImGui_ImplFreeType_Data*)atlas->FontLoaderData;
ImGui_ImplFreeType_FontSrcData* bd_font_data = IM_NEW(ImGui_ImplFreeType_FontSrcData);
IM_ASSERT(src->FontLoaderData == nullptr);
src->FontLoaderData = bd_font_data;
if (!bd_font_data->InitFont(bd->Library, src, (ImGuiFreeTypeLoaderFlags)atlas->FontLoaderFlags))
{
IM_DELETE(bd_font_data);
src->FontLoaderData = nullptr;
return false;
}
return true;
}
static void ImGui_ImplFreeType_FontSrcDestroy(ImFontAtlas* atlas, ImFontConfig* src)
{
IM_UNUSED(atlas);
ImGui_ImplFreeType_FontSrcData* bd_font_data = (ImGui_ImplFreeType_FontSrcData*)src->FontLoaderData;
IM_DELETE(bd_font_data);
src->FontLoaderData = nullptr;
}
static bool ImGui_ImplFreeType_FontBakedInit(ImFontAtlas* atlas, ImFontConfig* src, ImFontBaked* baked, void* loader_data_for_baked_src)
{
IM_UNUSED(atlas);
float size = baked->Size;
if (src->MergeMode && src->SizePixels != 0.0f)
size *= (src->SizePixels / baked->OwnerFont->Sources[0]->SizePixels);
size *= src->ExtraSizeScale;
ImGui_ImplFreeType_FontSrcData* bd_font_data = (ImGui_ImplFreeType_FontSrcData*)src->FontLoaderData;
bd_font_data->BakedLastActivated = baked;
// We use one FT_Size per (source + baked) combination.
ImGui_ImplFreeType_FontSrcBakedData* bd_baked_data = (ImGui_ImplFreeType_FontSrcBakedData*)loader_data_for_baked_src;
IM_ASSERT(bd_baked_data != nullptr);
IM_PLACEMENT_NEW(bd_baked_data) ImGui_ImplFreeType_FontSrcBakedData();
FT_New_Size(bd_font_data->FtFace, &bd_baked_data->FtSize);
FT_Activate_Size(bd_baked_data->FtSize);
const float rasterizer_density = src->RasterizerDensity * baked->RasterizerDensity;
if (((bd_font_data->FtFace->face_flags & FT_FACE_FLAG_FIXED_SIZES) != 0) && ((bd_font_data->FtFace->face_flags & FT_FACE_FLAG_SCALABLE) == 0) && ((bd_font_data->UserFlags & ImGuiFreeTypeLoaderFlags_Bitmap) != 0))
{
IM_ASSERT(bd_font_data->FtFace->num_fixed_sizes > 0);
// Loop over sizes and pick the closest, larger (or better equal) size.
int best_index = 0;
float best_height = bd_font_data->FtFace->available_sizes[best_index].y_ppem / FT_SCALEFACTOR;
for (int i = 1; i < bd_font_data->FtFace->num_fixed_sizes; i++)
{
const float cur_height = bd_font_data->FtFace->available_sizes[i].y_ppem / FT_SCALEFACTOR;
// FIXME: is this overkill?
// FIXME: is-float-close with epsilon param would be nice, maybe
if (ImFabs(cur_height - size) < 0.001f)
{
best_index = i;
best_height = cur_height;
break;
}
else if (cur_height < size)
{
if (best_height < cur_height)
{
best_index = i;
best_height = cur_height;
}
}
else
{
if (best_height < size && best_height < cur_height)
{
best_index = i;
best_height = cur_height;
}
}
}
FT_Select_Size(bd_font_data->FtFace, best_index);
}
else
{
// Vuhdo 2017: "I'm not sure how to deal with font sizes properly. As far as I understand, currently ImGui assumes that the 'pixel_height'
// is a maximum height of an any given glyph, i.e. it's the sum of font's ascender and descender. Seems strange to me.
// FT_Set_Pixel_Sizes() doesn't seem to get us the same result."
// (FT_Set_Pixel_Sizes() essentially calls FT_Request_Size() with FT_SIZE_REQUEST_TYPE_NOMINAL)
FT_Size_RequestRec req;
req.type = (bd_font_data->UserFlags & ImGuiFreeTypeLoaderFlags_Bitmap) ? FT_SIZE_REQUEST_TYPE_NOMINAL : FT_SIZE_REQUEST_TYPE_REAL_DIM;
req.width = 0;
req.height = (uint32_t)(size * FT_SCALEFACTOR * rasterizer_density);
req.horiResolution = 0;
req.vertResolution = 0;
FT_Request_Size(bd_font_data->FtFace, &req);
}
// Output
if (src->MergeMode == false)
{
// Read metrics
FT_Size_Metrics metrics = bd_baked_data->FtSize->metrics;
const float scale = 1.0f / (rasterizer_density * src->ExtraSizeScale);
baked->Ascent = (float)FT_CEIL(metrics.ascender) * scale; // The pixel extents above the baseline in pixels (typically positive).
baked->Descent = (float)FT_CEIL(metrics.descender) * scale; // The extents below the baseline in pixels (typically negative).
//LineSpacing = (float)FT_CEIL(metrics.height) * scale; // The baseline-to-baseline distance. Note that it usually is larger than the sum of the ascender and descender taken as absolute values. There is also no guarantee that no glyphs extend above or below subsequent baselines when using this distance. Think of it as a value the designer of the font finds appropriate.
//LineGap = (float)FT_CEIL(metrics.height - metrics.ascender + metrics.descender) * scale; // The spacing in pixels between one row's descent and the next row's ascent.
//MaxAdvanceWidth = (float)FT_CEIL(metrics.max_advance) * scale; // This field gives the maximum horizontal cursor advance for all glyphs in the font.
}
return true;
}
static void ImGui_ImplFreeType_FontBakedDestroy(ImFontAtlas* atlas, ImFontConfig* src, ImFontBaked* baked, void* loader_data_for_baked_src)
{
IM_UNUSED(atlas);
IM_UNUSED(baked);
IM_UNUSED(src);
ImGui_ImplFreeType_FontSrcBakedData* bd_baked_data = (ImGui_ImplFreeType_FontSrcBakedData*)loader_data_for_baked_src;
IM_ASSERT(bd_baked_data != nullptr);
FT_Done_Size(bd_baked_data->FtSize);
bd_baked_data->~ImGui_ImplFreeType_FontSrcBakedData(); // ~IM_PLACEMENT_DELETE()
}
static bool ImGui_ImplFreeType_FontBakedLoadGlyph(ImFontAtlas* atlas, ImFontConfig* src, ImFontBaked* baked, void* loader_data_for_baked_src, ImWchar codepoint, ImFontGlyph* out_glyph, float* out_advance_x)
{
ImGui_ImplFreeType_FontSrcData* bd_font_data = (ImGui_ImplFreeType_FontSrcData*)src->FontLoaderData;
uint32_t glyph_index = FT_Get_Char_Index(bd_font_data->FtFace, codepoint);
if (glyph_index == 0)
return false;
if (bd_font_data->BakedLastActivated != baked) // <-- could use id
{
// Activate current size
ImGui_ImplFreeType_FontSrcBakedData* bd_baked_data = (ImGui_ImplFreeType_FontSrcBakedData*)loader_data_for_baked_src;
FT_Activate_Size(bd_baked_data->FtSize);
bd_font_data->BakedLastActivated = baked;
}
const FT_Glyph_Metrics* metrics = ImGui_ImplFreeType_LoadGlyph(bd_font_data, codepoint);
if (metrics == nullptr)
return false;
FT_Face face = bd_font_data->FtFace;
FT_GlyphSlot slot = face->glyph;
const float rasterizer_density = src->RasterizerDensity * baked->RasterizerDensity;
float bitmap_x_scale = 1.0f;
float bitmap_y_scale = 1.0f;
if (((face->face_flags & FT_FACE_FLAG_FIXED_SIZES) != 0) && ((face->face_flags & FT_FACE_FLAG_SCALABLE) == 0) && ((bd_font_data->UserFlags & ImGuiFreeTypeLoaderFlags_Bitmap) != 0))
{
// FIXME: what if this just means invisible?
IM_ASSERT(bd_font_data->FtFace->size->metrics.x_ppem > 0);
IM_ASSERT(bd_font_data->FtFace->size->metrics.y_ppem > 0);
// Scale fixed size bitmap to target size
bitmap_x_scale = baked->Size / bd_font_data->FtFace->size->metrics.x_ppem;
bitmap_y_scale = baked->Size / bd_font_data->FtFace->size->metrics.y_ppem;
}
// Load metrics only mode
const float advance_x = ((slot->advance.x / FT_SCALEFACTOR) * bitmap_x_scale) / rasterizer_density;
if (out_advance_x != NULL)
{
IM_ASSERT(out_glyph == NULL);
*out_advance_x = advance_x;
return true;
}
// Render glyph into a bitmap (currently held by FreeType)
FT_Render_Mode render_mode = (bd_font_data->UserFlags & ImGuiFreeTypeLoaderFlags_Monochrome) ? FT_RENDER_MODE_MONO : FT_RENDER_MODE_NORMAL;
FT_Error error = FT_Render_Glyph(slot, render_mode);
const FT_Bitmap* ft_bitmap = &slot->bitmap;
if (error != 0 || ft_bitmap == nullptr)
return false;
const int bitmap_w = (int)ft_bitmap->width;
const int bitmap_h = (int)ft_bitmap->rows;
const bool is_visible = (bitmap_w != 0 && bitmap_h != 0);
// Prepare glyph
out_glyph->Codepoint = codepoint;
out_glyph->AdvanceX = advance_x;
// Pack and retrieve position inside texture atlas
if (is_visible)
{
const int w = (int)ImCeil(bitmap_w * bitmap_x_scale);
const int h = (int)ImCeil(bitmap_h * bitmap_y_scale);
const bool rescaling = h != bitmap_h || w != bitmap_w;
ImFontAtlasRectId pack_id = ImFontAtlasPackAddRect(atlas, w, h);
if (pack_id == ImFontAtlasRectId_Invalid)
{
// Pathological out of memory case (TexMaxWidth/TexMaxHeight set too small?)
IM_ASSERT(pack_id != ImFontAtlasRectId_Invalid && "Out of texture memory.");
return false;
}
ImTextureRect* r = ImFontAtlasPackGetRect(atlas, pack_id);
// Render pixels to our temporary buffer, while making sure we have space for an extra copy used during rescaling.
atlas->Builder->TempBuffer.resize(((rescaling ? bitmap_w * bitmap_h : 0) + w * h) * 4);
uint32_t* temp_buffer = (uint32_t*)atlas->Builder->TempBuffer.Data;
// Blit (and convert) into the first bitmap_w * bitmap_h * 4 bytes.
ImGui_ImplFreeType_BlitGlyph(ft_bitmap, temp_buffer, bitmap_w);
if (rescaling)
{
uint32_t* dst_buffer = temp_buffer + bitmap_w * bitmap_h;
// Perform rescale, from temp_buffer (bitmap_w * bitmap_h) to dst_buffer (w * h)
ImFontAtlasTextureBlockResize(temp_buffer, bitmap_w, bitmap_h, dst_buffer, w, h);
// Redirect to rescaled part of the buffer
temp_buffer = dst_buffer;
}
const float ref_size = baked->OwnerFont->Sources[0]->SizePixels;
const float offsets_scale = (ref_size != 0.0f) ? (baked->Size / ref_size) : 1.0f;
float font_off_x = ImFloor(src->GlyphOffset.x * offsets_scale + 0.5f); // Snap scaled offset.
float font_off_y = ImFloor(src->GlyphOffset.y * offsets_scale + 0.5f) + baked->Ascent;
float recip_h = 1.0f / rasterizer_density;
float recip_v = 1.0f / rasterizer_density;
// Register glyph
float glyph_off_x = ImCeil((float)face->glyph->bitmap_left * bitmap_x_scale);
float glyph_off_y = ImCeil((float)-face->glyph->bitmap_top * bitmap_y_scale);
out_glyph->X0 = glyph_off_x * recip_h + font_off_x;
out_glyph->Y0 = glyph_off_y * recip_v + font_off_y;
out_glyph->X1 = (glyph_off_x + w) * recip_h + font_off_x;
out_glyph->Y1 = (glyph_off_y + h) * recip_v + font_off_y;
out_glyph->Visible = true;
out_glyph->Colored = (ft_bitmap->pixel_mode == FT_PIXEL_MODE_BGRA);
out_glyph->PackId = pack_id;
ImFontAtlasBakedSetFontGlyphBitmap(atlas, baked, src, out_glyph, r, (const unsigned char*)temp_buffer, ImTextureFormat_RGBA32, w * 4);
}
return true;
}
static bool ImGui_ImplFreetype_FontSrcContainsGlyph(ImFontAtlas* atlas, ImFontConfig* src, ImWchar codepoint)
{
IM_UNUSED(atlas);
ImGui_ImplFreeType_FontSrcData* bd_font_data = (ImGui_ImplFreeType_FontSrcData*)src->FontLoaderData;
int glyph_index = FT_Get_Char_Index(bd_font_data->FtFace, codepoint);
return glyph_index != 0;
}
const ImFontLoader* ImGuiFreeType::GetFontLoader()
{
static ImFontLoader loader;
loader.Name = "FreeType";
loader.LoaderInit = ImGui_ImplFreeType_LoaderInit;
loader.LoaderShutdown = ImGui_ImplFreeType_LoaderShutdown;
loader.FontSrcInit = ImGui_ImplFreeType_FontSrcInit;
loader.FontSrcDestroy = ImGui_ImplFreeType_FontSrcDestroy;
loader.FontSrcContainsGlyph = ImGui_ImplFreetype_FontSrcContainsGlyph;
loader.FontBakedInit = ImGui_ImplFreeType_FontBakedInit;
loader.FontBakedDestroy = ImGui_ImplFreeType_FontBakedDestroy;
loader.FontBakedLoadGlyph = ImGui_ImplFreeType_FontBakedLoadGlyph;
loader.FontBakedSrcLoaderDataSize = sizeof(ImGui_ImplFreeType_FontSrcBakedData);
return &loader;
}
void ImGuiFreeType::SetAllocatorFunctions(void* (*alloc_func)(size_t sz, void* user_data), void (*free_func)(void* ptr, void* user_data), void* user_data)
{
GImGuiFreeTypeAllocFunc = alloc_func;
GImGuiFreeTypeFreeFunc = free_func;
GImGuiFreeTypeAllocatorUserData = user_data;
}
bool ImGuiFreeType::DebugEditFontLoaderFlags(unsigned int* p_font_loader_flags)
{
bool edited = false;
edited |= ImGui::CheckboxFlags("NoHinting", p_font_loader_flags, ImGuiFreeTypeLoaderFlags_NoHinting);
edited |= ImGui::CheckboxFlags("NoAutoHint", p_font_loader_flags, ImGuiFreeTypeLoaderFlags_NoAutoHint);
edited |= ImGui::CheckboxFlags("ForceAutoHint",p_font_loader_flags, ImGuiFreeTypeLoaderFlags_ForceAutoHint);
edited |= ImGui::CheckboxFlags("LightHinting", p_font_loader_flags, ImGuiFreeTypeLoaderFlags_LightHinting);
edited |= ImGui::CheckboxFlags("MonoHinting", p_font_loader_flags, ImGuiFreeTypeLoaderFlags_MonoHinting);
edited |= ImGui::CheckboxFlags("Bold", p_font_loader_flags, ImGuiFreeTypeLoaderFlags_Bold);
edited |= ImGui::CheckboxFlags("Oblique", p_font_loader_flags, ImGuiFreeTypeLoaderFlags_Oblique);
edited |= ImGui::CheckboxFlags("Monochrome", p_font_loader_flags, ImGuiFreeTypeLoaderFlags_Monochrome);
edited |= ImGui::CheckboxFlags("LoadColor", p_font_loader_flags, ImGuiFreeTypeLoaderFlags_LoadColor);
edited |= ImGui::CheckboxFlags("Bitmap", p_font_loader_flags, ImGuiFreeTypeLoaderFlags_Bitmap);
return edited;
}
#ifdef IMGUI_ENABLE_FREETYPE_LUNASVG
// For more details, see https://gitlab.freedesktop.org/freetype/freetype-demos/-/blob/master/src/rsvg-port.c
// The original code from the demo is licensed under CeCILL-C Free Software License Agreement (https://gitlab.freedesktop.org/freetype/freetype/-/blob/master/LICENSE.TXT)
struct LunasvgPortState
{
FT_Error err = FT_Err_Ok;
lunasvg::Matrix matrix;
std::unique_ptr<lunasvg::Document> svg = nullptr;
};
static FT_Error ImGuiLunasvgPortInit(FT_Pointer* _state)
{
*_state = IM_NEW(LunasvgPortState)();
return FT_Err_Ok;
}
static void ImGuiLunasvgPortFree(FT_Pointer* _state)
{
IM_DELETE(*(LunasvgPortState**)_state);
}
static FT_Error ImGuiLunasvgPortRender(FT_GlyphSlot slot, FT_Pointer* _state)
{
LunasvgPortState* state = *(LunasvgPortState**)_state;
// If there was an error while loading the svg in ImGuiLunasvgPortPresetSlot(), the renderer hook still get called, so just returns the error.
if (state->err != FT_Err_Ok)
return state->err;
// rows is height, pitch (or stride) equals to width * sizeof(int32)
lunasvg::Bitmap bitmap((uint8_t*)slot->bitmap.buffer, slot->bitmap.width, slot->bitmap.rows, slot->bitmap.pitch);
#if LUNASVG_VERSION_MAJOR >= 3
state->svg->render(bitmap, state->matrix); // state->matrix is already scaled and translated
#else
state->svg->setMatrix(state->svg->matrix().identity()); // Reset the svg matrix to the default value
state->svg->render(bitmap, state->matrix); // state->matrix is already scaled and translated
#endif
state->err = FT_Err_Ok;
return state->err;
}
static FT_Error ImGuiLunasvgPortPresetSlot(FT_GlyphSlot slot, FT_Bool cache, FT_Pointer* _state)
{
FT_SVG_Document document = (FT_SVG_Document)slot->other;
LunasvgPortState* state = *(LunasvgPortState**)_state;
FT_Size_Metrics& metrics = document->metrics;
// This function is called twice, once in the FT_Load_Glyph() and another right before ImGuiLunasvgPortRender().
// If it's the latter, don't do anything because it's // already done in the former.
if (cache)
return state->err;
state->svg = lunasvg::Document::loadFromData((const char*)document->svg_document, document->svg_document_length);
if (state->svg == nullptr)
{
state->err = FT_Err_Invalid_SVG_Document;
return state->err;
}
#if LUNASVG_VERSION_MAJOR >= 3
lunasvg::Box box = state->svg->boundingBox();
#else
lunasvg::Box box = state->svg->box();
#endif
double scale = std::min(metrics.x_ppem / box.w, metrics.y_ppem / box.h);
double xx = (double)document->transform.xx / (1 << 16);
double xy = -(double)document->transform.xy / (1 << 16);
double yx = -(double)document->transform.yx / (1 << 16);
double yy = (double)document->transform.yy / (1 << 16);
double x0 = (double)document->delta.x / 64 * box.w / metrics.x_ppem;
double y0 = -(double)document->delta.y / 64 * box.h / metrics.y_ppem;
#if LUNASVG_VERSION_MAJOR >= 3
// Scale, transform and pre-translate the matrix for the rendering step
state->matrix = lunasvg::Matrix::translated(-box.x, -box.y);
state->matrix.multiply(lunasvg::Matrix(xx, xy, yx, yy, x0, y0));
state->matrix.scale(scale, scale);
// Apply updated transformation to the bounding box
box.transform(state->matrix);
#else
// Scale and transform, we don't translate the svg yet
state->matrix.identity();
state->matrix.scale(scale, scale);
state->matrix.transform(xx, xy, yx, yy, x0, y0);
state->svg->setMatrix(state->matrix);
// Pre-translate the matrix for the rendering step
state->matrix.translate(-box.x, -box.y);
// Get the box again after the transformation
box = state->svg->box();
#endif
// Calculate the bitmap size
slot->bitmap_left = FT_Int(box.x);
slot->bitmap_top = FT_Int(-box.y);
slot->bitmap.rows = (unsigned int)(ImCeil((float)box.h));
slot->bitmap.width = (unsigned int)(ImCeil((float)box.w));
slot->bitmap.pitch = slot->bitmap.width * 4;
slot->bitmap.pixel_mode = FT_PIXEL_MODE_BGRA;
// Compute all the bearings and set them correctly. The outline is scaled already, we just need to use the bounding box.
double metrics_width = box.w;
double metrics_height = box.h;
double horiBearingX = box.x;
double horiBearingY = -box.y;
double vertBearingX = slot->metrics.horiBearingX / 64.0 - slot->metrics.horiAdvance / 64.0 / 2.0;
double vertBearingY = (slot->metrics.vertAdvance / 64.0 - slot->metrics.height / 64.0) / 2.0;
slot->metrics.width = FT_Pos(IM_ROUND(metrics_width * 64.0)); // Using IM_ROUND() assume width and height are positive
slot->metrics.height = FT_Pos(IM_ROUND(metrics_height * 64.0));
slot->metrics.horiBearingX = FT_Pos(horiBearingX * 64);
slot->metrics.horiBearingY = FT_Pos(horiBearingY * 64);
slot->metrics.vertBearingX = FT_Pos(vertBearingX * 64);
slot->metrics.vertBearingY = FT_Pos(vertBearingY * 64);
if (slot->metrics.vertAdvance == 0)
slot->metrics.vertAdvance = FT_Pos(metrics_height * 1.2 * 64.0);
state->err = FT_Err_Ok;
return state->err;
}
#endif // #ifdef IMGUI_ENABLE_FREETYPE_LUNASVG
//-----------------------------------------------------------------------------
#ifdef __GNUC__
#pragma GCC diagnostic pop
#endif
#ifdef _MSC_VER
#pragma warning (pop)
#endif
#endif // #ifndef IMGUI_DISABLE
+83
View File
@@ -0,0 +1,83 @@
// dear imgui: FreeType font builder (used as a replacement for the stb_truetype builder)
// (headers)
#pragma once
#include "imgui.h" // IMGUI_API
#ifndef IMGUI_DISABLE
// Usage:
// - Add '#define IMGUI_ENABLE_FREETYPE' in your imconfig to automatically enable support
// for imgui_freetype in imgui. It is equivalent to selecting the default loader with:
// io.Fonts->SetFontLoader(ImGuiFreeType::GetFontLoader())
// Optional support for OpenType SVG fonts:
// - Add '#define IMGUI_ENABLE_FREETYPE_PLUTOSVG' to use plutosvg (not provided). See #7927.
// - Add '#define IMGUI_ENABLE_FREETYPE_LUNASVG' to use lunasvg (not provided). See #6591.
// Forward declarations
struct ImFontAtlas;
struct ImFontLoader;
// Hinting greatly impacts visuals (and glyph sizes).
// - By default, hinting is enabled and the font's native hinter is preferred over the auto-hinter.
// - When disabled, FreeType generates blurrier glyphs, more or less matches the stb_truetype.h
// - The Default hinting mode usually looks good, but may distort glyphs in an unusual way.
// - The Light hinting mode generates fuzzier glyphs but better matches Microsoft's rasterizer.
// You can set those flags globally in ImFontAtlas::FontLoaderFlags
// You can set those flags on a per font basis in ImFontConfig::FontLoaderFlags
typedef unsigned int ImGuiFreeTypeLoaderFlags;
enum ImGuiFreeTypeLoaderFlags_
{
ImGuiFreeTypeLoaderFlags_NoHinting = 1 << 0, // Disable hinting. This generally generates 'blurrier' bitmap glyphs when the glyph are rendered in any of the anti-aliased modes.
ImGuiFreeTypeLoaderFlags_NoAutoHint = 1 << 1, // Disable auto-hinter.
ImGuiFreeTypeLoaderFlags_ForceAutoHint = 1 << 2, // Indicates that the auto-hinter is preferred over the font's native hinter.
ImGuiFreeTypeLoaderFlags_LightHinting = 1 << 3, // A lighter hinting algorithm for gray-level modes. Many generated glyphs are fuzzier but better resemble their original shape. This is achieved by snapping glyphs to the pixel grid only vertically (Y-axis), as is done by Microsoft's ClearType and Adobe's proprietary font renderer. This preserves inter-glyph spacing in horizontal text.
ImGuiFreeTypeLoaderFlags_MonoHinting = 1 << 4, // Strong hinting algorithm that should only be used for monochrome output.
ImGuiFreeTypeLoaderFlags_Bold = 1 << 5, // Styling: Should we artificially embolden the font?
ImGuiFreeTypeLoaderFlags_Oblique = 1 << 6, // Styling: Should we slant the font, emulating italic style?
ImGuiFreeTypeLoaderFlags_Monochrome = 1 << 7, // Disable anti-aliasing. Combine this with MonoHinting for best results!
ImGuiFreeTypeLoaderFlags_LoadColor = 1 << 8, // Enable FreeType color-layered glyphs
ImGuiFreeTypeLoaderFlags_Bitmap = 1 << 9, // Enable FreeType bitmap glyphs
#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS
ImGuiFreeTypeBuilderFlags_NoHinting = ImGuiFreeTypeLoaderFlags_NoHinting,
ImGuiFreeTypeBuilderFlags_NoAutoHint = ImGuiFreeTypeLoaderFlags_NoAutoHint,
ImGuiFreeTypeBuilderFlags_ForceAutoHint = ImGuiFreeTypeLoaderFlags_ForceAutoHint,
ImGuiFreeTypeBuilderFlags_LightHinting = ImGuiFreeTypeLoaderFlags_LightHinting,
ImGuiFreeTypeBuilderFlags_MonoHinting = ImGuiFreeTypeLoaderFlags_MonoHinting,
ImGuiFreeTypeBuilderFlags_Bold = ImGuiFreeTypeLoaderFlags_Bold,
ImGuiFreeTypeBuilderFlags_Oblique = ImGuiFreeTypeLoaderFlags_Oblique,
ImGuiFreeTypeBuilderFlags_Monochrome = ImGuiFreeTypeLoaderFlags_Monochrome,
ImGuiFreeTypeBuilderFlags_LoadColor = ImGuiFreeTypeLoaderFlags_LoadColor,
ImGuiFreeTypeBuilderFlags_Bitmap = ImGuiFreeTypeLoaderFlags_Bitmap,
#endif
};
// Obsolete names (will be removed)
#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS
typedef ImGuiFreeTypeLoaderFlags_ ImGuiFreeTypeBuilderFlags_;
#endif
namespace ImGuiFreeType
{
// This is automatically assigned when using '#define IMGUI_ENABLE_FREETYPE'.
// If you need to dynamically select between multiple builders:
// - you can manually assign this builder with 'atlas->SetFontLoader(ImGuiFreeType::GetFontLoader())'
// - prefer deep-copying this into your own ImFontLoader instance if you use hot-reloading that messes up static data.
IMGUI_API const ImFontLoader* GetFontLoader();
// Override allocators. By default ImGuiFreeType will use IM_ALLOC()/IM_FREE()
// However, as FreeType does lots of allocations we provide a way for the user to redirect it to a separate memory heap if desired.
IMGUI_API void SetAllocatorFunctions(void* (*alloc_func)(size_t sz, void* user_data), void (*free_func)(void* ptr, void* user_data), void* user_data = nullptr);
// Display UI to edit ImFontAtlas::FontLoaderFlags (shared) or ImFontConfig::FontLoaderFlags (single source)
IMGUI_API bool DebugEditFontLoaderFlags(ImGuiFreeTypeLoaderFlags* p_font_loader_flags);
// Obsolete names (will be removed)
#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS
//IMGUI_API const ImFontBuilderIO* GetBuilderForFreeType(); // Renamed/changed in 1.92. Change 'io.Fonts->FontBuilderIO = ImGuiFreeType::GetBuilderForFreeType()' to 'io.Fonts->SetFontLoader(ImGuiFreeType::GetFontLoader())' if you need runtime selection.
//static inline bool BuildFontAtlas(ImFontAtlas* atlas, unsigned int flags = 0) { atlas->FontBuilderIO = GetBuilderForFreeType(); atlas->FontLoaderFlags = flags; return atlas->Build(); } // Prefer using '#define IMGUI_ENABLE_FREETYPE'
#endif
}
#endif // #ifndef IMGUI_DISABLE
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,149 @@
/* Copyright (C) 2010-2020 The RetroArch team
*
* ---------------------------------------------------------------------------------------
* The following license statement only applies to this file (file_path_io.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.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <sys/stat.h>
#include <boolean.h>
#include <file/file_path.h>
#include <compat/strl.h>
#include <compat/posix_string.h>
#include <retro_miscellaneous.h>
#include <string/stdstring.h>
#define VFS_FRONTEND
#include <vfs/vfs_implementation.h>
#ifdef _WIN32
#include <direct.h>
#else
#include <unistd.h> /* stat() is defined here */
#endif
/* TODO/FIXME - globals */
static retro_vfs_stat_t path_stat_cb = retro_vfs_stat_impl;
static retro_vfs_mkdir_t path_mkdir_cb = retro_vfs_mkdir_impl;
void path_vfs_init(const struct retro_vfs_interface_info* vfs_info)
{
const struct retro_vfs_interface*
vfs_iface = vfs_info->iface;
path_stat_cb = retro_vfs_stat_impl;
path_mkdir_cb = retro_vfs_mkdir_impl;
if (vfs_info->required_interface_version < PATH_REQUIRED_VFS_VERSION || !vfs_iface)
return;
path_stat_cb = vfs_iface->stat;
path_mkdir_cb = vfs_iface->mkdir;
}
int path_stat(const char *path)
{
return path_stat_cb(path, NULL);
}
/**
* path_is_directory:
* @path : path
*
* Checks if path is a directory.
*
* @return true if path is a directory, otherwise false.
*/
bool path_is_directory(const char *path)
{
return (path_stat_cb(path, NULL) & RETRO_VFS_STAT_IS_DIRECTORY) != 0;
}
bool path_is_character_special(const char *path)
{
return (path_stat_cb(path, NULL) & RETRO_VFS_STAT_IS_CHARACTER_SPECIAL) != 0;
}
bool path_is_valid(const char *path)
{
return (path_stat_cb(path, NULL) & RETRO_VFS_STAT_IS_VALID) != 0;
}
int32_t path_get_size(const char *path)
{
int32_t filesize = 0;
if (path_stat_cb(path, &filesize) != 0)
return filesize;
return -1;
}
/**
* path_mkdir:
* @dir : directory
*
* Create directory on filesystem.
*
* Recursive function.
*
* @return true if directory could be created, otherwise false.
**/
bool path_mkdir(const char *dir)
{
bool norecurse = false;
char *basedir = NULL;
if (!(dir && *dir))
return false;
/* Use heap. Real chance of stack
* overflow if we recurse too hard. */
if (!(basedir = strdup(dir)))
return false;
path_parent_dir(basedir, strlen(basedir));
if (!*basedir || !strcmp(basedir, dir))
{
free(basedir);
return false;
}
if ( path_is_directory(basedir)
|| path_mkdir(basedir))
norecurse = true;
free(basedir);
if (norecurse)
{
int ret = path_mkdir_cb(dir);
/* Don't treat this as an error. */
if (ret == -2 && path_is_directory(dir))
return true;
else if (ret == 0)
return true;
}
return false;
}
+4 -1
View File
@@ -3008,7 +3008,10 @@ static bool glsm_state_ctx_init(glsm_ctx_params_t *params)
return false;
#ifdef HAVE_OPENGLES
hw_render.context_type = RETRO_HW_CONTEXT_OPENGLES2;
hw_render.context_type = params->context_type != RETRO_HW_CONTEXT_NONE
? params->context_type : RETRO_HW_CONTEXT_OPENGLES2;
hw_render.version_major = params->major;
hw_render.version_minor = params->minor;
#else
hw_render.context_type = RETRO_HW_CONTEXT_OPENGL;
if (params->context_type != RETRO_HW_CONTEXT_NONE)
+339 -172
View File
@@ -1,4 +1,4 @@
/* Copyright (C) 2010-2019 The RetroArch team
/* Copyright (C) 2010-2020 The RetroArch team
*
* ---------------------------------------------------------------------------------------
* The following license statement only applies to this file (file_path.h).
@@ -51,6 +51,28 @@ enum
RARCH_FILE_UNSUPPORTED
};
struct path_linked_list
{
char *path;
struct path_linked_list *next;
};
/**
* Create a new linked list with one item in it
* The path on this item will be set to NULL
**/
struct path_linked_list* path_linked_list_new(void);
/* Free the entire linked list */
void path_linked_list_free(struct path_linked_list *in_path_linked_list);
/**
* Add a node to the linked list with this path
* If the first node's path if it's not yet set,
* set this instead
**/
void path_linked_list_add_path(struct path_linked_list *in_path_linked_list, char *path);
/**
* path_is_compressed_file:
* @path : path
@@ -81,12 +103,12 @@ bool path_is_compressed_file(const char *path);
* path_get_archive_delim:
* @path : path
*
* Gets delimiter of an archive file. Only the first '#'
* Find delimiter of an archive file. Only the first '#'
* after a compression extension is considered.
*
* Returns: pointer to the delimiter in the path if it contains
* a compressed file, otherwise NULL.
*/
* @return pointer to the delimiter in the path if it contains
* a path inside a compressed file, otherwise NULL.
**/
const char *path_get_archive_delim(const char *path);
/**
@@ -96,10 +118,28 @@ const char *path_get_archive_delim(const char *path);
* Gets extension of file. Only '.'s
* after the last slash are considered.
*
* Returns: extension part from the path.
*/
* Hidden non-leaf function cost:
* - calls string_is_empty()
* - calls strrchr
*
* @return extension part from the path.
**/
const char *path_get_extension(const char *path);
/**
* path_get_extension_mutable:
* @path : path
*
* Specialized version of path_get_extension(). Return
* value is mutable.
*
* Gets extension of file. Only '.'s
* after the last slash are considered.
*
* @return extension part from the path.
**/
char *path_get_extension_mutable(const char *path);
/**
* path_remove_extension:
* @path : path
@@ -108,7 +148,10 @@ const char *path_get_extension(const char *path);
* text after and including the last '.'.
* Only '.'s after the last slash are considered.
*
* Returns:
* Hidden non-leaf function cost:
* - calls strrchr
*
* @return
* 1) If path has an extension, returns path with the
* extension removed.
* 2) If there is no extension, returns NULL.
@@ -122,10 +165,24 @@ char *path_remove_extension(char *path);
*
* Get basename from @path.
*
* Returns: basename from path.
* Hidden non-leaf function cost:
* - Calls path_get_archive_delim()
*
* @return basename from path.
**/
const char *path_basename(const char *path);
/**
* path_basename_nocompression:
* @path : path
*
* Specialized version of path_basename().
* Get basename from @path.
*
* @return basename from path.
**/
const char *path_basename_nocompression(const char *path);
/**
* path_basedir:
* @path : path
@@ -133,44 +190,56 @@ const char *path_basename(const char *path);
* Extracts base directory by mutating path.
* Keeps trailing '/'.
**/
void path_basedir(char *path);
size_t path_basedir(char *path);
/**
* path_parent_dir:
* @path : path
* @s : path
* @len : size of buffer
*
* Extracts parent directory by mutating path.
* Assumes that path is a directory. Keeps trailing '/'.
* If the path was already at the root directory, returns empty string
**/
void path_parent_dir(char *path);
size_t path_parent_dir(char *s, size_t len);
/**
* path_resolve_realpath:
* @buf : buffer for path
* @s : input and output buffer for path
* @size : size of buffer
* @resolve_symlinks : whether to resolve symlinks or not
*
* Turns relative paths into absolute paths and
* resolves use of "." and ".." in absolute paths.
* If relative, rebases on current working dir.
* Resolves use of ".", "..", multiple slashes etc in absolute paths.
*
* Relative paths are rebased on the current working dir.
*
* @return @s if successful, NULL otherwise.
* Note: Not implemented on consoles
* Note: Symlinks are only resolved on Unix-likes
* Note: The current working dir might not be what you expect,
* e.g. on Android it is "/"
* Use of fill_pathname_resolve_relative() should be preferred
**/
void path_resolve_realpath(char *buf, size_t size);
char *path_resolve_realpath(char *s, size_t len, bool resolve_symlinks);
/**
* path_relative_to:
* @out : buffer to write the relative path to
* @s : buffer to write the relative path to
* @path : path to be expressed relatively
* @base : relative to this
* @size : size of output buffer
* @len : size of output buffer
*
* Turns @path into a path relative to @base and writes it to @out.
* Turns @path into a path relative to @base and writes it to @s.
*
* @base is assumed to be a base directory, i.e. a path ending with '/' or '\'.
* Both @path and @base are assumed to be absolute paths without "." or "..".
*
* E.g. path /a/b/e/f.cgp with base /a/b/c/d/ turns into ../../e/f.cgp
*
* @return Length of the string copied into @s
**/
void path_relative_to(char *out, const char *path, const char *base, size_t size);
size_t path_relative_to(char *s, const char *path, const char *base,
size_t len);
/**
* path_is_absolute:
@@ -178,261 +247,339 @@ void path_relative_to(char *out, const char *path, const char *base, size_t size
*
* Checks if @path is an absolute path or a relative path.
*
* Returns: true if path is absolute, false if path is relative.
* @return true if path is absolute, false if path is relative.
**/
bool path_is_absolute(const char *path);
/**
* fill_pathname:
* @out_path : output path
* @s : output path
* @in_path : input path
* @replace : what to replace
* @size : buffer size of output path
* @len : buffer size of output path
*
* FIXME: Verify
*
* Replaces filename extension with 'replace' and outputs result to out_path.
* Replaces filename extension with 'replace' and outputs result to s.
* The extension here is considered to be the string from the last '.'
* to the end.
*
* Only '.'s after the last slash are considered as extensions.
* If no '.' is present, in_path and replace will simply be concatenated.
* 'size' is buffer size of 'out_path'.
* 'len' is buffer size of 's'.
* E.g.: in_path = "/foo/bar/baz/boo.c", replace = ".asm" =>
* out_path = "/foo/bar/baz/boo.asm"
* s = "/foo/bar/baz/boo.asm"
* E.g.: in_path = "/foo/bar/baz/boo.c", replace = "" =>
* out_path = "/foo/bar/baz/boo"
* s = "/foo/bar/baz/boo"
*
* Hidden non-leaf function cost:
* - calls strlcpy 2x
* - calls strrchr
* - calls strlcat
*
* @return Length of the string copied into @s
*/
void fill_pathname(char *out_path, const char *in_path,
const char *replace, size_t size);
size_t fill_pathname(char *s, const char *in_path,
const char *replace, size_t len);
/**
* fill_dated_filename:
* @out_filename : output filename
* @s : output filename
* @ext : extension of output filename
* @size : buffer size of output filename
* @len : buffer size of output filename
*
* Creates a 'dated' filename prefixed by 'RetroArch', and
* concatenates extension (@ext) to it.
*
* E.g.:
* out_filename = "RetroArch-{month}{day}-{Hours}{Minutes}.{@ext}"
* s = "RetroArch-{month}{day}-{Hours}{Minutes}.{@ext}"
*
* Hidden non-leaf function cost:
* - Calls rtime_localtime()
* - Calls strftime
* - Calls strlcat
*
**/
void fill_dated_filename(char *out_filename,
const char *ext, size_t size);
size_t fill_dated_filename(char *s, const char *ext, size_t len);
/**
* fill_str_dated_filename:
* @out_filename : output filename
* @s : output filename
* @in_str : input string
* @ext : extension of output filename
* @size : buffer size of output filename
* @len : buffer size of output filename
*
* Creates a 'dated' filename prefixed by the string @in_str, and
* concatenates extension (@ext) to it.
*
* E.g.:
* out_filename = "RetroArch-{year}{month}{day}-{Hour}{Minute}{Second}.{@ext}"
* s = "RetroArch-{year}{month}{day}-{Hour}{Minute}{Second}.{@ext}"
*
* Hidden non-leaf function cost:
* - Calls time
* - Calls rtime_localtime()
* - Calls strlcpy 2x
* - Calls string_is_empty()
* - Calls strftime
* - Calls strlcat
*
* @return Length of the string copied into @s
**/
void fill_str_dated_filename(char *out_filename,
const char *in_str, const char *ext, size_t size);
/**
* fill_pathname_noext:
* @out_path : output path
* @in_path : input path
* @replace : what to replace
* @size : buffer size of output path
*
* Appends a filename extension 'replace' to 'in_path', and outputs
* result in 'out_path'.
*
* Assumes in_path has no extension. If an extension is still
* present in 'in_path', it will be ignored.
*
*/
void fill_pathname_noext(char *out_path, const char *in_path,
const char *replace, size_t size);
size_t fill_str_dated_filename(char *s, const char *in_str, const char *ext, size_t len);
/**
* find_last_slash:
* @str : input path
* @str : path
*
* Gets a pointer to the last slash in the input path.
* Find last slash in path. Tries to find
* a backslash on Windows too which takes precedence
* over regular slash.
*
* Returns: a pointer to the last slash in the input path.
* Leaf function.
*
* @return pointer to last slash/backslash found in @str.
**/
char *find_last_slash(const char *str);
/**
* fill_pathname_dir:
* @in_dir : input directory path
* @in_basename : input basename to be appended to @in_dir
* @s : input directory path
* @in_basename : input basename to be appended to @s
* @replace : replacement to be appended to @in_basename
* @size : size of buffer
* @len : size of buffer
*
* Appends basename of 'in_basename', to 'in_dir', along with 'replace'.
* Appends basename of 'in_basename', to 's', along with 'replace'.
* Basename of in_basename is the string after the last '/' or '\\',
* i.e the filename without directories.
*
* If in_basename has no '/' or '\\', the whole 'in_basename' will be used.
* 'size' is buffer size of 'in_dir'.
* 'len' is buffer size of 's'.
*
* E.g..: in_dir = "/tmp/some_dir", in_basename = "/some_content/foo.c",
* replace = ".asm" => in_dir = "/tmp/some_dir/foo.c.asm"
* E.g..: s = "/tmp/some_dir", in_basename = "/some_content/foo.c",
* replace = ".asm" => s = "/tmp/some_dir/foo.c.asm"
*
* Hidden non-leaf function cost:
* - Calls fill_pathname_slash()
* - Calls path_basename()
* - Calls strlcpy 2x
**/
void fill_pathname_dir(char *in_dir, const char *in_basename,
const char *replace, size_t size);
size_t fill_pathname_dir(char *s, const char *in_basename,
const char *replace, size_t len);
/**
* fill_pathname_base:
* @out : output path
* @s : output path
* @in_path : input path
* @size : size of output path
* @len : size of output path
*
* Copies basename of @in_path into @out_path.
* Copies basename of @in_path into @s.
*
* Hidden non-leaf function cost:
* - Calls path_basename()
* - Calls strlcpy
*
* @return length of the string copied into @s
**/
void fill_pathname_base(char *out_path, const char *in_path, size_t size);
void fill_pathname_base_noext(char *out_dir,
const char *in_path, size_t size);
void fill_pathname_base_ext(char *out,
const char *in_path, const char *ext,
size_t size);
size_t fill_pathname_base(char *s, const char *in_path, size_t len);
/**
* fill_pathname_basedir:
* @out_dir : output directory
* @s : output directory
* @in_path : input path
* @size : size of output directory
* @len : size of output directory
*
* Copies base directory of @in_path into @out_path.
* Copies base directory of @in_path into @s.
* If in_path is a path without any slashes (relative current directory),
* @out_path will get path "./".
* @s will get path "./".
*
* Hidden non-leaf function cost:
* - Calls strlcpy
* - Calls path_basedir()
**/
void fill_pathname_basedir(char *out_path, const char *in_path, size_t size);
void fill_pathname_basedir_noext(char *out_dir,
const char *in_path, size_t size);
size_t fill_pathname_basedir(char *s, const char *in_path, size_t len);
/**
* fill_pathname_parent_dir_name:
* @out_dir : output directory
* @s : output string
* @in_dir : input directory
* @size : size of output directory
* @len : size of @s
*
* Copies only the parent directory name of @in_dir into @out_dir.
* Copies only the parent directory name of @in_dir into @s.
* The two buffers must not overlap. Removes trailing '/'.
* Returns true on success, false if a slash was not found in the path.
*
* Hidden non-leaf function cost:
* - Calls strdup
* - Can call strlcpy
*
* @return Length of the string copied into @s
**/
bool fill_pathname_parent_dir_name(char *out_dir,
const char *in_dir, size_t size);
size_t fill_pathname_parent_dir_name(char *s,
const char *in_dir, size_t len);
/**
* fill_pathname_parent_dir:
* @out_dir : output directory
* @s : output directory
* @in_dir : input directory
* @size : size of output directory
* @len : size of output directory
*
* Copies parent directory of @in_dir into @out_dir.
* Copies parent directory of @in_dir into @s.
* Assumes @in_dir is a directory. Keeps trailing '/'.
* If the path was already at the root directory, @out_dir will be an empty string.
* If the path was already at the root directory, @s will be an empty string.
*
* Hidden non-leaf function cost:
* - Can call strlcpy if (@s!= @in_dir)
* - Calls strlen if (@s == @in_dir)
* - Calls path_parent_dir()
*
* @return Length of the string copied into @s
**/
void fill_pathname_parent_dir(char *out_dir,
const char *in_dir, size_t size);
size_t fill_pathname_parent_dir(char *s,
const char *in_dir, size_t len);
/**
* fill_pathname_resolve_relative:
* @out_path : output path
* @s : output path
* @in_refpath : input reference path
* @in_path : input path
* @size : size of @out_path
* @len : size of @s
*
* Joins basedir of @in_refpath together with @in_path.
* If @in_path is an absolute path, out_path = in_path.
* If @in_path is an absolute path, s = in_path.
* E.g.: in_refpath = "/foo/bar/baz.a", in_path = "foobar.cg",
* out_path = "/foo/bar/foobar.cg".
* s = "/foo/bar/foobar.cg".
**/
void fill_pathname_resolve_relative(char *out_path, const char *in_refpath,
const char *in_path, size_t size);
void fill_pathname_resolve_relative(char *s, const char *in_refpath,
const char *in_path, size_t len);
/**
* fill_pathname_join:
* @out_path : output path
* @s : output path
* @dir : directory
* @path : path
* @size : size of output path
* @len : size of output path
*
* Joins a directory (@dir) and path (@path) together.
* Makes sure not to get two consecutive slashes
* Makes sure not to get two consecutive slashes
* between directory and path.
*
* Hidden non-leaf function cost:
* - calls strlcpy at least once
* - calls fill_pathname_slash()
*
* Deprecated. Use fill_pathname_join_special() instead
* if you can ensure @dir != @s
*
* @return Length of the string copied into @s
**/
void fill_pathname_join(char *out_path, const char *dir,
const char *path, size_t size);
size_t fill_pathname_join(char *s, const char *dir,
const char *path, size_t len);
void fill_pathname_join_special_ext(char *out_path,
/**
* fill_pathname_join_special:
* @s : output path
* @dir : directory. Cannot be identical to @s
* @path : path
* @len : size of output path
*
*
* Specialized version of fill_pathname_join.
* Unlike fill_pathname_join(),
* @dir and @s CANNOT be identical.
*
* Joins a directory (@dir) and path (@path) together.
* Makes sure not to get two consecutive slashes
* between directory and path.
*
* Hidden non-leaf function cost:
* - calls strlcpy 2x
*
* @return Length of the string copied into @s
**/
size_t fill_pathname_join_special(char *s,
const char *dir, const char *path, size_t len);
size_t fill_pathname_join_special_ext(char *s,
const char *dir, const char *path,
const char *last, const char *ext,
size_t size);
void fill_pathname_join_concat_noext(char *out_path,
const char *dir, const char *path,
const char *concat,
size_t size);
void fill_pathname_join_concat(char *out_path,
const char *dir, const char *path,
const char *concat,
size_t size);
void fill_pathname_join_noext(char *out_path,
const char *dir, const char *path, size_t size);
size_t len);
/**
* fill_pathname_join_delim:
* @out_path : output path
* @s : output path
* @dir : directory
* @path : path
* @delim : delimiter
* @size : size of output path
* @len : size of output path
*
* Joins a directory (@dir) and path (@path) together
* using the given delimiter (@delim).
*
* Hidden non-leaf function cost:
* - can call strlen
* - can call strlcpy
* - can call strlcat
**/
void fill_pathname_join_delim(char *out_path, const char *dir,
const char *path, const char delim, size_t size);
size_t fill_pathname_join_delim(char *s, const char *dir,
const char *path, const char delim, size_t len);
void fill_pathname_join_delim_concat(char *out_path, const char *dir,
const char *path, const char delim, const char *concat,
size_t size);
size_t fill_pathname_expand_special(char *s,
const char *in_path, size_t len);
size_t fill_pathname_abbreviate_special(char *s,
const char *in_path, size_t len);
/**
* fill_short_pathname_representation:
* @out_rep : output representation
* @in_path : input path
* @size : size of output representation
* fill_pathname_abbreviated_or_relative:
*
* Generates a short representation of path. It should only
* be used for displaying the result; the output representation is not
* binding in any meaningful way (for a normal path, this is the same as basename)
* In case of more complex URLs, this should cut everything except for
* the main image file.
* Fills the supplied path with either the abbreviated path or
* the relative path, which ever one has less depth / number of slashes
*
* E.g.: "/path/to/game.img" -> game.img
* "/path/to/myarchive.7z#folder/to/game.img" -> game.img
*/
void fill_short_pathname_representation(char* out_rep,
const char *in_path, size_t size);
* If lengths of abbreviated and relative paths are the same,
* the relative path will be used
* @in_path can be an absolute, relative or abbreviated path
*
* @return Length of the string copied into @s
**/
size_t fill_pathname_abbreviated_or_relative(char *s,
const char *in_refpath, const char *in_path, size_t len);
void fill_short_pathname_representation_noext(char* out_rep,
const char *in_path, size_t size);
/**
* sanitize_path_part:
*
* @path_part : directory or filename
* @len : length of path_part
*
* Takes single part of a path eg. single filename
* or directory, and removes any special chars that are
* unavailable.
*
* @returns newly allocated string that has been sanitized.
* Caller is responsible for freeing the returned string.
**/
char *sanitize_path_part(const char *path_part, size_t len);
void fill_pathname_expand_special(char *out_path,
const char *in_path, size_t size);
/**
* pathname_conform_slashes_to_os:
*
* @path : path
*
* Leaf function.
*
* Changes the slashes to the correct kind for the os
* So forward slash on linux and backslash on Windows
**/
void pathname_conform_slashes_to_os(char *s);
void fill_pathname_abbreviate_special(char *out_path,
const char *in_path, size_t size);
/**
* pathname_make_slashes_portable:
* @path : path
*
* Leaf function.
*
* Change all slashes to forward so they are more
* portable between Windows and Linux
**/
void pathname_make_slashes_portable(char *s);
/**
* path_basedir:
@@ -441,7 +588,7 @@ void fill_pathname_abbreviate_special(char *out_path,
* Extracts base directory by mutating path.
* Keeps trailing '/'.
**/
void path_basedir_wrapper(char *path);
void path_basedir_wrapper(char *s);
/**
* path_char_is_slash:
@@ -449,12 +596,12 @@ void path_basedir_wrapper(char *path);
*
* Checks if character (@c) is a slash.
*
* Returns: true (1) if character is a slash, otherwise false (0).
*/
* @return true if character is a slash, otherwise false.
**/
#ifdef _WIN32
#define path_char_is_slash(c) (((c) == '/') || ((c) == '\\'))
#define PATH_CHAR_IS_SLASH(c) (((c) == '/') || ((c) == '\\'))
#else
#define path_char_is_slash(c) ((c) == '/')
#define PATH_CHAR_IS_SLASH(c) ((c) == '/')
#endif
/**
@@ -462,30 +609,34 @@ void path_basedir_wrapper(char *path);
*
* Gets the default slash separator.
*
* Returns: default slash separator.
*/
* @return default slash separator.
**/
#ifdef _WIN32
#define path_default_slash() "\\"
#define path_default_slash_c() '\\'
#define PATH_DEFAULT_SLASH() "\\"
#define PATH_DEFAULT_SLASH_C() '\\'
#else
#define path_default_slash() "/"
#define path_default_slash_c() '/'
#define PATH_DEFAULT_SLASH() "/"
#define PATH_DEFAULT_SLASH_C() '/'
#endif
/**
* fill_pathname_slash:
* @path : path
* @size : size of path
* @s : path
* @len : size of path
*
* Assumes path is a directory. Appends a slash
* if not already there.
* Hidden non-leaf function cost:
* - can call strlcat once if it returns false
* - calls strlen
**/
void fill_pathname_slash(char *path, size_t size);
size_t fill_pathname_slash(char *s, size_t len);
#if !defined(RARCH_CONSOLE) && defined(RARCH_INTERNAL)
void fill_pathname_application_path(char *buf, size_t size);
void fill_pathname_application_dir(char *buf, size_t size);
void fill_pathname_home_dir(char *buf, size_t size);
size_t fill_pathname_application_path(char *s, size_t len);
size_t fill_pathname_application_dir(char *s, size_t len);
size_t fill_pathname_home_dir(char *s, size_t len);
#endif
/**
@@ -494,7 +645,16 @@ void fill_pathname_home_dir(char *buf, size_t size);
*
* Create directory on filesystem.
*
* Returns: true (1) if directory could be created, otherwise false (0).
* Recursive function.
*
* Hidden non-leaf function cost:
* - Calls strdup
* - Calls path_parent_dir()
* - Calls strcmp
* - Calls path_is_directory()
* - Calls path_mkdir()
*
* @return true if directory could be created, otherwise false.
**/
bool path_mkdir(const char *dir);
@@ -504,10 +664,17 @@ bool path_mkdir(const char *dir);
*
* Checks if path is a directory.
*
* Returns: true (1) if path is a directory, otherwise false (0).
* @return true if path is a directory, otherwise false.
*/
bool path_is_directory(const char *path);
/* Time format strings with AM-PM designation require special
* handling due to platform dependence
* @return Length of the string written to @s
*/
size_t strftime_am_pm(char *s, size_t len, const char* format,
const void* timeptr);
bool path_is_character_special(const char *path);
int path_stat(const char *path);
@@ -0,0 +1,434 @@
/* Copyright (C) 2010-2020 The RetroArch team
*
* ---------------------------------------------------------------------------------------
* The following license statement only applies to this file (file_stream.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_FILE_STREAM_H
#define __LIBRETRO_SDK_FILE_STREAM_H
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <stddef.h>
#include <sys/types.h>
#include <libretro.h>
#include <retro_common_api.h>
#include <retro_inline.h>
#include <boolean.h>
#include <stdarg.h>
#include <vfs/vfs_implementation.h>
/** @defgroup file_stream File Streams
*
* All functions in this header will use the VFS interface set in \ref filestream_vfs_init if possible,
* or else they will fall back to built-in equivalents.
*
* @note These functions are modeled after those in the C standard library
* (and may even use them internally),
* but identical behavior is not guaranteed.
*
* @{
*/
/**
* The minimum version of the VFS interface required by the \c filestream functions.
*/
#define FILESTREAM_REQUIRED_VFS_VERSION 2
RETRO_BEGIN_DECLS
/**
* Opaque handle to a file stream.
* @warning This is not interchangeable with \c FILE* or \c retro_vfs_file_handle.
*/
typedef struct RFILE RFILE;
#define FILESTREAM_REQUIRED_VFS_VERSION 2
/**
* Initializes the \c filestream functions to use the VFS interface provided by the frontend.
* Optional; if not called, all \c filestream functions
* will use libretro-common's built-in implementations.
*
* @param vfs_info The VFS interface returned by the frontend.
* If \c vfs_info::iface (but \em not \c vfs_info itself) is \c NULL,
* then libretro-common's built-in VFS implementation will be used.
*/
void filestream_vfs_init(const struct retro_vfs_interface_info* vfs_info);
/**
* Returns the size of the given file, in bytes.
*
* @param stream The open file to query.
* @return The size of \c stream in bytes,
* or -1 if there was an error.
* @see retro_vfs_size_t
*/
int64_t filestream_get_size(RFILE *stream);
/**
* Sets the size of the given file,
* truncating or extending it as necessary.
*
* @param stream The file to resize.
* @param length The new size of \c stream, in bytes.
* @return 0 if the resize was successful,
* or -1 if there was an error.
* @see retro_vfs_truncate_t
*/
int64_t filestream_truncate(RFILE *stream, int64_t length);
/**
* Opens a file for reading or writing.
*
* @param path Path to the file to open.
* Should be in the format described in \ref GET_VFS_INTERFACE.
* @param mode The mode to open the file in.
* Should be one or more of the flags in \refitem RETRO_VFS_FILE_ACCESS OR'd together,
* and \c RETRO_VFS_FILE_ACCESS_READ or \c RETRO_VFS_FILE_ACCESS_WRITE
* (or both) must be included.
* @param hints Optional hints to pass to the frontend.
*
* @return The opened file, or \c NULL if there was an error.
* Must be cleaned up with \c filestream_close when no longer needed.
* @see retro_vfs_open_t
*/
RFILE* filestream_open(const char *path, unsigned mode, unsigned hints);
/**
* Sets the current position of the file stream.
* Use this to read specific sections of a file.
*
* @param stream The file to set the stream position of.
* @param offset The new stream position, in bytes.
* @param seek_position The position to seek from.
* Should be one of the values in \refitem RETRO_VFS_SEEK_POSITION.
* @return The new stream position in bytes relative to the beginning,
* or -1 if there was an error.
* @see RETRO_VFS_SEEK_POSITION
* @see retro_vfs_seek_t
*/
int64_t filestream_seek(RFILE *stream, int64_t offset, int seek_position);
/**
* Reads data from the given file into a buffer.
* If the read is successful,
* the file's stream position will advance by the number of bytes read.
*
* @param stream The file to read from.
* @param data The buffer in which to store the read data.
* @param len The size of \c data, in bytes.
* @return The number of bytes read,
* or -1 if there was an error.
* May be less than \c len, but never more.
* @see retro_vfs_read_t
*/
int64_t filestream_read(RFILE *stream, void *data, int64_t len);
/**
* Writes data from a buffer to the given file.
* If the write is successful,
* the file's stream position will advance by the number of bytes written.
*
* @param stream The file to write to.
* @param data The buffer containing the data to write.
* @param len The size of \c data, in bytes.
* @return The number of bytes written,
* or -1 if there was an error.
* May be less than \c len, but never more.
* @see retro_vfs_write_t
*/
int64_t filestream_write(RFILE *stream, const void *data, int64_t len);
/**
* Returns the current position of the given file in bytes.
*
* @param stream The file to return the stream position for.
* @return The current stream position in bytes relative to the beginning,
* or -1 if there was an error.
* @see retro_vfs_tell_t
*/
int64_t filestream_tell(RFILE *stream);
/**
* Rewinds the given file to the beginning.
* Equivalent to <tt>filestream_seek(stream, 0, RETRO_VFS_SEEK_POSITION_START)</tt>.
* @param stream The file to rewind.
* May be \c NULL, in which case this function does nothing.
*/
void filestream_rewind(RFILE *stream);
/**
* Closes the given file.
*
* @param stream The file to close.
* This should have been created with \c filestream_open.
* Behavior is undefined if \c NULL.
* @return 0 if the file was closed successfully,
* or -1 if there was an error.
* @post \c stream is no longer valid and should not be used,
* even if this function fails.
* @see retro_vfs_close_t
*/
int filestream_close(RFILE *stream);
/**
* Opens a file, reads its contents into a newly-allocated buffer,
* then closes it.
*
* @param path[in] Path to the file to read.
* Should be in the format described in \ref GET_VFS_INTERFACE.
* @param buf[out] A pointer to the address of the newly-allocated buffer.
* The buffer will contain the entirety of the file at \c path.
* Will be allocated with \c malloc and must be freed with \c free.
* @param len[out] Pointer to the size of the buffer in bytes.
* May be \c NULL, in which case the length is not written.
* Value is unspecified if this function fails.
* @return 1 if the file was read successfully,
* 0 if there was an error.
* @see filestream_write_file
*/
int64_t filestream_read_file(const char *path, void **buf, int64_t *len);
/**
* Reads a line of text from the given file,
* up to a given length.
*
* Will read to the next newline or until the buffer is full,
* whichever comes first.
*
* @param stream The file to read from.
* @param s The buffer to write the retrieved line to.
* Will contain at most \c len - 1 characters
* plus a null terminator.
* The newline character (if any) will not be included.
* The line delimiter must be Unix-style (\c '\n').
* Carriage returns (\c '\r') will not be treated specially.
* @param len The length of the buffer \c s, in bytes.
* @return \s if successful, \c NULL if there was an error.
*/
char* filestream_gets(RFILE *stream, char *s, size_t len);
/**
* Reads a single character from the given file.
*
* @param stream The file to read from.
* @return The character read, or -1 upon reaching the end of the file.
*/
int filestream_getc(RFILE *stream);
/**
* Reads formatted text from the given file,
* with arguments provided as a standard \c va_list.
*
* @param stream The file to read from.
* @param format The string to write, possibly including scanf-compatible format specifiers.
* @param args Argument list with zero or more elements
* whose values will be updated according to the semantics of \c format.
* @return The number of arguments in \c args that were successfully assigned,
* or -1 if there was an error.
* @see https://en.cppreference.com/w/c/io/fscanf
* @see https://en.cppreference.com/w/c/variadic
*/
int filestream_vscanf(RFILE *stream, const char* format, va_list *args);
/**
* Reads formatted text from the given file.
*
* @param stream The file to read from.
* @param format The string to write, possibly including scanf-compatible format specifiers.
* @param ... Zero or more arguments that will be updated according to the semantics of \c format.
* @return The number of arguments in \c ... that were successfully assigned,
* or -1 if there was an error.
* @see https://en.cppreference.com/w/c/io/fscanf
*/
int filestream_scanf(RFILE *stream, const char* format, ...);
/**
* Determines if there's any more data left to read from this file.
*
* @param stream The file to check the position of.
* @return -1 if this stream has reached the end of the file,
* 0 if not.
*/
int filestream_eof(RFILE *stream);
/**
* Writes the entirety of a given buffer to a file at a given path.
* Any file that already exists will be overwritten.
*
* @param path Path to the file that will be written to.
* @param data The buffer to write to \c path.
* @param size The size of \c data, in bytes.
* @return \c true if the file was written successfully,
* \c false if there was an error.
*/
bool filestream_write_file(const char *path, const void *data, int64_t size);
/**
* Writes a single character to the given file.
*
* @param stream The file to write to.
* @param c The character to write.
* @return The character written,
* or -1 if there was an error.
* Will return -1 if \c stream is \c NULL.
*/
int filestream_putc(RFILE *stream, int c);
/**
* Writes formatted text to the given file,
* with arguments provided as a standard \c va_list.
*
* @param stream The file to write to.
* @param format The string to write, possibly including printf-compatible format specifiers.
* @param args A list of arguments to be formatted and inserted in the resulting string.
* @return The number of characters written,
* or -1 if there was an error.
* @see https://en.cppreference.com/w/c/io/vfprintf
* @see https://en.cppreference.com/w/c/variadic
*/
int filestream_vprintf(RFILE *stream, const char* format, va_list args);
/**
* Writes formatted text to the given file.
*
* @param stream The file to write to.
* @param format The string to write, possibly including printf-compatible format specifiers.
* @param ... Zero or more arguments to be formatted and inserted into the resulting string.
* @return The number of characters written,
* or -1 if there was an error.
* @see https://en.cppreference.com/w/c/io/printf
*/
int filestream_printf(RFILE *stream, const char* format, ...);
/**
* Checks if there was an error in using the given file stream.
*
* @param stream The file stream to check for errors.
* @return \c true if there was an error in using this stream,
* \c false if not or if \c stream is \c NULL.
*/
int filestream_error(RFILE *stream);
/**
* Flushes pending writes to the operating system's file layer.
* There is no guarantee that pending writes will be written to disk immediately.
*
* @param stream The file to flush.
* @return 0 if the flush was successful,
* or -1 if there was an error.
* @see retro_vfs_flush_t
*/
int filestream_flush(RFILE *stream);
/**
* Deletes the file at the given path.
* If the file is open by any process,
* the behavior is platform-specific.
*
* @note This function might or might not delete directories recursively,
* depending on the platform and the underlying VFS implementation.
*
* @param path The file to delete.
* @return 0 if the file was deleted successfully,
* or -1 if there was an error.
* @see retro_vfs_remove_t
*/
int filestream_delete(const char *path);
/**
* Moves a file to a new location, with a new name.
*
* @param old_path Path to the file to rename.
* @param new_path The target name and location of the file.
* @return 0 if the file was renamed successfully,
* or -1 if there was an error.
* @see retro_vfs_rename_t
*/
int filestream_rename(const char *old_path, const char *new_path);
/**
* Copies a file to a new location.
*
* @param src_path Path to the file to rename.
* @param dst_path The target name and location of the file.
* @return 0 if the file was copied successfully,
* or -1 if there was an error.
*/
int filestream_copy(const char *src_path, const char *dst_path);
/**
* Compares and verifies files.
*
* @param src_path Path to the file.
* @param dst_path Path to the other file.
* @return 0 if the files are equal,
* or -1 if there was an error.
*/
int filestream_cmp(const char *src_path, const char *dst_path);
/**
* Get the path that was used to open a file.
*
* @param stream The file to get the path of.
* @return The path that was used to open \c stream,
* or \c NULL if there was an error.
* The string is owned by \c stream and must not be modified or freed by the caller.
*/
const char* filestream_get_path(RFILE *stream);
/**
* Determines if a file exists at the given path.
*
* @param path The path to check for existence.
* @return \c true if a file exists at \c path,
* \c false if not or if \c path is \c NULL or empty.
*/
bool filestream_exists(const char *path);
/**
* Reads a line from the given file into a newly-allocated buffer.
*
* @param stream The file to read from.
* @return Pointer to the line read from \c stream,
* or \c NULL if there was an error.
* Must be freed with \c free when no longer needed.
*/
char* filestream_getline(RFILE *stream);
/**
* Returns the open file handle
* that was originally returned by the VFS interface.
*
* @param stream File handle returned by \c filestream_open.
* @return The file handle returned by the underlying VFS implementation.
*/
libretro_vfs_implementation_file* filestream_get_vfs_handle(RFILE *stream);
RETRO_END_DECLS
/** @} */
#endif
@@ -1,4 +1,4 @@
/* Copyright (C) 2010-2019 The RetroArch team
/* Copyright (C) 2010-2020 The RetroArch team
*
* ---------------------------------------------------------------------------------------
* The following license statement only applies to this file (stdstring.h).
@@ -35,6 +35,28 @@
RETRO_BEGIN_DECLS
#define STRLEN_CONST(x) ((sizeof((x))-1))
#define string_is_not_equal(a, b) !string_is_equal((a), (b))
#define TOLOWER(c) ((c) | (lr_char_props[(unsigned char)(c)] & 0x20))
#define TOUPPER(c) ((c) & ~(lr_char_props[(unsigned char)(c)] & 0x20))
/* C standard says \f \v are space, but this one disagrees */
#define ISSPACE(c) (lr_char_props[(unsigned char)(c)] & 0x80)
#define ISDIGIT(c) (lr_char_props[(unsigned char)(c)] & 0x40)
#define ISALPHA(c) (lr_char_props[(unsigned char)(c)] & 0x20)
#define ISLOWER(c) (lr_char_props[(unsigned char)(c)] & 0x04)
#define ISUPPER(c) (lr_char_props[(unsigned char)(c)] & 0x02)
#define ISALNUM(c) (lr_char_props[(unsigned char)(c)] & 0x60)
#define ISUALPHA(c) (lr_char_props[(unsigned char)(c)] & 0x28)
#define ISUALNUM(c) (lr_char_props[(unsigned char)(c)] & 0x68)
#define IS_XDIGIT(c) (lr_char_props[(unsigned char)(c)] & 0x01)
/* Deprecated alias, all callers should use string_is_equal_case_insensitive instead */
#define string_is_equal_noncase string_is_equal_case_insensitive
static INLINE bool string_is_empty(const char *data)
{
return !data || (*data == '\0');
@@ -45,31 +67,49 @@ static INLINE bool string_is_equal(const char *a, const char *b)
return (a && b) ? !strcmp(a, b) : false;
}
#define STRLEN_CONST(x) ((sizeof((x))-1))
#define string_is_not_equal(a, b) !string_is_equal((a), (b))
#define string_add_pair_open(s, size) strlcat((s), " (", (size))
#define string_add_pair_close(s, size) strlcat((s), ")", (size))
#define string_add_bracket_open(s, size) strlcat((s), "{", (size))
#define string_add_bracket_close(s, size) strlcat((s), "}", (size))
#define string_add_single_quote(s, size) strlcat((s), "'", (size))
#define string_add_quote(s, size) strlcat((s), "\"", (size))
#define string_add_colon(s, size) strlcat((s), ":", (size))
#define string_add_glob_open(s, size) strlcat((s), "glob('*", (size))
#define string_add_glob_close(s, size) strlcat((s), "*')", (size))
#define string_is_not_equal_fast(a, b, size) (memcmp(a, b, size) != 0)
#define string_is_equal_fast(a, b, size) (memcmp(a, b, size) == 0)
static INLINE void string_add_between_pairs(char *s, const char *str,
size_t size)
static INLINE bool string_starts_with_size(const char *str, const char *prefix,
size_t len)
{
string_add_pair_open(s, size);
strlcat(s, str, size);
string_add_pair_close(s, size);
return (str && prefix) ? !strncmp(prefix, str, len) : false;
}
static INLINE bool string_starts_with(const char *str, const char *prefix)
{
return (str && prefix) ? !strncmp(prefix, str, strlen(prefix)) : false;
}
static INLINE bool string_ends_with_size(const char *s, const char *suffix,
size_t len, size_t suffix_len)
{
return (len < suffix_len) ? false :
!memcmp(suffix, s + (len - suffix_len), suffix_len);
}
static INLINE bool string_ends_with(const char *s, const char *suffix)
{
return s && suffix && string_ends_with_size(s, suffix, strlen(s), strlen(suffix));
}
/**
* strlen_size:
*
* Leaf function.
*
* @return the length of 'str' (c.f. strlen()), but only
* checks the first 'size' characters
* - If 'str' is NULL, returns 0
* - If 'str' is not NULL and no '\0' character is found
* in the first 'size' characters, returns 'size'
**/
static INLINE size_t strlen_size(const char *str, size_t len)
{
size_t i = 0;
if (str)
while (i < len && str[i]) i++;
return i;
}
static INLINE bool string_is_equal_case_insensitive(const char *a,
const char *b)
{
@@ -89,22 +129,23 @@ static INLINE bool string_is_equal_case_insensitive(const char *a,
return (result == 0);
}
static INLINE bool string_is_equal_noncase(const char *a, const char *b)
static INLINE bool string_starts_with_case_insensitive(const char *str,
const char *prefix)
{
int result = 0;
const unsigned char *p1 = (const unsigned char*)a;
const unsigned char *p2 = (const unsigned char*)b;
const unsigned char *p1 = (const unsigned char*)str;
const unsigned char *p2 = (const unsigned char*)prefix;
if (!a || !b)
if (!str || !prefix)
return false;
if (p1 == p2)
return false;
return true;
while ((result = tolower (*p1) - tolower (*p2++)) == 0)
if (*p1++ == '\0')
while ((result = tolower (*p1++) - tolower (*p2)) == 0)
if (*p2++ == '\0')
break;
return (result == 0);
return (result == 0 || *p2 == '\0');
}
char *string_to_upper(char *s);
@@ -113,21 +154,225 @@ char *string_to_lower(char *s);
char *string_ucwords(char *s);
char *string_replace_substring(const char *in, const char *pattern,
const char *by);
char *string_replace_substring(
const char *in, size_t in_len,
const char *pattern, size_t pattern_len,
const char *replacement, size_t replacement_len);
/* Remove leading whitespaces */
/**
* string_trim_whitespace_left:
*
* Remove leading whitespaces
**/
char *string_trim_whitespace_left(char *const s);
/* Remove trailing whitespaces */
/**
* string_trim_whitespace_right:
*
* Remove trailing whitespaces
**/
char *string_trim_whitespace_right(char *const s);
/* Remove leading and trailing whitespaces */
/**
* string_trim_whitespace:
*
* Remove leading and trailing whitespaces
**/
char *string_trim_whitespace(char *const s);
/* max_lines == 0 means no limit */
char *word_wrap(char *buffer, const char *string,
int line_width, bool unicode, unsigned max_lines);
/**
* word_wrap:
* @dst : pointer to destination buffer.
* @dst_size : size of destination buffer.
* @src : pointer to input string.
* @src_len : length of @src
* @line_width : max number of characters per line.
* @wideglyph_width : not used, but is necessary to keep
* compatibility with word_wrap_wideglyph().
* @max_lines : max lines of destination string.
* 0 means no limit.
*
* Wraps string specified by 'src' to destination buffer
* specified by 'dst' and 'dst_size'.
* This function assumes that all glyphs in the string
* have an on-screen pixel width similar to that of
* regular Latin characters - i.e. it will not wrap
* correctly any text containing so-called 'wide' Unicode
* characters (e.g. CJK languages, emojis, etc.).
**/
size_t word_wrap(char *dst, size_t dst_size, const char *src, size_t src_len,
int line_width, int wideglyph_width, unsigned max_lines);
/**
* word_wrap_wideglyph:
* @dst : pointer to destination buffer.
* @dst_size : size of destination buffer.
* @src : pointer to input string.
* @src_len : length of @src
* @line_width : max number of characters per line.
* @wideglyph_width : effective width of 'wide' Unicode glyphs.
* the value here is normalised relative to the
* typical on-screen pixel width of a regular
* Latin character:
* - a regular Latin character is defined to
* have an effective width of 100
* - wideglyph_width = 100 * (wide_character_pixel_width / latin_character_pixel_width)
* - e.g. if 'wide' Unicode characters in 'src'
* have an on-screen pixel width twice that of
* regular Latin characters, wideglyph_width
* would be 200
* @max_lines : max lines of destination string.
* 0 means no limit.
*
* Wraps string specified by @src to destination buffer
* specified by @dst and @dst_size.
* This function assumes that all glyphs in the string
* are:
* - EITHER 'non-wide' Unicode glyphs, with an on-screen
* pixel width similar to that of regular Latin characters
* - OR 'wide' Unicode glyphs (e.g. CJK languages, emojis, etc.)
* with an on-screen pixel width defined by @wideglyph_width
* Note that wrapping may occur in inappropriate locations
* if @src string contains 'wide' Unicode characters whose
* on-screen pixel width deviates greatly from the set
* @wideglyph_width value.
**/
size_t word_wrap_wideglyph(
char *dst, size_t dst_size,
const char *src, size_t src_len,
int line_width, int wideglyph_width,
unsigned max_lines);
/**
* string_tokenize:
*
* Splits string into tokens separated by @delim
* > Returned token string must be free()'d
* > Returns NULL if token is not found
* > After each call, @str is set to the position after the
* last found token
* > Tokens *include* empty strings
* Usage example:
* char *str = "1,2,3,4,5,6,7,,,10,";
* char **str_ptr = &str;
* char *token = NULL;
* while ((token = string_tokenize(str_ptr, ",")))
* {
* printf("%s\n", token);
* free(token);
* token = NULL;
* }
**/
char* string_tokenize(char **str, const char *delim);
/**
* string_remove_all_chars:
* @str : input string (must be non-NULL, otherwise UB)
*
* Leaf function.
*
* Removes every instance of character @c from @str
**/
void string_remove_all_chars(char *str, char c);
/**
* string_replace_all_chars:
* @str : input string (must be non-NULL, otherwise UB)
* @find : character to find
* @replace : character to replace @find with
*
* Hidden non-leaf function cost:
* - Calls strchr (in a loop)
*
* Replaces every instance of character @find in @str
* with character @replace
**/
void string_replace_all_chars(char *str, char find, char replace);
/**
* string_to_unsigned:
* @str : input string
*
* Converts string to unsigned integer.
*
* @return 0 if string is invalid, otherwise > 0
**/
unsigned string_to_unsigned(const char *str);
/**
* string_hex_to_unsigned:
* @str : input string (must be non-NULL, otherwise UB)
*
* Converts hexadecimal string to unsigned integer.
* Handles optional leading '0x'.
*
* @return 0 if string is invalid, otherwise > 0
**/
unsigned string_hex_to_unsigned(const char *str);
/**
* string_count_occurrences_single_character:
*
* Leaf function.
*
* Get the total number of occurrences of character @c in @str.
*
* @return Total number of occurrences of character @c
*/
int string_count_occurrences_single_character(const char *str, char c);
/**
* string_replace_whitespace_with_single_character:
*
* Leaf function.
*
* Replaces all spaces with given character @c.
**/
void string_replace_whitespace_with_single_character(char *str, char c);
/**
* string_replace_multi_space_with_single_space:
*
* Leaf function.
*
* Replaces multiple spaces with a single space in a string.
**/
void string_replace_multi_space_with_single_space(char *str);
/**
* string_remove_all_whitespace:
*
* Leaf function.
*
* Remove all spaces from the given string.
**/
void string_remove_all_whitespace(char *str_trimmed, const char *str);
/* Retrieve the last occurance of the given character in a string. */
int string_index_last_occurance(const char *str, char c);
/**
* string_find_index_substring_string:
* @str : input string (must be non-NULL, otherwise UB)
* @substr : substring to find in @str
*
* Hidden non-leaf function cost:
* - Calls strstr
*
* Find the position of substring @substr in string @str.
**/
int string_find_index_substring_string(const char *str, const char *substr);
/**
* string_copy_only_ascii:
*
* Leaf function.
*
* Strips non-ASCII characters from a string.
**/
void string_copy_only_ascii(char *str_stripped, const char *str);
extern const unsigned char lr_char_props[256];
RETRO_END_DECLS
@@ -0,0 +1,63 @@
/* Copyright (C) 2010-2020 The RetroArch team
*
* ---------------------------------------------------------------------------------------
* The following license statement only applies to this file (rtime.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_RTIME_H__
#define __LIBRETRO_SDK_RTIME_H__
#include <retro_common_api.h>
#include <stdint.h>
#include <stddef.h>
#include <time.h>
RETRO_BEGIN_DECLS
/* TODO/FIXME: Move all generic time handling functions
* to this file */
/**
* Must be called before using \c rtime_localtime().
* May be called multiple times without ill effects,
* but must only be called from the main thread.
*/
void rtime_init(void);
/**
* Must be called upon program or core termination.
* May be called multiple times without ill effects,
* but must only be called from the main thread.
*/
void rtime_deinit(void);
/**
* Thread-safe wrapper around standard \c localtime(),
* which by itself is not guaranteed to be thread-safe.
* @param timep Pointer to a time_t object to convert.
* @param result Pointer to a tm object to store the result in.
* @return \c result.
* @see https://en.cppreference.com/w/c/chrono/localtime
*/
struct tm *rtime_localtime(const time_t *timep, struct tm *result);
RETRO_END_DECLS
#endif
@@ -0,0 +1,700 @@
/* Copyright (C) 2010-2020 The RetroArch team
*
* ---------------------------------------------------------------------------------------
* The following license statement only applies to this file (file_stream.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.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdarg.h>
#include <ctype.h>
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#ifdef _MSC_VER
#include <compat/msvc.h>
#endif
#include <retro_miscellaneous.h>
#include <file/file_path.h>
#include <string/stdstring.h>
#include <streams/file_stream.h>
#define VFS_FRONTEND
#include <vfs/vfs_implementation.h>
#define VFS_ERROR_RETURN_VALUE -1
struct RFILE
{
struct retro_vfs_file_handle *hfile;
bool err_flag;
};
static retro_vfs_get_path_t filestream_get_path_cb = NULL;
static retro_vfs_open_t filestream_open_cb = NULL;
static retro_vfs_close_t filestream_close_cb = NULL;
static retro_vfs_size_t filestream_size_cb = NULL;
static retro_vfs_truncate_t filestream_truncate_cb = NULL;
static retro_vfs_tell_t filestream_tell_cb = NULL;
static retro_vfs_seek_t filestream_seek_cb = NULL;
static retro_vfs_read_t filestream_read_cb = NULL;
static retro_vfs_write_t filestream_write_cb = NULL;
static retro_vfs_flush_t filestream_flush_cb = NULL;
static retro_vfs_remove_t filestream_remove_cb = NULL;
static retro_vfs_rename_t filestream_rename_cb = NULL;
/* VFS Initialization */
void filestream_vfs_init(const struct retro_vfs_interface_info* vfs_info)
{
const struct retro_vfs_interface *
vfs_iface = vfs_info->iface;
filestream_get_path_cb = NULL;
filestream_open_cb = NULL;
filestream_close_cb = NULL;
filestream_tell_cb = NULL;
filestream_size_cb = NULL;
filestream_truncate_cb = NULL;
filestream_seek_cb = NULL;
filestream_read_cb = NULL;
filestream_write_cb = NULL;
filestream_flush_cb = NULL;
filestream_remove_cb = NULL;
filestream_rename_cb = NULL;
if (
(vfs_info->required_interface_version <
FILESTREAM_REQUIRED_VFS_VERSION)
|| !vfs_iface)
return;
filestream_get_path_cb = vfs_iface->get_path;
filestream_open_cb = vfs_iface->open;
filestream_close_cb = vfs_iface->close;
filestream_size_cb = vfs_iface->size;
filestream_truncate_cb = vfs_iface->truncate;
filestream_tell_cb = vfs_iface->tell;
filestream_seek_cb = vfs_iface->seek;
filestream_read_cb = vfs_iface->read;
filestream_write_cb = vfs_iface->write;
filestream_flush_cb = vfs_iface->flush;
filestream_remove_cb = vfs_iface->remove;
filestream_rename_cb = vfs_iface->rename;
}
/* Callback wrappers */
bool filestream_exists(const char *path)
{
RFILE *dummy = NULL;
if (!path || !*path)
return false;
if (!(dummy = filestream_open(
path,
RETRO_VFS_FILE_ACCESS_READ,
RETRO_VFS_FILE_ACCESS_HINT_NONE)))
return false;
if (filestream_close(dummy) != 0)
free(dummy);
dummy = NULL;
return true;
}
int64_t filestream_get_size(RFILE *stream)
{
int64_t output;
if (filestream_size_cb)
output = filestream_size_cb(stream->hfile);
else
output = retro_vfs_file_size_impl(
(libretro_vfs_implementation_file*)stream->hfile);
if (output == VFS_ERROR_RETURN_VALUE)
stream->err_flag = true;
return output;
}
int64_t filestream_truncate(RFILE *stream, int64_t length)
{
int64_t output;
if (filestream_truncate_cb)
output = filestream_truncate_cb(stream->hfile, length);
else
output = retro_vfs_file_truncate_impl(
(libretro_vfs_implementation_file*)stream->hfile, length);
if (output == VFS_ERROR_RETURN_VALUE)
stream->err_flag = true;
return output;
}
RFILE* filestream_open(const char *path, unsigned mode, unsigned hints)
{
struct retro_vfs_file_handle *fp = NULL;
RFILE* output = (RFILE*)malloc(sizeof(RFILE));
if (!output)
return NULL;
if (filestream_open_cb)
fp = (struct retro_vfs_file_handle*)
filestream_open_cb(path, mode, hints);
else
fp = (struct retro_vfs_file_handle*)
retro_vfs_file_open_impl(path, mode, hints);
if (!fp)
{
free(output);
return NULL;
}
output->err_flag = false;
output->hfile = fp;
return output;
}
char* filestream_gets(RFILE *stream, char *s, size_t len)
{
int c = 0;
char *p = s;
if (!stream)
return NULL;
/* get max bytes or up to a newline */
for (len--; len > 0; len--)
{
if ((c = filestream_getc(stream)) == EOF)
break;
*p++ = c;
if (c == '\n')
break;
}
*p = 0;
if (p == s && c == EOF)
return NULL;
return (s);
}
int filestream_getc(RFILE *stream)
{
char c = 0;
if (stream && filestream_read(stream, &c, 1) == 1)
return (int)(unsigned char)c;
return EOF;
}
int filestream_vscanf(RFILE *stream, const char* format, va_list *args)
{
char buf[4096];
char subfmt[64];
va_list args_copy;
const char *bufiter = buf;
int ret = 0;
int64_t startpos = filestream_tell(stream);
int64_t maxlen = filestream_read(stream, buf, sizeof(buf)-1);
if (maxlen <= 0)
return EOF;
buf[maxlen] = '\0';
/* Have to copy the input va_list here
* > Calling va_arg() on 'args' directly would
* cause the va_list to have an indeterminate value
* in the function calling filestream_vscanf(),
* leading to unexpected behaviour */
#ifdef __va_copy
__va_copy(args_copy, *args);
#else
va_copy(args_copy, *args);
#endif
while (*format)
{
if (*format == '%')
{
int sublen = 0;
char* subfmtiter = subfmt;
bool asterisk = false;
*subfmtiter++ = *format++; /* '%' */
/* %[*][width][length]specifier */
if (*format == '*')
{
asterisk = true;
*subfmtiter++ = *format++;
}
while (ISDIGIT((unsigned char)*format))
*subfmtiter++ = *format++; /* width */
/* length */
if (*format == 'h' || *format == 'l')
{
if (format[1] == format[0])
*subfmtiter++ = *format++;
*subfmtiter++ = *format++;
}
else if (
*format == 'j' ||
*format == 'z' ||
*format == 't' ||
*format == 'L')
{
*subfmtiter++ = *format++;
}
/* specifier - always a single character (except ]) */
if (*format == '[')
{
while (*format != ']')
*subfmtiter++ = *format++;
*subfmtiter++ = *format++;
}
else
*subfmtiter++ = *format++;
*subfmtiter++ = '%';
*subfmtiter++ = 'n';
*subfmtiter++ = '\0';
if (sizeof(void*) != sizeof(long*))
abort(); /* all pointers must have the same size */
if (asterisk)
{
int v = sscanf(bufiter, subfmt, &sublen);
if (v == EOF)
return EOF;
if (v != 0)
break;
}
else
{
int v = sscanf(bufiter, subfmt, va_arg(args_copy, void*), &sublen);
if (v == EOF)
return EOF;
if (v != 1)
break;
}
ret++;
bufiter += sublen;
}
else if (isspace((unsigned char)*format))
{
while (isspace((unsigned char)*bufiter))
bufiter++;
format++;
}
else
{
if (*bufiter != *format)
break;
bufiter++;
format++;
}
}
va_end(args_copy);
filestream_seek(stream, startpos + (bufiter - buf),
RETRO_VFS_SEEK_POSITION_START);
return ret;
}
int filestream_scanf(RFILE *stream, const char* format, ...)
{
int ret;
va_list vl;
va_start(vl, format);
ret = filestream_vscanf(stream, format, &vl);
va_end(vl);
return ret;
}
int64_t filestream_seek(RFILE *stream, int64_t offset, int seek_position)
{
int64_t output;
if (filestream_seek_cb)
output = filestream_seek_cb(stream->hfile, offset, seek_position);
else
output = retro_vfs_file_seek_impl(
(libretro_vfs_implementation_file*)stream->hfile,
offset, seek_position);
if (output == VFS_ERROR_RETURN_VALUE)
stream->err_flag = true;
return output;
}
int filestream_eof(RFILE *stream)
{
return filestream_tell(stream) == filestream_get_size(stream) ? EOF : 0;
}
int64_t filestream_tell(RFILE *stream)
{
int64_t output;
if (filestream_size_cb)
output = filestream_tell_cb(stream->hfile);
else
output = retro_vfs_file_tell_impl(
(libretro_vfs_implementation_file*)stream->hfile);
if (output == VFS_ERROR_RETURN_VALUE)
stream->err_flag = true;
return output;
}
void filestream_rewind(RFILE *stream)
{
if (!stream)
return;
filestream_seek(stream, 0L, RETRO_VFS_SEEK_POSITION_START);
stream->err_flag = false;
}
int64_t filestream_read(RFILE *stream, void *s, int64_t len)
{
int64_t output;
if (filestream_read_cb)
output = filestream_read_cb(stream->hfile, s, len);
else
output = retro_vfs_file_read_impl(
(libretro_vfs_implementation_file*)stream->hfile, s, len);
if (output == VFS_ERROR_RETURN_VALUE)
stream->err_flag = true;
return output;
}
int filestream_flush(RFILE *stream)
{
int output;
if (filestream_flush_cb)
output = filestream_flush_cb(stream->hfile);
else
output = retro_vfs_file_flush_impl(
(libretro_vfs_implementation_file*)stream->hfile);
if (output == VFS_ERROR_RETURN_VALUE)
stream->err_flag = true;
return output;
}
int filestream_delete(const char *path)
{
if (filestream_remove_cb)
return filestream_remove_cb(path);
return retro_vfs_file_remove_impl(path);
}
int filestream_rename(const char *old_path, const char *new_path)
{
if (filestream_rename_cb)
return filestream_rename_cb(old_path, new_path);
return retro_vfs_file_rename_impl(old_path, new_path);
}
int filestream_copy(const char *src, const char *dst)
{
char buf[256] = {0};
int64_t n = 0;
int ret = 0;
char path_dst[PATH_MAX_LENGTH] = {0};
RFILE *fp_src = filestream_open(src, RETRO_VFS_FILE_ACCESS_READ, RETRO_VFS_FILE_ACCESS_HINT_NONE);
RFILE *fp_dst = filestream_open(dst, RETRO_VFS_FILE_ACCESS_WRITE, RETRO_VFS_FILE_ACCESS_HINT_NONE);
if (!fp_src || !fp_dst)
ret = -1;
if (ret < 0)
goto close;
snprintf(path_dst, sizeof(path_dst), "%s", dst);
path_basedir(path_dst);
if (!path_is_directory(path_dst))
path_mkdir(path_dst);
while ((n = filestream_read(fp_src, buf, sizeof(buf))) > 0 && ret == 0)
{
if (filestream_write(fp_dst, buf, n) != n)
ret = -1;
}
close:
if (fp_src)
filestream_close(fp_src);
if (fp_dst)
filestream_close(fp_dst);
return ret;
}
int filestream_cmp(const char *src, const char *dst)
{
int ret = 0;
RFILE *fp_src = filestream_open(src, RETRO_VFS_FILE_ACCESS_READ,
RETRO_VFS_FILE_ACCESS_HINT_NONE);
RFILE *fp_dst = filestream_open(dst, RETRO_VFS_FILE_ACCESS_READ,
RETRO_VFS_FILE_ACCESS_HINT_NONE);
if (!fp_src || !fp_dst || filestream_get_size(fp_src) != filestream_get_size(fp_dst))
ret = -1;
if (ret >= 0)
{
char buf_src[256] = {0};
char buf_dst[256] = {0};
while ((filestream_read(fp_src, buf_src, sizeof(buf_src))) > 0 && ret == 0)
{
filestream_read(fp_dst, buf_dst, sizeof(buf_dst));
ret = memcmp(buf_src, buf_dst, sizeof(buf_src));
}
}
if (fp_src)
{
filestream_close(fp_src);
fp_src = NULL;
}
if (fp_dst)
{
filestream_close(fp_dst);
fp_dst = NULL;
}
return ret;
}
const char* filestream_get_path(RFILE *stream)
{
if (filestream_get_path_cb)
return filestream_get_path_cb(stream->hfile);
return retro_vfs_file_get_path_impl(
(libretro_vfs_implementation_file*)stream->hfile);
}
int64_t filestream_write(RFILE *stream, const void *s, int64_t len)
{
int64_t output;
if (filestream_write_cb)
output = filestream_write_cb(stream->hfile, s, len);
else
output = retro_vfs_file_write_impl(
(libretro_vfs_implementation_file*)stream->hfile, s, len);
if (output == VFS_ERROR_RETURN_VALUE)
stream->err_flag = true;
return output;
}
int filestream_putc(RFILE *stream, int c)
{
char c_char = (char)c;
if (!stream)
return EOF;
return filestream_write(stream, &c_char, 1) == 1
? (int)(unsigned char)c
: EOF;
}
int filestream_vprintf(RFILE *stream, const char* format, va_list args)
{
static char buffer[8 * 1024];
int _len = vsnprintf(buffer, sizeof(buffer),
format, args);
if (_len < 0)
return -1;
else if (_len == 0)
return 0;
return (int)filestream_write(stream, buffer, _len);
}
int filestream_printf(RFILE *stream, const char* format, ...)
{
va_list vl;
int ret;
va_start(vl, format);
ret = filestream_vprintf(stream, format, vl);
va_end(vl);
return ret;
}
int filestream_error(RFILE *stream)
{
return (stream && stream->err_flag);
}
int filestream_close(RFILE *stream)
{
int output;
struct retro_vfs_file_handle* fp = stream->hfile;
if (filestream_close_cb)
output = filestream_close_cb(fp);
else
output = retro_vfs_file_close_impl(
(libretro_vfs_implementation_file*)fp);
if (output == 0)
free(stream);
return output;
}
int64_t filestream_read_file(const char *path, void **buf, int64_t *len)
{
int64_t ret = 0;
int64_t content_buf_size = 0;
void *content_buf = NULL;
RFILE *file = filestream_open(path,
RETRO_VFS_FILE_ACCESS_READ,
RETRO_VFS_FILE_ACCESS_HINT_NONE);
if (!file)
{
*buf = NULL;
return 0;
}
if ((content_buf_size = filestream_get_size(file)) < 0)
goto error;
if (!(content_buf = malloc((size_t)(content_buf_size + 1))))
goto error;
if ((int64_t)(uint64_t)(content_buf_size + 1) != (content_buf_size + 1))
goto error;
if ((ret = filestream_read(file, content_buf, (int64_t)content_buf_size)) <
0)
goto error;
if (filestream_close(file) != 0)
free(file);
*buf = content_buf;
/* Allow for easy reading of strings to be safe.
* Will only work with sane character formatting (Unix). */
((char*)content_buf)[ret] = '\0';
if (len)
*len = ret;
return 1;
error:
if (filestream_close(file) != 0)
free(file);
if (content_buf)
free(content_buf);
if (len)
*len = -1;
*buf = NULL;
return 0;
}
bool filestream_write_file(const char *path, const void *data, int64_t size)
{
int64_t ret = 0;
RFILE *file = filestream_open(path,
RETRO_VFS_FILE_ACCESS_WRITE,
RETRO_VFS_FILE_ACCESS_HINT_NONE);
if (!file)
return false;
ret = filestream_write(file, data, size);
if (filestream_close(file) != 0)
free(file);
return (ret == size);
}
char *filestream_getline(RFILE *stream)
{
char *newline_tmp = NULL;
size_t cur_size = 8;
size_t idx = 0;
int in = 0;
char *newline = (char*)malloc(9);
if (!stream || !newline)
{
if (newline)
free(newline);
return NULL;
}
in = filestream_getc(stream);
while (in != EOF && in != '\n')
{
if (idx == cur_size)
{
cur_size *= 2;
if (!(newline_tmp = (char*)realloc(newline, cur_size + 1)))
{
free(newline);
return NULL;
}
newline = newline_tmp;
}
newline[idx++] = in;
in = filestream_getc(stream);
}
newline[idx] = '\0';
return newline;
}
libretro_vfs_implementation_file* filestream_get_vfs_handle(RFILE *stream)
{
return (libretro_vfs_implementation_file*)stream->hfile;
}
+580 -94
View File
@@ -1,4 +1,4 @@
/* Copyright (C) 2010-2018 The RetroArch team
/* Copyright (C) 2010-2020 The RetroArch team
*
* ---------------------------------------------------------------------------------------
* The following license statement only applies to this file (stdstring.c).
@@ -22,10 +22,32 @@
#include <stdint.h>
#include <ctype.h>
#include <string.h>
#include <compat/strl.h>
#include <string/stdstring.h>
#include <encodings/utf.h>
const uint8_t lr_char_props[256] = {
/*x0 x1 x2 x3 x4 x5 x6 x7 x8 x9 xA xB xC xD xE xF */
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x80,0x80,0x00,0x00,0x80,0x00,0x00, /* 0x */
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 1x */
0x80,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 2x !"#$%&'()*+,-./ */
0x41,0x41,0x41,0x41,0x41,0x41,0x41,0x41,0x41,0x41,0x00,0x00,0x00,0x00,0x00,0x00, /* 3x 0123456789:;<=>? */
0x00,0x23,0x23,0x23,0x23,0x23,0x23,0x22,0x22,0x22,0x22,0x22,0x22,0x22,0x22,0x22, /* 4x @ABCDEFGHIJKLMNO */
0x22,0x22,0x22,0x22,0x22,0x22,0x22,0x22,0x22,0x22,0x22,0x00,0x00,0x00,0x00,0x08, /* 5x PQRSTUVWXYZ[\]^_ */
0x00,0x25,0x25,0x25,0x25,0x25,0x25,0x24,0x24,0x24,0x24,0x24,0x24,0x24,0x24,0x24, /* 6x `abcdefghijklmno */
0x24,0x24,0x24,0x24,0x24,0x24,0x24,0x24,0x24,0x24,0x24,0x00,0x00,0x00,0x00,0x00, /* 7x pqrstuvwxyz{|}~ */
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 8x */
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 9x */
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* Ax */
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* Bx */
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* Cx */
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* Dx */
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* Ex */
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* Fx */
};
char *string_to_upper(char *s)
{
char *cs = (char *)s;
@@ -45,9 +67,13 @@ char *string_to_lower(char *s)
char *string_ucwords(char *s)
{
char *cs = (char *)s;
if (!s || *s == '\0')
return s;
for ( ; *cs != '\0'; cs++)
{
if (*cs == ' ')
if (*cs == ' ' && *(cs + 1) != '\0')
*(cs+1) = toupper((unsigned char)*(cs+1));
}
@@ -55,10 +81,13 @@ char *string_ucwords(char *s)
return s;
}
char *string_replace_substring(const char *in,
const char *pattern, const char *replacement)
char *string_replace_substring(
const char *in, size_t in_len,
const char *pattern, size_t pattern_len,
const char *replacement, size_t replacement_len)
{
size_t numhits, pattern_len, replacement_len, outlen;
size_t _len;
size_t numhits = 0;
const char *inat = NULL;
const char *inprev = NULL;
char *out = NULL;
@@ -69,9 +98,6 @@ char *string_replace_substring(const char *in,
if (!pattern || !replacement)
return strdup(in);
pattern_len = strlen(pattern);
replacement_len = strlen(replacement);
numhits = 0;
inat = in;
while ((inat = strstr(inat, pattern)))
@@ -80,10 +106,9 @@ char *string_replace_substring(const char *in,
numhits++;
}
outlen = strlen(in) - pattern_len*numhits + replacement_len*numhits;
out = (char *)malloc(outlen+1);
_len = in_len - pattern_len * numhits + replacement_len*numhits;
if (!out)
if (!(out = (char *)malloc(_len + 1)))
return NULL;
outat = out;
@@ -96,56 +121,69 @@ char *string_replace_substring(const char *in,
outat += inat-inprev;
memcpy(outat, replacement, replacement_len);
outat += replacement_len;
inat += pattern_len;
inat += pattern_len;
inprev = inat;
}
strcpy(outat, inprev);
_len = (in + in_len) - inprev;
strlcpy(outat, inprev, _len + 1);
return out;
}
/* Remove leading whitespaces */
/**
* string_trim_whitespace_left:
*
* Remove leading whitespaces
**/
char *string_trim_whitespace_left(char *const s)
{
if(s && *s)
if (s && *s)
{
size_t len = strlen(s);
char *current = s;
while(*current && isspace((unsigned char)*current))
{
while (*current && ISSPACE((unsigned char)*current))
++current;
--len;
}
if(s != current)
memmove(s, current, len + 1);
if (s != current)
{
size_t _len = strlen(current);
memmove(s, current, _len + 1);
}
}
return s;
}
/* Remove trailing whitespaces */
/**
* string_trim_whitespace_right:
*
* Remove trailing whitespaces
**/
char *string_trim_whitespace_right(char *const s)
{
if(s && *s)
if (s && *s)
{
size_t len = strlen(s);
char *current = s + len - 1;
size_t _len = strlen(s);
char *current = s + _len - 1;
while(current != s && isspace((unsigned char)*current))
while (current != s && ISSPACE((unsigned char)*current))
{
--current;
--len;
--_len;
}
current[isspace((unsigned char)*current) ? 0 : 1] = '\0';
current[ISSPACE((unsigned char)*current) ? 0 : 1] = '\0';
}
return s;
}
/* Remove leading and trailing whitespaces */
/**
* string_trim_whitespace:
*
* Remove leading and trailing whitespaces
**/
char *string_trim_whitespace(char *const s)
{
string_trim_whitespace_right(s); /* order matters */
@@ -154,86 +192,534 @@ char *string_trim_whitespace(char *const s)
return s;
}
char *word_wrap(char* buffer, const char *string, int line_width, bool unicode, unsigned max_lines)
/**
* word_wrap:
* @s : pointer to destination buffer.
* @len : size of destination buffer.
* @src : pointer to input string.
* @line_width : max number of characters per line.
* @wideglyph_width : not used, but is necessary to keep
* compatibility with word_wrap_wideglyph().
* @max_lines : max lines of destination string.
* 0 means no limit.
*
* Wraps string specified by @src to destination buffer
* specified by @s and @len.
* This function assumes that all glyphs in the string
* have an on-screen pixel width similar to that of
* regular Latin characters - i.e. it will not wrap
* correctly any text containing so-called 'wide' Unicode
* characters (e.g. CJK languages, emojis, etc.).
**/
size_t word_wrap(
char *s, size_t len,
const char *src, size_t src_len,
int line_width, int wideglyph_width, unsigned max_lines)
{
unsigned i = 0;
unsigned len = (unsigned)strlen(string);
unsigned lines = 1;
char *last_space = NULL;
unsigned counter = 0;
unsigned lines = 1;
const char *src_end = src + src_len;
char *s_start = s;
size_t _len = len;
while (i < len)
/* Prevent buffer overflow */
if (len < src_len + 1)
return 0;
/* Early return if src string length is less
* than line width */
if (src_len < (size_t)line_width)
return strlcpy(s, src, len);
while (*src != '\0')
{
unsigned counter;
int pos = (int)(&buffer[i] - buffer);
unsigned char_len = (unsigned)(utf8skip(src, 1) - src);
counter++;
/* copy string until the end of the line is reached */
for (counter = 1; counter <= (unsigned)line_width; counter++)
if (*src == ' ')
last_space = s; /* Remember the location of the whitespace */
else if (*src == '\n')
{
const char *character;
unsigned char_len;
unsigned j = i;
/* If newlines embedded in the input,
* reset the index */
lines++;
counter = 0;
/* check if end of string reached */
if (i == len)
/* Early return if remaining src string
* length is less than line width */
if (src_end - src <= line_width)
{
buffer[i] = 0;
return buffer;
}
character = utf8skip(&string[i], 1);
char_len = (unsigned)(character - &string[i]);
if (!unicode)
counter += char_len - 1;
do
{
buffer[i] = string[i];
char_len--;
i++;
} while(char_len);
/* check for newlines embedded in the original input
* and reset the index */
if (buffer[j] == '\n')
{
lines++;
counter = 1;
_len = len - (size_t)(s - s_start);
return strlcpy(s, src, _len);
}
}
/* check for whitespace */
if (string[i] == ' ')
while (char_len--)
*s++ = *src++;
if (counter >= (unsigned)line_width)
{
if ((max_lines == 0 || lines < max_lines))
counter = 0;
if (last_space && (max_lines == 0 || lines < max_lines))
{
buffer[i] = '\n';
i++;
/* Replace nearest (previous) whitespace
* with newline character */
*last_space = '\n';
lines++;
src -= s - last_space - 1;
s = last_space + 1;
last_space = NULL;
/* Early return if remaining src string
* length is less than line width */
if (src_end - src < line_width)
{
_len = len - (size_t)(s - s_start);
return strlcpy(s, src, _len);
}
}
}
else
{
int k;
/* check for nearest whitespace back in string */
for (k = i; k > 0; k--)
{
if (string[k] != ' ' || (max_lines != 0 && lines >= max_lines))
continue;
buffer[k] = '\n';
/* set string index back to character after this one */
i = k + 1;
lines++;
break;
}
if (&buffer[i] - buffer == pos)
return buffer;
}
}
buffer[i] = 0;
*s = '\0';
return 0;
}
return buffer;
/**
* word_wrap_wideglyph:
* @dst : pointer to destination buffer.
* @len : size of destination buffer.
* @src : pointer to input string.
* @line_width : max number of characters per line.
* @wideglyph_width : effective width of 'wide' Unicode glyphs.
* the value here is normalised relative to the
* typical on-screen pixel width of a regular
* Latin character:
* - a regular Latin character is defined to
* have an effective width of 100
* - wideglyph_width = 100 * (wide_character_pixel_width / latin_character_pixel_width)
* - e.g. if 'wide' Unicode characters in 'src'
* have an on-screen pixel width twice that of
* regular Latin characters, wideglyph_width
* would be 200
* @max_lines : max lines of destination string.
* 0 means no limit.
*
* Wraps string specified by @src to destination buffer
* specified by @dst and @len.
* This function assumes that all glyphs in the string
* are:
* - EITHER 'non-wide' Unicode glyphs, with an on-screen
* pixel width similar to that of regular Latin characters
* - OR 'wide' Unicode glyphs (e.g. CJK languages, emojis, etc.)
* with an on-screen pixel width defined by @wideglyph_width
* Note that wrapping may occur in inappropriate locations
* if @src string contains 'wide' Unicode characters whose
* on-screen pixel width deviates greatly from the set
* @wideglyph_width value.
**/
size_t word_wrap_wideglyph(char *s, size_t len,
const char *src, size_t src_len, int line_width,
int wideglyph_width, unsigned max_lines)
{
char *lastspace = NULL;
char *lastwideglyph = NULL;
const char *src_end = src + src_len;
char *s_start = s;
unsigned lines = 1;
/* 'line_width' means max numbers of characters per line,
* but this metric is only meaningful when dealing with
* 'regular' glyphs that have an on-screen pixel width
* similar to that of regular Latin characters.
* When handing so-called 'wide' Unicode glyphs, it is
* necessary to consider the actual on-screen pixel width
* of each character.
* In order to do this, we create a distinction between
* regular Latin 'non-wide' glyphs and 'wide' glyphs, and
* normalise all values relative to the on-screen pixel
* width of regular Latin characters:
* - Regular 'non-wide' glyphs have a normalised width of 100
* - 'line_width' is therefore normalised to 100 * (width_in_characters)
* - 'wide' glyphs have a normalised width of
* 100 * (wide_character_pixel_width / latin_character_pixel_width)
* - When a character is detected, the position in the current
* line is incremented by the regular normalised width of 100
* - If that character is then determined to be a 'wide'
* glyph, the position in the current line is further incremented
* by the difference between the normalised 'wide' and 'non-wide'
* width values */
unsigned counter_normalized = 0;
int line_width_normalized = line_width * 100;
int additional_counter_normalized = wideglyph_width - 100;
/* Early return if src string length is less
* than line width */
if (src_end - src < line_width)
return strlcpy(s, src, len);
while (*src != '\0')
{
size_t remaining;
unsigned char_len = (unsigned)(utf8skip(src, 1) - src);
counter_normalized += 100;
/* Prevent buffer overflow */
if (char_len >= len)
break;
if (*src == ' ')
lastspace = s; /* Remember the location of the whitespace */
else if (*src == '\n')
{
/* If newlines embedded in the input,
* reset the index */
lines++;
counter_normalized = 0;
/* Early return if remaining src string
* length is less than line width */
if (src_end - src <= line_width)
{
remaining = len - (size_t)(s - s_start);
return strlcpy(s, src, remaining);
}
}
else if (char_len >= 3)
{
/* Remember the location of the first byte
* whose length as UTF-8 >= 3*/
lastwideglyph = s;
counter_normalized += additional_counter_normalized;
}
len -= char_len;
while (char_len--)
*s++ = *src++;
if (counter_normalized >= (unsigned)line_width_normalized)
{
counter_normalized = 0;
if (max_lines != 0 && lines >= max_lines)
continue;
else if (lastwideglyph
&& (!lastspace || lastwideglyph > lastspace))
{
/* Insert newline character */
*lastwideglyph = '\n';
lines++;
src -= s - lastwideglyph;
s = lastwideglyph + 1;
lastwideglyph = NULL;
/* Early return if remaining src string
* length is less than line width */
if (src_end - src <= line_width)
{
remaining = len - (size_t)(s - s_start);
return strlcpy(s, src, remaining);
}
}
else if (lastspace)
{
/* Replace nearest (previous) whitespace
* with newline character */
*lastspace = '\n';
lines++;
src -= s - lastspace - 1;
s = lastspace + 1;
lastspace = NULL;
/* Early return if remaining src string
* length is less than line width */
if (src_end - src < line_width)
{
remaining = len - (size_t)(s - s_start);
return strlcpy(s, src, remaining);
}
}
}
}
*s = '\0';
return 0;
}
/**
* string_tokenize:
*
* Splits string into tokens separated by @delim
* > Returned token string must be free()'d
* > Returns NULL if token is not found
* > After each call, @str is set to the position after the
* last found token
* > Tokens *include* empty strings
* Usage example:
* char *str = "1,2,3,4,5,6,7,,,10,";
* char **str_ptr = &str;
* char *token = NULL;
* while ((token = string_tokenize(str_ptr, ",")))
* {
* printf("%s\n", token);
* free(token);
* token = NULL;
* }
**/
char* string_tokenize(char **str, const char *delim)
{
/* Taken from https://codereview.stackexchange.com/questions/216956/strtok-function-thread-safe-supports-empty-tokens-doesnt-change-string# */
char *str_ptr = NULL;
char *delim_ptr = NULL;
char *token = NULL;
size_t _len = 0;
size_t delim_len = 0;
/* Sanity checks */
if (!str || string_is_empty(delim))
return NULL;
/* Note: we don't check string_is_empty() here,
* empty strings are valid */
if (!(str_ptr = *str))
return NULL;
delim_len = strlen(delim);
/* Search for delimiter */
if ((delim_ptr = strstr(str_ptr, delim)))
_len = delim_ptr - str_ptr;
else
_len = strlen(str_ptr);
/* Allocate token string */
if (!(token = (char *)malloc((_len + 1) * sizeof(char))))
return NULL;
/* Copy token */
strlcpy(token, str_ptr, (_len + 1) * sizeof(char));
token[_len] = '\0';
/* Update input string pointer */
*str = delim_ptr ? delim_ptr + delim_len : NULL;
return token;
}
/**
* string_remove_all_chars:
* @s : input string (must be non-NULL, otherwise UB)
*
* Leaf function.
*
* Removes every instance of character @c from @s
**/
void string_remove_all_chars(char *s, char c)
{
char *read_ptr = s;
char *write_ptr = s;
while (*read_ptr != '\0')
{
/* Only write if the character is not the one to remove */
if (*read_ptr != c)
*write_ptr++ = *read_ptr;
read_ptr++;
}
*write_ptr = '\0';
}
/**
* string_replace_all_chars:
* @s : input string (must be non-NULL, otherwise UB)
* @find : character to find
* @replace : character to replace @find with
*
* Replaces every instance of character @find in @s
* with character @replace
**/
void string_replace_all_chars(char *s, char find, char replace)
{
char *str_ptr = s;
while ((str_ptr = strchr(str_ptr, find)))
*str_ptr++ = replace;
}
/**
* string_to_unsigned:
* @str : input string
*
* Converts string to unsigned integer.
*
* @return 0 if string is invalid, otherwise > 0
**/
unsigned string_to_unsigned(const char *str)
{
const char *ptr = NULL;
if (string_is_empty(str))
return 0;
for (ptr = str; *ptr != '\0'; ptr++)
{
if (!ISDIGIT((unsigned char)*ptr))
return 0;
}
return (unsigned)strtoul(str, NULL, 10);
}
/**
* string_hex_to_unsigned:
* @str : input string (must be non-NULL, otherwise UB)
*
* Converts hexadecimal string to unsigned integer.
* Handles optional leading '0x'.
*
* @return 0 if string is invalid, otherwise > 0
**/
unsigned string_hex_to_unsigned(const char *str)
{
const char *hex_str = str;
const char *ptr = NULL;
if (string_is_empty(str))
return 0;
/* Remove leading '0x', if present */
if ( str[0] == '0'
&& (str[1] == 'x' || str[1] == 'X'))
{
hex_str = str + 2;
if (string_is_empty(hex_str))
return 0;
}
/* Check for valid characters */
for (ptr = hex_str; *ptr != '\0'; ptr++)
{
if (!isxdigit((unsigned char)*ptr))
return 0;
}
return (unsigned)strtoul(hex_str, NULL, 16);
}
/**
* string_count_occurrences_single_character:
*
* Leaf function.
*
* Get the total number of occurrences of character @c in @str.
*
* @return Total number of occurrences of character @c
*/
int string_count_occurrences_single_character(const char *str, char c)
{
int count = 0;
for (; *str; str++)
if (*str == c)
count++;
return count;
}
/**
* string_replace_whitespace_with_single_character:
*
* Leaf function.
*
* Replaces all spaces with given character @c.
**/
/* FIX: Added unsigned char cast to ISSPACE argument for
* correct behaviour with high-byte / signed char inputs. */
void string_replace_whitespace_with_single_character(char *s, char c)
{
for (; *s; s++)
if (ISSPACE((unsigned char)*s))
*s = c;
}
/**
* string_replace_multi_space_with_single_space:
*
* Leaf function.
*
* Replaces multiple spaces with a single space in a string.
**/
/* FIX: Added unsigned char cast to ISSPACE arguments. */
void string_replace_multi_space_with_single_space(char *s)
{
char *str_trimmed = s;
bool prev_is_space = false;
bool curr_is_space = false;
for (; *s; s++)
{
curr_is_space = ISSPACE((unsigned char)*s);
if (prev_is_space && curr_is_space)
continue;
*str_trimmed++ = *s;
prev_is_space = curr_is_space;
}
*str_trimmed = '\0';
}
/**
* string_remove_all_whitespace:
*
* Leaf function.
*
* Remove all spaces from the given string.
**/
/* FIX: Added unsigned char cast to ISSPACE argument. */
void string_remove_all_whitespace(char *s, const char *str)
{
for (; *str; str++)
if (!ISSPACE((unsigned char)*str))
*s++ = *str;
*s = '\0';
}
/**
* Retrieve the last occurrence of the given character in a string.
*/
int string_index_last_occurance(const char *str, char c)
{
const char *pos = strrchr(str, c);
if (pos)
return (int)(pos - str);
return -1;
}
/**
* string_find_index_substring_string:
* @str : input string (must be non-NULL, otherwise UB)
* @substr : substring to find in @str
*
* Find the position of substring @substr in string @str.
**/
int string_find_index_substring_string(const char *str, const char *substr)
{
const char *pos = strstr(str, substr);
if (pos)
return (int)(pos - str);
return -1;
}
/**
* string_copy_only_ascii:
*
* Leaf function.
*
* Strips non-ASCII characters from a string.
**/
void string_copy_only_ascii(char *s, const char *str)
{
for (; *str; str++)
if (*str > 0x1F && *str < 0x7F)
*s++ = *str;
*s = '\0';
}
+78
View File
@@ -0,0 +1,78 @@
/* Copyright (C) 2010-2020 The RetroArch team
*
* ---------------------------------------------------------------------------------------
* The following license statement only applies to this file (rtime.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.
*/
#ifdef HAVE_THREADS
#include <rthreads/rthreads.h>
#include <stdlib.h>
#endif
#include <string.h>
#include <time/rtime.h>
#ifdef HAVE_THREADS
/* TODO/FIXME - global */
slock_t *rtime_localtime_lock = NULL;
#endif
/* Must be called before using rtime_localtime() */
void rtime_init(void)
{
rtime_deinit();
#ifdef HAVE_THREADS
if (!rtime_localtime_lock)
rtime_localtime_lock = slock_new();
#endif
}
/* Must be called upon program termination */
void rtime_deinit(void)
{
#ifdef HAVE_THREADS
if (rtime_localtime_lock)
{
slock_free(rtime_localtime_lock);
rtime_localtime_lock = NULL;
}
#endif
}
/* Thread-safe wrapper for localtime() */
struct tm *rtime_localtime(const time_t *timep, struct tm *result)
{
struct tm *time_info = NULL;
/* Lock mutex */
#ifdef HAVE_THREADS
slock_lock(rtime_localtime_lock);
#endif
time_info = localtime(timep);
if (time_info)
memcpy(result, time_info, sizeof(struct tm));
/* Unlock mutex */
#ifdef HAVE_THREADS
slock_unlock(rtime_localtime_lock);
#endif
return result;
}
+1 -1
View File
@@ -4,7 +4,7 @@
*********************************************************************/
#define MAX_BLOCK_SIZE 1600
#define MAX_BLOCK_COUNT 16
#define MAX_BLOCK_COUNT 1024 /* 16 blocks (~1,000 timers) ran out with a few dozen connections a second */
#define DECLARE_HEAP(type, orderby) \
struct heap_ ## type { \
+23 -11
View File
@@ -69,6 +69,8 @@ static void debug_q(struct pico_queue *q)
static inline int32_t pico_enqueue(struct pico_queue *q, struct pico_frame *p)
{
uint32_t size;
if ((q->max_frames) && (q->max_frames <= q->frames))
return -1;
@@ -98,18 +100,19 @@ static inline int32_t pico_enqueue(struct pico_queue *q, struct pico_frame *p)
q->size += p->buffer_len + q->overhead;
q->frames++;
debug_q(q);
/* Read under the lock: once unlocked, another thread may dequeue (and free) the frame,
* and a size of 0 would make the caller free it again. */
size = q->size;
if (q->shared)
PICOTCP_MUTEX_UNLOCK(q->mutex);
return (int32_t)q->size;
return (int32_t)size;
}
static inline struct pico_frame *pico_dequeue(struct pico_queue *q)
{
struct pico_frame *p = q->head;
if (!p)
return NULL;
struct pico_frame *p;
if (q->frames < 1)
return NULL;
@@ -117,15 +120,19 @@ static inline struct pico_frame *pico_dequeue(struct pico_queue *q)
if (q->shared)
PICOTCP_MUTEX_LOCK(q->mutex);
q->head = p->next;
q->frames--;
q->size -= p->buffer_len - q->overhead;
if (q->head == NULL)
q->tail = NULL;
p = q->head;
if (p)
{
q->head = p->next;
q->frames--;
q->size -= p->buffer_len - q->overhead;
if (q->head == NULL)
q->tail = NULL;
debug_q(q);
debug_q(q);
p->next = NULL;
p->next = NULL;
}
if (q->shared)
PICOTCP_MUTEX_UNLOCK(q->mutex);
@@ -160,6 +167,11 @@ static inline void pico_queue_empty(struct pico_queue *q)
static inline void pico_queue_protect(struct pico_queue *q)
{
#ifdef PICO_SUPPORT_MUTEX
/* Create the mutex now: creating it lazily on first use races between threads */
if (q->mutex == NULL)
q->mutex = pico_mutex_init();
#endif
q->shared = 1;
}
+6 -1
View File
@@ -1710,7 +1710,12 @@ int pico_socket_connect(struct pico_socket *s, const void *remote_addr, uint16_t
return -1;
}
pico_socket_alter_state(s, PICO_SOCKET_STATE_BOUND, 0, 0);
/* A socket with the same quad is already in the tree: carrying on would leave this one
* untracked, and deleting it later would unlink (and free) the other one. */
if (pico_socket_alter_state(s, PICO_SOCKET_STATE_BOUND, 0, 0) != 0) {
pico_err = PICO_ERR_EADDRINUSE;
return -1;
}
#ifdef PICO_SUPPORT_UDP
if (PROTO(s) == PICO_PROTO_UDP) {
+114 -83
View File
@@ -25,6 +25,7 @@
#include "hw/naomi/naomi_cart.h"
#include "reios/reios.h"
#include "hw/sh4/modules/mmu.h"
#include "hw/sh4/modules/modules.h"
#include "hw/sh4/sh4_if.h"
#include "hw/sh4/sh4_mem.h"
#include "hw/sh4/sh4_sched.h"
@@ -38,23 +39,23 @@
#include "network/ice.h"
#include "hw/mem/mem_watch.h"
#include "network/net_handshake.h"
#include "network/naomi_network.h"
#include "serialize.h"
#include "hw/pvr/pvr.h"
#include "profiler/fc_profiler.h"
#include "oslib/storage.h"
#include "wsi/context.h"
#include <chrono>
#ifndef LIBRETRO
#include "ui/gui.h"
#endif
#include "hw/sh4/sh4_interpreter.h"
#include "hw/sh4/dyna/ngen.h"
#ifdef USE_DREAMLINK_DEVICES
#include "sdl/dreamlink.h"
#endif
#include "oslib/i18n.h"
#include <algorithm>
#include <chrono>
#include <exception>
#include <future>
#include <mutex>
#include <string>
#include <utility>
settings_t settings;
constexpr char const *BIOS_TITLE = "Dreamcast BIOS";
@@ -262,7 +263,34 @@ static void loadSpecialSettings()
|| prod_id == "T1208M" // Gigawing (JP)
|| prod_id == "T1235M" // Vampire Chronicle for Matching Service
|| prod_id == "T22901N" // Roadsters (US)
|| prod_id == "T28202M"))// Shin Nihon Pro Wrestling 4
|| prod_id == "T28202M" // Shin Nihon Pro Wrestling 4
|| prod_id == "T9512N" // The Grinch (US)
|| prod_id == "51034" // Generator Vol.1
|| prod_id == "MK-51057" // Generator Vol.2
|| prod_id == "T15128N" // Coaster Works
|| prod_id == "T9505N" // ESPN-NBA 2 Night
|| prod_id == "T-9703N" // NFL Blitz 2000
|| prod_id == "T-8106N" // Shadow Man
|| prod_id == "T15106N" // Slave Zero
|| prod_id == "T40402N" // Tom Clancy's Rainbow Six - Rouge Spear
|| prod_id == "T15125N" // Unreal Tournament
|| prod_id == "6107140" // Dorimaga GD Vol.1
|| prod_id == "T41202M" // Boku no tennis jinsei
|| prod_id == "T9503M" // Eisei Meijin III
|| prod_id == "T41201M" // Jet Coaster Dream
|| prod_id == "T40902M" // Jet Coaster Dream 2
|| prod_id == "T20105M" // Kanon
|| prod_id == "T19702M" // Memories Off Complete
|| prod_id == "T17001M" // Pen Pen Tri Iceron (JP)
|| prod_id == "T1301M" // Revive - Sosei
|| prod_id == "T15003M" // Seitai Heiki Expendable
|| prod_id == "T38805M" // Sengoku Turb FID
|| prod_id == "T20104M" // Sentimental Graffiti 2
|| prod_id == "T16601M" // Shin Honkaku Hanafuda
|| prod_id == "T1236M" // Super Street Fighter IIX For Matching Service
|| prod_id == "T36501M" // Vigilante 8 (JP)
|| prod_id == "T5301M" // World Neverland Plus - Olerud Kingdom Story
|| prod_id == "HDR-0198")) // World Series Baseball 2K2 (JP)
{
NOTICE_LOG(BOOT, "Game doesn't support RGB. Using TV Composite instead");
config::Cable.override(3);
@@ -274,15 +302,15 @@ static void loadSpecialSettings()
|| prod_id == "T0020M" // Force Five Atomiswave DC Conversion
|| prod_id == "HDR-0187" // Fushigi no Dungeon - Fuurai no Shiren Gaiden - Onna Kenshi Asuka Kenzan!
|| prod_id == "T15104D 50" // Slave Zero (PAL)
|| prod_id == "MK-51152") // World Series Baseball 2K2
|| prod_id == "MK-51152" // World Series Baseball 2K2
|| ip_meta.isMILCD())
{
NOTICE_LOG(BOOT, "Forcing real BIOS");
config::UseReios.override(false);
}
else if (prod_id == "T17708N" // Stupid Invaders (US)
|| prod_id == "T17711D" // Stupid Invaders (EU)
|| prod_id == "T46509M" // Suika (JP)
|| prod_id == "T36901M") // Cool Boarders Burrrn (JP)
|| prod_id == "T46509M") // Suika (JP)
{
NOTICE_LOG(BOOT, "Forcing HLE BIOS");
config::UseReios.override(true);
@@ -302,7 +330,8 @@ static void loadSpecialSettings()
|| prod_id == "T7014D 50" // Super Runabout (EU)
|| prod_id == "T10001D 50" // MTV Sport - Skateboarding (PAL)
|| prod_id == "MK-5101050" // Snow Surfers
|| prod_id == "12502D-50") // Caesar's Palace (PAL)
|| prod_id == "12502D-50" // Caesar's Palace (PAL)
|| prod_id == "T46605D 80") // Evil Twin - Cyprien's Chronicles
{
NOTICE_LOG(BOOT, "Forcing PAL broadcasting");
config::Broadcast.override(1);
@@ -385,10 +414,21 @@ static void loadSpecialSettings()
NOTICE_LOG(BOOT, "Forcing %d transparent layers", layers);
config::PerPixelLayers.override(layers);
}
if (prod_id == "HDR-0113" // Power Smash
|| prod_id == "HDR-0091") // Pro Yakyuu Team de Asobou Net!
{
NOTICE_LOG(BOOT, "Forcing DCNet use");
config::UseDCNet.override(true);
}
if ((prod_id == "HDR-0164" // Shenmue II (JP)
|| prod_id == "MK-5118450") // Shenmue II (EU)
&& !memcmp(ip_meta.disk_num, "4/4 ", 5))
config::WidescreenGameHacks.override(false);
}
else if (settings.platform.isArcade())
{
if (prod_id == "COSMIC SMASH IN JAPAN")
if (prod_id == "COSMIC SMASH IN JAPAN"
|| prod_id == "MONKEY BALL JAPAN VERSION")
{
INFO_LOG(BOOT, "Enabling translucent depth multipass for game %s", prod_id.c_str());
config::TranslucentPolygonDepthMask.override(true);
@@ -415,6 +455,9 @@ static void loadSpecialSettings()
INFO_LOG(BOOT, "Enabling Extra depth scaling for game %s", prod_id.c_str());
config::ExtraDepthScale.override(10000.f);
}
if (prod_id == "SEGA STRIKE FIGHTER IN JPN-SLAVE")
// slave 1 left channel is connected to a bass shaker, which produces an annoying buzzing sound on regular speakers
settings.aica.muteAudio = true;
}
}
@@ -542,7 +585,7 @@ int getGamePlatform(const std::string& filename)
return DC_PLATFORM_DREAMCAST; // unknown
if (extension == "zip" || extension == "7z")
return naomi_cart_GetPlatform(filename.c_str());
if (extension == "bin" || extension == "dat" || extension == "lst")
if (extension == "bin" || extension == "dat")
return DC_PLATFORM_NAOMI;
return DC_PLATFORM_DREAMCAST;
@@ -557,15 +600,16 @@ void Emulator::loadGame(const char *path, LoadProgress *progress)
if (path != nullptr && strlen(path) > 0)
{
settings.content.path = path;
if (settings.naomi.slave) {
settings.content.fileName = path;
}
else
{
try {
hostfs::FileInfo info = hostfs::storage().getFileInfo(settings.content.path);
settings.content.fileName = info.name;
if (settings.content.title.empty())
settings.content.title = get_file_basename(info.name);
} catch (const hostfs::StorageException& e) {
if (settings.naomi.slave)
settings.content.fileName = path;
else
throw;
}
}
else
@@ -631,32 +675,25 @@ void Emulator::loadGame(const char *path, LoadProgress *progress)
}
else if (settings.platform.isArcade())
{
nvmem::loadFiles();
naomi_cart_LoadRom(settings.content.path, settings.content.fileName, progress);
nvmem::loadFiles();
loadGameSpecificSettings();
// Reload the BIOS in case a game-specific region is set
naomi_cart_LoadBios(path);
}
if (!settings.naomi.slave)
{
#ifdef USE_DREAMLINK_DEVICES
// Note: ordinarily we would like to use the 'events' system for this step.
// However, this call can change 'config::MapleExpansionDevices', and therefore needs to run before 'mcfg_CreateDevices'.
reconnectDreamLinks();
#endif
mcfg_DestroyDevices();
mcfg_CreateDevices();
if (settings.platform.isNaomi())
// Must be done after the maple devices are created and EEPROM is accessible
naomi_cart_ConfigureEEPROM();
}
mcfg_DestroyDevices();
mcfg_CreateDevices();
if (settings.platform.isNaomi())
// Must be done after the maple devices are created and EEPROM is accessible
naomi_cart_ConfigureEEPROM();
#ifdef USE_RACHIEVEMENTS
// RA probably isn't expecting to travel back in the past so disable it
if (config::GGPOEnable)
config::EnableAchievements.override(false);
// Hardcore mode disables all cheats, under/overclocking, load state, lua and forces dynarec on
settings.raHardcoreMode = config::EnableAchievements && config::AchievementsHardcoreMode
&& !NaomiNetworkSupported();
&& !naomiNetworkSupported();
#endif
cheatManager.reset(settings.content.gameId);
if (cheatManager.isWidescreen())
@@ -668,21 +705,21 @@ void Emulator::loadGame(const char *path, LoadProgress *progress)
loadGameSpecificSettings();
NetworkHandshake::init();
settings.input.fastForwardMode = false;
EventManager::event(Event::Start);
if (!settings.content.path.empty())
{
#ifndef LIBRETRO
if (config::GGPOEnable)
dc_loadstate(-1);
else if (config::AutoLoadState && !NaomiNetworkSupported() && !settings.naomi.multiboard)
else if (config::AutoLoadState && !naomiNetworkSupported() && !settings.naomi.multiboard)
dc_loadstate(config::SavestateSlot);
#endif
}
EventManager::event(Event::Start);
if (progress)
{
#ifdef GDB_SERVER
if(config::GDBWaitForConnection)
if (config::GDB && config::GDBWaitForConnection)
progress->label = "Waiting for debugger...";
else
#endif
@@ -698,34 +735,40 @@ void Emulator::loadGame(const char *path, LoadProgress *progress)
void Emulator::runInternal()
{
if (singleStep)
{
getSh4Executor()->Step();
singleStep = false;
}
else if (stepRangeTo != 0)
{
while (Sh4cntx.pc >= stepRangeFrom && Sh4cntx.pc < stepRangeTo)
runner.init();
try {
if (singleStep)
{
getSh4Executor()->Step();
singleStep = false;
}
else if (stepRangeTo != 0)
{
while (Sh4cntx.pc >= stepRangeFrom && Sh4cntx.pc < stepRangeTo)
getSh4Executor()->Step();
stepRangeFrom = 0;
stepRangeTo = 0;
}
else
{
do {
resetRequested = false;
stepRangeFrom = 0;
stepRangeTo = 0;
}
else
{
do {
resetRequested = false;
getSh4Executor()->Run();
getSh4Executor()->Run();
if (resetRequested)
{
nvmem::saveFiles();
dc_reset(false);
if (!restartCpu())
resetRequested = false;
}
} while (resetRequested);
if (resetRequested)
{
nvmem::saveFiles();
dc_reset(false);
if (!restartCpu())
resetRequested = false;
}
} while (resetRequested);
}
} catch (...) {
runner.term();
throw;
}
}
@@ -738,7 +781,7 @@ void Emulator::unloadGame()
{
#ifndef LIBRETRO
if (state == Loaded && config::AutoSaveState && !settings.content.path.empty()
&& !settings.naomi.multiboard && !config::GGPOEnable && !NaomiNetworkSupported())
&& !settings.naomi.multiboard && !config::GGPOEnable && !naomiNetworkSupported())
gui_saveState(false);
#endif
try {
@@ -750,10 +793,7 @@ void Emulator::unloadGame()
mcfg_DestroyDevices(true);
config::Settings::instance().reset();
config::Settings::instance().load(false);
settings.content.path.clear();
settings.content.gameId.clear();
settings.content.fileName.clear();
settings.content.title.clear();
settings.content.reset();
settings.platform.system = DC_PLATFORM_DREAMCAST;
custom_texture.terminate();
state = Init;
@@ -857,7 +897,6 @@ void loadGameSpecificSettings()
loadSpecialSettings();
config::Settings::instance().setGameId(settings.content.gameId);
custom_texture.init();
// Reload per-game settings
config::Settings::instance().load(true);
@@ -889,11 +928,8 @@ void Emulator::stepRange(u32 from, u32 to)
void Emulator::loadstate(Deserializer& deser)
{
if (!custom_texture.preloaded())
{
custom_texture.terminate();
custom_texture.init();
}
if (config::customTexturePreloadMode() == config::CustomTexturePreloadMode::Off)
custom_texture.refresh();
#if FEAT_AREC == DYNAREC_JIT
aica::arm::recompiler::flush();
#endif
@@ -954,7 +990,6 @@ void Emulator::run()
{
verify(state == Running);
startTime = sh4_sched_now64();
renderTimeout = false;
if (!singleStep && stepRangeTo == 0)
getSh4Executor()->Start();
try {
@@ -962,7 +997,7 @@ void Emulator::run()
if (ggpo::active())
ggpo::nextFrame();
} catch (const std::exception& e) {
ERROR_LOG(COMMON, "Exception: %s\n", e.what());
ERROR_LOG(COMMON, "Exception: %s", e.what());
setNetworkState(false);
state = Error;
getSh4Executor()->Stop();
@@ -1000,7 +1035,6 @@ void Emulator::start()
while (state == Running || singleStep || stepRangeTo != 0)
{
startTime = sh4_sched_now64();
renderTimeout = false;
runInternal();
if (!ggpo::nextFrame())
break;
@@ -1066,8 +1100,7 @@ bool Emulator::render()
if (state != Running)
return false;
run();
// TODO if stopping due to a user request, no frame has been rendered
return !renderTimeout;
return true;
}
if (!checkStatus())
return false;
@@ -1079,10 +1112,10 @@ bool Emulator::render()
void Emulator::vblank()
{
EventManager::event(Event::VBlank);
runner.execTasks();
// Time out if a frame hasn't been rendered for 50 ms
if (sh4_sched_now64() - startTime <= 10000000)
if (sh4_sched_now64() - startTime <= 50_sh4ms)
return;
renderTimeout = true;
if (ggpo::active())
ggpo::endOfFrame();
else if (!config::ThreadedRendering)
@@ -1118,7 +1151,6 @@ void Emulator::diskChange()
{
config::Settings::instance().reset();
config::Settings::instance().load(false);
custom_texture.terminate();
if (!settings.content.path.empty())
{
hostfs::FileInfo info = hostfs::storage().getFileInfo(settings.content.path);
@@ -1127,8 +1159,7 @@ void Emulator::diskChange()
}
else
{
settings.content.fileName.clear();
settings.content.gameId.clear();
settings.content.reset();
settings.content.title = BIOS_TITLE;
}
cheatManager.reset(settings.content.gameId);
+10 -3
View File
@@ -20,6 +20,7 @@
*/
#pragma once
#include "types.h"
#include "stdclass.h"
#include <atomic>
#include <future>
@@ -27,7 +28,7 @@
#include <mutex>
#include <utility>
#include <vector>
#include <time.h>
#include <ctime>
void loadGameSpecificSettings();
void SaveSettings();
@@ -39,6 +40,7 @@ void dc_savestate(int index = 0, const u8 *pngData = nullptr, u32 pngSize = 0);
void dc_loadstate(int index = 0);
time_t dc_getStateCreationDate(int index);
void dc_getStateScreenshot(int index, std::vector<u8>& pngData);
bool dc_savestateAllowed();
enum class Event {
Start,
@@ -49,7 +51,8 @@ enum class Event {
VBlank,
Network,
DiskChange,
max = DiskChange
LocaleChange,
max = LocaleChange
};
class EventManager
@@ -185,6 +188,10 @@ public:
Sh4Executor *getSh4Executor();
void run(std::function<void()> func) {
runner.runOnThread(func);
}
void dc_reset(bool hard); // for tests only
private:
@@ -205,13 +212,13 @@ private:
bool resetRequested = false;
bool singleStep = false;
u64 startTime = 0;
bool renderTimeout = false;
u32 stepRangeFrom = 0;
u32 stepRangeTo = 0;
bool stopRequested = false;
std::mutex mutex;
Sh4Executor *interpreter = nullptr;
Sh4Executor *recompiler = nullptr;
ThreadRunner runner;
};
extern Emulator emu;
+11 -9
View File
@@ -402,7 +402,8 @@ static void Write_SB_ADST(u32 addr, u32 data)
SB_ADSUSP &= ~0x10;
// Schedule the end of DMA transfer interrupt
int cycles = len * (SH4_MAIN_CLOCK / 2 / G2_BUS_CLOCK); // 16 bits @ 25 MHz
// theoretical: 16 bits @ 25 MHz, actual: 11.3 MiB/s, here: 15.9 MiB/s
int cycles = len * (SH4_MAIN_CLOCK / 2 / G2_BUS_CLOCK) * 3;
if (cycles <= 512)
dma_end_sched(0, 0, 0, nullptr);
else
@@ -544,6 +545,9 @@ void serialize(Serializer& ser)
ser << aica_reg;
sgc::serialize(ser);
sh4_sched_serialize(ser, aica_schid);
sh4_sched_serialize(ser, rtc_schid);
sh4_sched_serialize(ser, dma_sched_id);
}
void deserialize(Deserializer& deser)
@@ -559,10 +563,7 @@ void deserialize(Deserializer& deser)
deser >> arm::armFiqEnable;
deser >> arm::armMode;
deser >> arm::Arm7Enabled;
if (deser.version() >= Deserializer::V19)
deser >> arm::arm7ClockTicks;
else
arm::arm7ClockTicks = 0;
deser >> arm::arm7ClockTicks;
dsp::state.deserialize(deser);
@@ -573,11 +574,7 @@ void deserialize(Deserializer& deser)
}
if (!deser.rollback())
{
aica_ram.deserialize(deser);
if (settings.platform.isAtomiswave())
deser.skip(6_MB, Deserializer::V30);
}
deser >> VREG;
deser >> ARMRST;
deser >> rtc_EN;
@@ -586,6 +583,11 @@ void deserialize(Deserializer& deser)
deser >> aica_reg;
sgc::deserialize(deser);
if (deser.version() >= Deserializer::V62) {
sh4_sched_deserialize(deser, aica_schid);
sh4_sched_deserialize(deser, rtc_schid);
sh4_sched_deserialize(deser, dma_sched_id);
}
}
} // namespace aica
+3
View File
@@ -6,6 +6,9 @@ namespace aica
{
extern RamRegion aica_ram;
extern int aica_schid;
extern int rtc_schid;
extern int dma_sched_id;
u32 GetRTC_now();
template<typename T> T readRtcReg(u32 addr);
template<typename T> void writeRtcReg(u32 addr, T data);
-4
View File
@@ -41,16 +41,12 @@ struct DSPState
void deserialize(Deserializer& deser)
{
deser.skip(4096 * 8, Deserializer::V18); // DynCode
deser >> TEMP;
deser >> MEMS;
deser >> MIXS;
deser >> RBP;
deser >> RBL;
deser.skip(44, Deserializer::V18);
deser >> MDEC_CT;
deser.skip(33596 - 4096 * 8 - sizeof(TEMP) - sizeof(MEMS) - sizeof(MIXS) - 4 * 3 - 44,
Deserializer::V18); // other dsp stuff
if (!deser.rollback())
dirty = true;
}
+8 -1
View File
@@ -26,6 +26,7 @@
#include "aica.h"
#include "aica_if.h"
#include "oslib/virtmem.h"
#include "stdclass.h"
namespace aica::dsp
{
@@ -403,7 +404,13 @@ void recompile()
{
virtmem::jit_set_exec(pCodeBuffer, CodeBufferSize, false);
X64DSPAssembler assembler(pCodeBuffer, CodeBufferSize);
assembler.Compile(&state);
try {
assembler.Compile(&state);
} catch (const Xbyak::Error& e) {
virtmem::jit_set_exec(pCodeBuffer, CodeBufferSize, true);
ERROR_LOG(AICA_ARM, "Xbyak error: %s", e.what());
throw FlycastException(strprintf("DSP recompilation error: %s", e.what()));
}
virtmem::jit_set_exec(pCodeBuffer, CodeBufferSize, true);
}
+14 -6
View File
@@ -26,14 +26,15 @@
#include "aica.h"
#include "aica_if.h"
#include "oslib/virtmem.h"
#include "stdclass.h"
namespace aica
{
namespace dsp
{
DECLARE_CODE_CACHE(CodeBuffer, 32_KB)
constexpr size_t CodeBufferSize = 32_KB;
DECLARE_CODE_CACHE(CodeBuffer, CodeBufferSize)
static u8 *pCodeBuffer;
class X86DSPAssembler : public Xbyak::CodeGenerator
@@ -367,24 +368,31 @@ private:
void recompile()
{
X86DSPAssembler assembler(pCodeBuffer, sizeof(CodeBuffer));
assembler.Compile(&state);
X86DSPAssembler assembler(pCodeBuffer, CodeBufferSize);
try {
assembler.Compile(&state);
} catch (const Xbyak::Error& e) {
ERROR_LOG(AICA_ARM, "Xbyak error: %s", e.what());
throw FlycastException(strprintf("DSP recompilation error: %s", e.what()));
}
}
void recInit()
{
if (!virtmem::prepare_jit_block(CodeBuffer, sizeof(CodeBuffer), (void**)&pCodeBuffer))
if (!virtmem::prepare_jit_block(CodeBuffer, CodeBufferSize, (void**)&pCodeBuffer))
die("mprotect failed in x86 dsp");
}
void recTerm()
{
if (pCodeBuffer != nullptr && pCodeBuffer != CodeBuffer)
virtmem::release_jit_block(pCodeBuffer, CodeBufferSize);
pCodeBuffer = nullptr;
}
void runStep()
{
((void (*)())&CodeBuffer[0])();
((void (*)())&pCodeBuffer[0])();
}
} // namespace dsp
+58 -73
View File
@@ -95,15 +95,14 @@ static const char* stream_names[]=
};
//x.8 format
static const s32 adpcm_qs[8] =
static constexpr s32 adpcm_qs[8] =
{
0x0e6, 0x0e6, 0x0e6, 0x0e6, 0x133, 0x199, 0x200, 0x266,
};
//x.3 format
static const s32 adpcm_scale[16] =
static constexpr s32 adpcm_scale[8] =
{
1,3,5,7,9,11,13,15,
-1,-3,-5,-7,-9,-11,-13,-15,
1, 3, 5, 7, 9, 11, 13, 15,
};
static const s32 qtable[32] = {
@@ -203,12 +202,9 @@ public:
{
if (deser.version() < Deserializer::V49)
{
if (deser.version() >= Deserializer::V22)
{
deser.skip<int>(); // beepOn
deser.skip<int>(); // beepPeriod
deser.skip<int>(); // beepCounter
}
deser.skip<int>(); // beepOn
deser.skip<int>(); // beepPeriod
deser.skip<int>(); // beepCounter
init();
}
else
@@ -420,7 +416,7 @@ struct ChannelEx
ChannelCommonData* ccd;
const u8 *SA;
u32 SA;
u32 CA;
fp_22_10 step;
u32 update_rate;
@@ -431,6 +427,7 @@ struct ChannelEx
{
u32 LSA;
u32 LEA;
u32 alignedLEA;
u8 looped;
} loop;
@@ -766,7 +763,7 @@ struct ChannelEx
ChannelNumber, stream_names[ccd->PCMS], (44100.0 * update_rate) / 1024, ccd->LPCTL,
ccd->AR, ccd->D1R, ccd->DL << 5, ccd->D2R, ccd->RR,
ccd->KRS, ccd->OCT, ccd->FNS,
(int)(SA - &aica_ram[0]), ccd->LSA, ccd->LEA);
SA, ccd->LSA, ccd->LEA);
}
void KEY_OFF()
@@ -791,17 +788,17 @@ struct ChannelEx
//SA,PCMS
void UpdateSA()
{
u32 addr = (ccd->SA_hi << 16) | ccd->SA_low;
SA = (ccd->SA_hi << 16) | ccd->SA_low;
if (ccd->PCMS == PCM16)
addr &= ~1; //0: 16 bit
SA = &aica_ram[addr & ARAM_MASK];
SA &= ~1; //0: 16 bit
}
//LSA,LEA
void UpdateLoop()
{
loop.LSA = ccd->LSA;
loop.LEA = ccd->LEA;
// ADPCM long stream needs 4-sample alignment for LEA
loop.alignedLEA = (loop.LEA + 3) & ~3;
}
u32 EG_EffRate(u32 rate)
@@ -1056,15 +1053,21 @@ static SampleType DecodeADPCM(u32 sample, s32 prev, s32& quant)
return std::clamp(rv, -32768, 32767);
}
template<typename T>
static T readAicaRam(u32 addr) {
return *(const T *)&aica_ram[addr & ARAM_MASK & ~(sizeof(T) - 1)];
}
template<PCMSType PCMS, bool Last>
void ChannelEx::StepDecodeSample(u32 CA)
{
// Only ADPCM needs all intermediate samples to be decoded
if constexpr (!Last && (PCMS == PCM16 || PCMS == PCM8 || PCMS == NOISE))
return;
// TODO bound checking of sample addresses
u32 next_addr = CA + 1;
if (next_addr >= loop.LEA && loop.LEA > loop.LSA)
const u32 lea = PCMS == ADPCM_STREAM ? loop.alignedLEA : loop.LEA;
if (next_addr >= lea && lea > loop.LSA)
next_addr = loop.LSA;
SampleType s0, s1;
@@ -1074,42 +1077,30 @@ void ChannelEx::StepDecodeSample(u32 CA)
noise_state = noise_state * RAND_FACTOR + RAND_TERM;
s0 = noise_state;
s0 >>= 16;
s1 = noise_state * RAND_FACTOR + RAND_TERM;
noise_state = noise_state * RAND_FACTOR + RAND_TERM;
s1 = noise_state;
s1 >>= 16;
break;
case PCM16:
{
const s16 *sptr16 = (const s16 *)SA;
s0 = sptr16[CA];
s1 = sptr16[next_addr];
break;
}
s0 = readAicaRam<s16>(SA + CA * 2);
s1 = readAicaRam<s16>(SA + next_addr * 2);
break;
case PCM8:
{
const s8 *sptr8 = (const s8 *)SA;
s0 = sptr8[CA] << 8;
s1 = sptr8[next_addr] << 8;
break;
}
s0 = readAicaRam<s8>(SA + CA) << 8;
s1 = readAicaRam<s8>(SA + next_addr) << 8;
break;
case ADPCM:
case ADPCM_STREAM:
{
const u8 *uptr8 = (const u8 *)SA;
u8 ad1 = uptr8[CA >> 1];
u8 ad2 = uptr8[next_addr >> 1];
u8 ad1 = readAicaRam<u8>(SA + (CA >> 1));
ad1 >>= (CA & 1) * 4;
ad2 >>= (next_addr & 1) * 4;
ad1 &= 0xF;
ad2 &= 0xF;
s32 q = adpcm.last_quant;
if (PCMS == ADPCM && CA == loop.LSA)
if (CA == loop.LSA)
{
if (!adpcm.in_loop)
{
@@ -1117,7 +1108,7 @@ void ChannelEx::StepDecodeSample(u32 CA)
adpcm.loopstart_quant = q;
adpcm.loopstart_prev_sample = this->s0;
}
else
else if (PCMS == ADPCM)
{
q = adpcm.loopstart_quant;
this->s0 = adpcm.loopstart_prev_sample;
@@ -1127,6 +1118,9 @@ void ChannelEx::StepDecodeSample(u32 CA)
adpcm.last_quant = q;
if constexpr (Last)
{
u8 ad2 = readAicaRam<u8>(SA + (next_addr >> 1));
ad2 >>= (next_addr & 1) * 4;
ad2 &= 0xF;
SampleType prev = s0;
if (PCMS == ADPCM && next_addr == loop.LSA && adpcm.in_loop)
{
@@ -1158,11 +1152,7 @@ void ChannelEx::StreamStep()
sp.ip--;
u32 CA = this->CA + 1;
u32 ca_t = CA;
if constexpr (PCMS == ADPCM_STREAM)
ca_t &= ~3; // in adpcm "stream" mode, LEA and LSA are supposed to be 4-sample aligned
// but some games don't respect this rule
const u32 lea = PCMS == ADPCM_STREAM ? loop.alignedLEA : loop.LEA;
if constexpr (LPSLNK)
{
@@ -1173,13 +1163,13 @@ void ChannelEx::StreamStep()
}
}
if (ca_t >= loop.LEA)
if (CA >= lea)
{
if (loop.LSA > loop.LEA)
if (loop.LSA > lea)
{
// When LSA > LEA, aica won't stop when reaching LEA but will continue until LSA.
// It will then reset CA to 0 and stop playing, even for looping sounds.
if (ca_t >= loop.LSA)
if (CA >= loop.LSA)
{
loop.looped = 1;
CA = 0;
@@ -1515,28 +1505,31 @@ void ReadCommonReg(u32 reg,bool byte)
case 0x2810: // EG, SGC, LP
case 0x2811:
{
u32 chan=CommonData->MSLC;
ChannelEx& channel = Chans[CommonData->MSLC];
CommonData->LP=Chans[chan].loop.looped;
CommonData->LP = channel.loop.looped;
if (CommonData->AFSEL == 1)
WARN_LOG(AICA, "FEG monitor (AFSEL=1) not supported");
s32 aeg = Chans[chan].AEG.GetValue();
s32 aeg = channel.AEG.GetValue();
if (aeg > 0x3BF)
CommonData->EG = 0x1FFF;
else
CommonData->EG = aeg; //AEG is only 10 bits, FEG is 13 bits
CommonData->SGC=Chans[chan].AEG.state;
CommonData->SGC = channel.AEG.state;
if (!byte || reg == 0x2811)
Chans[chan].loop.looped = 0;
channel.loop.looped = 0;
}
break;
case 0x2814: //CA
case 0x2815: //CA
{
u32 chan=CommonData->MSLC;
CommonData->CA = Chans[chan].CA;
//printf("[%d] CA read %d\n",chan,Chans[chan].CA);
const ChannelEx& channel = Chans[CommonData->MSLC];
CommonData->CA = channel.CA;
// This helps VC 2K sport games, which don't detect the end of a sound when CA is too close to LEA.
// Not sure how real hw works but simple tests show it's difficult to get any CA higher than LEA-60 if not worse.
if (channel.loop.LEA > channel.loop.LSA && channel.loop.LEA > 3)
CommonData->CA = std::min(CommonData->CA, channel.loop.LEA - 3);
}
break;
}
@@ -1639,9 +1632,7 @@ void serialize(Serializer& ser)
{
for (const ChannelEx& channel : Chans)
{
u32 addr = channel.SA - &aica_ram[0];
ser << addr;
ser << channel.SA;
ser << channel.CA;
ser << channel.step;
ser << channel.s0;
@@ -1678,10 +1669,7 @@ void deserialize(Deserializer& deser)
for (ChannelEx& channel : Chans)
{
channel.quiet = true;
u32 addr;
deser >> addr;
channel.SA = addr + &aica_ram[0];
deser >> channel.SA;
deser >> channel.CA;
deser >> channel.step;
channel.UpdatePitch();
@@ -1728,16 +1716,13 @@ void deserialize(Deserializer& deser)
deser >> cdda_sector;
deser >> cdda_index;
midiSendBuffer.clear();
if (deser.version() >= Deserializer::V28)
u32 size;
deser >> size;
for (u32 i = 0; i < size; i++)
{
u32 size;
deser >> size;
for (u32 i = 0; i < size; i++)
{
u8 b;
deser >> b;
midiSendBuffer.push_back(b);
}
u8 b;
deser >> b;
midiSendBuffer.push_back(b);
}
}
+21 -7
View File
@@ -382,13 +382,22 @@ void DYNACALL interpret(u32 opcode)
}
template<u32 Pd>
void DYNACALL MSR_do(u32 v)
void DYNACALL MSR_do(u32 v, u32 mask)
{
if (Pd)
{
if(armMode > 0x10 && armMode < 0x1f) /* !=0x10 ?*/
{
reg[RN_SPSR].I = (reg[RN_SPSR].I & 0x00FFFF00) | (v & 0xFF0000FF);
u32 newValue = reg[RN_SPSR].I;
if (mask & 1)
newValue = (newValue & 0xFFFFFF00) | (v & 0x000000FF);
if (mask & 2)
newValue = (newValue & 0xFFFF00FF) | (v & 0x0000FF00);
if (mask & 4)
newValue = (newValue & 0xFF00FFFF) | (v & 0x00FF0000);
if (mask & 8)
newValue = (newValue & 0x00FFFFFF) | (v & 0xFF000000);
reg[RN_SPSR].I = newValue;
}
}
else
@@ -398,10 +407,15 @@ void DYNACALL MSR_do(u32 v)
u32 newValue = reg[RN_CPSR].I;
if(armMode > 0x10)
{
newValue = (newValue & 0xFFFFFF00) | (v & 0x000000FF);
if (mask & 1)
newValue = (newValue & 0xFFFFFF00) | (v & 0x000000FF);
if (mask & 2)
newValue = (newValue & 0xFFFF00FF) | (v & 0x0000FF00);
if (mask & 4)
newValue = (newValue & 0xFF00FFFF) | (v & 0x00FF0000);
}
newValue = (newValue & 0x00FFFFFF) | (v & 0xFF000000);
if (mask & 8)
newValue = (newValue & 0x00FFFFFF) | (v & 0xFF000000);
newValue |= 0x10;
if(armMode > 0x10)
{
@@ -411,8 +425,8 @@ void DYNACALL MSR_do(u32 v)
CPUUpdateFlags();
}
}
template void DYNACALL MSR_do<0>(u32 v);
template void DYNACALL MSR_do<1>(u32 v);
template void DYNACALL MSR_do<0>(u32 v, u32 mask);
template void DYNACALL MSR_do<1>(u32 v, u32 mask);
} // namespace recompiler
#endif // FEAT_AREC != DYNAREC_NONE
+4 -2
View File
@@ -164,15 +164,17 @@ static ArmOp decodeArmOp(u32 opcode, u32 arm_pc)
op.rd = ArmOp::Operand((Arm7Reg)bits.rd);
verify(bits.rd != 15);
}
else if ((bits.full & 0x0FBFFFF0) == 0x0129F000)
else if ((bits.full & 0x0FB0FFF0) == 0x0120F000)
{
op.op_type = ArmOp::MSR;
op.psrMask = (bits.full >> 16) & 0xF;
op.arg[0] = ArmOp::Operand((Arm7Reg)bits.rm);
op.cycles++;
}
else if ((bits.full & 0x0DBFF000) == 0x0128F000)
else if ((bits.full & 0x0DB0F000) == 0x0120F000)
{
op.op_type = ArmOp::MSR;
op.psrMask = (bits.full >> 16) & 0xF;
if (bits.imm_op == 0)
{
// source is reg
+3 -1
View File
@@ -101,6 +101,8 @@ struct ArmOp
u8 flags = 0;
u8 cycles = 6;
bool spsr = false;
// For MSR
u8 psrMask = 0;
bool isLogicalOp() const
{
@@ -428,7 +430,7 @@ void term();
void flush();
void compile();
void *getMemOp(bool load, bool byte);
template<u32 Pd> void DYNACALL MSR_do(u32 v);
template<u32 Pd> void DYNACALL MSR_do(u32 v, u32 mask);
void DYNACALL interpret(u32 opcode);
extern u8* icPtr;
+1
View File
@@ -408,6 +408,7 @@ static void emitMSR(const ArmOp& op)
ass.Mov(r0, op.arg[0].getImmediate());
else
ass.Mov(r0, regalloc->map(op.arg[0].getReg().armreg));
ass.Mov(r1, op.psrMask);
if (op.spsr)
call((void *)recompiler::MSR_do<1>);
+14 -23
View File
@@ -427,32 +427,11 @@ class Arm7Compiler : public MacroAssembler
Add(rd, w0, op2);
break;
case 8: // TST
#ifdef STRICT_MODE
// In armv7, TST and TEQ do not affect the V flag.
// This isn't the case in armv8 so we need to save
// and restore it.
// Since this is a bit complicated/slow, let's assume nobody
// relies on this.
Cset(w3, vs);
#endif
Tst(rn, op2);
#ifdef STRICT_MODE
Mrs(x0, NZCV);
Bfi(x0, x3, 28, 1); // V is bit 28
Msr(NZCV, x0);
#endif
break;
case 9: // TEQ
Eor(w0, rn, op2);
#ifdef STRICT_MODE
Cset(w3, vs);
#endif
Tst(w0, w0);
#ifdef STRICT_MODE
Mrs(x0, NZCV);
Bfi(x0, x3, 28, 1); // V is bit 28
Msr(NZCV, x0);
#endif
break;
case 10: // CMP
Cmp(rn, op2);
@@ -474,10 +453,21 @@ class Arm7Compiler : public MacroAssembler
die("invalid op");
break;
}
if (set_carry_bit)
if (set_carry_bit || logical_op_set_flags)
{
Mrs(x0, NZCV);
Bfi(x0, x14, 29, 1); // C is bit 29 in NZCV
if (set_carry_bit)
Bfi(x0, x14, 29, 1); // C is bit 29 in NZCV
if (logical_op_set_flags)
{
// ARMv7 logical ops (AND, EOR, ORR, BIC, MOV, MVN, TST, TEQ) leave V alone.
// The AArch64 flag-setting equivalents (ANDS, BICS, TST) clear it, so put
// back the V the op started with. loadFlags() ran before the op, so the
// saved PSR still holds it.
Ldr(w3, arm_reg_operand(RN_PSR_FLAGS));
Ubfx(w3, w3, 28, 1); // w3 = V
Bfi(x0, x3, 28, 1); // V is bit 28 in NZCV
}
Msr(NZCV, x0);
}
}
@@ -551,6 +541,7 @@ class Arm7Compiler : public MacroAssembler
Mov(w0, op.arg[0].getImmediate());
else
Mov(w0, regalloc->map(op.arg[0].getReg().armreg));
Mov(w1, op.psrMask);
if (op.spsr)
call((void*)recompiler::MSR_do<1>);
else
+18 -6
View File
@@ -28,6 +28,7 @@ using namespace Xbyak::util;
#include "arm7_rec.h"
#include "oslib/unwind_info.h"
#include "oslib/virtmem.h"
#include "stdclass.h"
namespace aica::arm
{
@@ -45,11 +46,11 @@ static UnwindInfo unwinder;
class Arm7Compiler;
#ifdef _WIN32
static const std::array<Xbyak::Reg32, 8> alloc_regs {
static constexpr std::array<Xbyak::Reg32, 8> alloc_regs {
ebx, ebp, edi, esi, r12d, r13d, r14d, r15d
};
#else
static const std::array<Xbyak::Reg32, 6> alloc_regs {
static constexpr std::array<Xbyak::Reg32, 6> alloc_regs {
ebx, ebp, r12d, r13d, r14d, r15d
};
#endif
@@ -780,6 +781,7 @@ class Arm7Compiler : public Xbyak::CodeGenerator
mov(call_regs[0], op.arg[0].getImmediate());
else
mov(call_regs[0], regalloc->map(op.arg[0].getReg().armreg));
mov(call_regs[1], op.psrMask);
if (op.spsr)
call(recompiler::MSR_do<1>);
else
@@ -1002,8 +1004,13 @@ void arm7backend_compile(const std::vector<ArmOp>& block_ops, u32 cycles)
virtmem::jit_set_exec(protStart, protSize, false);
Arm7Compiler assembler;
assembler.compile(block_ops, cycles);
try {
assembler.compile(block_ops, cycles);
} catch (const Xbyak::Error& e) {
virtmem::jit_set_exec(protStart, protSize, true);
ERROR_LOG(AICA_ARM, "Xbyak error: %s", e.what());
throw FlycastException(strprintf("ARM recompilation error: %s", e.what()));
}
virtmem::jit_set_exec(protStart, protSize, true);
}
@@ -1015,8 +1022,13 @@ void arm7backend_flush()
unwinder.clear();
Arm7Compiler assembler;
assembler.generateMainLoop();
try {
assembler.generateMainLoop();
} catch (const Xbyak::Error& e) {
virtmem::jit_set_exec(protStart, protSize, true);
ERROR_LOG(AICA_ARM, "Xbyak error: %s", e.what());
throw FlycastException(strprintf("ARM recompilation error: %s", e.what()));
}
virtmem::jit_set_exec(protStart, protSize, true);
}
+28 -11
View File
@@ -21,6 +21,8 @@
#include "hw/holly/holly_intc.h"
#include "network/netservice.h"
#include "serialize.h"
#include "hw/sh4/sh4_sched.h"
#include "cfg/option.h"
static RTL8139State *rtl8139device;
@@ -49,6 +51,24 @@ static u8 GAPS_ram[GAPSPCI_RAM_SIZE];
static u8 GAPS_regs[GAPSPCI_REGS_SIZE];
static u32 dmaOffset;
static bool interruptPending;
static constexpr u32 MAX_ETH_FRAME_SIZE = 1514;
static int rxSchedId = -1;
constexpr int RX_POLL_CYCLES = 5_sh4ms;
static int rxDeliver(int tag, int cycles, int jitter, void *arg)
{
u8 frame[MAX_ETH_FRAME_SIZE];
while (rtl8139_can_receive(rtl8139device))
{
const u32 size = net::modbba::sendEthFrame(frame);
if (size == 0)
break;
if (size < 6 || size > MAX_ETH_FRAME_SIZE)
continue;
rtl8139_receive(rtl8139device, frame, size);
}
return RX_POLL_CYCLES;
}
static void setInterrupt()
{
@@ -64,7 +84,7 @@ void pci_set_irq(PCIDevice *pci_dev, int level)
setInterrupt();
}
void bba_Init()
static void bba_Init()
{
NICConf nicConf = { 0xc, 0xa, 0xf, 0xe, 0, 0 };
rtl8139device = rtl8139_init(&nicConf);
@@ -73,6 +93,9 @@ void bba_Init()
memset(&GAPS_regs[0], 0, sizeof(GAPS_regs));
memcpy(&GAPS_regs[0], "GAPSPCI_BRIDGE_2", 0x10);
memcpy(&GAPS_regs[0x1c], "SEGA", 4);
rxSchedId = sh4_sched_register(0, rxDeliver);
sh4_sched_request(rxSchedId, RX_POLL_CYCLES);
}
void bba_Term()
@@ -83,6 +106,8 @@ void bba_Term()
rtl8139_destroy(rtl8139device);
rtl8139device = nullptr;
}
sh4_sched_unregister(rxSchedId);
rxSchedId = -1;
}
void bba_Reset(bool hard)
@@ -90,7 +115,8 @@ void bba_Reset(bool hard)
if (hard)
{
bba_Term();
bba_Init();
if (config::EmulateBBA && (settings.platform.isConsole() || settings.platform.isAtomiswave()))
bba_Init();
}
}
@@ -213,15 +239,6 @@ ssize_t qemu_send_packet(RTL8139State *s, const uint8_t *buf, int size)
return size;
}
int bba_recv_frame(const u8 *data, u32 len)
{
if (!rtl8139_can_receive(rtl8139device))
return 0;
rtl8139_receive(rtl8139device, data, len);
return 1;
}
void pci_dma_read(PCIDevice *dev, dma_addr_t addr, void *buf, dma_addr_t len)
{
memcpy(buf, &GAPS_ram[addr & GAPSPCI_RAM_MASK], len);
-2
View File
@@ -19,11 +19,9 @@
#pragma once
#include "types.h"
void bba_Init();
void bba_Term();
void bba_Reset(bool hard);
u32 bba_ReadMem(u32 addr, u32 sz);
void bba_WriteMem(u32 addr, u32 data, u32 sz);
void bba_Serialize(Serializer& ser);
void bba_Deserialize(Deserializer& deser);
int bba_recv_frame(const u8 *data, u32 len);
+20 -2
View File
@@ -2411,6 +2411,25 @@ void rtl8139_serialize(RTL8139State *s, Serializer& ser)
bool rtl8139_deserialize(RTL8139State *s, Deserializer& deser)
{
if (s == nullptr)
{
deser.skip(256 * 3 + sizeof(PCIDevice::io_regions)
+ 8 + 8 + 4 * 4 + 4 * 4
+ 4 + 4 + 4 + 4
+ 2 + 2
+ 4 + 4 + 4 + 2
+ 6
+ 2
+ 2
+ 5 * 2
+ sizeof(MACAddr)
+ 4
+ sizeof(EEprom9346::contents) + sizeof(EEprom9346::mode) + sizeof(EEprom9346::tick)
+ sizeof(EEprom9346::address) + sizeof(EEprom9346::input) + sizeof(EEprom9346::output) + 4
+ 4 + 4 + 8
+ 12);
return false;
}
deser.deserialize(s->parent_obj.config, 256);
deser.deserialize(s->parent_obj.cmask, 256);
deser.deserialize(s->parent_obj.wmask, 256);
@@ -2472,8 +2491,7 @@ bool rtl8139_deserialize(RTL8139State *s, Deserializer& deser)
deser >> s->TimerInt;
deser >> s->TCTR_base;
if (deser.version() >= Deserializer::V38)
sh4_sched_deserialize(deser, s->schedId);
sh4_sched_deserialize(deser, s->schedId);
return s->bChipCmdState & CmdRxEnb;
}
+6 -14
View File
@@ -18,14 +18,15 @@
#include "oslib/oslib.h"
#include "stdclass.h"
#include "oslib/storage.h"
#include "oslib/directory.h"
bool MemChip::Load(const std::string& file)
{
FILE *f = hostfs::storage().openFile(file, "rb");
hostfs::File *f = hostfs::storage().openFile(file, "rb");
if (f)
{
bool rv = std::fread(data + write_protect_size, 1, size - write_protect_size, f) == size - write_protect_size;
std::fclose(f);
bool rv = f->read(data + write_protect_size, 1, size - write_protect_size) == size - write_protect_size;
delete f;
if (rv)
this->load_filename = file;
@@ -40,17 +41,8 @@ void MemChip::Load(const u8 *data, size_t size)
memcpy(this->data + write_protect_size, data, this->size - write_protect_size);
}
void WritableChip::Save(const std::string& file)
{
FILE *f = nowide::fopen(file.c_str(), "wb");
if (f == nullptr)
{
ERROR_LOG(FLASHROM, "Cannot save flash/nvmem to file '%s'", file.c_str());
return;
}
if (std::fwrite(data + write_protect_size, 1, size - write_protect_size, f) != size - write_protect_size)
ERROR_LOG(FLASHROM, "Failed or truncated write to flash file '%s'", file.c_str());
std::fclose(f);
void WritableChip::Save(const std::string& file) {
safeFullSave(file, data + write_protect_size, size - write_protect_size);
}
bool MemChip::Load(const std::string &prefix, const std::string &names_ro, const std::string &title)
-8
View File
@@ -61,7 +61,6 @@ public:
}
bool Load(const std::string& file);
virtual bool Reload() { return true; }
bool Load(const std::string &prefix, const std::string &names_ro,
const std::string &title);
void Load(const u8 *data, size_t size);
@@ -80,13 +79,6 @@ protected:
public:
virtual void Write(u32 addr, u32 data, u32 size) = 0;
bool Reload() override
{
if (load_filename.empty())
return false;
return Load(this->load_filename);
}
void Save(const std::string& file);
void Save(const std::string &prefix, const std::string &name_ro,
const std::string &title);
+109 -101
View File
@@ -135,11 +135,14 @@ static void add_isp_to_nvmem(DCFlashChip *flash, const char *username)
if (flash->WriteBlock(FLASH_PT_USER, FLASH_USER_ISP2, &isp2) != 1)
WARN_LOG(FLASHROM, "Failed to save ISP2 information to flash RAM");
}
if (!flash->ReadBlock(FLASH_PT_USER, FLASH_USER_BBA, block) || memcmp(&block[2], "SEGA", 4))
flash_bba_block bba{};
if (!flash->ReadBlock(FLASH_PT_USER, FLASH_USER_BBA, &bba)
|| memcmp(bba.sega, "SEGA", 4)
|| bba.netmask == 0)
{
flash_bba_block bba{};
memcpy(bba.sega, "SEGA", 4);
// Leave everything at 0 to use DHCP
bba.netmask = 0xffffff00;
// Leave everything else at 0 to use DHCP
if (flash->WriteBlock(FLASH_PT_USER, FLASH_USER_BBA, &bba) != 1)
WARN_LOG(FLASHROM, "Failed to save BBA information to flash RAM");
}
@@ -147,81 +150,78 @@ static void add_isp_to_nvmem(DCFlashChip *flash, const char *username)
static void fixUpDCFlash()
{
if (settings.platform.isConsole())
static_cast<DCFlashChip*>(sys_nvmem)->Validate();
// overwrite factory flash settings
if (config::Region <= 2)
{
static_cast<DCFlashChip*>(sys_nvmem)->Validate();
// overwrite factory flash settings
if (config::Region <= 2)
{
sys_nvmem->data[0x1a002] = '0' + config::Region;
sys_nvmem->data[0x1a0a2] = '0' + config::Region;
}
if (config::Language <= 5)
{
sys_nvmem->data[0x1a003] = '0' + config::Language;
sys_nvmem->data[0x1a0a3] = '0' + config::Language;
}
if (config::Broadcast <= 3)
{
sys_nvmem->data[0x1a004] = '0' + config::Broadcast;
sys_nvmem->data[0x1a0a4] = '0' + config::Broadcast;
}
// overwrite user settings
struct flash_syscfg_block syscfg;
int res = static_cast<DCFlashChip*>(sys_nvmem)->ReadBlock(FLASH_PT_USER, FLASH_USER_SYSCFG, &syscfg);
if (!res)
{
// write out default settings
memset(&syscfg, 0xff, sizeof(syscfg));
syscfg.time_lo = 0;
syscfg.time_hi = 0;
syscfg.time_zone = 0;
syscfg.lang = 0;
syscfg.mono = 0;
syscfg.autostart = 1;
}
u32 now = aica::GetRTC_now();
syscfg.time_lo = now & 0xffff;
syscfg.time_hi = now >> 16;
if (config::Language <= 5)
syscfg.lang = config::Language;
if (static_cast<DCFlashChip*>(sys_nvmem)->WriteBlock(FLASH_PT_USER, FLASH_USER_SYSCFG, &syscfg) != 1)
WARN_LOG(FLASHROM, "Failed to save time and language to flash RAM");
add_isp_to_nvmem(static_cast<DCFlashChip*>(sys_nvmem), config::ISPUsername.get().c_str());
// Check the console ID used by some network games (chuchu rocket)
u8 *console_id = &sys_nvmem->data[0x1A058];
if (!memcmp(console_id, "\377\377\377\377\377\377", 6))
{
srand(now);
u8 sum = 0;
for (int i = 0; i < 6; i++)
{
console_id[i] = rand();
console_id[i + 0xA0] = console_id[i]; // copy at 1A0F8
sum += console_id[i];
}
console_id[-1] = console_id[0xA0 - 1] = sum;
console_id[-2] = console_id[0xA0 - 2] = ~sum;
}
else
{
// Fix checksum
u8 sum = 0;
for (int i = 0; i < 6; i++)
sum += console_id[i];
console_id[-1] = console_id[0xA0 - 1] = sum;
console_id[-2] = console_id[0xA0 - 2] = ~sum;
}
// machine_version: ff - VA0, fe - VA1, fd - VA2
// must be != 0xff
console_id[7] = console_id[0xA0 + 7] = 0xfe;
sys_nvmem->data[0x1a002] = '0' + config::Region;
sys_nvmem->data[0x1a0a2] = '0' + config::Region;
}
if (config::Language <= 5)
{
sys_nvmem->data[0x1a003] = '0' + config::Language;
sys_nvmem->data[0x1a0a3] = '0' + config::Language;
}
if (config::Broadcast <= 3)
{
sys_nvmem->data[0x1a004] = '0' + config::Broadcast;
sys_nvmem->data[0x1a0a4] = '0' + config::Broadcast;
}
// overwrite user settings
struct flash_syscfg_block syscfg;
int res = static_cast<DCFlashChip*>(sys_nvmem)->ReadBlock(FLASH_PT_USER, FLASH_USER_SYSCFG, &syscfg);
if (!res)
{
// write out default settings
memset(&syscfg, 0xff, sizeof(syscfg));
syscfg.time_lo = 0;
syscfg.time_hi = 0;
syscfg.time_zone = 0;
syscfg.lang = 0;
syscfg.mono = 0;
syscfg.autostart = 1;
}
u32 now = aica::GetRTC_now();
syscfg.time_lo = now & 0xffff;
syscfg.time_hi = now >> 16;
if (config::Language <= 5)
syscfg.lang = config::Language;
if (static_cast<DCFlashChip*>(sys_nvmem)->WriteBlock(FLASH_PT_USER, FLASH_USER_SYSCFG, &syscfg) != 1)
WARN_LOG(FLASHROM, "Failed to save time and language to flash RAM");
add_isp_to_nvmem(static_cast<DCFlashChip*>(sys_nvmem), config::ISPUsername.get().c_str());
// Check the console ID used by some network games (chuchu rocket)
u8 *console_id = &sys_nvmem->data[0x1A058];
if (!memcmp(console_id, "\377\377\377\377\377\377", 6))
{
srand(now);
u8 sum = 0;
for (int i = 0; i < 6; i++)
{
console_id[i] = rand();
console_id[i + 0xA0] = console_id[i]; // copy at 1A0F8
sum += console_id[i];
}
console_id[-1] = console_id[0xA0 - 1] = sum;
console_id[-2] = console_id[0xA0 - 2] = ~sum;
}
else
{
// Fix checksum
u8 sum = 0;
for (int i = 0; i < 6; i++)
sum += console_id[i];
console_id[-1] = console_id[0xA0 - 1] = sum;
console_id[-2] = console_id[0xA0 - 2] = ~sum;
}
// machine_version: ff - VA0, fe - VA1, fd - VA2
// must be != 0xff
console_id[7] = console_id[0xA0 + 7] = 0xfe;
}
static std::unique_ptr<u8[]> loadFlashResource(const std::string& name, size_t& size)
@@ -239,14 +239,27 @@ static void loadDefaultAWBiosFlash()
sys_rom->Load(buffer.get(), size);
}
static std::string getArcadeFlashPath()
{
std::string suffix;
if (settings.content.gameId.substr(0, 6) == " DERBY" && config::MultiboardSlaves >= 2)
suffix = "-main.nvmem";
else
suffix = ".nvmem";
return hostfs::getArcadeFlashPath() + suffix;
}
static bool loadFlash()
{
bool rc = true;
if (settings.platform.isConsole())
if (settings.platform.isConsole()) {
rc = sys_nvmem->Load(getRomPrefix(), "%nvmem.bin", "nvram");
fixUpDCFlash();
}
else if (!settings.naomi.slave)
{
rc = sys_nvmem->Load(hostfs::getArcadeFlashPath() + ".nvmem");
const std::string path = getArcadeFlashPath();
rc = sys_nvmem->Load(path);
if (!rc)
{
std::string flashName = get_file_basename(settings.content.fileName) + ".nvmem";
@@ -256,13 +269,16 @@ static bool loadFlash()
if (buffer)
{
sys_nvmem->Load(buffer.get(), size);
INFO_LOG(FLASHROM, "flash/nvmem loaded from resource %s", flashName.c_str());
rc = true;
}
}
else {
INFO_LOG(FLASHROM, "flash/nvmem loaded from file %s", path.c_str());
}
}
if (!rc)
INFO_LOG(FLASHROM, "flash/nvmem is missing, will create new file...");
fixUpDCFlash();
if (config::GGPOEnable)
sys_nvmem->digest(settings.network.md5.nvmem);
@@ -282,19 +298,20 @@ static bool loadFlash()
bool loadFiles()
{
loadFlash();
if (!settings.platform.isAtomiswave())
{
if (sys_rom->Load(getRomPrefix(), "%boot.bin;%boot.bin.bin;%bios.bin;%bios.bin.bin", "bootrom"))
{
if (config::GGPOEnable)
sys_rom->digest(settings.network.md5.bios);
bios_loaded = true;
}
else if (settings.platform.isConsole())
return false;
}
if (!settings.platform.isConsole())
return true;
return true;
if (sys_rom->Load(getRomPrefix(), "%boot.bin;%bios.bin", "bootrom"))
{
if (config::GGPOEnable)
sys_rom->digest(settings.network.md5.bios);
bios_loaded = true;
return true;
}
else {
// Use HLE BIOS
return false;
}
}
void saveFiles()
@@ -304,7 +321,7 @@ void saveFiles()
if (settings.platform.isConsole())
sys_nvmem->Save(getRomPrefix(), "nvmem.bin", "nvmem");
else
sys_nvmem->Save(hostfs::getArcadeFlashPath() + ".nvmem");
sys_nvmem->Save(getArcadeFlashPath());
if (settings.platform.isAtomiswave())
((WritableChip *)sys_rom)->Save(hostfs::getArcadeFlashPath() + ".nvmem2");
}
@@ -345,15 +362,6 @@ u8 *getBiosData()
return sys_rom->data;
}
void reloadAWBios()
{
if (!settings.platform.isAtomiswave())
return;
if (sys_rom->Reload())
return;
loadDefaultAWBiosFlash();
}
void init()
{
switch (settings.platform.system)
-1
View File
@@ -36,7 +36,6 @@ u8 *getFlashData();
u32 readBios(u32 addr, u32 sz);
void writeAWBios(u32 addr, u32 data, u32 sz);
u8 *getBiosData();
void reloadAWBios();
bool loadFiles();
void saveFiles();
+1 -1
View File
@@ -8,7 +8,7 @@
*/
#pragma once
#include "types.h"
#include <string.h>
#include <cstring>
class X76F100SerialFlash
{
+76 -66
View File
@@ -15,7 +15,9 @@
#include "imgread/common.h"
#include "serialize.h"
int gdrom_schid;
int gdrom_schid = -1;
static int statusSchid = -1;
static int nextStatus = -1;
//Sense: ASC - ASCQ - Key
int sns_asc;
@@ -32,15 +34,6 @@ cdda_t cdda;
static gd_states gd_state;
static DiscType gd_disk_type;
/*
GD rom reset -> GDS_WAITCMD
GDS_WAITCMD -> ATA/SPI command [Command code is on ata_command]
SPI Command -> GDS_WAITPACKET -> GDS_SPI_* , depending on input
GDS_SPI_READSECTOR -> Depending on features , it can do quite a few things
*/
static u32 data_write_mode=0;
//Registers
static u32 DriveSel;
@@ -51,6 +44,9 @@ static GD_SecCountT SecCount;
GD_SecNumbT SecNumber;
static GD_StatusT GDStatus;
#ifdef LIBRETRO
GD_StatusT get_gd_status(void) { return GDStatus; }
#endif
static ByteCount_t ByteCount;
@@ -151,6 +147,8 @@ void DmaBuffer::deserialize(Deserializer& deser)
{
index = 0;
deser >> size;
if (size > sizeof(cache))
throw Deserializer::Exception("Invalid GD-ROM DMA buffer size");
deser.deserialize(&cache[0], size);
}
}
@@ -168,12 +166,25 @@ void PioBuffer::deserialize(Deserializer& deser)
deser >> next_state;
deser >> index;
deser >> size;
if (size > Capacity / sizeof(_data[0]) || index > size)
throw Deserializer::Exception("Invalid GD-ROM PIO buffer state");
if (deser.version() < Deserializer::V54)
deser >> _data;
else
deser.deserialize(&_data[0], size);
}
static void cddaTransition(int state, u64 duration = 20_sh4ms, int transientState = GD_BUSY)
{
SecNumber.Status = transientState;
nextStatus = state;
sh4_sched_request(statusSchid, duration);
}
static void cddaCancelTransition() {
sh4_sched_request(statusSchid, -1);
}
static void gd_set_state(gd_states state)
{
gd_states prev=gd_state;
@@ -254,6 +265,9 @@ static void gd_set_state(gd_states state)
sector_count = maxSectors;
next_state = gds_readsector_pio;
}
else {
cddaTransition(GD_PAUSE, 67_sh4ms, GD_PLAY);
}
u16 *buffer = pio_buff.fill(sector_count * read_params.sector_type);
libGDR_ReadSector((u8*)buffer, read_params.start_sector, sector_count, read_params.sector_type);
@@ -342,6 +356,7 @@ void gd_setdisc()
gd_disk_type = newd;
SecNumber.DiscFormat = gd_disk_type >> 4;
cddaCancelTransition();
}
static void gd_reset()
@@ -437,6 +452,7 @@ static void gd_process_ata_cmd()
Error.full = 1;
sns_key = 0;
SecNumber.Status = GD_PAUSE;
cddaCancelTransition();
IntReason.full = 1;
// DC Checker expects these values
ByteCount.low = 0x14;
@@ -746,7 +762,9 @@ static void gd_process_spi_cmd()
{
#define readcmd packet_cmd.GDReadBlock
SecNumber.Status = GD_PLAY;
cdda.status = cdda_t::NoInfo;
cddaCancelTransition();
u32 sector_type = 2048;
if (readcmd.head == 1 && readcmd.subh == 1 && readcmd.data == 1 && readcmd.expdtype == 3 && readcmd.other == 0)
sector_type = 2340;
@@ -908,7 +926,6 @@ static void gd_process_spi_cmd()
case SPI_CD_PLAY:
{
const u32 param_type = packet_cmd.data_8[1] & 7;
printf_spicmd("SPI_CD_PLAY param_type=%d", param_type);
if (param_type == 1 || param_type == 2)
{
@@ -924,30 +941,24 @@ static void gd_process_spi_cmd()
cdda.EndAddr.FAD = ses_inf[3] << 16 | ses_inf[4] << 8 | ses_inf[5];
}
cdda.repeats = packet_cmd.data_8[6] & 0xF;
cdda.status = cdda_t::Playing;
SecNumber.Status = GD_PLAY;
GDStatus.DSC = 1;
printf_spicmd("SPI_CD_PLAY %d -> %d repeat %d", cdda.CurrAddr.FAD, cdda.EndAddr.FAD, cdda.repeats);
cddaTransition(GD_PLAY, 5_sh4ms, SecNumber.Status);
}
else if (param_type == 7)
{
printf_spicmd("SPI_CD_PLAY pause");
if (cdda.status == cdda_t::Paused)
{
// Resume from previous pos unless we're at the end
if (cdda.CurrAddr.FAD > cdda.EndAddr.FAD)
{
cdda.status = cdda_t::Terminated;
SecNumber.Status = GD_STANDBY;
}
cddaTransition(GD_STANDBY);
else
{
cdda.status = cdda_t::Playing;
SecNumber.Status = GD_PLAY;
}
cddaTransition(GD_PLAY);
}
}
else
else {
die("SPI_CD_PLAY: unknown parameter");
}
DEBUG_LOG(GDROM, "CDDA StartAddr=%d EndAddr=%d repeats=%d status=%d CurrAddr=%d",cdda.StartAddr.FAD,
cdda.EndAddr.FAD, cdda.repeats, cdda.status, cdda.CurrAddr.FAD);
@@ -961,42 +972,30 @@ static void gd_process_spi_cmd()
const u32 param_type = packet_cmd.data_8[1] & 7;
printf_spicmd("SPI_CD_SEEK param_type=%d", param_type);
SecNumber.Status = GD_PAUSE;
if (cdda.status == cdda_t::Playing)
cdda.status = cdda_t::Paused;
if (param_type == 1 || param_type == 2)
{
bool min_sec_frame = param_type == 2;
cdda.StartAddr.FAD = cdda.CurrAddr.FAD = GetFAD(&packet_cmd.data_8[2], min_sec_frame);
#ifdef STRICT_MODE
SecNumber.Status = GD_SEEK;
GDStatus.DSC = 0;
sh4_sched_request(gdrom_schid, SH4_MAIN_CLOCK / 50); // 20 ms
#else
GDStatus.DSC = 1;
#endif
cddaTransition(GD_PAUSE, 20_sh4ms, GD_SEEK);
}
else if (param_type == 3)
{
//stop audio , goto home
cdda.StartAddr.FAD = cdda.CurrAddr.FAD = 150;
cdda.status = cdda_t::NoInfo;
#ifdef STRICT_MODE
SecNumber.Status = GD_BUSY;
GDStatus.DSC = 0;
sh4_sched_request(gdrom_schid, SH4_MAIN_CLOCK / 50); // 20 ms
#else
SecNumber.Status = GD_STANDBY;
GDStatus.DSC = 1;
#endif
cddaTransition(GD_STANDBY, 5_sh4ms);
}
else if (param_type == 4)
{
//pause audio -- nothing more
SecNumber.Status = GD_PAUSE;
cdda.status = cdda_t::Paused;
cddaCancelTransition();
}
else
else {
die("SPI_CD_SEEK : not known parameter..");
}
DEBUG_LOG(GDROM, "CDDA StartAddr=%d EndAddr=%d repeats=%d status=%d CurrAddr=%d",cdda.StartAddr.FAD,
cdda.EndAddr.FAD, cdda.repeats, cdda.status, cdda.CurrAddr.FAD);
@@ -1014,7 +1013,7 @@ static void gd_process_spi_cmd()
case SPI_GET_SCD:
{
printf_spicmd("SPI_GET_SCD");
printf_spicmd("SPI_GET_SCD %d", packet_cmd.data_8[1] & 0xF);
const u32 format = packet_cmd.data_8[1] & 0xF;
const u32 alloc_len = (packet_cmd.data_8[3] << 8) | packet_cmd.data_8[4];
@@ -1227,16 +1226,6 @@ static int getGDROMTicks()
//is this needed ?
static int GDRomschd(int tag, int cycles, int jitter, void *arg)
{
if (SecNumber.Status == GD_SEEK)
{
SecNumber.Status = GD_PAUSE;
GDStatus.DSC = 1;
}
else if (SecNumber.Status == GD_BUSY)
{
SecNumber.Status = GD_STANDBY;
GDStatus.DSC = 1;
}
if (!(SB_GDST & 1) || !(SB_GDEN & 1) || (dma_buff.isEmpty() && read_params.remaining_sectors == 0))
return 0;
@@ -1295,13 +1284,33 @@ static int GDRomschd(int tag, int cycles, int jitter, void *arg)
asic_RaiseInterrupt(holly_GDROM_DMA);
}
// Read ALL sectors and all buffer
if (read_params.remaining_sectors == 0 && dma_buff.isEmpty())
//verify(!SB_GDST&1) -> dc can do multi read dma
if (read_params.remaining_sectors == 0 && dma_buff.isEmpty()) {
gd_set_state(gds_procpacketdone);
cddaTransition(GD_PAUSE, 67_sh4ms, GD_PLAY);
}
return getGDROMTicks();
}
static int statusSched(int tag, int cycles, int jitter, void *arg)
{
if (nextStatus != -1)
{
GDStatus.DSC = 1;
int prevCddaStatus = cdda.status;
if (nextStatus == GD_PAUSE && cdda.status == cdda_t::Playing)
cdda.status = cdda_t::Paused;
else if (nextStatus == GD_STANDBY)
cdda.status = cdda_t::Terminated;
else if (nextStatus == GD_PLAY)
cdda.status = cdda_t::Playing;
DEBUG_LOG(GDROM, "statusSched: Status %d -> %d, cdda status %d -> %d", SecNumber.Status, nextStatus, prevCddaStatus, cdda.status);
SecNumber.Status = nextStatus;
nextStatus = -1;
}
return 0;
}
//DMA Start
static void GDROM_DmaStart(u32 addr, u32 data)
{
@@ -1343,6 +1352,7 @@ static void GDROM_DmaEnable(u32 addr, u32 data)
void gdrom_reg_Init()
{
gdrom_schid = sh4_sched_register(0, &GDRomschd);
statusSchid = sh4_sched_register(0, &statusSched);
gd_disc_change();
}
@@ -1350,6 +1360,8 @@ void gdrom_reg_Term()
{
sh4_sched_unregister(gdrom_schid);
gdrom_schid = -1;
sh4_sched_unregister(statusSchid);
statusSchid = -1;
}
void gdrom_reg_Reset(bool hard)
@@ -1387,7 +1399,6 @@ void gdrom_reg_Reset(bool hard)
cdda = {};
gd_disk_type = NoDisk;
data_write_mode = 0;
DriveSel = 0xa0;
Error = {};
IntReason = {};
@@ -1420,7 +1431,6 @@ void serialize(Serializer& ser)
ser << cdda;
ser << gd_state;
ser << gd_disk_type;
ser << data_write_mode;
ser << DriveSel;
ser << Error;
@@ -1430,6 +1440,8 @@ void serialize(Serializer& ser)
ser << SecNumber;
ser << GDStatus;
ser << ByteCount;
sh4_sched_serialize(ser, gdrom_schid);
sh4_sched_serialize(ser, statusSchid);
}
void deserialize(Deserializer& deser)
@@ -1443,20 +1455,14 @@ void deserialize(Deserializer& deser)
deser >> packet_cmd;
deser >> set_mode_offset;
deser >> read_params;
if (deser.version() >= Deserializer::V17) {
dma_buff.deserialize(deser);
}
else {
deser >> packet_cmd;
dma_buff.clear();
}
dma_buff.deserialize(deser);
pio_buff.deserialize(deser);
deser.skip<u32>(Deserializer::V44); // set_mode_offset (repeat)
deser >> ata_command;
deser >> cdda;
deser >> gd_state;
deser >> gd_disk_type;
deser >> data_write_mode;
deser.skip<u32>(Deserializer::V62); // data_write_mode
deser >> DriveSel;
deser >> Error;
@@ -1466,6 +1472,10 @@ void deserialize(Deserializer& deser)
deser >> SecNumber;
deser >> GDStatus;
deser >> ByteCount;
if (deser.version() >= Deserializer::V62) {
sh4_sched_deserialize(deser, gdrom_schid);
sh4_sched_deserialize(deser, statusSchid);
}
}
}
+3 -3
View File
@@ -59,9 +59,9 @@ enum HollyInterruptID
holly_MAPLE_OVERRUN = holly_err | 0x09, //bit 9 = MAPLE : DMA over run
holly_MAPLE_FIFO = holly_err | 0x0a, //bit 10 = MAPLE : Write FIFO overflow
holly_MAPLE_ILLCMD = holly_err | 0x0b, //bit 11 = MAPLE : Illegal command
//bit 12 = G1 : Illegal Address set
//bit 13 = G1 : GD-DMA over run
//bit 14 = G1 : ROM/FLASH access at GD-DMA
holly_G1_ILLADDR = holly_err | 0x0c, //bit 12 = G1 : Illegal Address set
holly_G1_OVERRUN = holly_err | 0x0d, //bit 13 = G1 : GD-DMA over run
holly_G1_ACCESS = holly_err | 0x0e, //bit 14 = G1 : ROM/FLASH access at GD-DMA
holly_AICA_ILLADDR = holly_err | 0x0f, //bit 15 = G2 : AICA-DMA Illegal Address set
holly_EXT1_ILLADDR = holly_err | 0x10, //bit 16 = G2 : Ext-DMA1 Illegal Address set
holly_EXT2_ILLADDR = holly_err | 0x11, //bit 17 = G2 : Ext-DMA2 Illegal Address set
Loaded 100 of 530 files, more files were not shown because too many files have changed in this diff. Show more