48 Commits
Author SHA1 Message Date
jpolo1224 d8a98305b8 Library: fail a game the app cannot read, instead of hanging on its loading screen
A game that is visible but unreadable is the failure mode that costs the most
time, because nothing about it looks like a permissions problem. The core opens
games with ordinary file IO. Without All files access the scanner falls back to
SAF document trees, and a title found that way has no filesystem path to open --
but it is still listed, still launches, and then sits on its first loading
screen when the reads never arrive. The same shape appears when the grant is
revoked after a folder was added, or when a disc sits on storage the app was
never given. Moving the same files into the emulator's own games directory fixes
all of it at once, which is what makes the cause so hard to see: the game works
or does not depending only on where it lives.

Boot now proves the content is readable first, at two offsets. Size alone proves
nothing -- an entry can be listed with its real size and still refuse to deliver
bytes -- so it reads byte 0 and byte 32769, the ISO descriptor, the first place
any disc is read for real. Failure returns invalid_file_or_folder with a message
naming the two fixes, rather than handing the core content it cannot read.

The scanner says the same thing at the point it falls back, distinguishing "All
files access is not granted" from "this tree has no filesystem path", because
they need different fixes.

Read-only probe, and directory-installed titles skip it: fs::is_file is false for
them and they were never affected.

Reported by a tester whose whole library started working after moving the ISOs
into ARMSX3/config/games -- cover art, launching, and Prototype no longer locking
up, all from the move alone.
2026-08-21 20:50:04 -04:00
jpolo1224 2f0c63ac1b ISO: stop treating a short magic read as "not an ISO"
Reverts upstream 8a6c96745, taken in the ROP remap merge, back to the 0.8
behaviour. The check is sound on a desktop filesystem, but on Android the disc is
read through SAF/content URIs and this predicate decides how a title gets
mounted: System.cpp:1572, 1589, 1764 and 1878 all branch on it, as does
rpcsx-android.cpp:3935. A short read there does not fail the boot outright, it
silently routes the title down a different mount path -- which is what a game
that reaches its first loading screen and never leaves it looks like.

Not identical to 0.8: magic[] is zero-initialised. The original compared it
without checking the read at all, so a failed read compared uninitialised stack.
Zeroing restores "do not reject on a short read" without the undefined behaviour,
and a genuinely non-ISO file still fails the CD001 test.

iso_archive::is_valid() stays. It comes from a different upstream commit
(d6d5c6082), System.cpp calls it, and it is not part of this predicate.

Reported against NASCAR 2011, which reached the menus before the merge and now
sticks on the first loading screen. NOT confirmed as the cause -- the reporter's
build predates the local commits, and the other public candidates in that window
are the Adreno compute group size and the driver pipeline cache work.
2026-08-21 19:50:32 -04:00
jpolo1224 4b8ffa3702 Release: 0.9.4.1 2026-08-21 17:20:11 -04:00
jpolo1224 c01f2e9ba8 Boot: warn when PPU Threads is not 2
The PS3 has two PPU hardware threads and upstream's own config comment says this
must be 2. With 1, SPURS does not get the concurrent PPU progress it expects and
its task modules fail validation: the SPU executes its own HALT instruction and
the graphics pipeline stops. It presents as an intermittent freeze after 10-30
minutes, on hardware where the same build with the default is fine.

There is no UI for this value -- it is reachable only through a raw core
override, so it is set deliberately and then forgotten. Nothing reported it,
which meant an affected log was indistinguishable from a healthy one and the
setting had to be found by eye in the config dump, after a diagnosis that went
through the SPU halt, the reservation path and an instruction-level SPU test run
before arriving at a configuration difference.
2026-08-21 17:19:44 -04:00
jpolo1224 71c694c9ab Really Slick Screensavers as library backgrounds, and rename the ARMSX2-era settings mirror
Six of Terry Welsh's screensavers (GPL-2.0-or-later) run as animated library
backgrounds alongside Flurry: Flux, Plasma, SolarWinds, Hyperspace, Lattice and
Skyrocket.

They are GL 1.x -- immediate mode, the fixed-function matrix stack, display
lists, texgen -- so savers/gl1.c answers all of that on GLES2 rather than the
renderers being rewritten. Display lists are not an optimisation to skip here:
each saver compiles its particle shape once and replays it per particle with a
different matrix, so glCallList IS how they draw.

Flux, Plasma, SolarWinds and Hyperspace are byte-identical to upstream. They
build with RS_XSCREENSAVER, their own platform-neutral path, and everything it
expects from an X11 host is answered by savers/compat. Lattice and Skyrocket
ship Windows-only and were hand-ported; each carries a header listing exactly
what was removed. rss-glx has neutral versions of those two but is GPL-2.0-only,
which ARMSX2 (GPL-3.0) could not take.

Every saver is compiled inside its own namespace by a *_unit.cpp. They all
declare the same globals -- draw, idleProc, cleanUp, readyToDraw -- because each
was built as a separate executable, and two in one .so collide at link time.

Notes on the three bugs that were not obvious:

- The render thread gets a 16MB stack. Skyrocket's World constructor declares a
  1024x1024x3 starmap as a LOCAL, 3MB, then a 768KB sunsetmap. On a Windows main
  thread that was fine; here it was a SIGSEGV in memset before the first frame.

- gl1_frame_begin clears the first frames and then masks alpha off. These savers
  fade the previous frame instead of clearing, and that fade writes destination
  alpha, dragging a composited surface toward transparent -- which reaches the
  screen as TV static. Same fix Flurry needed.

- The saver lifecycle is serialised behind a mutex and a generation token. Each
  view owns a GL thread, so switching preset let the outgoing thread's teardown
  land after the incoming thread's init and wipe it; gl1 keeps its state in one
  global. A view may now only free the run it started.

Also renames the settings mirror armsx2-settings.json to armsx3-settings.json
(issue #82). The old name stays readable: after a reinstall it is the only copy
of a user's settings, so it is still restored from, still collected into new
backups, and now deleted alongside the new one -- leaving it behind would
silently re-seed settings that had just been purged. Old backup archives restore
unchanged.
2026-08-21 15:14:07 -04:00
jpolo1224 7673186c06 Flurry: preserve the EGL back buffer so the trails can accumulate
The library background rendered as full-screen TV static with the smoke
faintly visible behind it.

Flurry draws its trails by fading the PREVIOUS frame rather than clearing,
so it needs the back buffer to still hold what it drew last time. EGL
defaults EGL_SWAP_BEHAVIOR to EGL_BUFFER_DESTROYED, which leaves the buffer
undefined after every eglSwapBuffers, so every frame started from
uninitialised GPU memory -- which is what reached the screen as static.

Measured: a single fade darkened the buffer by 3.17%, but sixty consecutive
fades only achieved 6.5% in total, where sixty compounding 3.17% fades
should have taken a corner reading 123 down to 17. The fade was working
every frame; its result was not surviving the swap. A 64x64 readback of that
corner went from mean=128 ndiff=41 to mean=0 ndiff=0 with preservation on.

Preservation must be requested on the config and on the surface, and is then
queried back rather than assumed, since a driver may refuse it.

Also set the surface alpha once and mask further alpha writes off. Flurry
fades with GL_SRC_ALPHA/GL_ONE_MINUS_SRC_ALPHA, which writes destination
alpha as well as colour and drags it toward the fade's own value -- harmless
for a screensaver that owns the display, wrong for a surface the window
compositor blends. RGB is untouched, so the trails are unaffected.
2026-08-21 11:49:59 -04:00
jpolo1224 e9a21da302 Library: offer Flurry as the background
Calum Robinson's Flurry screensaver (2002), ported from the xscreensaver tree and
offered alongside the XMB wave. Asked for by a tester who has wanted it on a
handheld since the Mac original.

The renderer is his, unmodified. It draws the way 2002 did -- client-side vertex
arrays, a fixed-function ortho, GL_QUADS -- none of which GLES2 has, so
gl_compat.c answers those calls with a shader, a matrix uniform and an index
buffer instead. That keeps the diff against upstream to four lines: gluBuild2DMipmaps
becomes glTexImage2D plus glGenerateMipmap, GETTIMEOFDAY_TWO_ARGS is set by hand
rather than by xscreensaver's configure, DrawSpark is guarded because it is
immediate-mode and upstream never defines DRAW_SPARKS, and the xlockmore shell is
replaced by four entry points. Its own target rather than part of the JNI glue:
BSD-3-clause next to GPL, and plain C that wants none of the C++20 around it.

FlurryGlView is the same TextureView and EGL shell XmbGlView already uses, so it
reports through onGlStatus the same way and a device that cannot bring GL up
falls back to the 2D backdrop rather than showing a hole.

Off by default, and the description says why. This is a live particle simulation,
and an animated library background has been walked back here once before --
ARMSX2 shipped a looping video behind this same screen and removed it in 2.5.9
when the continuous decode turned out to cost real performance. The loop is
capped at 60fps for the same reason XmbGlView caps itself at 30: Flurry already
refuses to advance faster, but without a cap the thread still spins at panel rate.

Also adds @JvmStatic to Rpcs3Bridge.deleteState, which NativeApp calls from Java.
Kotlin-only compilation does not catch that.
2026-08-21 10:34:27 -04:00
jpolo1224 5d5b8aa4c1 Savestate: add a real delete for slots
Deleting a slot could never work. There was no delete entry point, so the picker
called java.io.File(getGamePathSlot(slot)).delete() -- and getGamePathSlot answers
OCCUPANCY, not a path. Its own declaration two lines below says so: "Not
getGamePathSlot, which answers a title id". So the call was
File("SHVH06660").delete(), which removes nothing and returns false. Every delete
failed, and the UI correctly reported that it had. Issue #80.

Added _rpcsx_deleteStateFromSlot, which resolves the file through
armsx3_slot_find, the same function load uses. That matters: the extension
depends on which build wrote the state -- .zst today, .gz and bare historically --
and building the path by hand is how this went wrong to begin with. The thumbnail
beside it goes too, best effort, so a removed state cannot leave a slot still
showing as occupied.
2026-08-21 09:39:45 -04:00
jpolo1224 a46dae38bc Savestate: do not resume while one is being written
Saving arms after_kill_callback with Emu.Restart and then kills the VM. The
restart runs BootGame, whose restore_on_no_boot does ensure(IsStopped()) -- and
that overload accepts only stopped, loading or stopping. A Resume landing
anywhere in that window puts the state at running and turns the assert into a
process abort.

Issue #81, from the log: "Emulation has been resumed!" at 0:03:23.275950 and
"{Savestate Prepare Thread} Verification failed (object: 0x0)" at 0:03:23.275981.
Thirty one microseconds apart, on different threads.

Android makes the window far easier to hit than desktop. CallFromMainThread runs
its callback INLINE on the calling thread here rather than deferring it, so the
whole kill-and-restart chain executes on the savestate thread while the UI thread
stays free to resume underneath it.

Guarded on m_emu_state_close_pending, which is the emulator's own marker for that
window and is cleared on every failure path, so this cannot leave a game stuck
unresumable. Placed in Resume() rather than in the Android entry point so it also
covers the resume that surface-loss recovery issues.
2026-08-21 09:39:45 -04:00
jpolo1224 b2caae9da2 NP: derive an Ethernet address on Android instead of failing
discover_ether_address() reads the MAC with SIOCGIFHWADDR. Android has not let an
app do that since Android 6 and enforces it hard from 10 -- the ioctl returns
EPERM and /sys/class/net/*/address is unreadable -- so on every modern Android
device that branch failed, np_handler logged

    NPHandler: Failed to discover ethernet or ip address!

and gave up. That leaves the network stack unidentified and blocks RPCN and any
game that asks it who it is. Reported as issue #79 against two unrelated titles,
with network settings that looked correct and could not have helped, because
nothing exposed in them reaches this.

The IP half was never the problem: it learns the local address from a UDP connect
to 8.8.8.8, which is a routing lookup and sends nothing.

Derive the address from Console PSID, exactly as the derive_mac_from_psid path at
the top of the same function already does. Nothing validates it against hardware
-- it identifies the console to the network stack and to peers -- and a locally
administered address is the correct thing to present when the platform refuses
the real one. Console PSID defaults to a per-install random value, so two users
do not end up sharing one.

Tried after the ioctl rather than in place of it, so a device or ROM that does
answer still uses its own.
2026-08-21 09:30:04 -04:00
jpolo1224 0262053a91 Revert "VK: make the fence poll mechanism selectable at runtime"
This reverts commit b91c6551ed.
2026-08-21 01:23:10 -04:00
jpolo1224 b91c6551ed VK: make the fence poll mechanism selectable at runtime
"Is this submission finished" costs 1.9ms per call and returns not-ready 0.0% of
the time on Adreno 740 -- about 5ms a frame in Minecraft, a quarter of the RSX
thread. Measured with the profiler's own counters (fence polls 2.6/frame,
1903712 ns each).

That is the same signature this code already carries a comment about:
vkGetFenceStatus measured 19.7ms per call and never once returned VK_NOT_READY,
"a wait wearing a query's name". The zero-timeout vkWaitForFences that replaced
it is ten times better and still, evidently, a wait -- the driver appears not to
honour a zero timeout either way. That is driver behaviour, so which call is best
is a per-device question and not something to decide here.

ARMSX3_FENCE_POLL in driver_env.txt selects between them:
  wait0   default, today's zero-timeout wait
  status  vkGetFenceStatus, what this used before
  defer   speculative callers do not ask the driver at all

The third is the interesting one. check_present_status walks the queued frames
looking for ones that have already finished and gives up on the first that has
not, and poke_all discards the result entirely -- neither wants to wait, so
neither should be the thing that does. They now pass allow_block=false. next()
and reset() keep the blocking form, so the command buffer ring cannot be starved
by this and "CB chain has run out of free entries" cannot fire because of it.

Default behaviour is unchanged. This ships as a lever rather than a fix on
purpose: the last renderer change justified by one game on one device was
reverted in 0.7.1 after wider testing, and the note on that revert asked for
pieces that come back one at a time with testing behind each. The profiler
already prints per-call timing, so each mode can be judged on the device in front
of whoever is testing.
2026-08-21 01:19:34 -04:00
jpolo1224 72410638ff cellAudio: let the untouched baseline come back down, but not on a flicker
89c6d08ed made untouched_expected a high-water mark to stop a silent port
resetting it every period. That fixed a measured, severe bug -- H.A.W.X. 2 ran
its audio clock at 55% of real time with the ring buffer permanently empty -- and
its own commit message flagged what was left unverified: whether anything depends
on the baseline falling again within a stable port configuration.

It does, and the mark latches. The fast path runs whenever untouched ==
active_ports, and stores min(max(untouched, expected), active_ports), so the first
fully silent period a title has pins the baseline at its maximum. From then on
`untouched > untouched_expected` can never be true and the loop stops waiting for
late ports for the rest of the session. Every game has a silent period.

Going back to upstream's instantaneous store is not the fix: that is exactly what
produced the H.A.W.X. stall. A port a game leaves started while writing only
zeros still flips the -0.0f tags, so it reads as touched on the few periods a
write lands in and untouched on the rest; following that dip drops the baseline
and the next period waits out the whole timeout, every flicker.

Hysteresis covers both, because the two differ in duration rather than shape. The
flicker is one period wide; a game that genuinely starts filling its ports stays
filled. Raise at once, lower only after the lower count has held for eight
periods -- far longer than any flicker, still a small fraction of the untouched
timeouts this feeds. For the H.A.W.X. case the behaviour is unchanged.

Bounded either way: the in_progress gate below is untouched, so no half-written
buffer was ever mixed by this. What the latch cost was the wait for a port that
had not started yet.
2026-08-21 00:57:55 -04:00
jpolo1224 0a3fcc622f Audio: stop the Oboe backend failing silently and permanently
Issue #73: F1 Championship Edition silent since v0.8 except during videos, on
every audio backend. The reporter also notes the races run at 10fps and the
videos are "a bit choppy" -- so audio dies when the machine is saturated and
survives when it is not. That is a load correlation, not a code-path split, and
it rules out the theory that cellAudio is never reached.

cellAudio.cpp has no commits at all in the v0.6..v0.8 window. Across every audio
setting the Android UI writes, the only delta is the default backend moving from
Cubeb to Oboe, with a migration that moved existing users too.

Cubeb starts the OS stream in Open() and never stops it while open; its Play and
Pause only flip a bool. Oboe's call requestStart and requestPause for real, and
this backend asked for an Exclusive low-latency stream with a two-burst buffer --
4 to 10ms. An Exclusive AAudio stream holds the app to the callback deadline and
the platform disconnects it when that is missed for long enough, which at 10fps
is continuous. ARMSX2's own Oboe backend, same team and same device class, uses
Shared with a 4096-frame capacity.

Three fixes here:

Open() asks for Shared, and sizes the buffer from the emulator's own Desired
Audio Buffer Duration rather than a burst multiple. cellAudio fills ahead by that
much, so a stream that cannot hold it underruns however the trigger is set.

Play() started the stream AFTER committing m_playing, and never rolled it back on
failure. onAudioReady requires m_playing but a stream that never started does not
call it, so cellAudio's backend_active never armed, every block it enqueued was
discarded by the !backend_active check in enqueue(), and the early return at the
top of Play() made that permanent for the session. Start first, claim second.

Operational() answered `m_stream && !m_reset_req`, which is true for a stream that
failed to start or was disconnected -- so cellAudio's "backend stopped
unexpectedly" recovery never ran. It now reports the stream state.

Also: audio::configure_audio() is what turns a changed audio node into a rebuilt
backend, and its only caller is main_application.cpp, which is Qt and not part of
this build. Writing Audio Renderer changed the YAML and nothing else until the
next boot, so "try another backend" was a null experiment -- which is how that
issue collected three backends' worth of identical results. Call it from the
Android settings path, alongside the overlay resets that had the same gap.

And the "Audio Backend (advanced)" row only affects Cubeb, since cubeb_backend is
read once in CubebBackend's constructor. Under Oboe it did nothing while staying
interactive and showing a selection. Hidden unless the renderer is Cubeb.
2026-08-21 00:54:16 -04:00
jpolo1224 43df8b0dc2 Android: say why an ARMv8.0 device cannot run, instead of dying on SIGILL
The core is built for armv8.1-a and that is a floor, not a preference:
util/simd.hpp emits SQRDMLAH and util/asm.hpp carries inline LSE atomics. On
ARMv8.0 silicon -- Cortex-A53/A57/A73, so Exynos 9610, Snapdragon 660 and the
like -- those are illegal opcodes.

What the user saw was a bare crash. The fault is a SIGILL inside a static
constructor while the linker is still running libarmsx3-core.so's initialisers,
so it happens before any of our code can report anything, and the top frame names
a log-channel registration rather than a CPU problem. Because the core is
dlopen'd lazily, it surfaced at whatever first needed it -- issue #15 filed it as
"crashes when selecting the firmware .pup file", which is where the crash appears
and not what it is about.

Check /proc/cpuinfo for atomics and asimdrdm before that dlopen and report it.
Checked across every core listed, not just the first, since emulator threads are
scheduled on all of them.

Fails OPEN by design: an unreadable or unfamiliar /proc/cpuinfo returns null and
loads as before. Blocking a device that would have worked is worse than the crash
this replaces. Verified on a Snapdragon 8 Gen 2 that all eight cores report both
flags, so nothing changes there.

The reason is also held in unsupportedCpuMessage so a screen can show it later; a
toast is easy to miss for something this final.
2026-08-21 00:39:58 -04:00
jpolo1224 a14670c282 Config database: add ARMSX3-local per-title overrides, starting with issue #77
The RPCS3 config database is maintained against desktop, so a fix specific to
this port has nowhere upstream to live. Add a small local map layered on top of
whatever the database says.

First entry: Tales of Symphonia Chronicles (BLUS31213, BLES01935) gets Frame
limit "PS3 Native". That is the only mode which honours the game's own
cellGcmSetFlipMode(CELL_GCM_DISPLAY_VSYNC) request -- every other mode flips
immediately, so a title pacing itself by flipping on alternate vblanks free-runs
to the 60 cap, which is issue #77: 60fps in a 30fps game and battles at double
speed.

Applied in three places because none of them alone is enough: after refresh(),
which clears the directory before writing; after setEnabled(), which moves the
whole directory aside; and at startup, so it works for users who never downloaded
the database at all. Merged rather than overwritten, and skipped entirely if the
database already sets a Frame limit for that title.

UNVERIFIED against the reporter's device. The mechanism is read from the code,
not measured, and the failure mode matters: PS3 Native applies NO limit when a
game does not request vsync, so if this title turns out not to, it would run
unbounded rather than capped at 60. Drop the serials from the map if the reporter
says it is worse.
2026-08-21 00:37:39 -04:00
jpolo1224 8c1952deae Frame limit: offer PS3 Native from the FPS cap row
PS3 Native is the only Frame limit mode that honours the game's own
cellGcmSetFlipMode(CELL_GCM_DISPLAY_VSYNC) request: in handle_emu_flip every
other mode falls through and flips immediately, while that one defers the flip to
a real vblank. A title that paces itself to 30fps by flipping on alternate
vblanks therefore free-runs to the 60 cap under Auto, our default -- reported in
issue #77 as Tales of Symphonia running at 60 and battles playing too fast.

The mode already existed and Rpcs3Settings could already write it, but nothing
reachable from the UI ever did: the only control is the Display FPS Cap row,
which maps to numeric presets or Second Frame Limit. It was reachable solely as a
raw core override. That also explains the reporter's follow-up -- capping by hand
does slow the game, but through the wall-clock limiter, which pushes flips off
the vblank boundary and brings tearing and audio glitches with it.

Added as -1 in that row rather than as a new setting, since it is mutually
exclusive with a numeric cap. Both clamps on the way to the core floored at 0 and
would have quietly turned it into "no cap".

setFrameLimitEnabled needed the same widening. It runs last, so its `cap > 0`
test read -1 as "no explicit rate" and wrote Auto back over PS3 Native -- the
collision its own comment describes, one value along.

Deliberately NOT the default: with no vsync request from the game this mode
applies no limit at all, so titles that never ask for vsync would run unbounded.
2026-08-21 00:34:23 -04:00
jpolo1224 c6a0878a97 RSX: restore two hunks lost resolving the ROP remap merge conflict
Both sat inside set_transform_program, the handler upstream rewrote for
ROP_OUTPUT_REMAP. Resolving that conflict took upstream's version as the base and
re-applied our profiler scopes, which put the scopes back and quietly dropped
these.

The first is the one that matters. e13fc184f made the redundant vertex program
check actually compare: the destination holds each word byte-swapped individually
by copy_data_swap_u32, while be_t<u64> swaps all eight bytes and so also exchanges
the two words. Without the rotl the check compares (w0,w1) against (w1,w0), which
can only match when w0 == w1 -- so it never fires, and every single upload marks
the ucode dirty. That forces a vertex program re-analysis, a program cache hint
drop and a full transform constant re-upload on every draw.

That is a per-draw cost on the RSX thread, restored to 0.9.4 by the merge after
being fixed, and it fits issue #78: Wipeout HD Fury reported as much higher CPU
and far worse performance on 0.9.4 than 0.9.3, with no other change in the window
that adds per-draw work.

The second is g_xform_program_words, which the profiler divides by
g_xform_program_calls to report average batch size. Losing the increment prints 0
rather than printing nothing -- exactly the failure mode I checked for on the
transform CONSTANT counter during the same merge, and missed on this one.

ISO.cpp, the other conflicted file, was checked the same way and lost nothing.
2026-08-21 00:23:37 -04:00
jpolo1224 b9689d07fd VK: allow the driver pipeline cache to be switched off at runtime
ARMSX3_PIPELINE_CACHE=0, read through driver_env.txt like the compute group size
override, so a suspected regression can be A/B'd on a single build.

Issue #78 reports Wipeout HD Fury running far worse on 0.9.4 than 0.9.3 with much
higher CPU. That window holds 28 commits, and most of them only remove work, so
the plausible suspects are this cache, the upstream ROP output remap, and the ISO
reader revert. Guessing between them is not worth a release each.

This one is worth being able to eliminate first because it is the only change that
introduces a structure shared between threads that previously shared none: up to
eight shader cache loader workers at boot, plus the async pipeline compiler
workers during play, all now insert into one VkPipelineCache. Concurrent use is
permitted by the spec, but the driver still has to serialise its own inserts.

Not evidence that it IS the cause -- it is the cheapest candidate to rule out.
2026-08-21 00:14:00 -04:00
jpolo1224 82f21b16d2 VK: credit sashkinbro for the pipeline cache format and the Adreno split
Both landed as our own commits and both owe him more than they said.

The on-disk pipeline cache header -- length, version, vendorID, deviceID and
pipelineCacheUUID -- is his design from EmuCoreC 47220b153, used as-is, including
using the UUID as the invalidation key so a driver swap rebuilds rather than
feeding a driver a blob it cannot read. What was missing there was the wiring:
nothing passed the cache to vkCreate*Pipelines, so it saved an empty file. That
part, and sharing it with the shader interpreter, are ours.

Splitting Adreno and Turnip out to a 64-wide group size is his too (b9f0f3631).
Our comment had held all of mobile at 32 on the claim that Mali was also 64-wide,
which is wrong -- Valhall warps are 16 lanes and Bifrost 4-8. His split was the
better call and the reason ours changed.
2026-08-20 18:13:35 -04:00
jpolo1224 2ed8442c11 Release: 0.9.4 (versionCode 20) 2026-08-20 17:58:45 -04:00
jpolo1224 ab58fb087b lv2: stop logging sys_memory_container_get_size on every call
Tales of Xillia 2 (BLUS31397) polls this syscall in a loop: 20,664 calls in a 19
minute session, 5,123 of them inside a single second. It was logged at warning, so
each one wrote a line to external storage on Android -- the same shape as the SPU
recompiler diagnostics, and enough on its own to stop frames landing.

Reported as issue #76, an intermittent hang every few minutes that ends with
"Game has stopped responding - it is no longer drawing frames". That message comes
from the 30 second frame-stall watchdog, which is what a saturated log writer
looks like from the RSX thread.

Moved to trace. Nothing is lost: a container's size is fixed at creation, and
sys_memory_container_create and _destroy both still log at warning, so the events
that carry information are still recorded. sys_memory_get_user_memory_size in the
same file already has the equivalent treatment upstream -- it only logs when the
values it reports actually change.

Not confirmed as the cause of #76 yet; the reporter has not attached a log, and 19
minutes of play here did not reproduce the hang. It is the largest log source this
title produces by a wide margin.
2026-08-20 17:55:36 -04:00
jpolo1224 9829bc5402 Android: wait for the previous VM to actually stop before booting
Booting a game while another was still tearing down left the app frozen on the
last frame of the previous game.

The boot gate asked Emu.IsStopped(), whose default overload is

    m_state <= system_state::stopping

so it answers true while the previous VM is still stopping -- and while it is
loading. Kill() only signals the threads and hands the joining to a detached
"Emulation Join Thread", so the state reaches stopping at once and the guard read
that as stopped. The entire wait block was skipped precisely when it was needed,
which is why no "previous VM still running" line was ever logged.

On device: Stopping emulator at 0:10:14, BootGame at 0:10:17, and at 0:10:31 the
join thread was still waiting on PPU[0x1000004] "SPU Interrupt Thread0" -- 17.4
seconds -- with seven SPUs parked in EXIT|w|G-PAUSE and one PPU thread spinning at
100%.

Use the IsStopped(true) overload, which requires system_state::stopped and is
reached only once that join thread has finished. The ten second bound and the
boot-anyway fallback are unchanged, so a teardown that genuinely hangs still gets
reported rather than freezing the UI.

Only the three checks in _rpcsx_boot are changed. Other IsStopped() callers here
want "not running" and the loose overload is right for them.

This does not fix why that interrupt thread fails to exit, which is still open. It
stops a slow teardown from becoming a boot into a half-destroyed VM.
2026-08-20 17:34:07 -04:00
jpolo1224 0fb1757a84 PPU: report thread perf stats once, not on every stop-path pass
cpu_on_stop() is a teardown hook and nothing enforces that it runs a single time.
A PPU thread that re-enters the stop path without exiting reports again on every
pass: one thread ("SPU Interrupt Thread2") was producing "PPU thread perf stats
are not available." roughly every 10 microseconds, near 100,000 lines a second.

That is survivable on a desktop. On Android the log goes to external storage, so
it pins the log writer and drags down the shutdown it is describing -- the
emulator logged "Stopping emulator..." and never reached "All threads have been
stopped", leaving the next boot stuck on the last frame of the previous game.

Guard the reporting with a flag so it happens once per thread. The flag is
deliberately left out of serialization: it describes this run's reporting, not
guest state.

This does not address why the thread re-enters the stop path, which is a separate
question -- it stops that from being an emulator-wide stall while it is open.
2026-08-20 17:19:03 -04:00
jpolo1224 53e225cf96 RPCN: stop burning a core whenever a game is not running
The client thread's inner loop breaks out to the outer sem_rpcn.acquire() for
every state except one: connected and authentified with no game running. The
only blocking wait sits inside the `authentified && !Emu.IsStopped()` branch, so
that case fell through to `while (true)` with nothing to wait on and span a full
core for as long as the user stayed signed in outside a game -- at the menu,
between titles, and throughout shutdown.

Measured on a Retroid Pocket 6 sitting at the library: `RPCN Client` at 100%,
utime 21964 against stime 58, so a userspace spin rather than a syscall storm.

Wait in the fall-through case instead. Breaking out to the outer semaphore would
also stop the spin but nothing releases it when a game starts, so the pings would
never resume.

Only reachable once RPCN actually authenticates, which is why it survived
upstream: signing in is new on Android.
2026-08-20 17:19:03 -04:00
jpolo1224 d9a0481dcb SPU: move the per-block recompiler diagnostics to trace
A 15 minute Prototype session wrote 56,881 log lines, and 49,644 of them came from
the SPU recompiler -- peaking at 2,473 lines in a single second, written to
/sdcard. The bursts land exactly when a game is already stalling to compile new
blocks, which is the worst possible moment to add synchronous file writes, and
they stop when compilation finishes. That matches the reported symptom: seconds of
lockup that recover on their own.

Every one of these is per-block or per-instruction:

    8963  New SPU block compiled successfully   was success
    7833  Precompiling fallthrough              was notice
    4337  Precompiling filler space             was notice
    3449  SPU block is a loop                   was notice
    2418  MFC_EAH / MFC_Cmd not constant        was warning, per INSTRUCTION
    1516  Trampoline simplified                 was error, and is routine
     843  SPU Block Dump                        was notice, and is multi-line
     696  GETLLAR pattern entry point           was notice
    ~1400 PUTLLC16 / pattern breakage family    was notice and success

Upstream can afford these: a desktop has a fast disk and nobody is writing to
external storage. Demoted to trace, so they stay available by raising the SPU
channel and cost nothing during normal play.

The genuine faults keep their level -- MFC_Cmd invalid size and unknown command
are still errors, and they are rare.

This does not claim compilation is free. It removes the logging so what remains
can be measured, which is not possible while the instrument is this loud.
2026-08-20 17:03:56 -04:00
jpolo1224 a2f0059551 VK: run the conversion kernels 64-wide on Adreno
Adreno waves are 64 lanes, and a workgroup narrower than the wave does not pack
together with its neighbours -- it occupies a whole wave and masks the surplus
lanes off. At 32 that idled half of every wave on every dispatch, and these
kernels run on every texture upload of a kilobyte or more, plus every deswizzle
and detile.

Nothing argues the other way here. The kernels carry no shared memory and no
barriers, so group size is a scheduling hint with no LDS or synchronisation
cost to trade against.

Adreno and Turnip only. The comment this replaces claimed Mali was also 64-wide
and used that to justify holding everything at 32; that is wrong -- Valhall
warps are 16 lanes and Bifrost 4-8, so 32 already spans several of them and
there is no half-empty wave to reclaim. Xclipse is RDNA-derived and prefers
wave32 for compute. Both stay where they were.

Group size is baked into the generated GLSL, so this invalidates shader and
pipeline caches once on first launch after the update.
2026-08-20 16:22:21 -04:00
jpolo1224 cbee3cd44b VK: allow the compute work group size to be overridden for benchmarking
The mobile branch of the per-vendor group size table picks 32 by falling
through to the NVIDIA case. Adreno and Mali both run 64-wide waves, so 32
plausibly leaves half of each wave idle -- but that is reasoning, not a
measurement, and guessing wrong here costs performance silently.

Read ARMSX3_CS_GROUP_SIZE, which driver_env.txt already plumbs through
setenv(), so both candidates can be compared on one build without a settings
field or a second APK. Powers of two only, clamped to maxComputeWorkGroupSize
and maxComputeWorkGroupInvocations, because an over-large local_size_x fails
shader compilation rather than validation. The default is unchanged; this only
makes the question answerable.
2026-08-20 16:08:20 -04:00
jpolo1224 5d71742da9 VK: persist the driver pipeline cache across runs
Every vkCreate*Pipelines call passed VK_NULL_HANDLE for the pipeline cache, so
the driver re-did the whole of its own compilation work for every pipeline, in
every run. On mobile that work is a visible stall the first time each pipeline
is seen -- and it was being thrown away at every shutdown.

Give render_device a VkPipelineCache seeded from <cache>/vk_pipeline_cache.bin
and written back at teardown, and hand it to both pipeline creation calls. The
file is keyed on vendorID, deviceID and pipelineCacheUUID, so a driver update
or an adrenotools driver swap rejects the old blob and rebuilds rather than
feeding a driver a cache it cannot read. Oversized blobs are dropped instead of
being allowed to grow without bound -- the cache is shared by every title, so
one cold run is the cheaper failure.

This is orthogonal to the RSX shader cache: that one remembers WHICH pipelines
a title needs, this one makes each one cheap to create.

The shader interpreter was opening a private cache and destroying it on the way
out, which discarded exactly the expensive ubershader compiles. It now borrows
the device's. Teardown order already guarantees the pipe compiler threads are
joined before the device is destroyed, so the readback needs no extra locking.

vkGetPipelineCacheData was missing from the generated Android dispatch table;
regenerated with it, no other entry point changed.
2026-08-20 16:08:07 -04:00
jpolo1224 21a64b9eb7 Merge RPCS3 upstream: ROP output remap and an ISO magic-check fix
Seventeen commits. The substantial one is kd-11's ROP_OUTPUT_REMAP series
across rsx/fp, glsl and both backends, which ARMSX3 did not have at all.

Two conflicts.

nv4097.cpp: upstream added the ROP remap plumbing to the format-change checks,
we have profiler instrumentation and an ARM64 observe() on the two hot FIFO
reads. Different parts of the same file, so upstream's version is the base and
ours is re-applied on top. The g_xform_const_words increment is included
deliberately: the profiler reports average batch size as words/calls, so
dropping it would have printed 0 rather than nothing, which is worse than an
absent stat.

ISO.cpp: took upstream's magic-read check. It is a real fix --
`!file.read_at(...) == 5` parses as `(!x) == 5`, which is false for every x, so
the guard never fired and a short read left `magic` uninitialised. Our reverted
reader has no check there at all, and read_at returns a byte count in this
version too, so the corrected form applies cleanly.

This does NOT undo the ISO reader revert. The refactor that broke reading for
some users is still reverted; only the one-line magic check comes across.
2026-08-20 15:42:37 -04:00
jpolo1224 39571dc7c9 RPCN: stop sending requests with required fields empty
"Server error 1" is ErrorType::Malformed, and it was our fault rather than the
server's: two buttons posted queries with a required field blank, and the
server rejects the whole query rather than naming the field.

Reset password sends the account's email so the server can mail a token, but
the email box was only rendered while creating an account. Outside that mode it
sent an empty address every time. The box is always shown now.

Resend token sends the password, and Save deliberately clears that box, so
anyone who saved before pressing it sent an empty one. Having typed it a moment
ago is not the same as it being in the field.

Both are checked before sending now, along with account creation, and the
message names the field to fill in rather than failing at the server.

The error text is better too. Malformed, Invalid and the unknown case were
surfacing as a number with no way to act on it. Malformed now says outright
that it is a bug and asks which button was pressed, because if it appears again
the guards above have missed a path.

Not fixed here: sign-in reporting Invalid Password for credentials that work on
desktop. That is a distinct server code rather than a catch-all, and the cause
is not yet known -- desktop stores the password exactly as this does, so it is
not a hashing difference.
2026-08-20 14:06:40 -04:00
Megamouse bab81aa23e Fix rpcn type cast warnings 2026-08-20 11:03:16 +02:00
Megamouse 8a6c96745a Fix iso magic read check 2026-08-20 11:03:16 +02:00
Megamouse 358101c47b Fix unused variable warnings 2026-08-20 11:03:16 +02:00
yahfz c4eff69711 SPU LLVM: Document AVX-512 XFloat lowering 2026-08-20 09:36:19 +03:00
yahfz 5d2601599e [SPU LLVM] Optimize AVX-512 XFloat conversion 2026-08-20 09:36:19 +03:00
digant73 e5e280cd3f Fix duplicate path addition 2026-08-20 00:27:30 +02:00
Megamouse c122edd638 unpkg: Fix buffer size checks 2026-08-19 23:30:34 +02:00
Megamouse d12d8782af unpkg: Mark pkg installation as failed if any thread throws an exception 2026-08-19 23:30:34 +02:00
kd-11 64da425e81 gl: Fix copy_image_static behavior when formats are mismatched 2026-08-19 20:43:05 +02:00
kd-11 b78bae0b9f rsx: Integrate ROP remapping to the interpreter system
- Base pipelines have the remapping active, optimized variants can toggle it away.
2026-08-19 14:08:55 +03:00
kd-11 ca25fbaa5d vk/gl: Support ROP output remap in the interpreter 2026-08-19 14:08:55 +03:00
kd-11 323e2d35a2 rsx/fp: Implement support for ROP_OUTPUT_REMAP in the backends 2026-08-19 14:08:55 +03:00
kd-11 57ecb42433 rsx/fp: Plumb through support for channel remapping during ROP 2026-08-19 14:08:55 +03:00
kd-11 84ceef9717 rsx/glsl: Shrink command space in ROP_CONTROL structure to make room 2026-08-19 14:08:55 +03:00
kd-11 0dd3d6528c rsx/prog: Move in-shader color remap to FP prolog
- We need it for some other stuff
2026-08-19 14:08:55 +03:00
Antonino Di Guardo ddd82ecada Make optional VSH on File âž” All Titles âž” Create LLVM Caches dialog (#19270)
Make optional VSH CPU compilation on `File âž” All Titles âž” Create LLVM
Caches` dialog. By default it is now excluded due to VSH caches are
fully covered by the dedicated submenu `File âž” Firmware`.
2026-08-19 08:15:27 +02:00
Ani 7973b8ac6d windows: Fix clang x64/arm64 builds 2026-08-18 21:54:09 +02:00
206 changed files with 112338 additions and 197 deletions
+2 -2
View File
@@ -34,8 +34,8 @@ android {
// agree -- an APK that installs below its core's target is a dlopen failure at boot.
minSdk = (project.findProperty("armsx3.minSdk") as String?)?.toInt() ?: 33
targetSdk = 37
versionCode = 19
versionName = "0.9.3.1"
versionCode = 21
versionName = "0.9.4.1"
// ARMSX2's UI reads these. STORAGE_ALL_FILES gates the all-files storage path in
// onboarding; IN_APP_UPDATER gates the in-app GitHub-release updater.
@@ -23,6 +23,131 @@ target_link_libraries(${CMAKE_PROJECT_NAME}
adrenotools
)
# ---------------------------------------------------------------------------
# Flurry -> libflurry.so
#
# Calum Robinson's Flurry screensaver (2002), offered as a live library background. Its own
# target rather than folded into the glue for two reasons: it is BSD-3-clause next to GPL code
# and the boundary should be visible, and it is plain C from 2002 that wants none of the C++20
# and adrenotools the glue is built with.
#
# gl_compat.c answers the GL 1.x calls the sources make -- client-side vertex arrays, a
# fixed-function ortho and GL_QUADS -- with a GLES2 shader, so the renderer itself stays
# unmodified. See its header for what is and is not emulated.
# ---------------------------------------------------------------------------
add_library(flurry SHARED
flurry/flurry_jni.c
flurry/gl_compat.c
flurry/flurry.c
flurry/flurry-smoke.c
flurry/flurry-spark.c
flurry/flurry-star.c
flurry/flurry-texture.c
)
target_include_directories(flurry PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/flurry)
set_target_properties(flurry PROPERTIES C_STANDARD 99)
# -ffast-math: a particle simulation judged by eye, running thousands of sqrt per frame.
# Upstream shipped hand-written frsqrte paths for exactly this reason.
target_compile_options(flurry PRIVATE -O2 -ffast-math -Wall -Wno-unused-parameter)
target_link_libraries(flurry log GLESv2 m)
# ---------------------------------------------------------------------------
# Really Slick Screensavers -> libsavers.so
#
# Terry Welsh's savers (GPL-2.0-or-later), compiled against a GLES2 shim rather than rewritten.
# The saver sources are BYTE-IDENTICAL to upstream: they are built with RS_XSCREENSAVER, which
# selects their platform-neutral path, and everything that path expects is answered by
# compat/. That keeps them re-pullable and keeps them recognisably his code.
#
# Separate from the flurry target because these are C++ and Flurry is C99, and because Flurry's
# shim is a different, smaller one. If a saver ever needs Flurry's, the two can merge.
add_library(savers SHARED
savers/savers_jni.cpp
savers/savers_platform.cpp
savers/gl1.c
# Each saver is compiled through a *_unit.cpp that wraps it in a namespace: they all
# declare the same globals (draw, idleProc, cleanUp, readyToDraw) because each was built
# as its own executable, and two in one .so collide at link time.
savers/flux/flux_unit.cpp
savers/flux/flux_port.cpp
savers/plasma/plasma_unit.cpp
savers/plasma/plasma_port.cpp
savers/solarwinds/solarwinds_unit.cpp
savers/solarwinds/solarwinds_port.cpp
# Hyperspace. extensions.cpp is deliberately NOT built: it is the WIN32/GLX loader for the
# ARB shader path, and the port takes the non-shader path instead. The entry points it
# would have resolved are declared in compat/arb_shaders.h and defined in savers_platform.
savers/hyperspace/unit_causticTextures.cpp
savers/hyperspace/unit_flare.cpp
savers/hyperspace/unit_goo.cpp
savers/hyperspace/unit_hyperspace.cpp
savers/hyperspace/unit_splinePath.cpp
savers/hyperspace/unit_starBurst.cpp
savers/hyperspace/unit_stretchedParticle.cpp
savers/hyperspace/unit_tunnel.cpp
savers/hyperspace/unit_wavyNormalCubeMaps.cpp
savers/hyperspace/hyperspace_port.cpp
savers/lattice/lattice_unit.cpp
savers/lattice/lattice_port.cpp
# Skyrocket. soundEngine.cpp is NOT built and its ~7MB of samples are not shipped: upstream
# drives OpenAL, the port runs with dSound = 0, and soundEngine.h is a stub.
savers/skyrocket/unit_flare.cpp
savers/skyrocket/unit_particle.cpp
savers/skyrocket/unit_shockwave.cpp
savers/skyrocket/unit_skyrocket.cpp
savers/skyrocket/unit_smoke.cpp
savers/skyrocket/unit_world.cpp
savers/skyrocket/skyrocket_port.cpp
savers/rslibs/rsMath/rsVec.cpp
savers/rslibs/rsMath/rsVec4.cpp
savers/rslibs/rsMath/rsMatrix.cpp
savers/rslibs/rsMath/rsQuat.cpp
savers/rslibs/Rgbhsl/Rgbhsl.cpp
# Implicit surfaces: Hyperspace's "goo" is a marching-cubes isosurface.
savers/rslibs/Implicit/impSurface.cpp
savers/rslibs/Implicit/impCubeVolume.cpp
savers/rslibs/Implicit/impCubeTables.cpp
savers/rslibs/Implicit/impShape.cpp
savers/rslibs/Implicit/impSphere.cpp
savers/rslibs/Implicit/impCapsule.cpp
savers/rslibs/Implicit/impEllipsoid.cpp
savers/rslibs/Implicit/impHexahedron.cpp
savers/rslibs/Implicit/impRoundedHexahedron.cpp
savers/rslibs/Implicit/impKnot.cpp
savers/rslibs/Implicit/impTorus.cpp
)
target_include_directories(savers PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}/savers
${CMAKE_CURRENT_SOURCE_DIR}/savers/compat
${CMAKE_CURRENT_SOURCE_DIR}/savers/rslibs
${CMAKE_CURRENT_SOURCE_DIR}/savers/flux
${CMAKE_CURRENT_SOURCE_DIR}/savers/plasma
${CMAKE_CURRENT_SOURCE_DIR}/savers/solarwinds
${CMAKE_CURRENT_SOURCE_DIR}/savers/hyperspace
${CMAKE_CURRENT_SOURCE_DIR}/savers/lattice
${CMAKE_CURRENT_SOURCE_DIR}/savers/skyrocket
)
# RS_XSCREENSAVER picks the savers' platform-neutral path. Without it they compile their Win32
# shell and nothing links.
target_compile_definitions(savers PRIVATE RS_XSCREENSAVER)
set_target_properties(savers PROPERTIES C_STANDARD 99 CXX_STANDARD 17)
# -ffast-math for the same reason as Flurry: particle simulation judged entirely by eye.
target_compile_options(savers PRIVATE -O2 -ffast-math -Wall -Wno-unused-parameter)
target_link_libraries(savers log GLESv2 m)
# ---------------------------------------------------------------------------
# Discord Social SDK bridge -> libarmsx2_discord.so
#
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,295 @@
/*
Copyright (c) 2002, Calum Robinson
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
* Neither the name of the author nor the names of its contributors may be used
to endorse or promote products derived from this software without specific
prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
/* Spark.cpp: implementation of the Spark class. */
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
#include "flurry.h"
void InitSpark(Spark *s)
{
int i;
for (i=0;i<3;i++)
{
s->position[i] = RandFlt(-100.0, 100.0);
}
}
/* Immediate mode and the matrix stack, neither of which exists in GLES2.
*
* Left in place rather than deleted because it is Calum Robinson's code and the update half of
* this file is very much live -- UpdateSpark and UpdateSparkColour drive the smoke. Upstream
* never defines DRAW_SPARKS either, so this has not been compiled by xscreensaver in years;
* the guard just makes that explicit instead of relying on the caller staying commented out. */
#ifdef DRAW_SPARKS
void DrawSpark(global_info_t *global, flurry_info_t *flurry, Spark *s)
{
const float black[4] = {0.0f,0.0f,0.0f,1.0f};
float width,sx,sy;
float a;
float c = 0.0625f;
float screenx;
float screeny;
float w,z, scale;
int k;
width = 60000.0f * global->sys_glWidth / 1024.0f;
z = s->position[2];
sx = s->position[0] * global->sys_glWidth / z + global->sys_glWidth * 0.5f;
sy = s->position[1] * global->sys_glWidth / z + global->sys_glHeight * 0.5f;
w = width*4.0f / z;
screenx = sx;
screeny = sy;
glPushMatrix();
glTranslatef(screenx,screeny,0.0f);
scale = w/50.0f;
glScalef(scale,scale,0.0f);
for (k=0;k<12;k++)
{
a = ((float) (random() % 3600)) / 10.0f;
glRotatef(a,0.0f,0.0f,1.0f);
glBegin(GL_QUAD_STRIP);
glColor4fv(black);
glVertex2f(-3.0f,0.0f);
a = 2.0f + (float) (random() & 255) * c;
glVertex2f(-3.0f,a);
glColor4fv(s->color);
glVertex2f(0.0f,0.0f);
glColor4fv(black);
glVertex2f(0.0f,a);
glVertex2f(3.0f,0.0f);
glVertex2f(3.0f,a);
glEnd();
}
glPopMatrix();
}
#endif /* DRAW_SPARKS */
#define BIGMYSTERY 1800.0
#define MAXANGLES 16384
void UpdateSparkColour(global_info_t *global, flurry_info_t *flurry, Spark *s)
{
const float rotationsPerSecond = (float) (2.0*PI*fieldSpeed/MAXANGLES);
double thisPointInRadians;
double thisAngle = flurry->fTime*rotationsPerSecond;
/*float cf;*/
float cycleTime = 20.0f;
float colorRot;
float redPhaseShift;
float greenPhaseShift;
float bluePhaseShift;
float baseRed;
float baseGreen;
float baseBlue;
float colorTime;
if (flurry->currentColorMode == rainbowColorMode)
{
cycleTime = 1.5f;
}
else if (flurry->currentColorMode == tiedyeColorMode)
{
cycleTime = 4.5f;
}
else if (flurry->currentColorMode == cyclicColorMode)
{
cycleTime = 20.0f;
}
else if (flurry->currentColorMode == slowCyclicColorMode)
{
cycleTime = 120.0f;
}
colorRot = (float) (2.0*PI/cycleTime);
redPhaseShift = 0.0f; /* cycleTime * 0.0f / 3.0f */
greenPhaseShift = cycleTime / 3.0f;
bluePhaseShift = cycleTime * 2.0f / 3.0f ;
colorTime = flurry->fTime;
if (flurry->currentColorMode == whiteColorMode)
{
baseRed = 0.1875f;
baseGreen = 0.1875f;
baseBlue = 0.1875f;
}
else if (flurry->currentColorMode == multiColorMode)
{
baseRed = 0.0625f;
baseGreen = 0.0625f;
baseBlue = 0.0625f;
}
else if (flurry->currentColorMode == darkColorMode)
{
baseRed = 0.0f;
baseGreen = 0.0f;
baseBlue = 0.0f;
}
else
{
if (flurry->currentColorMode < slowCyclicColorMode)
{
colorTime = (flurry->currentColorMode / 6.0f) * cycleTime;
}
else
{
colorTime = flurry->fTime + flurry->flurryRandomSeed;
}
baseRed = 0.109375f * ((float) cos((colorTime+redPhaseShift)*colorRot)+1.0f);
baseGreen = 0.109375f * ((float) cos((colorTime+greenPhaseShift)*colorRot)+1.0f);
baseBlue = 0.109375f * ((float) cos((colorTime+bluePhaseShift)*colorRot)+1.0f);
}
/*
cf = ((float) (cos(7.0*((flurry->fTime)*rotationsPerSecond))+cos(3.0*((flurry->fTime)*rotationsPerSecond))+cos(13.0*((flurry->fTime)*rotationsPerSecond))));
cf /= 6.0f;
cf += 2.0f;
*/
thisPointInRadians = 2.0 * PI * (double) s->mystery / (double) BIGMYSTERY;
s->color[0] = baseRed + 0.0625f * (0.5f + (float) cos((15.0 * (thisPointInRadians + 3.0*thisAngle))) + (float) sin((7.0 * (thisPointInRadians + thisAngle))));
s->color[1] = baseGreen + 0.0625f * (0.5f + (float) sin(((thisPointInRadians) + thisAngle)));
s->color[2] = baseBlue + 0.0625f * (0.5f + (float) cos((37.0 * (thisPointInRadians + thisAngle))));
}
void UpdateSpark(global_info_t *global, flurry_info_t *flurry, Spark *s)
{
const float rotationsPerSecond = (float) (2.0*PI*fieldSpeed/MAXANGLES);
double thisPointInRadians;
double thisAngle = flurry->fTime*rotationsPerSecond;
float cf;
int i;
double tmpX1,tmpY1,tmpZ1;
double tmpX2,tmpY2,tmpZ2;
double tmpX3,tmpY3,tmpZ3;
double tmpX4,tmpY4,tmpZ4;
double rotation;
double cr;
double sr;
float cycleTime = 20.0f;
float colorRot;
float redPhaseShift;
float greenPhaseShift;
float bluePhaseShift;
float baseRed;
float baseGreen;
float baseBlue;
float colorTime;
float old[3];
if (flurry->currentColorMode == rainbowColorMode) {
cycleTime = 1.5f;
} else if (flurry->currentColorMode == tiedyeColorMode) {
cycleTime = 4.5f;
} else if (flurry->currentColorMode == cyclicColorMode) {
cycleTime = 20.0f;
} else if (flurry->currentColorMode == slowCyclicColorMode) {
cycleTime = 120.0f;
}
colorRot = (float) (2.0*PI/cycleTime);
redPhaseShift = 0.0f; /* cycleTime * 0.0f / 3.0f */
greenPhaseShift = cycleTime / 3.0f;
bluePhaseShift = cycleTime * 2.0f / 3.0f ;
colorTime = flurry->fTime;
if (flurry->currentColorMode == whiteColorMode) {
baseRed = 0.1875f;
baseGreen = 0.1875f;
baseBlue = 0.1875f;
} else if (flurry->currentColorMode == multiColorMode) {
baseRed = 0.0625f;
baseGreen = 0.0625f;
baseBlue = 0.0625f;
} else if (flurry->currentColorMode == darkColorMode) {
baseRed = 0.0f;
baseGreen = 0.0f;
baseBlue = 0.0f;
} else {
if(flurry->currentColorMode < slowCyclicColorMode) {
colorTime = (flurry->currentColorMode / 6.0f) * cycleTime;
} else {
colorTime = flurry->fTime + flurry->flurryRandomSeed;
}
baseRed = 0.109375f * ((float) cos((colorTime+redPhaseShift)*colorRot)+1.0f);
baseGreen = 0.109375f * ((float) cos((colorTime+greenPhaseShift)*colorRot)+1.0f);
baseBlue = 0.109375f * ((float) cos((colorTime+bluePhaseShift)*colorRot)+1.0f);
}
for (i=0;i<3;i++) {
old[i] = s->position[i];
}
cf = ((float) (cos(7.0*((flurry->fTime)*rotationsPerSecond))+cos(3.0*((flurry->fTime)*rotationsPerSecond))+cos(13.0*((flurry->fTime)*rotationsPerSecond))));
cf /= 6.0f;
cf += 2.0f;
thisPointInRadians = 2.0 * PI * (double) s->mystery / (double) BIGMYSTERY;
s->color[0] = baseRed + 0.0625f * (0.5f + (float) cos((15.0 * (thisPointInRadians + 3.0*thisAngle))) + (float) sin((7.0 * (thisPointInRadians + thisAngle))));
s->color[1] = baseGreen + 0.0625f * (0.5f + (float) sin(((thisPointInRadians) + thisAngle)));
s->color[2] = baseBlue + 0.0625f * (0.5f + (float) cos((37.0 * (thisPointInRadians + thisAngle))));
s->position[0] = fieldRange * cf * (float) cos(11.0 * (thisPointInRadians + (3.0*thisAngle)));
s->position[1] = fieldRange * cf * (float) sin(12.0 * (thisPointInRadians + (4.0*thisAngle)));
s->position[2] = fieldRange * (float) cos((23.0 * (thisPointInRadians + (12.0*thisAngle))));
rotation = thisAngle*0.501 + 5.01 * (double) s->mystery / (double) BIGMYSTERY;
cr = cos(rotation);
sr = sin(rotation);
tmpX1 = s->position[0] * cr - s->position[1] * sr;
tmpY1 = s->position[1] * cr + s->position[0] * sr;
tmpZ1 = s->position[2];
tmpX2 = tmpX1 * cr - tmpZ1 * sr;
tmpY2 = tmpY1;
tmpZ2 = tmpZ1 * cr + tmpX1 * sr;
tmpX3 = tmpX2;
tmpY3 = tmpY2 * cr - tmpZ2 * sr;
tmpZ3 = tmpZ2 * cr + tmpY2 * sr + seraphDistance;
rotation = thisAngle*2.501 + 85.01 * (double) s->mystery / (double) BIGMYSTERY;
cr = cos(rotation);
sr = sin(rotation);
tmpX4 = tmpX3 * cr - tmpY3 * sr;
tmpY4 = tmpY3 * cr + tmpX3 * sr;
tmpZ4 = tmpZ3;
s->position[0] = (float) tmpX4 + RandBell(5.0f*fieldCoherence);
s->position[1] = (float) tmpY4 + RandBell(5.0f*fieldCoherence);
s->position[2] = (float) tmpZ4 + RandBell(5.0f*fieldCoherence);
for (i=0;i<3;i++) {
s->delta[i] = (s->position[i] - old[i])/flurry->fDeltaTime;
}
}
@@ -0,0 +1,106 @@
/*
Copyright (c) 2002, Calum Robinson
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
* Neither the name of the author nor the names of its contributors may be used
to endorse or promote products derived from this software without specific
prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
/* Star.c: implementation of the Star class. */
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
#include "flurry.h"
/* Construction/Destruction */
void InitStar(Star *s)
{
int i;
for (i=0;i<3;i++) {
s->position[i] = RandFlt(-10000.0, 10000.0);
}
s->rotSpeed = RandFlt(0.4, 0.9);
s->mystery = RandFlt(0.0, 10.0);
}
#define BIGMYSTERY 1800.0
#define MAXANGLES 16384
void UpdateStar(global_info_t *global, flurry_info_t *flurry, Star *s)
{
float rotationsPerSecond = (float) (2.0*PI*12.0/MAXANGLES) * s->rotSpeed /* speed control */;
double thisPointInRadians;
double thisAngle = flurry->fTime*rotationsPerSecond;
float cf;
double tmpX1,tmpY1,tmpZ1;
double tmpX2,tmpY2,tmpZ2;
double tmpX3,tmpY3,tmpZ3;
double tmpX4,tmpY4,tmpZ4;
double rotation;
double cr;
double sr;
s->ate = 0;
cf = ((float) (cos(7.0*((flurry->fTime)*rotationsPerSecond))+cos(3.0*((flurry->fTime)*rotationsPerSecond))+cos(13.0*((flurry->fTime)*rotationsPerSecond))));
cf /= 6.0f;
cf += 0.75f;
thisPointInRadians = 2.0 * PI * (double) s->mystery / (double) BIGMYSTERY;
s->position[0] = 250.0f * cf * (float) cos(11.0 * (thisPointInRadians + (3.0*thisAngle)));
s->position[1] = 250.0f * cf * (float) sin(12.0 * (thisPointInRadians + (4.0*thisAngle)));
s->position[2] = 250.0f * (float) cos((23.0 * (thisPointInRadians + (12.0*thisAngle))));
rotation = thisAngle*0.501 + 5.01 * (double) s->mystery / (double) BIGMYSTERY;
cr = cos(rotation);
sr = sin(rotation);
tmpX1 = s->position[0] * cr - s->position[1] * sr;
tmpY1 = s->position[1] * cr + s->position[0] * sr;
tmpZ1 = s->position[2];
tmpX2 = tmpX1 * cr - tmpZ1 * sr;
tmpY2 = tmpY1;
tmpZ2 = tmpZ1 * cr + tmpX1 * sr;
tmpX3 = tmpX2;
tmpY3 = tmpY2 * cr - tmpZ2 * sr;
tmpZ3 = tmpZ2 * cr + tmpY2 * sr + seraphDistance;
rotation = thisAngle*2.501 + 85.01 * (double) s->mystery / (double) BIGMYSTERY;
cr = cos(rotation);
sr = sin(rotation);
tmpX4 = tmpX3 * cr - tmpY3 * sr;
tmpY4 = tmpY3 * cr + tmpX3 * sr;
tmpZ4 = tmpZ3;
s->position[0] = (float) tmpX4;
s->position[1] = (float) tmpY4;
s->position[2] = (float) tmpZ4;
}
@@ -0,0 +1,238 @@
/*
Copyright (c) 2002, Calum Robinson
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
* Neither the name of the author nor the names of its contributors may be used
to endorse or promote products derived from this software without specific
prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
/*
* Texture.c
* AppleFlurry
*
* Created by calumr on Sat Jul 07 2001.
* Copyright (c) 2001 __CompanyName__. All rights reserved.
*
*/
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
#include "flurry.h"
/* #include <GL/gl.h> */
/* #include <GL/glu.h> */
#include <stdlib.h>
#include <math.h>
static GLubyte smallTextureArray[32][32];
static GLubyte bigTextureArray[256][256][2];
/* simple smoothing routine */
static void SmoothTexture(void)
{
GLubyte filter[32][32];
int i,j;
float t;
for (i=1;i<31;i++)
{
for (j=1;j<31;j++)
{
t = (float) smallTextureArray[i][j]*4;
t += (float) smallTextureArray[i-1][j];
t += (float) smallTextureArray[i+1][j];
t += (float) smallTextureArray[i][j-1];
t += (float) smallTextureArray[i][j+1];
t /= 8.0f;
filter[i][j] = (GLubyte) t;
}
}
for (i=1;i<31;i++)
{
for (j=1;j<31;j++)
{
smallTextureArray[i][j] = filter[i][j];
}
}
}
/* add some randomness to texture data */
static void SpeckleTexture(void)
{
int i,j;
int speck;
float t;
for (i=2;i<30;i++)
{
for (j=2;j<30;j++)
{
speck = 1;
while (speck <= 32 && random() % 2)
{
t = (float) MIN_(255,smallTextureArray[i][j]+speck);
smallTextureArray[i][j] = (GLubyte) t;
speck+=speck;
}
speck = 1;
while (speck <= 32 && random() % 2)
{
t = (float) MAX_(0,smallTextureArray[i][j]-speck);
smallTextureArray[i][j] = (GLubyte) t;
speck+=speck;
}
}
}
}
static void MakeSmallTexture(void)
{
static int firstTime = 1;
int i,j;
float r,t;
if (firstTime)
{
firstTime = 0;
for (i=0;i<32;i++)
{
for (j=0;j<32;j++)
{
r = (float) sqrt((i-15.5)*(i-15.5)+(j-15.5)*(j-15.5));
if (r > 15.0f)
{
smallTextureArray[i][j] = 0;
}
else
{
t = 255.0f * (float) cos(r*M_PI/31.0);
smallTextureArray[i][j] = (GLubyte) t;
}
}
}
}
else
{
for (i=0;i<32;i++)
{
for (j=0;j<32;j++)
{
r = (float) sqrt((i-15.5)*(i-15.5)+(j-15.5)*(j-15.5));
if (r > 15.0f)
{
t = 0.0f;
}
else
{
t = 255.0f * (float) cos(r*M_PI/31.0);
}
smallTextureArray[i][j] = (GLubyte) MIN_(255,(t+smallTextureArray[i][j]+smallTextureArray[i][j])/3);
}
}
}
SpeckleTexture();
SmoothTexture();
SmoothTexture();
}
static void CopySmallTextureToBigTexture(int k, int l)
{
int i,j;
for (i=0;i<32;i++)
{
for (j=0;j<32;j++)
{
bigTextureArray[i+k][j+l][0] = smallTextureArray[i][j];
bigTextureArray[i+k][j+l][1] = smallTextureArray[i][j];
}
}
}
static void AverageLastAndFirstTextures(void)
{
int i,j;
int t;
for (i=0;i<32;i++)
{
for (j=0;j<32;j++)
{
t = (smallTextureArray[i][j] + bigTextureArray[i][j][0]) / 2;
smallTextureArray[i][j] = (GLubyte) MIN_(255,t);
}
}
}
GLuint MakeTexture(void)
{
GLuint theTexture = 0;
int i,j;
for (i=0;i<8;i++)
{
for (j=0;j<8;j++)
{
if (i==7 && j==7)
{
AverageLastAndFirstTextures();
}
else
{
MakeSmallTexture();
}
CopySmallTextureToBigTexture(i*32,j*32);
}
}
glPixelStorei(GL_UNPACK_ALIGNMENT,1);
glGenTextures(1, &theTexture);
glBindTexture(GL_TEXTURE_2D, theTexture);
/* Set the tiling mode (this is generally always GL_REPEAT). */
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
/* Set the filtering. */
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
/* GL_LINEAR, not LINEAR_MIPMAP_NEAREST.
*
* A mipmapped min filter makes the texture INCOMPLETE unless the whole chain exists, and an
* incomplete texture samples as opaque black -- which, through this shader's c *= texture,
* multiplies the entire particle to nothing. glGenerateMipmap is also not guaranteed for
* GL_LUMINANCE_ALPHA, which is filterable but not colour-renderable, so the chain may never
* have been built. The sprite is a 256x256 blob drawn at roughly its own size; the mip chain
* was never doing much for it. */
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
/* gluBuild2DMipmaps upstream. There is no GLU on Android, and GLES2 builds the chain
* itself -- the internal format has to be spelled out rather than given as a component
* count, which is what the 2 meant in GL 1.x. */
glTexImage2D(GL_TEXTURE_2D, 0, GL_LUMINANCE_ALPHA, 256, 256, 0,
GL_LUMINANCE_ALPHA, GL_UNSIGNED_BYTE, bigTextureArray);
/* No glGenerateMipmap: see the min filter above. */
glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
return theTexture;
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,289 @@
/*
Copyright (c) 2002, Calum Robinson
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
* Neither the name of the author nor the names of its contributors may be used
to endorse or promote products derived from this software without specific
prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
/* -*- Mode: C; tab-width: 4 c-basic-offset: 4 indent-tabs-mode: t -*- */
/* flurry */
#ifndef __GLCODE__
#define __GLCODE__
/* Ported to Android/GLES2: see flurry_port.h for what these supplied. */
#include "flurry_port.h"
#include <stdlib.h>
#include <math.h>
typedef struct _global_info_t global_info_t;
typedef struct _flurry_info_t flurry_info_t;
#define sqr(X) ((X) * (X))
#define PI M_PI
#define DEG2RAD(X) (PI*(X)/180.0)
#define RAD2DEG(X) ((X)*180.0/PI)
#define rnd() (frand(1.0))
/* fabs: Absolute function. */
/* #undef abs */
/* #define abs(a) ( (a) > 0 ? (a) : -(a) ) */
/* Force sign clamping to (-1;0;1) */
#define sgn(a) ((a)<0?-1:((a)?1:0))
/* used to compute the min and max of two expresions */
#define MIN_(a, b) (((a) < (b)) ? (a) : (b))
#define MAX_(a, b) (((a) > (b)) ? (a) : (b))
typedef union {
float f[4];
#if 0
#if __VEC__
vector float v;
#endif
#endif /* 0 */
} floatToVector;
typedef union {
unsigned int i[4];
#if 0
#if __VEC__
vector unsigned int v;
#endif
#endif /* 0 */
} intToVector;
typedef struct SmokeParticleV
{
floatToVector color[4];
floatToVector position[3];
floatToVector oldposition[3];
floatToVector delta[3];
intToVector dead;
floatToVector time;
intToVector animFrame;
} SmokeParticleV;
#define NUMSMOKEPARTICLES 3600
typedef struct SmokeV
{
SmokeParticleV p[NUMSMOKEPARTICLES/4];
int nextParticle;
int nextSubParticle;
float lastParticleTime;
int firstTime;
long frame;
float old[3];
floatToVector seraphimVertices[NUMSMOKEPARTICLES*2+1];
floatToVector seraphimColors[NUMSMOKEPARTICLES*4+1];
float seraphimTextures[NUMSMOKEPARTICLES*2*4];
} SmokeV;
void InitSmoke(SmokeV *s);
void UpdateSmoke_ScalarBase(global_info_t *global, flurry_info_t *flurry, SmokeV *s);
#if 0
#ifdef __ppc__
void UpdateSmoke_ScalarFrsqrte(global_info_t *global, flurry_info_t *flurry, SmokeV *s);
#endif
#ifdef __VEC__
void UpdateSmoke_VectorBase(global_info_t *global, flurry_info_t *flurry, SmokeV *s);
void UpdateSmoke_VectorUnrolled(global_info_t *global, flurry_info_t *flurry, SmokeV *s);
#endif
#endif /* 0 */
void DrawSmoke_Scalar(global_info_t *global, flurry_info_t *flurry, SmokeV *s, float);
void DrawSmoke_Vector(global_info_t *global, flurry_info_t *flurry, SmokeV *s, float);
typedef struct Star
{
float position[3];
float mystery;
float rotSpeed;
int ate;
} Star;
void UpdateStar(global_info_t *global, flurry_info_t *flurry, Star *s);
void InitStar(Star *s);
typedef struct Spark
{
float position[3];
int mystery;
float delta[3];
float color[4];
} Spark;
void UpdateSparkColour(global_info_t *info, flurry_info_t *flurry, Spark *s);
void InitSpark(Spark *s);
void UpdateSpark(global_info_t *info, flurry_info_t *flurry, Spark *s);
void DrawSpark(global_info_t *info, flurry_info_t *flurry, Spark *s);
/* #define FastDistance2D(x,y) hypot(x,y) */
/* UInt8 sys_glBPP=32; */
/* int SSMODE = FALSE; */
/* int currentVideoMode = 0; */
/* int cohesiveness = 7; */
/* int fieldStrength; */
/* int colorCoherence = 7; */
/* int fieldIncoherence = 0; */
/* int ifieldSpeed = 120; */
static inline float FastDistance2D(float x, float y)
{
/* this function computes the distance from 0,0 to x,y with ~3.5% error */
float mn;
/* first compute the absolute value of x,y */
x = (x < 0.0f) ? -x : x;
y = (y < 0.0f) ? -y : y;
/* compute the minimum of x,y */
mn = x<y?x:y;
/* return the distance */
return(x+y-(mn*0.5f)-(mn*0.25f)+(mn*0.0625f));
}
#if 0
#ifdef __VEC__
static vector float FastDistance2DV(vector float x, vector float y) {
vector float mn, temp;
x = vec_abs(x);
y = vec_abs(y);
mn = vec_min(x,y);
temp = vec_add(x,y);
temp = vec_madd(mn, (vector float)(-0.6875), temp);
return temp;
}
#endif
#endif /* 0 */
#define RandFlt(min, max) ((min) + frand((max) - (min)))
#define RandBell(scale) ((scale) * (-(frand(.5) + frand(.5) + frand(.5))))
extern GLuint theTexture;
GLuint MakeTexture(void);
#define OPT_MODE_SCALAR_BASE 0x0
#if 0
#ifdef __ppc__
#define OPT_MODE_SCALAR_FRSQRTE 0x1
#endif
#ifdef __VEC__
#define OPT_MODE_VECTOR_SIMPLE 0x2
#define OPT_MODE_VECTOR_UNROLLED 0x3
#endif
#endif /* 0 */
typedef enum _ColorModes
{
redColorMode = 0,
magentaColorMode,
blueColorMode,
cyanColorMode,
greenColorMode,
yellowColorMode,
slowCyclicColorMode,
cyclicColorMode,
tiedyeColorMode,
rainbowColorMode,
whiteColorMode,
multiColorMode,
darkColorMode
} ColorModes;
#define gravity 1500000.0f
#define incohesion 0.07f
#define colorIncoherence 0.15f
#define streamSpeed 450.0
#define fieldCoherence 0
#define fieldSpeed 12.0f
#define numParticles 250
#define starSpeed 50
#define seraphDistance 2000.0f
#define streamSize 25000.0f
#define fieldRange 1000.0f
#define streamBias 7.0f
#define MAX_SPARKS 64
struct _flurry_info_t {
flurry_info_t *next;
ColorModes currentColorMode;
SmokeV *s;
Star *star;
Spark *spark[MAX_SPARKS];
float streamExpansion;
int numStreams;
double flurryRandomSeed;
double fTime;
double fOldTime;
double fDeltaTime;
double briteFactor;
float drag;
int dframe;
};
struct _global_info_t {
/* system values */
int optMode;
/* Port-only. Flurry never clears: it fades the screen by a few percent each frame, which
* is how the trails exist. That assumes it owns a buffer that started black. Here it gets
* a rotating set of swapchain buffers full of uninitialised GPU memory, and an 8% fade
* cannot win against that -- it shows as static the smoke sits behind. Clear each buffer
* once, then hand over to the fade. */
int port_clear_frames;
float sys_glWidth;
float sys_glHeight;
double gTimeCounter;
int first;
double oldFrameTime;
flurry_info_t *flurry;
GLuint texid;
};
#define kNumSpectrumEntries 512
double TimeInSecondsSinceStart(const global_info_t *global);
#endif /* Include/Define */
@@ -0,0 +1,108 @@
/*
* JNI shim between the wallpaper's GL thread and Flurry.
*
* Every function here must be called with the EGL context current -- the Kotlin side owns the
* context and guarantees that. Nothing is thread-safe beyond that assumption, which is fine
* because a wallpaper engine has exactly one GL thread.
*/
#include <jni.h>
#include <android/log.h>
#include <stdlib.h>
#include <time.h>
#include "gl_compat.h"
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, "Flurry", __VA_ARGS__)
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, "Flurry", __VA_ARGS__)
typedef struct _global_info_t global_info_t;
global_info_t *flurry_port_new(int preset);
global_info_t *flurry_port_new_custom(int streams, int colour, float thickness,
float speed, float brightness);
void flurry_port_resize(global_info_t *global, int width, int height);
int flurry_port_draw(global_info_t *global);
void flurry_port_free(global_info_t *global);
#define NS(name) Java_com_armsx2_ui_home_FlurryNative_##name
JNIEXPORT jlong JNICALL NS(nativeCreate)(JNIEnv *env, jclass clazz, jint preset)
{
(void) env; (void) clazz;
/* Flurry's entire look comes out of frand(); an unseeded run would be identical every
* time the wallpaper restarts, which on a phone is many times a day. */
srandom((unsigned) time(NULL) ^ (unsigned) clock());
if (!fx_init()) {
LOGE("GL compatibility layer failed to initialise");
return 0;
}
global_info_t *g = flurry_port_new(preset);
if (!g) {
LOGE("flurry_port_new failed");
return 0;
}
LOGI("Flurry started, preset %d", preset);
return (jlong) (intptr_t) g;
}
JNIEXPORT jlong JNICALL NS(nativeCreateCustom)(JNIEnv *env, jclass clazz, jint streams,
jint colour, jfloat thickness, jfloat speed,
jfloat brightness)
{
(void) env; (void) clazz;
srandom((unsigned) time(NULL) ^ (unsigned) clock());
if (!fx_init()) {
LOGE("GL compatibility layer failed to initialise");
return 0;
}
global_info_t *g = flurry_port_new_custom(streams, colour, thickness, speed, brightness);
if (!g) {
LOGE("flurry_port_new_custom failed");
return 0;
}
LOGI("Flurry started, custom: streams=%d colour=%d thickness=%.2f speed=%.2f brightness=%.2f",
streams, colour, (double) thickness, (double) speed, (double) brightness);
return (jlong) (intptr_t) g;
}
JNIEXPORT void JNICALL NS(nativeResize)(JNIEnv *env, jclass clazz, jlong handle, jint w, jint h)
{
(void) env; (void) clazz;
if (handle) flurry_port_resize((global_info_t *) (intptr_t) handle, w, h);
}
JNIEXPORT jboolean JNICALL NS(nativeDraw)(JNIEnv *env, jclass clazz, jlong handle)
{
(void) env; (void) clazz;
if (!handle) return JNI_FALSE;
return flurry_port_draw((global_info_t *) (intptr_t) handle) ? JNI_TRUE : JNI_FALSE;
}
JNIEXPORT void JNICALL NS(nativeDestroy)(JNIEnv *env, jclass clazz, jlong handle)
{
(void) env; (void) clazz;
if (!handle) return;
flurry_port_free((global_info_t *) (intptr_t) handle);
fx_shutdown();
}
/*
* The context went away without us being able to tidy up -- the usual case when a wallpaper is
* torn down. Forget the GL names instead of deleting them: the objects are already gone with
* the context, and deleting a name in a context that no longer exists is undefined.
*/
JNIEXPORT void JNICALL NS(nativeContextLost)(JNIEnv *env, jclass clazz, jlong handle)
{
(void) env; (void) clazz;
if (handle) flurry_port_free((global_info_t *) (intptr_t) handle);
fx_lost();
}
@@ -0,0 +1,27 @@
/*
* The Android environment for Calum Robinson's Flurry.
*
* Upstream this comes from xscreensaver: xlockmoreI.h pulls in the GL headers and the ModeInfo
* plumbing, yarandom.h supplies the RNG. Neither exists here, and the parts Flurry actually
* uses are small enough to state directly.
*/
#ifndef FLURRY_PORT_H
#define FLURRY_PORT_H
#include <stdlib.h>
#include <math.h>
#include <sys/time.h> /* gettimeofday, via xlockmoreI.h upstream */
#include "gl_compat.h"
/* Normally set by xscreensaver's configure. Bionic's gettimeofday takes two arguments like
* every other POSIX system, so the one-argument branch in currentTime() would not compile. */
#define GETTIMEOFDAY_TWO_ARGS 1
/* xscreensaver's yarandom.h: a float in [0, f). random() is bionic's, seeded in flurry_jni.c
* -- the smoke is driven entirely by it, so an unseeded run would look identical every time. */
#ifndef frand
#define frand(f) ((float) ((double) (f) * ((double) random() / ((double) RAND_MAX + 1.0))))
#endif
#endif /* FLURRY_PORT_H */
@@ -0,0 +1,329 @@
#include "gl_compat.h"
/*
* Undo the redirection for this file.
*
* gl_compat.h rewrites glEnable/glDisable/glDrawArrays and friends into the fx_* entry points
* so the Flurry sources need no edits -- but this file IMPLEMENTS those entry points, and it
* has to be able to call the real GL underneath them. Without these undefs, fx_enable's own
* glEnable(cap) expands to fx_enable(cap) and recurses forever. At -O2 that is a tail call and
* becomes an infinite LOOP rather than a stack overflow, so it does not crash: the thread just
* spins at 100% and nothing after it ever runs. The first glDisable in GLSetupRC was enough to
* hang the whole renderer with no error anywhere.
*/
#undef glEnable
#undef glDisable
#undef glDrawArrays
#undef glVertexPointer
#undef glColorPointer
#undef glTexCoordPointer
#undef glEnableClientState
#undef glDisableClientState
#undef glColor4f
#undef glColor3f
#undef glRectd
#undef glMatrixMode
#undef glLoadIdentity
#undef glOrtho
#undef glAlphaFunc
#undef glShadeModel
#undef glTexEnvf
#undef glDrawBuffer
#undef glFinish
#include <android/log.h>
#include <stdlib.h>
#include <string.h>
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, "Flurry", __VA_ARGS__)
/* Flurry draws at most NUMSMOKEPARTICLES quads in one call. Kept here rather than including
* flurry.h so this file stays independent of it; the assert in fx_draw_arrays catches it if
* the particle count ever grows past what the index buffer covers. */
#define FX_MAX_QUADS 4096
static const char *kVert =
"attribute vec2 aPos;\n"
"attribute vec4 aColor;\n"
"attribute vec2 aTex;\n"
"uniform mat4 uProj;\n"
"varying vec4 vColor;\n"
"varying vec2 vTex;\n"
"void main() {\n"
" vColor = aColor;\n"
" vTex = aTex;\n"
" gl_Position = uProj * vec4(aPos, 0.0, 1.0);\n"
"}\n";
/* mediump is deliberate: the smoke is additive and low contrast, highp costs real time on
* mobile fragment hardware, and this runs behind a home screen. */
static const char *kFrag =
"precision mediump float;\n"
"uniform sampler2D uTex;\n"
"uniform int uUseTex;\n"
"varying vec4 vColor;\n"
"varying vec2 vTex;\n"
"void main() {\n"
" vec4 c = vColor;\n"
" if (uUseTex == 1) c *= texture2D(uTex, vTex);\n"
" gl_FragColor = c;\n"
"}\n";
typedef struct {
GLint size;
GLenum type;
GLsizei stride;
const void *ptr;
int enabled;
} fx_array;
static struct {
GLuint program;
GLuint ibo;
GLint aPos, aColor, aTex;
GLint uProj, uTex, uUseTex;
GLfloat proj[16];
fx_array vertex, color, texcoord;
GLfloat current_color[4];
int ready;
} fx;
static GLuint compile(GLenum type, const char *src)
{
GLuint sh = glCreateShader(type);
if (!sh) return 0;
glShaderSource(sh, 1, &src, NULL);
glCompileShader(sh);
GLint ok = 0;
glGetShaderiv(sh, GL_COMPILE_STATUS, &ok);
if (!ok) {
char log[512];
glGetShaderInfoLog(sh, sizeof(log), NULL, log);
LOGE("shader compile failed: %s", log);
glDeleteShader(sh);
return 0;
}
return sh;
}
int fx_init(void)
{
if (fx.ready) return 1;
memset(&fx, 0, sizeof(fx));
GLuint vs = compile(GL_VERTEX_SHADER, kVert);
GLuint fs = compile(GL_FRAGMENT_SHADER, kFrag);
if (!vs || !fs) return 0;
fx.program = glCreateProgram();
glAttachShader(fx.program, vs);
glAttachShader(fx.program, fs);
glLinkProgram(fx.program);
GLint ok = 0;
glGetProgramiv(fx.program, GL_LINK_STATUS, &ok);
if (!ok) {
char log[512];
glGetProgramInfoLog(fx.program, sizeof(log), NULL, log);
LOGE("program link failed: %s", log);
return 0;
}
/* Attached shaders are refcounted by the program and can go now. */
glDeleteShader(vs);
glDeleteShader(fs);
fx.aPos = glGetAttribLocation(fx.program, "aPos");
fx.aColor = glGetAttribLocation(fx.program, "aColor");
fx.aTex = glGetAttribLocation(fx.program, "aTex");
fx.uProj = glGetUniformLocation(fx.program, "uProj");
fx.uTex = glGetUniformLocation(fx.program, "uTex");
fx.uUseTex = glGetUniformLocation(fx.program, "uUseTex");
/* One static index buffer for every quad draw. GL_QUADS is (0,1,2,3) per quad in draw
* order, so each becomes two triangles sharing the 0-2 diagonal. Built once because the
* pattern never depends on the data, only on how many quads are drawn. */
GLushort *idx = (GLushort *) malloc(sizeof(GLushort) * FX_MAX_QUADS * 6);
if (!idx) return 0;
for (int q = 0; q < FX_MAX_QUADS; q++) {
const GLushort base = (GLushort) (q * 4);
GLushort *o = idx + q * 6;
o[0] = base; o[1] = (GLushort)(base + 1); o[2] = (GLushort)(base + 2);
o[3] = base; o[4] = (GLushort)(base + 2); o[5] = (GLushort)(base + 3);
}
glGenBuffers(1, &fx.ibo);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, fx.ibo);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(GLushort) * FX_MAX_QUADS * 6, idx, GL_STATIC_DRAW);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
free(idx);
fx.ready = 1;
return 1;
}
void fx_shutdown(void)
{
if (fx.program) glDeleteProgram(fx.program);
if (fx.ibo) glDeleteBuffers(1, &fx.ibo);
memset(&fx, 0, sizeof(fx));
}
void fx_lost(void)
{
/* The context is already gone; deleting these names would be undefined. */
memset(&fx, 0, sizeof(fx));
}
void fx_ortho(float left, float right, float bottom, float top)
{
/* Column-major, near/far fixed at -1/1: Flurry is entirely 2D. */
const float rl = right - left, tb = top - bottom;
memset(fx.proj, 0, sizeof(fx.proj));
fx.proj[0] = 2.0f / rl;
fx.proj[5] = 2.0f / tb;
fx.proj[10] = -1.0f;
fx.proj[12] = -(right + left) / rl;
fx.proj[13] = -(top + bottom) / tb;
fx.proj[15] = 1.0f;
}
static void set_array(fx_array *a, GLint size, GLenum type, GLsizei stride, const void *ptr)
{
a->size = size;
a->type = type;
a->stride = stride;
a->ptr = ptr;
}
void fx_vertex_pointer(GLint size, GLenum type, GLsizei stride, const void *ptr) { set_array(&fx.vertex, size, type, stride, ptr); }
void fx_color_pointer(GLint size, GLenum type, GLsizei stride, const void *ptr) { set_array(&fx.color, size, type, stride, ptr); }
void fx_texcoord_pointer(GLint size, GLenum type, GLsizei stride, const void *ptr) { set_array(&fx.texcoord, size, type, stride, ptr); }
void fx_enable_client_state(GLenum cap)
{
if (cap == GL_VERTEX_ARRAY) fx.vertex.enabled = 1;
else if (cap == GL_COLOR_ARRAY) fx.color.enabled = 1;
else if (cap == GL_TEXTURE_COORD_ARRAY) fx.texcoord.enabled = 1;
}
void fx_disable_client_state(GLenum cap)
{
if (cap == GL_VERTEX_ARRAY) fx.vertex.enabled = 0;
else if (cap == GL_COLOR_ARRAY) fx.color.enabled = 0;
else if (cap == GL_TEXTURE_COORD_ARRAY) fx.texcoord.enabled = 0;
}
void fx_enable(GLenum cap)
{
/* GLES2 has no alpha test or lighting; passing either would set GL_INVALID_ENUM and the
* error would then be blamed on whatever ran next. */
/* GL_TEXTURE_2D as a capability is gone in GLES2 -- whether a sampler is used is decided by
* the shader. Passing it sets GL_INVALID_ENUM, and the sticky error then gets blamed on
* whatever ran next. */
if (cap == GL_ALPHA_TEST || cap == GL_LIGHTING || cap == GL_TEXTURE_2D_ENABLE_COMPAT) return;
glEnable(cap);
}
void fx_disable(GLenum cap)
{
if (cap == GL_ALPHA_TEST || cap == GL_LIGHTING || cap == GL_TEXTURE_2D_ENABLE_COMPAT) return;
glDisable(cap);
}
static void bind(GLint loc, const fx_array *a)
{
if (loc < 0) return;
if (!a->enabled || !a->ptr) {
glDisableVertexAttribArray((GLuint) loc);
return;
}
glEnableVertexAttribArray((GLuint) loc);
glVertexAttribPointer((GLuint) loc, a->size, a->type, GL_FALSE, a->stride, a->ptr);
}
void fx_color4f(float r, float g, float b, float a)
{
fx.current_color[0] = r;
fx.current_color[1] = g;
fx.current_color[2] = b;
fx.current_color[3] = a;
}
void fx_rect(float x0, float y0, float x1, float y1)
{
if (!fx.ready) return;
const GLfloat verts[8] = { x0, y0, x1, y0, x1, y1, x0, y1 };
glUseProgram(fx.program);
glUniformMatrix4fv(fx.uProj, 1, GL_FALSE, fx.proj);
glUniform1i(fx.uUseTex, 0);
if (fx.aColor >= 0) {
glDisableVertexAttribArray((GLuint) fx.aColor);
glVertexAttrib4fv((GLuint) fx.aColor, fx.current_color);
}
if (fx.aTex >= 0) glDisableVertexAttribArray((GLuint) fx.aTex);
if (fx.aPos >= 0) {
glEnableVertexAttribArray((GLuint) fx.aPos);
glVertexAttribPointer((GLuint) fx.aPos, 2, GL_FLOAT, GL_FALSE, 0, verts);
}
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, fx.ibo);
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_SHORT, 0);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
/* The arrays the smoke set up are still recorded; the next fx_draw_arrays rebinds them. */
}
void fx_draw_arrays(GLenum mode, GLint first, GLsizei count)
{
if (!fx.ready || count <= 0) return;
glUseProgram(fx.program);
glUniformMatrix4fv(fx.uProj, 1, GL_FALSE, fx.proj);
glUniform1i(fx.uTex, 0);
glUniform1i(fx.uUseTex, fx.texcoord.enabled ? 1 : 0);
bind(fx.aPos, &fx.vertex);
bind(fx.aColor, &fx.color);
bind(fx.aTex, &fx.texcoord);
/* An attribute the shader declares but no array feeds still needs a value, or the draw
* reads whatever the last one left behind. */
if (fx.aColor >= 0 && (!fx.color.enabled || !fx.color.ptr))
glVertexAttrib4f((GLuint) fx.aColor, 1.0f, 1.0f, 1.0f, 1.0f);
if (mode != GL_QUADS) {
glDrawArrays(mode, first, count);
return;
}
/* GL_QUADS, the only mode Flurry actually uses. `first` is always 0 upstream; honouring a
* non-zero one would mean an index buffer per offset, so it is rejected loudly instead of
* being silently drawn wrong. */
if (first != 0) {
LOGE("fx_draw_arrays: GL_QUADS with first=%d is not supported", first);
return;
}
GLsizei quads = count / 4;
if (quads > FX_MAX_QUADS) {
LOGE("fx_draw_arrays: %d quads exceeds the %d the index buffer covers; clamping",
quads, FX_MAX_QUADS);
quads = FX_MAX_QUADS;
}
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, fx.ibo);
glDrawElements(GL_TRIANGLES, quads * 6, GL_UNSIGNED_SHORT, 0);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
}
@@ -0,0 +1,108 @@
/*
* Just enough OpenGL 1.x for Flurry, implemented on GLES2.
*
* Flurry is from 2002 and draws the only way that existed then: client-side vertex arrays
* (glVertexPointer and friends), a fixed-function ortho projection, and GL_QUADS. GLES2 has
* none of those. Rather than rewrite the renderer -- and lose the property that this is
* recognisably Calum Robinson's code -- the calls are redirected here and answered with a
* shader, an index buffer, and a matrix uniform.
*
* The surface is small because the smoke is the only thing drawn: DRAW_SPARKS is never
* defined upstream, so the immediate-mode spark path (glBegin/glVertex2f, matrix stack) is
* dead code and is not emulated. If sparks are ever switched on, this file needs a lot more
* in it.
*/
#ifndef FLURRY_GL_COMPAT_H
#define FLURRY_GL_COMPAT_H
#include <GLES2/gl2.h>
#ifdef __cplusplus
extern "C" {
#endif
/* GL 1.x names the sources use that GLES2 does not define. */
#ifndef GL_QUADS
#define GL_QUADS 0x0007
#endif
#ifndef GL_QUAD_STRIP
#define GL_QUAD_STRIP 0x0008
#endif
#ifndef GL_ALPHA_TEST
#define GL_ALPHA_TEST 0x0BC0
#endif
#ifndef GL_LIGHTING
#define GL_LIGHTING 0x0B50
#endif
#ifndef GL_VERTEX_ARRAY
#define GL_VERTEX_ARRAY 0x8074
#endif
#ifndef GL_COLOR_ARRAY
#define GL_COLOR_ARRAY 0x8076
#endif
#ifndef GL_TEXTURE_COORD_ARRAY
#define GL_TEXTURE_COORD_ARRAY 0x8078
#endif
#ifndef GL_LINEAR_MIPMAP_NEAREST
#define GL_LINEAR_MIPMAP_NEAREST 0x2701
#endif
#ifndef GL_TEXTURE_2D_ENABLE_COMPAT
#define GL_TEXTURE_2D_ENABLE_COMPAT 0x0DE1
#endif
/* Lifetime. fx_init needs a current context; fx_lost forgets GL names without touching them,
* for when the context has already gone away underneath us. */
int fx_init(void);
void fx_shutdown(void);
void fx_lost(void);
/* Replaces the projection matrix calls. */
void fx_ortho(float left, float right, float bottom, float top);
/* Array + draw emulation. */
void fx_vertex_pointer(GLint size, GLenum type, GLsizei stride, const void *ptr);
void fx_color_pointer(GLint size, GLenum type, GLsizei stride, const void *ptr);
void fx_texcoord_pointer(GLint size, GLenum type, GLsizei stride, const void *ptr);
void fx_enable_client_state(GLenum cap);
void fx_disable_client_state(GLenum cap);
void fx_draw_arrays(GLenum mode, GLint first, GLsizei count);
/* glEnable/glDisable filtered: GLES2 rejects GL_ALPHA_TEST and GL_LIGHTING outright, and an
* invalid enum here would leave a sticky GL error for everything after it. */
void fx_enable(GLenum cap);
void fx_disable(GLenum cap);
/* The per-frame fade. Flurry darkens the whole screen with a translucent black rect before
* drawing, which is what gives the smoke its trails -- so this is not decoration, it is the
* effect. glRectd and the current-colour state it reads are both gone in GLES2. */
void fx_color4f(float r, float g, float b, float a);
void fx_rect(float x0, float y0, float x1, float y1);
#ifdef __cplusplus
}
#endif
/* The redirection itself. Included by the Flurry sources ahead of any GL use. */
#define glVertexPointer fx_vertex_pointer
#define glColorPointer fx_color_pointer
#define glTexCoordPointer fx_texcoord_pointer
#define glEnableClientState fx_enable_client_state
#define glDisableClientState fx_disable_client_state
#define glDrawArrays fx_draw_arrays
#define glEnable fx_enable
#define glDisable fx_disable
/* No analogue and nothing depends on the effect. */
#define glAlphaFunc(func, ref) ((void) 0)
#define glShadeModel(mode) ((void) 0)
#define glMatrixMode(mode) ((void) 0)
#define glLoadIdentity() ((void) 0)
#define glTexEnvf(t, p, v) ((void) 0)
#define glOrtho(l, r, b, t, n, f) fx_ortho((float) (l), (float) (r), (float) (b), (float) (t))
#define glColor4f fx_color4f
#define glColor3f(r, g, b) fx_color4f((r), (g), (b), 1.0f)
#define glRectd(x0, y0, x1, y1) fx_rect((float) (x0), (float) (y0), (float) (x1), (float) (y1))
#define glDrawBuffer(mode) ((void) 0)
#define glFinish() ((void) 0)
#endif /* FLURRY_GL_COMPAT_H */
@@ -93,6 +93,7 @@ struct RPCSXApi {
bool (*saveStateToSlot)(unsigned int slot);
bool (*loadStateFromSlot)(unsigned int slot);
bool (*hasStateInSlot)(unsigned int slot);
bool (*deleteStateFromSlot)(unsigned int slot);
std::string (*patchEngineVersion)();
int (*patchesImport)(std::string_view content);
std::string (*patchesList)(std::string_view serial);
@@ -204,6 +205,7 @@ struct RPCSXLibrary : RPCSXApi {
result.saveStateToSlot = reinterpret_cast<decltype(saveStateToSlot)>(dlsym(handle, "_rpcsx_saveStateToSlot"));
result.loadStateFromSlot = reinterpret_cast<decltype(loadStateFromSlot)>(dlsym(handle, "_rpcsx_loadStateFromSlot"));
result.hasStateInSlot = reinterpret_cast<decltype(hasStateInSlot)>(dlsym(handle, "_rpcsx_hasStateInSlot"));
result.deleteStateFromSlot = reinterpret_cast<decltype(deleteStateFromSlot)>(dlsym(handle, "_rpcsx_deleteStateFromSlot"));
result.patchEngineVersion = reinterpret_cast<decltype(patchEngineVersion)>(dlsym(handle, "_rpcsx_patchEngineVersion"));
result.patchesImport = reinterpret_cast<decltype(patchesImport)>(dlsym(handle, "_rpcsx_patchesImport"));
result.patchesList = reinterpret_cast<decltype(patchesList)>(dlsym(handle, "_rpcsx_patchesList"));
@@ -1007,6 +1009,15 @@ Java_net_rpcsx_RPCSX_hasStateInSlot(JNIEnv *, jobject, jint slot) {
return rpcsxLib.hasStateInSlot(static_cast<unsigned int>(slot));
}
extern "C" JNIEXPORT jboolean JNICALL
Java_net_rpcsx_RPCSX_deleteStateFromSlot(JNIEnv *, jobject, jint slot) {
if (rpcsxLib.deleteStateFromSlot == nullptr || slot < 0) {
return false;
}
return rpcsxLib.deleteStateFromSlot(static_cast<unsigned int>(slot));
}
// ---------------------------------------------------------------------------
// Patches
// ---------------------------------------------------------------------------
@@ -0,0 +1,5 @@
/* Redirects the savers' <GL/gl.h> to the GLES2 shim. See ../../gl1.h. */
#ifndef SAVERS_COMPAT_GL_H
#define SAVERS_COMPAT_GL_H
#include "gl1.h"
#endif
@@ -0,0 +1,6 @@
/* The Implicit library includes <GL/glext.h> for the multitexture entry points. GLES2 has
* those in its core header, which gl1.h already pulls in. */
#ifndef SAVERS_COMPAT_GLEXT_H
#define SAVERS_COMPAT_GLEXT_H
#include "gl1.h"
#endif
@@ -0,0 +1,6 @@
/* Redirects the savers' <GL/glu.h> to the GLES2 shim, which answers gluPerspective,
* gluLookAt and the quadric spheres. See ../../gl1.h. */
#ifndef SAVERS_COMPAT_GLU_H
#define SAVERS_COMPAT_GLU_H
#include "gl1.h"
#endif
@@ -0,0 +1,73 @@
/*
* The ARB_shader_objects surface Hyperspace expects.
*
* Hyperspace has two rendering paths and picks between them with dShaders. The shader path uses
* ARB assembly-era shader objects, which GLES2 does not have at all. Rather than translate them,
* the types are declared so the code compiles and the entry points are looked up at runtime --
* on GLES2 they come back null, extensions.cpp reports the extension missing, and the saver
* takes its own non-shader path, which is a supported upstream configuration rather than
* something invented here.
*/
#ifndef SAVERS_COMPAT_ARB_SHADERS_H
#define SAVERS_COMPAT_ARB_SHADERS_H
#include <EGL/egl.h>
#include "gl1.h"
typedef unsigned int GLhandleARB;
typedef char GLcharARB;
typedef void (*PFNGLACTIVETEXTUREARBPROC)(GLenum);
typedef void (*PFNGLMULTITEXCOORD2FARBPROC)(GLenum, GLfloat, GLfloat);
typedef GLhandleARB (*PFNGLCREATEPROGRAMOBJECTARBPROC)(void);
typedef GLhandleARB (*PFNGLCREATESHADEROBJECTARBPROC)(GLenum);
typedef void (*PFNGLSHADERSOURCEARBPROC)(GLhandleARB, GLsizei, const GLcharARB **, const GLint *);
typedef void (*PFNGLCOMPILESHADERARBPROC)(GLhandleARB);
typedef void (*PFNGLATTACHOBJECTARBPROC)(GLhandleARB, GLhandleARB);
typedef void (*PFNGLLINKPROGRAMARBPROC)(GLhandleARB);
typedef void (*PFNGLUSEPROGRAMOBJECTARBPROC)(GLhandleARB);
typedef GLint (*PFNGLGETUNIFORMLOCATIONARBPROC)(GLhandleARB, const GLcharARB *);
typedef void (*PFNGLUNIFORM1IARBPROC)(GLint, GLint);
typedef void (*PFNGLUNIFORM1FARBPROC)(GLint, GLfloat);
typedef void (*PFNGLUNIFORM4FARBPROC)(GLint, GLfloat, GLfloat, GLfloat, GLfloat);
typedef void (*PFNGLGETOBJECTPARAMETERIVARBPROC)(GLhandleARB, GLenum, GLint *);
typedef void (*PFNGLGETINFOLOGARBPROC)(GLhandleARB, GLsizei, GLsizei *, GLcharARB *);
typedef void (*PFNGLDELETEOBJECTARBPROC)(GLhandleARB);
/* Multitexture unit names; core in GLES2 under the unsuffixed spelling. */
#ifndef GL_TEXTURE0_ARB
#define GL_TEXTURE0_ARB GL_TEXTURE0
#define GL_TEXTURE1_ARB GL_TEXTURE1
#define GL_TEXTURE2_ARB GL_TEXTURE2
#define GL_TEXTURE3_ARB GL_TEXTURE3
#endif
#ifndef GL_VERTEX_SHADER_ARB
#define GL_VERTEX_SHADER_ARB 0x8B31
#define GL_FRAGMENT_SHADER_ARB 0x8B30
#define GL_OBJECT_COMPILE_STATUS_ARB 0x8B81
#define GL_OBJECT_LINK_STATUS_ARB 0x8B82
#endif
/* Upstream only includes its own extensions.h under WIN32 -- its X11 build relies on the system
* GL exporting these symbols directly, which Mesa does and Android does not. So they are
* declared here, at global scope, where the savers' namespaced code still finds them.
*
* Nothing calls them: every use sits behind dShaders, which the port forces off. glActiveTexture
* is the one exception, being core in GLES2, so that one points at the real function. */
extern PFNGLACTIVETEXTUREARBPROC glActiveTextureARB;
extern PFNGLCREATESHADEROBJECTARBPROC glCreateShaderObjectARB;
extern PFNGLSHADERSOURCEARBPROC glShaderSourceARB;
extern PFNGLCOMPILESHADERARBPROC glCompileShaderARB;
extern PFNGLCREATEPROGRAMOBJECTARBPROC glCreateProgramObjectARB;
extern PFNGLATTACHOBJECTARBPROC glAttachObjectARB;
extern PFNGLLINKPROGRAMARBPROC glLinkProgramARB;
extern PFNGLUSEPROGRAMOBJECTARBPROC glUseProgramObjectARB;
extern PFNGLGETUNIFORMLOCATIONARBPROC glGetUniformLocationARB;
extern PFNGLUNIFORM1IARBPROC glUniform1iARB;
/* Upstream resolves entry points through the platform's GetProcAddress; on Android that is
* EGL's. Anything ARB-only returns null here, which is the point. */
#define glXGetProcAddressARB(name) ((void *) eglGetProcAddress((const char *) (name)))
#endif
@@ -0,0 +1,11 @@
/* Case alias: Helios includes <rgbhsl/rgbhsl.h>, Flux includes <Rgbhsl/Rgbhsl.h>. Windows did
* not care and neither does macOS, but a case-sensitive build host would fail on one of them.
*
* The target is spelled as an explicit relative path, NOT as <Rgbhsl/Rgbhsl.h>. On a
* case-insensitive filesystem the search path would match "compat/Rgbhsl" to this very
* directory and the file would include itself -- the guard then silently swallows it and the
* real declarations never arrive. */
#ifndef SAVERS_RGBHSL_LOWER_ALIAS_H
#define SAVERS_RGBHSL_LOWER_ALIAS_H
#include "../../rslibs/Rgbhsl/Rgbhsl.h"
#endif
@@ -0,0 +1,57 @@
/*
* Stands in for the X11 screensaver shell (upstream rsXScreenSaver, LGPL-2.1).
*
* The savers carry three platform paths: WIN32, RS_XSCREENSAVER, and nothing. The X11 one is
* closest to what a background needs -- it exposes initSaver(), reshape(), draw(), idleProc()
* and cleanUp() as plain functions and leaves the whole Win32 dialog shell out -- so we compile
* with RS_XSCREENSAVER defined and answer what that path expects here.
*
* The point of doing it this way is that the saver sources stay BYTE-IDENTICAL to upstream.
* Nothing is patched, so they can be re-pulled, and they stay recognisably Terry Welsh's code,
* which their GPL headers ask us to keep intact.
*
* Declarations mirror upstream's types exactly -- note these flags are int, not bool.
*/
#ifndef SAVERS_COMPAT_RSXSCREENSAVER_H
#define SAVERS_COMPAT_RSXSCREENSAVER_H
#include <string>
#include <vector>
#include "rsUtility/rsTimer.h"
/* The savers poll these before drawing. The host view stops calling us when it is not visible
* rather than flagging suspension, so they stay 0. */
extern int checkingPassword;
extern int isSuspended;
extern int doingPreview;
/* Pixel-format bits the X11 host reported; meaningless here and read only by savers deciding
* whether they can rely on buffer preservation. */
extern int pfd_swap_exchange;
extern int pfd_swap_copy;
extern unsigned int dFrameRateLimit;
/* The on-screen frame-rate overlay. Left off -- it is debug furniture for a wallpaper. */
extern int kStatistics;
/* GLX buffer swapping is the host view's job: it owns the EGL surface. */
extern void *xdisplay;
extern unsigned long xwindow;
#define glXSwapBuffers(dpy, win) ((void) 0)
/* The savers build their frame-rate string with a bare to_string(); upstream picks it up from
* this header's include chain. */
using std::to_string;
/* Command-line parsing has no meaning here -- settings arrive through each saver's port API,
* which calls setDefaults() and then assigns the globals directly. Accepting and ignoring the
* call keeps handleCommandLine() compiling unmodified. */
inline int getArgumentsValue(int, char **, std::string, std::string &) { return 0; }
template <typename T>
inline int getArgumentsValue(int, char **, std::string, T &) { return 0; }
template <typename T>
inline int getArgumentsValue(int, char **, std::string, T &, T, T) { return 0; }
#endif
File diff suppressed because it is too large Load Diff

Some files were not shown because too many files have changed in this diff Show More