29 Commits
Author SHA1 Message Date
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
47 changed files with 748 additions and 170 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 = 20
versionName = "0.9.4"
// 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.
@@ -572,6 +572,10 @@ val EN: Map<String, String> = mapOf(
"rpcn.hosts.removed" to "Server removed.",
"rpcn.hosts.resetDone" to "Back to the official server (np.rpcs3.net).",
"rpcn.hosts.selected" to "Switched server. Test sign-in to check it.",
"rpcn.need.username" to "Enter your username first.",
"rpcn.need.password" to "Enter your password first \u2014 Save clears the box, so type it again.",
"rpcn.need.newPassword" to "Enter the new password you want to set.",
"rpcn.need.email" to "Enter the email address for this account.",
"rpcn.account.saved" to "Account saved:",
"rpcn.account.saved.note" to "It stays saved between restarts. RPCN keeps no permanent session, so games sign in with this account when they go online \u2014 there is nothing to log into again.",
"rpcn.account.connected" to "Signed in as",
@@ -85,6 +85,10 @@ fun RpcnAccountSection() {
val msgCreateHint = str("rpcn.create.hint")
val msgTokenSent = str("rpcn.token.sent")
val msgResetSent = str("rpcn.reset.sent")
val msgNeedUsername = str("rpcn.need.username")
val msgNeedPassword = str("rpcn.need.password")
val msgNeedNewPassword = str("rpcn.need.newPassword")
val msgNeedEmail = str("rpcn.need.email")
val msgHostAdded = str("rpcn.hosts.added")
val msgHostRemoved = str("rpcn.hosts.removed")
val msgHostsReset = str("rpcn.hosts.resetDone")
@@ -329,7 +333,12 @@ fun RpcnAccountSection() {
modifier = Modifier.fillMaxWidth().padding(top = 6.dp),
)
if (creating) {
// Always shown, not only while creating an account.
//
// Password reset sends this address to the server, and hiding the box outside creation
// mode meant Reset password posted an EMPTY email. The server rejects that whole query
// as Malformed, which reached the user as the meaningless "Server error 1".
run {
OutlinedTextField(
value = email,
onValueChange = { email = it },
@@ -385,6 +394,12 @@ fun RpcnAccountSection() {
if (!creating) {
creating = true
status = msgCreateHint
} else if (npid.isBlank()) {
status = msgNeedUsername
} else if (password.isBlank()) {
status = msgNeedPassword
} else if (email.isBlank()) {
status = msgNeedEmail
} else {
Rpcs3Bridge.rpcnSetConfig(host.trim(), "", "", "")
run(msgCreated) {
@@ -395,20 +410,39 @@ fun RpcnAccountSection() {
}
}) { Text(if (creating) str("rpcn.create.go") else str("rpcn.create")) }
// Checked here rather than sent and rejected. A required field left empty makes the
// whole query Malformed, and the server's answer to that is an error number with no
// way for the user to know which box to fill in. Note the password box is CLEARED by
// Save, so "I typed it a moment ago" is not the same as "it is in the field now".
TextButton(enabled = !busy, onClick = {
Rpcs3Bridge.rpcnSetConfig(host.trim(), "", "", "")
run(msgTokenSent) {
Rpcs3Bridge.rpcnResendToken(npid.trim(), password)
when {
npid.isBlank() -> status = msgNeedUsername
password.isBlank() -> status = msgNeedPassword
else -> {
Rpcs3Bridge.rpcnSetConfig(host.trim(), "", "", "")
run(msgTokenSent) {
Rpcs3Bridge.rpcnResendToken(npid.trim(), password)
}
}
}
}) { Text(str("rpcn.token.resend")) }
TextButton(enabled = !busy, onClick = {
Rpcs3Bridge.rpcnSetConfig(host.trim(), "", "", "")
run(msgResetSent) {
if (token.isBlank()) {
Rpcs3Bridge.rpcnSendResetToken(npid.trim(), email.trim())
} else {
Rpcs3Bridge.rpcnResetPassword(npid.trim(), token.trim(), password)
when {
npid.isBlank() -> status = msgNeedUsername
// No token yet: ask the server to email one, which needs the address.
token.isBlank() && email.isBlank() -> status = msgNeedEmail
// Token in hand: this is the actual reset, so a new password is required.
token.isNotBlank() && password.isBlank() -> status = msgNeedNewPassword
else -> {
Rpcs3Bridge.rpcnSetConfig(host.trim(), "", "", "")
run(msgResetSent) {
if (token.isBlank()) {
Rpcs3Bridge.rpcnSendResetToken(npid.trim(), email.trim())
} else {
Rpcs3Bridge.rpcnResetPassword(npid.trim(), token.trim(), password)
}
}
}
}
}) { Text(str("rpcn.reset")) }
+28 -4
View File
@@ -2784,7 +2784,17 @@ static std::string rpcn_describe(rpcn::ErrorType error) {
case rpcn::ErrorType::LoginAlreadyLoggedIn:
return "That account is already logged in somewhere else.";
case rpcn::ErrorType::LoginError: return "The server refused the login.";
default: return fmt::format("Server error %d.", static_cast<int>(error));
// These three were reaching users as "Server error 1" and the like, which says nothing about
// what to do. Malformed in particular is OUR fault, not the server's: it means a required
// field was sent empty, which is exactly what Reset password did when the email box was
// hidden outside account creation.
case rpcn::ErrorType::Malformed:
return "The request was incomplete -- a required field was empty. This is a bug; please "
"report which button you pressed.";
case rpcn::ErrorType::Invalid:
return "The server rejected that request as out of order. Try Test sign-in first.";
default: return fmt::format("Unexpected server error %d.", static_cast<int>(error));
}
}
@@ -3354,15 +3364,29 @@ extern "C" int _rpcsx_boot(std::string_view path_) {
// Kill() is idempotent and returns quickly when already stopped. The bound is generous
// because a real teardown joins the RSX and SPU threads and flushes caches; past it we
// boot anyway and let BootGame report a normal error rather than hanging the UI.
if (!Emu.IsStopped()) {
// IsStopped(true), not IsStopped().
//
// The default overload is `m_state <= system_state::stopping`, so it answers TRUE while the
// previous VM is still stopping -- and loading. This whole block was therefore skipped exactly
// when it was needed: Kill() signals the threads and hands the actual joining to a detached
// "Emulation Join Thread", the state reaches stopping immediately, and the guard read that as
// stopped. Observed on device as a boot starting three seconds into a teardown that never
// finished, with the join thread still waiting on an SPU interrupt thread seventeen seconds
// later, seven SPUs parked in EXIT|w|G-PAUSE, and the app frozen on the last frame of the
// previous game. There was no "previous VM still running" line in the log, because the check
// passed.
//
// The `true` overload requires system_state::stopped, which is only reached once that join
// thread has run to completion.
if (!Emu.IsStopped(true)) {
rpcsx_android.notice("boot: previous VM still running, stopping it first");
Emu.Kill();
for (int waited = 0; !Emu.IsStopped() && waited < 10000; waited += 20) {
for (int waited = 0; !Emu.IsStopped(true) && waited < 10000; waited += 20) {
std::this_thread::sleep_for(std::chrono::milliseconds(20));
}
if (!Emu.IsStopped()) {
if (!Emu.IsStopped(true)) {
rpcsx_android.error("boot: previous VM did not stop in time, booting anyway");
}
}
+29 -7
View File
@@ -1269,10 +1269,14 @@ void package_reader::extract_worker()
while (read_size < size)
{
const u64 block_size = std::min<u64>(BUF_SIZE, size - read_size);
const u64 available_buffer_size = buffer.size() - read_size;
u64 available_buffer_size = original_size - read_size;
if (buffer.data() == ptr)
{
available_buffer_size = buffer.size() - read_size;
ensure(buffer.size() == original_size + BUF_PADDING);
}
ensure(buffer.data() == ptr);
ensure(buffer.size() == original_size + BUF_PADDING);
ensure(available_buffer_size >= block_size);
const usz advance_size = decrypt(entry.file_offset + pos, block_size, is_psp ? PKG_AES_KEY2 : m_dec_key.data(), std::span<u8>{static_cast<u8*>(ptr) + read_size, available_buffer_size});
@@ -1431,14 +1435,32 @@ package_install_result package_reader::extract_data(std::deque<package_reader>&
if (reader.m_num_failures == 0)
{
const usz thread_count = std::min<usz>(utils::get_thread_count(), reader.m_install_entries.size());
atomic_t<u32> num_threads_succeeded {0}; // Check if any thread didn't finish. For example when hitting an exception.
named_thread_group workers("PKG Installer "sv, std::max<u32>(::narrow<u32>(thread_count), 1) - 1, [&]()
if (thread_count > 1)
{
named_thread_group workers("PKG Installer "sv, ::narrow<u32>(thread_count) - 1, [&]()
{
reader.extract_worker();
num_threads_succeeded++;
});
reader.extract_worker();
num_threads_succeeded++;
workers.join();
}
else
{
reader.extract_worker();
});
num_threads_succeeded++;
}
reader.extract_worker();
workers.join();
if (thread_count != num_threads_succeeded)
{
pkg_log.error("%d thread(s) failed with an exception!", thread_count - num_threads_succeeded);
reader.m_num_failures++;
}
}
num_failures += reader.m_num_failures;
+1 -1
View File
@@ -408,7 +408,7 @@ std::pair<vm::addr_t, u32> vm::hle_malloc_allocator::alloc(u32 size, u32 align)
return { vm::cast(addr), addr ? size : 0 };
}
void vm::hle_malloc_allocator::dealloc(u32 addr, u32 size) noexcept
void vm::hle_malloc_allocator::dealloc(u32 addr, u32 /*size*/) noexcept
{
const auto ppu = ensure(cpu_thread::get_current<ppu_thread>());
ppu_execute<&_sys_free>(*ppu, addr);
+7 -1
View File
@@ -2339,8 +2339,14 @@ void ppu_thread::cpu_on_stop()
ppu_log.notice("thread context: %s", ret);
}
if (is_stopped())
// Report once. Nothing guarantees this hook runs a single time, and a PPU thread that
// re-enters the stop path without exiting reports on every pass -- measured at ~100,000
// lines per second from one thread, which saturates the log writer and stalls the whole
// emulator during shutdown on Android, where the log goes to external storage.
if (is_stopped() && !perf_stats_reported)
{
perf_stats_reported = true;
if (last_succ == 0 && last_fail == 0 && exec_bytes == 0)
{
perf_log.notice("PPU thread perf stats are not available.");
+5
View File
@@ -317,6 +317,11 @@ public:
u64 last_succ = 0;
u64 exec_bytes = 0; // Amount of "bytes" executed (4 for each instruction)
// cpu_on_stop() is a teardown hook with nothing enforcing that it runs once, and a thread
// that re-enters the stop path reports its perf stats again every time. Deliberately not
// serialized: it describes this run's reporting, not guest state.
bool perf_stats_reported = false;
u32 dbg_step_pc = 0;
atomic_t<ppu_debugger_mode> debugger_mode{};
-3
View File
@@ -40,9 +40,6 @@ bool spu_thread::read_reg(const u32 addr, u32& value)
{
const u32 offset = addr - (RAW_SPU_BASE_ADDR + RAW_SPU_OFFSET * index) - RAW_SPU_PROB_OFFSET;
raw_spu_log_stats_t stats{};
stats.mmio_offset = offset;
const auto [old_stats, is_changed] = mmio_stats.fetch_op([&](raw_spu_log_stats_t& old)
{
if (old.mmio_offset == offset)
+20 -15
View File
@@ -1466,7 +1466,7 @@ void spu_cache::initialize(bool build_existing_cache)
{
if (ls[start_new / 4] && g_spu_itype.decode(ls[start_new / 4]) != spu_itype::UNK)
{
spu_log.notice("Precompiling fallthrough to 0x%05x", start_new);
spu_log.trace("Precompiling fallthrough to 0x%05x", start_new);
func2 = compiler->analyse(ls.data(), start_new, &targets);
block_addr = start_new;
continue;
@@ -1530,7 +1530,7 @@ void spu_cache::initialize(bool build_existing_cache)
}
spu_log.notice("Precompiling filler space at 0x%05x (next=0x%05x)", new_entry, next_func);
spu_log.trace("Precompiling filler space at 0x%05x (next=0x%05x)", new_entry, next_func);
func2 = compiler->analyse(ls.data(), new_entry, &targets);
block_addr = new_entry;
}
@@ -2139,7 +2139,12 @@ spu_function_t spu_runtime::rebuild_ubertrampoline(u32 id_inst)
if (w.level >= w.beg->first.size() || w.level >= it->first.size())
{
// If functions cannot be compared, assume smallest function
spu_log.error("Trampoline simplified at ??? (level=%u)", w.level);
// Routine control-flow simplification, not a failure -- it was at error level
// and fired ~1500 times in a 15 minute session. Every diagnostic in this
// recompiler is per-block or per-instruction, which upstream can afford and a
// phone writing to /sdcard cannot: the burst lands exactly while a game is
// already stalling to compile. Still reachable by raising the SPU channel.
spu_log.trace("Trampoline simplified at ??? (level=%u)", w.level);
#if defined(ARCH_X64)
make_jump(0xe9, w.beg->second); // jmp rel32
#elif defined(ARCH_ARM64)
@@ -2178,7 +2183,7 @@ spu_function_t spu_runtime::rebuild_ubertrampoline(u32 id_inst)
if (it == m_flat_list.end())
{
spu_log.error("Trampoline simplified (II) at ??? (level=%u)", w.level);
spu_log.trace("Trampoline simplified (II) at ??? (level=%u)", w.level);
#if defined(ARCH_X64)
make_jump(0xe9, w.beg->second); // jmp rel32
#elif defined(ARCH_ARM64)
@@ -6069,7 +6074,7 @@ spu_program spu_recompiler_base::analyse(const be_t<u32>* ls, u32 entry_point, s
getllar_starts[previous.lsa_pc] = true;
g_fxo->get<putllc16_statistics_t>().breaking_reason[cause]++;
if (!spu_log.notice)
if (!spu_log.trace)
{
return;
}
@@ -6109,7 +6114,7 @@ spu_program spu_recompiler_base::analyse(const be_t<u32>* ls, u32 entry_point, s
}
fmt::append(tracing, " of %d failures", fail_count);
spu_log.notice("%s\n%s", break_error, tracing);
spu_log.trace("%s\n%s", break_error, tracing);
}
};
@@ -6126,7 +6131,7 @@ spu_program spu_recompiler_base::analyse(const be_t<u32>* ls, u32 entry_point, s
g_fxo->get<rchcnt_statistics_t>().breaking_reason[cause]++;
if (!spu_log.notice)
if (!spu_log.trace)
{
return;
}
@@ -6166,7 +6171,7 @@ spu_program spu_recompiler_base::analyse(const be_t<u32>* ls, u32 entry_point, s
}
fmt::append(tracing, " of %d failures", fail_count);
spu_log.notice("%s\n%s", break_error, tracing);
spu_log.trace("%s\n%s", break_error, tracing);
}
};
@@ -6176,7 +6181,7 @@ spu_program spu_recompiler_base::analyse(const be_t<u32>* ls, u32 entry_point, s
{
g_fxo->get<reduced_statistics_t>().breaking_reason[cause]++;
if (!spu_log.notice)
if (!spu_log.trace)
{
return;
}
@@ -6221,12 +6226,12 @@ spu_program spu_recompiler_base::analyse(const be_t<u32>* ls, u32 entry_point, s
}
fmt::append(tracing, " of %d failures", fail_count);
spu_log.notice("%s\n%s", break_error, tracing);
spu_log.trace("%s\n%s", break_error, tracing);
std::string block_dump;
this->dump(result, block_dump, previous.loop_pc, previous.loop_end + 1);
spu_log.notice("SPU Block Dump:\n%s", block_dump);
spu_log.trace("SPU Block Dump:\n%s", block_dump);
}
};
@@ -6437,7 +6442,7 @@ spu_program spu_recompiler_base::analyse(const be_t<u32>* ls, u32 entry_point, s
{
if (!std::exchange(logged_block[target_pc / 4], true))
{
spu_log.notice("SPU block is a loop at [0x%05x -> 0x%05x]", state_it->pc, target_pc);
spu_log.trace("SPU block is a loop at [0x%05x -> 0x%05x]", state_it->pc, target_pc);
}
state_it->parent_target_index++;
@@ -7959,7 +7964,7 @@ spu_program spu_recompiler_base::analyse(const be_t<u32>* ls, u32 entry_point, s
if (getllar_starts.emplace(atomic16->lsa_pc, false).second)
{
g_fxo->get<putllc16_statistics_t>().all++;
spu_log.notice("[0x%05x] GETLLAR pattern entry point", pos);
spu_log.trace("[0x%05x] GETLLAR pattern entry point", pos);
}
}
@@ -9287,7 +9292,7 @@ spu_program spu_recompiler_base::analyse(const be_t<u32>* ls, u32 entry_point, s
add_pattern(inst_attr::putllc16, pattern.put_pc - result.entry_point, value.data);
}
spu_log.success("PUTLLC16 Pattern Detected! (mem_count=%d, put_pc=0x%x, pc_rel=%d, offset=0x%x, const=%u, two_regs=%d, reg=%u, runtime=%d, 0x%x-%s, pattern-hash=%s) (putllc0=%d, putllc16+0=%d, all=%d)"
spu_log.trace("PUTLLC16 Pattern Detected! (mem_count=%d, put_pc=0x%x, pc_rel=%d, offset=0x%x, const=%u, two_regs=%d, reg=%u, runtime=%d, 0x%x-%s, pattern-hash=%s) (putllc0=%d, putllc16+0=%d, all=%d)"
, pattern.mem_count, pattern.put_pc, value.type == v_relative, value.off18, value.type == v_const, value.type == v_reg2, value.reg, value.runtime16_select, entry_point, func_hash, pattern_hash, +stats.nowrite, ++stats.single, +stats.all);
}
@@ -9393,7 +9398,7 @@ spu_program spu_recompiler_base::analyse(const be_t<u32>* ls, u32 entry_point, s
if (likely_putllc_loop && !had_putllc_evaluation)
{
spu_log.notice("Likely missed PUTLLC16 patterns. (entry=0x%x)", entry_point);
spu_log.trace("Likely missed PUTLLC16 patterns. (entry=0x%x)", entry_point);
}
if (result.data.empty())
+27 -3
View File
@@ -733,6 +733,17 @@ class spu_llvm_recompiler : public spu_recompiler_base, public cpu_translator
ensure(val && val->getType() == get_type<u32[4]>());
const auto x = m_ir->CreateZExt(val, get_type<u64[4]>());
// Use integer operations here so LLVM can fold the masks into VPTERNLOG
if (m_use_avx512)
{
const auto s = m_ir->CreateAnd(m_ir->CreateShl(x, 32), 0x8000000000000000);
const auto m = m_ir->CreateAnd(m_ir->CreateShl(x, 29), 0x0fffffffe0000000);
const auto f = m_ir->CreateAdd(m_ir->CreateOr(s, m), splat<u64[4]>(0x3800000000000000).eval(m_ir));
const auto e = m_ir->CreateAnd(val, 0x7f800000);
return uint64_as_double(m_ir->CreateSelect(m_ir->CreateIsNotNull(e), f, s));
}
const auto s = m_ir->CreateShl(m_ir->CreateAnd(x, 0x80000000), 32);
const auto a = m_ir->CreateAnd(x, 0x7fffffff);
const auto m = m_ir->CreateShl(m_ir->CreateAdd(a, splat<u64[4]>(0x1c0000000).eval(m_ir)), 29);
@@ -746,6 +757,19 @@ class spu_llvm_recompiler : public spu_recompiler_base, public cpu_translator
{
ensure(val && val->getType() == get_type<f64[4]>());
// Use integer operations here so LLVM can fold the masks into VPTERNLOG
if (m_use_avx512)
{
const auto d = double_as_uint64(val);
const auto smax = splat<u64[4]>(0x47ffffffe0000000).eval(m_ir);
const auto smin = splat<u64[4]>(0x3810000000000000).eval(m_ir);
const auto a = m_ir->CreateAnd(d, 0x7fffffffe0000000);
const auto n = m_ir->CreateICmpUGE(a, smin);
const auto c = m_ir->CreateSelect(m_ir->CreateICmpULT(a, smax), a, smax);
const auto r = m_ir->CreateOr(c, m_ir->CreateAnd(d, 0x8000000000000000));
return uint64_as_double(m_ir->CreateSelect(n, r, splat<u64[4]>(0).eval(m_ir)));
}
const auto smax = uint64_as_double(splat<u64[4]>(0x47ffffffe0000000).eval(m_ir));
const auto smin = uint64_as_double(splat<u64[4]>(0x3810000000000000).eval(m_ir));
@@ -4131,7 +4155,7 @@ public:
cache.add(func);
}
spu_log.success("New SPU block compiled successfully (size=%u)", func_size);
spu_log.trace("New SPU block compiled successfully (size=%u)", func_size);
}
return fn;
@@ -5268,7 +5292,7 @@ public:
}
}
spu_log.warning("[0x%x] MFC_EAH: $%u is not a zero constant", m_pos, +op.rt);
spu_log.trace("[0x%x] MFC_EAH: $%u is not a zero constant", m_pos, +op.rt);
//m_ir->CreateStore(val.value, spu_ptr(&spu_thread::ch_mfc_cmd, &spu_mfc_cmd::eah));
return;
}
@@ -5626,7 +5650,7 @@ public:
}
// Fallback to unoptimized WRCH implementation (TODO)
spu_log.warning("[0x%x] MFC_Cmd: $%u is not a constant", m_pos, +op.rt);
spu_log.trace("[0x%x] MFC_Cmd: $%u is not a constant", m_pos, +op.rt);
break;
}
case MFC_WrListStallAck:
+1 -1
View File
@@ -1053,7 +1053,7 @@ void fmt_class_string<CellError>::format(std::string& out, u64 arg)
if (upper == s_error_codes_formatting_by_type.begin())
{
// Format as unknown
format_enum(out, arg, [](auto error)
format_enum(out, arg, [](auto /*error*/)
{
return unknown;
});
+10 -1
View File
@@ -447,7 +447,16 @@ error_code sys_memory_container_get_size(cpu_thread& cpu, vm::ptr<sys_memory_inf
{
cpu.state += cpu_flag::wait;
sys_memory.warning("sys_memory_container_get_size(mem_info=*0x%x, cid=0x%x)", mem_info, cid);
// A pure query with no side effects, and a game is free to poll it in a loop: Tales of
// Xillia 2 (BLUS31397) called it 20,664 times in a 19 minute session, 5,123 of them inside
// a single second. At warning level that is thousands of lines a second onto external
// storage, which is the same way the SPU recompiler diagnostics stalled the emulator.
//
// Nothing is lost: a container's size is fixed at creation, and both
// sys_memory_container_create and _destroy already log at warning, so the interesting
// events are still on the record. Compare sys_memory_get_user_memory_size above, which
// upstream already rate-limits by only logging when the reported values change.
sys_memory.trace("sys_memory_container_get_size(mem_info=*0x%x, cid=0x%x)", mem_info, cid);
const auto ct = idm::get_unlocked<lv2_memory_container>(cid);
+32 -15
View File
@@ -614,6 +614,23 @@ namespace rpcn
{
}
}
else
{
// Connected and authentified, but no game is running.
//
// Every other exit from this loop breaks out to the outer sem_rpcn.acquire(),
// and the only blocking wait lives inside the branch above -- so this 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 at the menu. Breaking out instead
// would park until something released the semaphore, which nothing does on
// game start, so wait here and re-check.
//
// Only reachable once RPCN is actually authentified, which is why it went
// unnoticed upstream.
if (!sem_rpcn.try_acquire_for(500ms))
{
}
}
}
}
}
@@ -726,7 +743,7 @@ namespace rpcn
if (data.size() != 4)
return error_and_disconnect("Invalid size of ServerInfo packet");
received_version = reinterpret_cast<le_t<u32>&>(data[0]);
received_version = read_from_ptr<le_t<u32>>(data, 0);
server_info_received = true;
break;
}
@@ -1662,7 +1679,7 @@ namespace rpcn
{
std::vector<u8> data(COMMUNICATION_ID_SIZE + sizeof(u16));
rpcn_client::write_communication_id(communication_id, data);
reinterpret_cast<le_t<u16>&>(data[COMMUNICATION_ID_SIZE]) = server_id;
write_to_ptr<le_t<u16>>(data, COMMUNICATION_ID_SIZE, server_id);
return forge_send(CommandType::GetWorldList, req_id, data);
}
@@ -2215,7 +2232,7 @@ namespace rpcn
pb_req.SerializeToString(&serialized);
std::vector<u8> data(serialized.size() + sizeof(u32));
reinterpret_cast<le_t<u32>&>(data[0]) = static_cast<u32>(serialized.size());
write_to_ptr<le_t<u32>>(data, 0, static_cast<u32>(serialized.size()));
memcpy(data.data() + sizeof(u32), serialized.data(), serialized.size());
return forge_send(CommandType::SendMessage, rpcn_request_counter.fetch_add(1), data);
@@ -2315,9 +2332,9 @@ namespace rpcn
std::vector<u8> data(COMMUNICATION_ID_SIZE + sizeof(u32) + bufsize + sizeof(u32) + score_data.size());
rpcn_client::write_communication_id(communication_id, data);
reinterpret_cast<le_t<u32>&>(data[COMMUNICATION_ID_SIZE]) = static_cast<u32>(bufsize);
write_to_ptr<le_t<u32>>(data, COMMUNICATION_ID_SIZE, static_cast<u32>(bufsize));
memcpy(data.data() + COMMUNICATION_ID_SIZE + sizeof(u32), serialized.data(), bufsize);
reinterpret_cast<le_t<u32>&>(data[COMMUNICATION_ID_SIZE + sizeof(u32) + bufsize]) = static_cast<u32>(score_data.size());
write_to_ptr<le_t<u32>>(data, COMMUNICATION_ID_SIZE + sizeof(u32) + bufsize, static_cast<u32>(score_data.size()));
memcpy(data.data() + COMMUNICATION_ID_SIZE + sizeof(u32) + bufsize + sizeof(u32), score_data.data(), score_data.size());
return forge_send(CommandType::RecordScoreData, req_id, data);
@@ -2618,8 +2635,8 @@ namespace rpcn
{
std::vector<u8> data(COMMUNICATION_ID_SIZE + sizeof(s32) + sizeof(s64));
rpcn_client::write_communication_id(communication_id, data);
reinterpret_cast<le_t<s32>&>(data[COMMUNICATION_ID_SIZE]) = trophy_id;
reinterpret_cast<le_t<s64>&>(data[COMMUNICATION_ID_SIZE + sizeof(s32)]) = timestamp;
write_to_ptr<le_t<s32>>(data, COMMUNICATION_ID_SIZE, trophy_id);
write_to_ptr<le_t<s64>>(data, COMMUNICATION_ID_SIZE + sizeof(s32), timestamp);
return forge_send(CommandType::UnlockTrophy, rpcn_request_counter.fetch_add(1), data);
}
@@ -2632,14 +2649,14 @@ namespace rpcn
std::vector<u8> data(COMMUNICATION_ID_SIZE + sizeof(u32) + count * (sizeof(s32) + sizeof(s64))), reply_data;
rpcn_client::write_communication_id(communication_id, data);
reinterpret_cast<le_t<u32>&>(data[COMMUNICATION_ID_SIZE]) = count;
write_to_ptr<le_t<u32>>(data, COMMUNICATION_ID_SIZE, count);
usz offset = COMMUNICATION_ID_SIZE + sizeof(u32);
for (const auto& [tid, ts] : local_unlocked)
{
reinterpret_cast<le_t<s32>&>(data[offset]) = tid;
write_to_ptr<le_t<s32>>(data, offset, tid);
offset += sizeof(s32);
reinterpret_cast<le_t<s64>&>(data[offset]) = ts;
write_to_ptr<le_t<s64>>(data, offset, ts);
offset += sizeof(s64);
}
@@ -2933,7 +2950,7 @@ namespace rpcn
rpcn_client::write_communication_id(com_id, data);
reinterpret_cast<le_t<u32>&>(data[COMMUNICATION_ID_SIZE]) = static_cast<u32>(bufsize);
write_to_ptr<le_t<u32>>(data, COMMUNICATION_ID_SIZE, static_cast<u32>(bufsize));
memcpy(data.data() + COMMUNICATION_ID_SIZE + sizeof(u32), serialized_data.data(), bufsize);
return forge_send(command, packet_id, data);
@@ -2944,7 +2961,7 @@ namespace rpcn
const usz bufsize = serialized_data.size();
std::vector<u8> data(sizeof(u32) + bufsize);
reinterpret_cast<le_t<u32>&>(data[0]) = static_cast<u32>(bufsize);
write_to_ptr<le_t<u32>>(data, 0, static_cast<u32>(bufsize));
memcpy(data.data() + sizeof(u32), serialized_data.data(), bufsize);
return forge_send(command, packet_id, data);
@@ -2956,9 +2973,9 @@ namespace rpcn
std::vector<u8> packet(packet_size);
packet[0] = static_cast<u8>(PacketType::Request);
reinterpret_cast<le_t<u16>&>(packet[1]) = static_cast<u16>(command);
reinterpret_cast<le_t<u32>&>(packet[3]) = ::narrow<u32>(packet_size);
reinterpret_cast<le_t<u64>&>(packet[7]) = packet_id;
write_to_ptr<le_t<u16>>(packet, 1, static_cast<u16>(command));
write_to_ptr<le_t<u32>>(packet, 3, ::narrow<u32>(packet_size));
write_to_ptr<le_t<u64>>(packet, 7, packet_id);
memcpy(packet.data() + RPCN_HEADER_SIZE, data.data(), data.size());
return packet;
+23
View File
@@ -3,6 +3,8 @@
#include "TextureUtils.h"
#include "../RSXThread.h"
#include "../rsx_utils.h"
#include "../color_utils.h"
#include "3rdparty/bcdec/bcdec.hpp"
#include "util/asm.hpp"
@@ -1870,4 +1872,25 @@ namespace rsx
return false;
}
}
u32 get_ROP_output_shuffle_index(rsx::surface_color_format format)
{
switch (format)
{
case surface_color_format::b8:
return static_cast<u32>(ROP_channel_remap::BBBB);
case surface_color_format::g8b8:
return static_cast<u32>(ROP_channel_remap::GBGB);
case surface_color_format::x1r5g5b5_z1r5g5b5:
case surface_color_format::x8r8g8b8_z8r8g8b8:
case surface_color_format::x8b8g8r8_z8b8g8r8:
return static_cast<u32>(ROP_channel_remap::RGB0);
case surface_color_format::x1r5g5b5_o1r5g5b5:
case surface_color_format::x8r8g8b8_o8r8g8b8:
case surface_color_format::x8b8g8r8_o8b8g8r8:
return static_cast<u32>(ROP_channel_remap::RGB1);
default:
return static_cast<u32>(ROP_channel_remap::RGBA);
}
}
}
+2
View File
@@ -422,4 +422,6 @@ namespace rsx
{
return is_border_clamped_texture(tex.wrap_s(), tex.wrap_t(), tex.wrap_r(), tex.dimension());
}
u32 get_ROP_output_shuffle_index(rsx::surface_color_format format);
}
+7 -1
View File
@@ -662,7 +662,7 @@ namespace rsx
}
}
void draw_command_processor::fill_fragment_state_buffer(void* buffer, const RSXFragmentProgram& /*fragment_program*/) const
void draw_command_processor::fill_fragment_state_buffer(void* buffer, const RSXFragmentProgram& fragment_program) const
{
#pragma pack(push, 1)
struct fragment_context_t
@@ -684,6 +684,12 @@ namespace rsx
const u32 alpha_func = static_cast<u32>(REGS(m_ctx)->alpha_func());
rop_control.set_alpha_test_func(alpha_func);
if (fragment_program.ctrl & RSX_SHADER_CONTROL_ROP_OUTPUT_REMAP)
{
const u32 remap_index = get_ROP_output_shuffle_index(REGS(m_ctx)->surface_color());
rop_control.set_output_remap(remap_index);
}
// Generate wpos coefficients
// wpos equation is now as follows (ignoring pixel center offset):
// wpos.y = (frag_coord / resolution_scale) * ((window_origin!=top)?-1.: 1.) + ((window_origin!=top)? window_height : 0)
+1
View File
@@ -240,6 +240,7 @@ void GLFragmentDecompilerThread::insertGlobalFunctions(std::stringstream &OS)
m_shader_props.ROP_alpha_to_coverage_test = !!(m_prog.ctrl & RSX_SHADER_CONTROL_ALPHA_TO_COVERAGE);
m_shader_props.ROP_polygon_stipple_test = !!(m_prog.ctrl & RSX_SHADER_CONTROL_POLYGON_STIPPLE);
m_shader_props.ROP_discard = !!(m_prog.ctrl & RSX_SHADER_CONTROL_USES_KIL);
m_shader_props.ROP_channel_remap = !!(m_prog.ctrl & RSX_SHADER_CONTROL_ROP_OUTPUT_REMAP);
m_shader_props.require_tex1D_ops = properties.has_tex1D;
m_shader_props.require_tex2D_ops = properties.has_tex2D;
+3 -1
View File
@@ -145,6 +145,7 @@ namespace gl
if (fp_ctrl & CELL_GCM_SHADER_CONTROL_DEPTH_EXPORT) opt |= COMPILER_OPT_ENABLE_DEPTH_EXPORT;
if (fp_ctrl & CELL_GCM_SHADER_CONTROL_32_BITS_EXPORTS) opt |= COMPILER_OPT_ENABLE_F32_EXPORT;
if (fp_ctrl & RSX_SHADER_CONTROL_USES_KIL) opt |= COMPILER_OPT_ENABLE_KIL;
if (fp_ctrl & RSX_SHADER_CONTROL_ROP_OUTPUT_REMAP) opt |= COMPILER_OPT_ENABLE_ROP_REMAP;
if (metadata.referenced_textures_mask) opt |= COMPILER_OPT_ENABLE_TEXTURES;
if (metadata.has_branch_instructions) opt |= COMPILER_OPT_ENABLE_FLOW_CTRL;
if (metadata.has_pack_instructions) opt |= COMPILER_OPT_ENABLE_PACKING;
@@ -412,6 +413,7 @@ namespace gl
{
.domain = ::glsl::program_domain::glsl_fragment_program,
.require_lit_emulation = true,
.ROP_channel_remap = !!(compiler_options & COMPILER_OPT_ENABLE_ROP_REMAP),
};
::glsl::insert_glsl_legacy_function(builder, properties);
@@ -576,7 +578,7 @@ namespace gl
}
}
void shader_interpreter::flush_vertex_texture_bindings(glsl::program* program)
void shader_interpreter::flush_vertex_texture_bindings(glsl::program* /*program*/)
{
// TODO
}
+7 -2
View File
@@ -578,8 +578,13 @@ namespace gl
{
ensure(desc.sections_to_copy.size() == 1);
const auto& section = desc.sections_to_copy.front();
return create_temporary_subresource_impl(cmd, section.src, static_cast<GLenum>(section.src->get_internal_format()),
GL_TEXTURE_2D, desc.gcm_format, desc.width, desc.height, 1, 1, desc.remap, &section);
return create_temporary_subresource_impl(
cmd, section.src,
GL_NONE, // NOTE: Do not force this to section.get_sized_internal_fmt(). Leave it as GL_NONE, let the callee find the right type in case of bitcast.
GL_TEXTURE_2D, desc.gcm_format,
desc.width, desc.height, 1, 1,
desc.remap,
&section);
}
gl::texture_view* generate_cubemap_from_images(gl::command_context& cmd, const deferred_subresource& desc) override

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