[Fix] (Espryt): order a dispatch after unbarriered storage-buffer writes so adjacent components survive

This commit is contained in:
2026-10-12 05:59:28 -04:00
parent c639e17514
commit 363fce247f
2 changed files with 81 additions and 2 deletions
+34 -2
View File
@@ -455,6 +455,30 @@ namespace MobileGL::MG_Backend::DirectGLES {
return request;
}
// True from a draw or dispatch that ran with a storage buffer bound until the next
// barrier that orders storage-buffer writes. Every bound storage point counts as written,
// the client's own conservative set (see serverGpuWritten below).
//
// WHAT IT IS FOR: two dispatches with no barrier between them that write DIFFERENT
// components of one vector in the same storage buffer. GL leaves the two unordered, which
// allows them to run concurrently, but every write still lands - they are separate memory
// locations. A driver that stores a vector component as a read-modify-write of the whole
// vector loses one of them instead: KHR-GL43.shader_storage_buffer_object.
// advanced-switchBuffers-cs writes digest.x .. digest.w of one uvec4 from four
// back-to-back dispatches and read back a zero component in 8 runs of 20 on Adreno (0 of
// 20 with a storage barrier between the dispatches, 0 of 20 with uint[4]). The fix is the
// barrier the application did not owe: PrepareForCompute issues it when the previous
// shader work may have written a storage buffer and nothing ordered those writes since.
//
// Compute only. A dispatch that follows a dispatch is the shape the test has and the one
// a driver may overlap; draws keep their own ordering and are not taxed, they only SET
// the flag here - one store in the walk that already visits every bound point.
static Bool g_shaderStorageWritesUnordered = false;
// Called by the application's own glMemoryBarrier: a barrier that orders storage writes
// already did the work.
static void NoteShaderStorageWritesOrdered() { g_shaderStorageWritesUnordered = false; }
// The record arm of SyncBufferBindingPoints. Same shape, same order, same branches; the
// four frontend reads become the applier's window and a handle per entry.
//
@@ -502,6 +526,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
// point is read-only by definition.
if (shaderBufferClass != MG_Pipe::kMGPipeShaderBufferClassUniform) {
backendResource->serverGpuWritten = true;
g_shaderStorageWritesUnordered = true;
}
const auto backendBufferId = backendResource->id;
if (entry.Offset == 0 && entry.Size == MG_Pipe::kMGPipeWholeBuffer) {
@@ -6219,14 +6244,19 @@ namespace MobileGL::MG_Backend::DirectGLES {
noFrontendProgram;
const TextureImpl::DrawTextureSyncKeys textureKeys = TextureImpl::CaptureDrawTextureSyncKeys();
// Read BEFORE this dispatch's own binding walk sets it again (see the flag's comment).
const Bool storageWritesUnordered = BufferImpl::g_shaderStorageWritesUnordered;
BufferImpl::g_shaderStorageWritesUnordered = false;
BufferImpl::SyncComputeBuffers(includeDispatchIndirectBuffer);
TextureImpl::SyncNeccessaryTextures(textureKeys);
TextureImpl::SyncImageTextureBindings();
TextureImpl::MarkWritableImageBufferTexturesGpuWritten();
// Both syncs above may have re-uploaded a texture this dispatch reads or stores
// (Managers.h, NoteInPlaceTextureUpload).
// (Managers.h, NoteInPlaceTextureUpload), and the previous shader work may have left
// storage writes unordered; one barrier covers whichever of the two applies.
{
GLbitfield barriers = TextureImpl::TakeInPlaceTextureUploadBarrierBits();
if (storageWritesUnordered) barriers |= GL_SHADER_STORAGE_BARRIER_BIT;
if (barriers != 0 && g_GLESFuncs.glMemoryBarrier) g_GLESFuncs.glMemoryBarrier(barriers);
}
// P5e (pg), kimi row 92: the DISPATCH half of the same retirement -
@@ -9992,7 +10022,9 @@ namespace MobileGL::MG_Backend::DirectGLES {
}
void MemoryBarrier(GLbitfield barriers) {
g_GLESFuncs.glMemoryBarrier(LowerAtomicCounterBarrierBits(barriers));
barriers = LowerAtomicCounterBarrierBits(barriers);
if ((barriers & GL_SHADER_STORAGE_BARRIER_BIT) != 0) BufferImpl::NoteShaderStorageWritesOrdered();
g_GLESFuncs.glMemoryBarrier(barriers);
if (g_GLESCapabilities.IsAngleRenderer) {
g_GLESFuncs.glFlush();
}
@@ -186,4 +186,51 @@ void main() {
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, static_cast<GLuint>(i), 0);
}
}
// Four back-to-back dispatches with NO barrier between them, each writing one component of the
// same uvec4 (KHR-GL43.shader_storage_buffer_object.advanced-switchBuffers-cs). GL leaves them
// unordered, but the four components are separate memory locations and all four writes have
// to land; a driver that stores a vector component as a read-modify-write of the whole vector
// loses some unless the backend orders the dispatches (DirectGLES's PrepareForCompute does).
TEST_F(SsboArrayDynamicIndexScenario, BackToBackDispatchesKeepEachOthersComponents) {
if (!Ready() || IsSkipped()) return;
const GLuint program = CompileComputeProgram(R"(#version 430 core
layout(local_size_x = 1) in;
layout(binding = 0, std430) buffer Input { uint cookie[4]; } g_in;
layout(binding = 1, std430) buffer Output { uvec4 digest; };
void main() {
switch (g_in.cookie[0] + g_in.cookie[1] + g_in.cookie[2] + g_in.cookie[3]) {
case 0x000000ffu: digest.x = 0xff000000u; break;
case 0x0000ff00u: digest.y = 0x00ff0000u; break;
case 0x00ff0000u: digest.z = 0x0000ff00u; break;
case 0xff000000u: digest.w = 0x000000ffu; break;
}
}
)");
ASSERT_NE(program, 0u) << m_buildLog;
GLuint inputs[4] = {};
for (int i = 0; i < 4; ++i) {
// Four cookies summing to 0xff << 8i: dispatch i selects case i.
const unsigned int part = 0x11u << (8 * i);
inputs[i] = MakeStorageBuffer({part, part, part * 2, (0xffu << (8 * i)) - part * 4});
}
const GLuint output = MakeStorageBuffer({0u, 0u, 0u, 0u});
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 1, output);
glUseProgram(program);
for (int i = 0; i < 4; ++i) {
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 0, inputs[i]);
glDispatchCompute(1, 1, 1);
}
glMemoryBarrier(GL_BUFFER_UPDATE_BARRIER_BIT);
glUseProgram(0);
EXPECT_EQ(FirstGLError(), 0u);
const std::vector<unsigned int> digest = ReadBuffer(output, 4);
EXPECT_EQ(digest[0], 0xff000000u);
EXPECT_EQ(digest[1], 0x00ff0000u);
EXPECT_EQ(digest[2], 0x0000ff00u);
EXPECT_EQ(digest[3], 0x000000ffu);
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 0, 0);
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 1, 0);
glDeleteProgram(program);
}
} // namespace MGITest