mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-11-07 10:03:23 +09:00
[Perf] (Magma): a renamed wire store reuses its retired buffers once their submissions complete (FCL inproc apply thread 1.74 -> 1.61 ms/frame at cpuhunt).
This commit is contained in:
@@ -239,6 +239,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
resource->indirectReadBarrierPending = false;
|
resource->indirectReadBarrierPending = false;
|
||||||
resource->stagedCoverage.clear();
|
resource->stagedCoverage.clear();
|
||||||
DropWireShadow(*resource);
|
DropWireShadow(*resource);
|
||||||
|
ReleaseRenameSpares(*resource);
|
||||||
if (resource->size == 0) return;
|
if (resource->size == 0) return;
|
||||||
|
|
||||||
if (!CreateWireStoreBuffer(*resource)) {
|
if (!CreateWireStoreBuffer(*resource)) {
|
||||||
@@ -279,6 +280,12 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
resource.buffer.Map() != nullptr;
|
resource.buffer.Map() != nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void VkBufferManager::ReleaseRenameSpares(WireBufferResource& resource) {
|
||||||
|
for (auto& spare : resource.renameSpares)
|
||||||
|
DeferWireRelease(std::move(spare.buffer), spare.lastUseSerial != 0 ? spare.lastUseSerial : m_frameSerial);
|
||||||
|
resource.renameSpares.clear();
|
||||||
|
}
|
||||||
|
|
||||||
void VkBufferManager::DropWireShadow(WireBufferResource& resource) {
|
void VkBufferManager::DropWireShadow(WireBufferResource& resource) {
|
||||||
if (resource.shadow.empty()) return;
|
if (resource.shadow.empty()) return;
|
||||||
m_wireShadowBytes -= resource.shadow.size();
|
m_wireShadowBytes -= resource.shadow.size();
|
||||||
@@ -300,19 +307,45 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
MG_Config::Features.MagmaWriteRename == MG_Config::QuirkOverride::ForceOff)
|
MG_Config::Features.MagmaWriteRename == MG_Config::QuirkOverride::ForceOff)
|
||||||
return false;
|
return false;
|
||||||
VkBufferObject old = std::move(resource.buffer);
|
VkBufferObject old = std::move(resource.buffer);
|
||||||
if (!CreateWireStoreBuffer(resource) ||
|
Bool reused = false;
|
||||||
!resource.buffer.Upload(resource.shadow.data(), static_cast<VkDeviceSize>(resource.size), 0)) {
|
for (SizeT i = 0; pVulkanRenderer != nullptr && i < resource.renameSpares.size(); ++i) {
|
||||||
resource.buffer.Destroy();
|
if (!pVulkanRenderer->IsSubmitIndexComplete(resource.renameSpares[i].submitIndex)) continue;
|
||||||
|
resource.buffer = std::move(resource.renameSpares[i].buffer);
|
||||||
|
resource.renameSpares.erase(resource.renameSpares.begin() + static_cast<std::ptrdiff_t>(i));
|
||||||
|
reused = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (!reused) {
|
||||||
|
if (!CreateWireStoreBuffer(resource)) {
|
||||||
|
resource.buffer.Destroy();
|
||||||
|
resource.buffer = std::move(old);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
++m_wireStoreCount;
|
||||||
|
}
|
||||||
|
if (!resource.buffer.Upload(resource.shadow.data(), static_cast<VkDeviceSize>(resource.size), 0)) {
|
||||||
|
// Keep the store on its old buffer; the new one goes the deferred way (it is idle).
|
||||||
|
DeferWireRelease(std::move(resource.buffer), 0);
|
||||||
resource.buffer = std::move(old);
|
resource.buffer = std::move(old);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
// Busy means a GPU command recorded so far may name the old buffer: release it behind
|
// Busy means a GPU command recorded so far may name the old buffer: it waits behind this
|
||||||
// this frame's serial and the current sync point, whatever its own stamps said.
|
// frame's serial and the current sync point, whatever its own stamps said - as a spare
|
||||||
DeferWireRelease(std::move(old), m_frameSerial);
|
// for the next rename, or (past the bound) through DeferWireRelease.
|
||||||
|
constexpr SizeT kRenameSpares = 4;
|
||||||
|
if (resource.renameSpares.size() >= kRenameSpares) {
|
||||||
|
auto& oldest = resource.renameSpares.front();
|
||||||
|
DeferWireRelease(std::move(oldest.buffer), oldest.lastUseSerial);
|
||||||
|
resource.renameSpares.erase(resource.renameSpares.begin());
|
||||||
|
}
|
||||||
|
WireBufferResource::RenameSpare spare;
|
||||||
|
spare.buffer = std::move(old);
|
||||||
|
spare.submitIndex = pVulkanRenderer != nullptr ? pVulkanRenderer->GetSyncPointSubmitIndex() : 0;
|
||||||
|
spare.lastUseSerial = m_frameSerial;
|
||||||
|
resource.renameSpares.push_back(std::move(spare));
|
||||||
resource.lastUseSerial = 0;
|
resource.lastUseSerial = 0;
|
||||||
resource.lastUseSubmitIndex = 0;
|
resource.lastUseSubmitIndex = 0;
|
||||||
resource.gpuWritesPending = false;
|
resource.gpuWritesPending = false;
|
||||||
++m_wireStoreCount;
|
|
||||||
++m_wireRenames;
|
++m_wireRenames;
|
||||||
NoteWireStorePeaks();
|
NoteWireStorePeaks();
|
||||||
PublishWireReclaimGauges();
|
PublishWireReclaimGauges();
|
||||||
@@ -1154,6 +1187,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
const auto found = m_wireBuffers.find(WireBufferKey(res));
|
const auto found = m_wireBuffers.find(WireBufferKey(res));
|
||||||
if (found == m_wireBuffers.end()) return;
|
if (found == m_wireBuffers.end()) return;
|
||||||
DropWireShadow(found->second);
|
DropWireShadow(found->second);
|
||||||
|
ReleaseRenameSpares(found->second);
|
||||||
DeferWireRelease(std::move(found->second.buffer),
|
DeferWireRelease(std::move(found->second.buffer),
|
||||||
found->second.imported ? T0ReleaseSerial(found->second.lastUseSerial)
|
found->second.imported ? T0ReleaseSerial(found->second.lastUseSerial)
|
||||||
: found->second.lastUseSerial); // P11 B2, as Respecify
|
: found->second.lastUseSerial); // P11 B2, as Respecify
|
||||||
|
|||||||
@@ -204,6 +204,17 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
// WriteWireBuffer). Empty: no shadow (a large store, the budget was spent, or a GPU
|
// WriteWireBuffer). Empty: no shadow (a large store, the budget was spent, or a GPU
|
||||||
// write, shader-writable bind or client mapping made the host copy unknowable).
|
// write, shader-writable bind or client mapping made the host copy unknowable).
|
||||||
Vector<Uint8> shadow;
|
Vector<Uint8> shadow;
|
||||||
|
// P15: buffers this store was renamed away from, kept for the next rename instead of
|
||||||
|
// allocating one (a UI store is renamed several times a frame). A spare is reused only
|
||||||
|
// once the submission that can name it has completed - DeferWireRelease's own rule -
|
||||||
|
// and its handle never died, so no handle-keyed memo can mistake it for another
|
||||||
|
// buffer. Bounded; the rest go through DeferWireRelease.
|
||||||
|
struct RenameSpare {
|
||||||
|
VkBufferObject buffer;
|
||||||
|
Uint64 submitIndex = 0;
|
||||||
|
Uint64 lastUseSerial = 0;
|
||||||
|
};
|
||||||
|
Vector<RenameSpare> renameSpares;
|
||||||
Bool gpuWritesPending = false;
|
Bool gpuWritesPending = false;
|
||||||
// Only ranges actually submitted by resource_subdata are covered. No
|
// Only ranges actually submitted by resource_subdata are covered. No
|
||||||
// shadow is retained: flush cannot replay stale bytes over GPU writes.
|
// shadow is retained: flush cannot replay stale bytes over GPU writes.
|
||||||
@@ -220,6 +231,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
Uint64 m_wireRenames = 0;
|
Uint64 m_wireRenames = 0;
|
||||||
SizeT m_wireShadowBytes = 0;
|
SizeT m_wireShadowBytes = 0;
|
||||||
void DropWireShadow(WireBufferResource& resource);
|
void DropWireShadow(WireBufferResource& resource);
|
||||||
|
void ReleaseRenameSpares(WireBufferResource& resource);
|
||||||
Bool CreateWireStoreBuffer(WireBufferResource& resource);
|
Bool CreateWireStoreBuffer(WireBufferResource& resource);
|
||||||
Bool RenameBusyWireStore(WireBufferResource& resource);
|
Bool RenameBusyWireStore(WireBufferResource& resource);
|
||||||
static Uint64 WireBufferKey(MG_Pipe::MGPipeHandle res) {
|
static Uint64 WireBufferKey(MG_Pipe::MGPipeHandle res) {
|
||||||
|
|||||||
@@ -220,6 +220,32 @@ void main() { o0 = vColor * uTint; }
|
|||||||
Destroy(s);
|
Destroy(s);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Two renames of ONE store inside one frame: the second must not reuse the buffer the first
|
||||||
|
// draw still reads (nothing is submitted yet, so a reused spare would be overwritten under it).
|
||||||
|
TEST_F(WriteRenameScenario, ThreeDrawsAroundTwoRenamesInOneFrameEachReadTheirOwnBytes) {
|
||||||
|
if (!Ready()) return;
|
||||||
|
std::vector<Vertex> v(4);
|
||||||
|
PutQuad(v, 0, 0.0f, 0.0f, 0.33f, 1.0f, 255, 0, 0); // left third, red
|
||||||
|
std::string error;
|
||||||
|
Scene s = MakeScene(v, &error);
|
||||||
|
ASSERT_NE(s.program, 0u) << error;
|
||||||
|
|
||||||
|
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
|
||||||
|
std::vector<Vertex> middle(4), right(4);
|
||||||
|
PutQuad(middle, 0, 0.33f, 0.0f, 0.66f, 1.0f, 0, 255, 0);
|
||||||
|
glBufferSubData(GL_ARRAY_BUFFER, 0, static_cast<GLsizeiptr>(middle.size() * sizeof(Vertex)), middle.data());
|
||||||
|
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
|
||||||
|
PutQuad(right, 0, 0.66f, 0.0f, 1.0f, 1.0f, 0, 0, 255);
|
||||||
|
glBufferSubData(GL_ARRAY_BUFFER, 0, static_cast<GLsizeiptr>(right.size() * sizeof(Vertex)), right.data());
|
||||||
|
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
|
||||||
|
EXPECT_EQ(FirstGLError(), 0u);
|
||||||
|
|
||||||
|
EXPECT_TRUE(Near(At(kSize / 6, kSize / 2), 255, 0, 0)) << "left: the first draw's bytes";
|
||||||
|
EXPECT_TRUE(Near(At(kSize / 2, kSize / 2), 0, 255, 0)) << "middle: after the first rename";
|
||||||
|
EXPECT_TRUE(Near(At(kSize * 5 / 6, kSize / 2), 0, 0, 255)) << "right: after the second rename";
|
||||||
|
Destroy(s);
|
||||||
|
}
|
||||||
|
|
||||||
TEST_F(WriteRenameScenario, ARenamedStoreKeepsDrawingRightInLaterFrames) {
|
TEST_F(WriteRenameScenario, ARenamedStoreKeepsDrawingRightInLaterFrames) {
|
||||||
if (!Ready()) return;
|
if (!Ready()) return;
|
||||||
std::vector<Vertex> v(4);
|
std::vector<Vertex> v(4);
|
||||||
|
|||||||
@@ -31,6 +31,9 @@ case "${1:-status}" in
|
|||||||
SER=$SER bash "$HERE/pin_clocks.sh" pin
|
SER=$SER bash "$HERE/pin_clocks.sh" pin
|
||||||
"$0" status ;;
|
"$0" status ;;
|
||||||
stop)
|
stop)
|
||||||
|
# Trace-replay packages a job started must not idle on the device after it (they hold GPU
|
||||||
|
# memory and a surface): every MobileGL plugin package is force-stopped at session end.
|
||||||
|
su_sh "for p in \$(pm list packages top.mobilegl.plugin | sed s/package://); do am force-stop \$p; done"
|
||||||
su_sh "if [ -f $MARK ]; then grep \"^svc \" $MARK | while read k s st; do [ \"\$st\" = running ] && setprop ctl.start \$s; done; else for s in $SERVICES; do setprop ctl.start \$s; done; fi"
|
su_sh "if [ -f $MARK ]; then grep \"^svc \" $MARK | while read k s st; do [ \"\$st\" = running ] && setprop ctl.start \$s; done; else for s in $SERVICES; do setprop ctl.start \$s; done; fi"
|
||||||
sleep 3
|
sleep 3
|
||||||
bad=$(su_sh "if [ -f $MARK ]; then grep \"^svc \" $MARK | while read k s st; do [ \"\$st\" = running ] && [ \"\$(getprop init.svc.\$s)\" != running ] && echo \$s; done; fi" | tr -d '\r')
|
bad=$(su_sh "if [ -f $MARK ]; then grep \"^svc \" $MARK | while read k s st; do [ \"\$st\" = running ] && [ \"\$(getprop init.svc.\$s)\" != running ] && echo \$s; done; fi" | tr -d '\r')
|
||||||
|
|||||||
Reference in New Issue
Block a user