Repository navigation
Hello Neighbor (PPSA35154) #6991
Replies: 2 comments
Update: main 47a266c, after #6060The slowness under "Current blocker" 3 was the import-compare cost tracked in #5692, which #6060 fixes. On main 47a266c (2026-10-17), the startup log now says Same machine and settings as above (
Where the time goes now, in game with the patch (10 s window):
Without the patch, Draw-input patch against 47a266c
diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp
index 5993201d6..30204cf3e 100644
--- a/core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp
+++ b/core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp
@@ -899,6 +899,13 @@ void reportDrawEnd(const State& state, const DrawTimer& timer, const ShaderResou
}
+static VkMemoryPropertyFlags DrawInputMemory() {
+ static const VkMemoryPropertyFlags flags = std::getenv("APS5_DRAW_INPUT_DEVICE") != nullptr
+ ? VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT | VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT
+ : VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT;
+ return flags;
+}
+
DrawInputCopy CopyDrawInput(const Context& context, Recorder* recorder, std::uint64_t address, std::size_t bytes, std::size_t alignment, Recorder::SnapshotUse use) {
Require(use != Recorder::SnapshotUse::Storage, "a draw input is a vertex or index buffer");
DrawInputCopy copy;
@@ -912,7 +919,7 @@ DrawInputCopy CopyDrawInput(const Context& context, Recorder* recorder, std::uin
return copy;
}
}
- copy.buffer = std::make_shared<Buffer>(context, bytes, use == Recorder::SnapshotUse::Vertex ? VK_BUFFER_USAGE_VERTEX_BUFFER_BIT : VK_BUFFER_USAGE_INDEX_BUFFER_BIT);
+ copy.buffer = std::make_shared<Buffer>(context, bytes, use == Recorder::SnapshotUse::Vertex ? VK_BUFFER_USAGE_VERTEX_BUFFER_BIT : VK_BUFFER_USAGE_INDEX_BUFFER_BIT, DrawInputMemory());
GuestMemory::Read(address, copy.buffer->Bytes(), alignment);
return copy;
}
@@ -921,7 +928,7 @@ DrawInputCopy CopyZeroPaddedDrawInput(const Context& context, std::uint64_t addr
Require(validBytes <= bytes, "the valid bytes of a vertex fetch exceed the fetch");
GuestMemory::FlushGpuWrites(address, bytes);
DrawInputCopy copy;
- copy.buffer = std::make_shared<Buffer>(context, bytes, VK_BUFFER_USAGE_VERTEX_BUFFER_BIT);
+ copy.buffer = std::make_shared<Buffer>(context, bytes, VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, DrawInputMemory());
const auto span = copy.buffer->Bytes();
GuestMemory::Read(address, span.subspan(0, validBytes), 1);
std::fill(span.begin() + static_cast<std::ptrdiff_t>(validBytes), span.end(), std::byte{0});
@@ -935,7 +942,8 @@ void KeepDrawInput(Recorder* recorder, std::uint64_t address, const DrawInputCop
const HostImport* InPlaceDrawInput(const Context& context, std::uint64_t address, std::size_t bytes, std::size_t alignment, bool* behindGpuWrite) {
auto* recorder = context.recorder;
- if (recorder == nullptr || bytes == 0) return nullptr;
+ static const bool noInPlace = std::getenv("APS5_NO_INPLACE_DRAW_INPUT") != nullptr;
+ if (recorder == nullptr || bytes == 0 || noInPlace) return nullptr;
bool behind = false;
if (behindGpuWrite == nullptr) {
GuestMemory::FlushGpuWrites(address, bytes);
@@ -1091,10 +1099,12 @@ DrawInputs prepareDrawInputs(const Context& context, const State& state, const P
if (import == nullptr) copy = CopyDrawInput(context, context.recorder, draw.indexAddress, static_cast<std::size_t>(indexBytes), draw.indexSize, use);
else GuestMemory::CheckRange(reinterpret_cast<const void*>(draw.indexAddress), static_cast<std::size_t>(indexBytes), draw.indexSize);
std::optional<std::uint32_t> highest;
+ bool indicesInGuest = (DrawInputMemory() & VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT) != 0;
if (import != nullptr) {
highest = InPlaceIndexRange(*context.recorder, draw.indexAddress, static_cast<std::size_t>(indexBytes), draw.indexSize, skipRestart).highest;
} else if (!copy.reused) {
- highest = HighestDrawIndex(copy.buffer->Bytes().first(static_cast<std::size_t>(indexBytes)), draw.indexSize, skipRestart);
+ const auto bytes = indicesInGuest ? std::span<const std::byte>(reinterpret_cast<const std::byte*>(draw.indexAddress), static_cast<std::size_t>(indexBytes)) : copy.buffer->Bytes().first(static_cast<std::size_t>(indexBytes));
+ highest = HighestDrawIndex(bytes, draw.indexSize, skipRestart);
KeepDrawInput(context.recorder, draw.indexAddress, copy, use, skipRestart ? (highest ? *highest + 1u : 0u) : highest.value_or(0u));
} else if (!skipRestart) {
highest = copy.derived;
@@ -1115,6 +1125,7 @@ DrawInputs prepareDrawInputs(const Context& context, const State& state, const P
GuestMemory::Read(draw.indexAddress, bytes, draw.indexSize);
highest = ClampDrawIndices(bytes, draw.indexSize, skipRestart, limit);
import = nullptr;
+ indicesInGuest = false;
copy = DrawInputCopy{};
copy.buffer = std::move(clamped);
if (!highest) {
@@ -1127,7 +1138,7 @@ DrawInputs prepareDrawInputs(const Context& context, const State& state, const P
Require(!fanGeometry || !state.primitiveRestart || *highest != (draw.indexSize == 2 ? 0xffffu : 0xffffffffu), "primitive restart in a triangle fan geometry draw is unsupported");
inputs.maxIndex = *highest;
if (skipRestart && state.stages.mesh && state.stages.mesh->inputPrimitive == 6) {
- const auto indexSpan = import != nullptr ? std::span<const std::byte>(reinterpret_cast<const std::byte*>(draw.indexAddress), static_cast<std::size_t>(indexBytes)) : copy.buffer->Bytes().first(static_cast<std::size_t>(indexBytes));
+ const auto indexSpan = import != nullptr || indicesInGuest ? std::span<const std::byte>(reinterpret_cast<const std::byte*>(draw.indexAddress), static_cast<std::size_t>(indexBytes)) : copy.buffer->Bytes().first(static_cast<std::size_t>(indexBytes));
inputs.restartTable = MeshRestartTable(indexSpan, draw.indexSize);
}
if (import != nullptr) {Done with AI assistance. The runs ran on my own machine. |
Update 2: imports under 1 MiB and #6678Method:
What #6678 fixes here:
Missing functions:
Where the time goes now (sampled on main with the settle floor lowered to 64 KiB locally, which gives the same proofs as #6678, plus the draw-input patch; 9.8 FPS while sampled):
Done with AI assistance. The runs ran on my own machine. |
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Existing thread
Title
Hello Neighbor (PPSA35154)
Content version tested
01.001.000
AnyPS5 commit
main 153d5d4. Locally it also has my unmerged mouse-capture fix in MouseInput.cpp, which does not affect this title. The second run below adds the draw-input patch shown under "How it was run". v0.1.2 was also tested: see "Current blocker".
System tested
Windows 11 Pro, Ryzen 9 9950X3D, RTX 5090 32 GB (NVIDIA 617.42, Resizable BAR on), 64 GB RAM, WinLibs GCC 15.2.0
Furthest point
In game
What is shown
The menu works and save slots are found. New Game loads (slowly), and the title is playable in game: the level renders, the player can move, and the cutscenes play. Keyboard and mouse work in the menus and in game, and audio works.
Frame rate in game: 1 to 2 FPS. With
APS5_PROFILE_DRAW=1 APS5_PROFILE_GPU=1:No
NotImplementedstop, no unresolved import, no missing function in any run.Where the time goes (run with the patch, 10 s window in game,
APS5_PROFILE_DRAW=1):readTargettakes 2.9 s per 10 s, over 89,216 resident target lookups (88,136 with a partial scissor, 1,080 full). 648 of these lookups take 1 ms or more, 1.6 s in total.lookuptakes 3.4 s per 10 s.[rescache]shows 1,393 hits against 36,522 misses, of which 18,387 are "same key (not inserted or evicted)".[textures]stage A shows 1 fast hit against 298,729 full lookups.The slow target lookups compare untracked memory byte by byte. At startup the log says
[write-watch] Windows direct host imports use comparisons; separate shared aliases retain guest write tracking.In the main menu without input, after about 80 s, the draw time is dominated by the same path: 1,872 lookups of 1 ms or more take 4.2 s of 7.8 s. A worker sample of that phase (
APS5_SAMPLE_WORKER, steady-state difference of two snapshots) shows:StorageTexture::Refresh: 86 % inclusive. Inside it:compareUntracked→GuestMemory::EqualsCommittedUnsynced: 27.5 %.upload→captureGuestBytes→ReadCommittedUnsynced: 25 %.The render targets appear to sit in host-imported memory, so every refresh in a new collect epoch compares the whole surface (about 33 MB for a 3840×2160 target, about 2 ms). When the GPU has written it back, the surface is also captured and uploaded again. The
comparedGuestBytesmemo only holds within one epoch unless the range is hinted, and the queue bumps the epoch about 40 times per frame here.Experiments in the menu:
APS5_NO_HOST_IMPORT=1removes the compares (readTarget71 ms), but the menu drops from about 10.7 to 1.5 presents/s.APS5_HOST_IMPORT_MIB=1024aborts withan address-based build copies 891 MiB of 4627 registered ranges, past 64 MiB (APS5_ADDRESS_COPY_MAX_MIB).Current blocker
AGC graphics: storing storage image results pending over memory outside the write watch without the guest bytes saved before them is not implemented. This is agc: a second write-back of a storage image over unwatched memory throws ("without the guest bytes saved before them") #5711, fixed by fix(agc): save the bytes a GPU-side store writes outside the write watch #5760. Main 153d5d4 gets in game.FATAL: unhandled exception 0xc0000005on the title's main thread, riphello-neighbor.exe+0x3418666(the relinked eboot), read of0x1fc3087a8c,rbx = rcx = 0. The second run, with the draw-input patch, got past that cutscene. That is one run each, so it is not shown whether the patch is related.Also seen once on v0.1.2:
queue 0x20 WAIT_REG_MEM at 0x1020f337a0 timed out after 1000ms (function 3 ref 0x1 ...). It was not seen on main.How it was run
SonyPackageExtraction.ExtractAsync).eboot.binand the 5 modules insce_module(libc, libScePfs, libSceFontGsm, libSceJobManager, libSceNpCppWebApi) are fSELF. Unwrapped withtools/unwrap_self.py.relinker --windows source/eboot.bin hello-neighbor.exe. Layout as in USAGE.md, withlibs/built from the same commit.That function is used for the draw-input copies in
CopyDrawInputandCopyZeroPaddedDrawInput.APS5_NO_INPLACE_DRAW_INPUT=1makesInPlaceDrawInputreturn nothing, and with device memory the index scan reads the guest bytes instead of the copy. The same patch measured +30 % on Astro Bot at Crash Site; it is described in the Astro Bot thread #357.Tested with AI assistance (Claude Code). I played and measured the runs on my own machine.
All reactions