[WIP] Mesa-style buffer overhaul: resource abstraction + immediate transfer ops

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-08 14:47:51 +08:00
co-authored by Claude Fable 5
parent 2ed96e9678
commit b8ffd25148
15 changed files with 1048 additions and 642 deletions
@@ -9,6 +9,7 @@
#include "BackendObject_DirectGLES.h" #include "BackendObject_DirectGLES.h"
#include "MG_Backend/BackendObject.h" #include "MG_Backend/BackendObject.h"
#include <MG_Backend/DirectGLES/DirectGLES.h> #include <MG_Backend/DirectGLES/DirectGLES.h>
#include <MG_Backend/DirectGLES/Managers.h>
#include <MG_Backend/DirectGLES/Utils.h> #include <MG_Backend/DirectGLES/Utils.h>
#include <MG_Util/BackendLoaders/OpenGL/Loader.h> #include <MG_Util/BackendLoaders/OpenGL/Loader.h>
#include <MG_Util/Classifiers/TextureEnumClassifier.h> #include <MG_Util/Classifiers/TextureEnumClassifier.h>
@@ -632,6 +633,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
MG_Util::BackendLoader::AcquireGLESFunctions(m_GLESFunctions, m_EGLFunctions.eglGetProcAddress); MG_Util::BackendLoader::AcquireGLESFunctions(m_GLESFunctions, m_EGLFunctions.eglGetProcAddress);
DirectGLES::SetEGLFuncsTable(m_EGLFunctions); DirectGLES::SetEGLFuncsTable(m_EGLFunctions);
DirectGLES::SetGLESFuncsTable(m_GLESFunctions); DirectGLES::SetGLESFuncsTable(m_GLESFunctions);
BufferImpl::RegisterBufferBackendOps();
m_initialized = true; m_initialized = true;
} }
+43 -50
View File
@@ -109,7 +109,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
const Uint8* ResolveIndirectCommandBytes(const void* indirect, SizeT requiredBytes, const char* label) { const Uint8* ResolveIndirectCommandBytes(const void* indirect, SizeT requiredBytes, const char* label) {
auto drawBuffer = MG_State::pGLContext->GetBufferBindingSlot(BufferTarget::DrawIndirect).GetBoundObject(); auto drawBuffer = MG_State::pGLContext->GetBufferBindingSlot(BufferTarget::DrawIndirect).GetBoundObject();
if (drawBuffer) { if (drawBuffer) {
drawBuffer->MarkPersistentMappedRangeDirty(); drawBuffer->SyncPersistentMappedRange();
const auto drawData = drawBuffer->GetDataReadOnly(); const auto drawData = drawBuffer->GetDataReadOnly();
const SizeT commandOffset = reinterpret_cast<SizeT>(indirect); const SizeT commandOffset = reinterpret_cast<SizeT>(indirect);
if (!drawData || commandOffset + requiredBytes > drawData->size()) { if (!drawData || commandOffset + requiredBytes > drawData->size()) {
@@ -177,16 +177,9 @@ namespace MobileGL::MG_Backend::DirectGLES {
namespace BufferImpl { namespace BufferImpl {
void CreateAndSyncBufferObject(const SharedPtr<MG_State::GLState::BufferObject>& bufferObject) { void CreateAndSyncBufferObject(const SharedPtr<MG_State::GLState::BufferObject>& bufferObject) {
bufferObject->MarkPersistentMappedRangeDirty(); // Immediate BufferBackendOps keep existing storage current; this only
if (!(bufferObject->GetChangeBits() & BufferChangeBits::DirtyBit)) return; // needs to materialize the resource (and replay pending ops).
EnsureBufferResource(bufferObject);
const auto& backendBufferIt = g_backendBufferObjects.find(bufferObject.get());
Bool exist = (backendBufferIt != g_backendBufferObjects.end());
auto& backendObj = exist ? backendBufferIt->second : g_backendBufferObjects.GetOrCreate(bufferObject);
if (!exist) {
backendObj = MakeShared<BackendBufferObject>();
}
backendObj->SyncToBackend(bufferObject);
} }
void SyncBufferBindingPoints(BufferTarget target, GLenum glTarget) { void SyncBufferBindingPoints(BufferTarget target, GLenum glTarget) {
@@ -202,16 +195,15 @@ namespace MobileGL::MG_Backend::DirectGLES {
continue; continue;
} }
CreateAndSyncBufferObject(obj); auto* backendResource = EnsureBufferResource(obj);
const auto& backendBufferIt = g_backendBufferObjects.find(obj.get()); if (!backendResource || backendResource->id == 0) {
if (backendBufferIt == g_backendBufferObjects.end()) {
MGLOG_E("No backend buffer found for %s binding point %zu.", MGLOG_E("No backend buffer found for %s binding point %zu.",
MG_Util::ConvertGLEnumToString(glTarget).c_str(), i); MG_Util::ConvertGLEnumToString(glTarget).c_str(), i);
continue; continue;
} }
const auto& range = point.GetRange(); const auto& range = point.GetRange();
auto backendBufferId = backendBufferIt->second->GetBackendBufferId(); auto backendBufferId = backendResource->id;
if (range.start == 0 && range.end >= obj->GetSize()) { if (range.start == 0 && range.end >= obj->GetSize()) {
g_GLESFuncs.glBindBufferBase(glTarget, static_cast<GLuint>(i), backendBufferId); g_GLESFuncs.glBindBufferBase(glTarget, static_cast<GLuint>(i), backendBufferId);
} else { } else {
@@ -233,20 +225,19 @@ namespace MobileGL::MG_Backend::DirectGLES {
return; return;
} }
CreateAndSyncBufferObject(bufferObject); auto* backendResource = EnsureBufferResource(bufferObject);
const auto& backendBufferIt = g_backendBufferObjects.find(bufferObject.get()); if (!backendResource || backendResource->id == 0) {
if (backendBufferIt == g_backendBufferObjects.end()) {
MGLOG_E("No backend buffer found for %s.", MG_Util::ConvertGLEnumToString(glTarget).c_str()); MGLOG_E("No backend buffer found for %s.", MG_Util::ConvertGLEnumToString(glTarget).c_str());
return; return;
} }
backendBufferIt->second->Bind(glTarget); BindBufferId(glTarget, backendResource->id);
} }
void SyncNeccessaryBuffers(Bool includeIBO = false, Bool includeIndirectBuffer = false) { void SyncNeccessaryBuffers(Bool includeIBO = false, Bool includeIndirectBuffer = false) {
#ifdef TRACY_ENABLE #ifdef TRACY_ENABLE
ZoneScopedC(TRACY_ZONECOLOR_BACKEND); ZoneScopedC(TRACY_ZONECOLOR_BACKEND);
#endif #endif
g_backendBufferObjects.CollectGarbageIfNeeded(); ProcessDeferredBufferReleases();
// All buffers we need are: // All buffers we need are:
// 1.VBO 2.IBO (if needed) 3.UBO 4.IndirectBuffer (if needed) // 1.VBO 2.IBO (if needed) 3.UBO 4.IndirectBuffer (if needed)
@@ -291,7 +282,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
#ifdef TRACY_ENABLE #ifdef TRACY_ENABLE
ZoneScopedC(TRACY_ZONECOLOR_BACKEND); ZoneScopedC(TRACY_ZONECOLOR_BACKEND);
#endif #endif
g_backendBufferObjects.CollectGarbageIfNeeded(); ProcessDeferredBufferReleases();
SyncBufferBindingPoints(BufferTarget::Uniform, GL_UNIFORM_BUFFER); SyncBufferBindingPoints(BufferTarget::Uniform, GL_UNIFORM_BUFFER);
SyncBufferBindingPoints(BufferTarget::ShaderStorage, GL_SHADER_STORAGE_BUFFER); SyncBufferBindingPoints(BufferTarget::ShaderStorage, GL_SHADER_STORAGE_BUFFER);
if (includeDispatchIndirectBuffer) { if (includeDispatchIndirectBuffer) {
@@ -945,16 +936,15 @@ namespace MobileGL::MG_Backend::DirectGLES {
auto range = point.GetRange(); auto range = point.GetRange();
if (bufferObj) { if (bufferObj) {
const auto& backendBufferIt = BufferImpl::g_backendBufferObjects.find(bufferObj.get()); auto* backendResource = BufferImpl::EnsureBufferResource(bufferObj);
if (backendBufferIt != BufferImpl::g_backendBufferObjects.end()) { if (backendResource && backendResource->id != 0) {
const auto& backendBufferObject = backendBufferIt->second; BufferImpl::BindBufferId(GL_UNIFORM_BUFFER, backendResource->id);
backendBufferObject->Bind(GL_UNIFORM_BUFFER);
if (range.end == 0) { if (range.end == 0) {
g_GLESFuncs.glBindBufferBase(GL_UNIFORM_BUFFER, lastUBOBinding, g_GLESFuncs.glBindBufferBase(GL_UNIFORM_BUFFER, lastUBOBinding,
backendBufferObject->GetBackendBufferId()); backendResource->id);
} else { } else {
g_GLESFuncs.glBindBufferRange( g_GLESFuncs.glBindBufferRange(
GL_UNIFORM_BUFFER, lastUBOBinding, backendBufferObject->GetBackendBufferId(), GL_UNIFORM_BUFFER, lastUBOBinding, backendResource->id,
(GLintptr)range.start, (GLintptr)(range.end - range.start)); (GLintptr)range.start, (GLintptr)(range.end - range.start));
} }
} else { } else {
@@ -1232,8 +1222,8 @@ namespace MobileGL::MG_Backend::DirectGLES {
return; return;
} }
drawBuffer->MarkPersistentMappedRangeDirty(); drawBuffer->SyncPersistentMappedRange();
parameterBuffer->MarkPersistentMappedRangeDirty(); parameterBuffer->SyncPersistentMappedRange();
const auto drawData = drawBuffer->GetDataReadOnly(); const auto drawData = drawBuffer->GetDataReadOnly();
const auto parameterData = parameterBuffer->GetDataReadOnly(); const auto parameterData = parameterBuffer->GetDataReadOnly();
@@ -2802,8 +2792,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
MGLOG_E("ReadPixels: GL_FLOAT fallback PBO is too small"); MGLOG_E("ReadPixels: GL_FLOAT fallback PBO is too small");
return true; return true;
} }
pixelPackBufferObject->UploadSubData({packed.data(), packed.size()}, pboOffset); pixelPackBufferObject->WritebackFromBackend({packed.data(), packed.size()}, pboOffset);
pixelPackBufferObject->ClearDirty();
} else if (pixels != nullptr && !packed.empty()) { } else if (pixels != nullptr && !packed.empty()) {
Memcpy(pixels, packed.data(), packed.size()); Memcpy(pixels, packed.data(), packed.size());
} }
@@ -2860,8 +2849,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
MGLOG_E("ReadPixels: depth GL_FLOAT fallback PBO is too small"); MGLOG_E("ReadPixels: depth GL_FLOAT fallback PBO is too small");
return true; return true;
} }
pixelPackBufferObject->UploadSubData({packed.data(), packed.size()}, pboOffset); pixelPackBufferObject->WritebackFromBackend({packed.data(), packed.size()}, pboOffset);
pixelPackBufferObject->ClearDirty();
} else if (pixels != nullptr && !packed.empty()) { } else if (pixels != nullptr && !packed.empty()) {
Memcpy(pixels, packed.data(), packed.size()); Memcpy(pixels, packed.data(), packed.size());
} }
@@ -2918,8 +2906,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
MGLOG_E("ReadPixels: stencil GL_UNSIGNED_INT fallback PBO is too small"); MGLOG_E("ReadPixels: stencil GL_UNSIGNED_INT fallback PBO is too small");
return true; return true;
} }
pixelPackBufferObject->UploadSubData({packed.data(), packed.size()}, pboOffset); pixelPackBufferObject->WritebackFromBackend({packed.data(), packed.size()}, pboOffset);
pixelPackBufferObject->ClearDirty();
} else if (pixels != nullptr && !packed.empty()) { } else if (pixels != nullptr && !packed.empty()) {
Memcpy(pixels, packed.data(), packed.size()); Memcpy(pixels, packed.data(), packed.size());
} }
@@ -2982,18 +2969,16 @@ namespace MobileGL::MG_Backend::DirectGLES {
Bool usePBO; Bool usePBO;
GLuint prevPixelPackBuffer = 0; GLuint prevPixelPackBuffer = 0;
if (pixelPackBufferObject) { if (pixelPackBufferObject) {
BufferImpl::CreateAndSyncBufferObject(pixelPackBufferObject); auto* backendResource = BufferImpl::EnsureBufferResource(pixelPackBufferObject);
MGLOG_D("ReadPixels: Using PBO %u", pixelPackBufferObject->GetExternalIndex()); MGLOG_D("ReadPixels: Using PBO %u", pixelPackBufferObject->GetExternalIndex());
usePBO = true; usePBO = true;
const auto& backendBufferIt = BufferImpl::g_backendBufferObjects.find(pixelPackBufferObject.get());
if (backendBufferIt == BufferImpl::g_backendBufferObjects.end()) { if (!backendResource || backendResource->id == 0) {
MGLOG_E("ReadPixels: No backend buffer found for PBO %u.", MGLOG_E("ReadPixels: No backend buffer found for PBO %u.",
pixelPackBufferObject ? pixelPackBufferObject->GetExternalIndex() : 0); pixelPackBufferObject ? pixelPackBufferObject->GetExternalIndex() : 0);
return; return;
} }
const auto& backendBufferObject = backendBufferIt->second; BufferImpl::BindBufferId(GL_PIXEL_PACK_BUFFER, backendResource->id);
backendBufferObject->Bind(GL_PIXEL_PACK_BUFFER);
g_GLESFuncs.glGetIntegerv(GL_PIXEL_PACK_BUFFER_BINDING, (GLint*)&prevPixelPackBuffer); g_GLESFuncs.glGetIntegerv(GL_PIXEL_PACK_BUFFER_BINDING, (GLint*)&prevPixelPackBuffer);
} else { } else {
usePBO = false; usePBO = false;
@@ -3010,13 +2995,11 @@ namespace MobileGL::MG_Backend::DirectGLES {
if (pboMappedPtr) { if (pboMappedPtr) {
MGLOG_D("ReadPixels: Copying data from PBO to client memory"); MGLOG_D("ReadPixels: Copying data from PBO to client memory");
SizeT size = pixelPackBufferObject->GetSize(); SizeT size = pixelPackBufferObject->GetSize();
pixelPackBufferObject->UploadSubData({pboMappedPtr, size}, 0); pixelPackBufferObject->WritebackFromBackend({pboMappedPtr, size}, 0);
pixelPackBufferObject->ClearDirty();
MGLOG_D("ReadPixels: Unmapping PBO"); MGLOG_D("ReadPixels: Unmapping PBO");
g_GLESFuncs.glUnmapBuffer(GL_PIXEL_PACK_BUFFER); g_GLESFuncs.glUnmapBuffer(GL_PIXEL_PACK_BUFFER);
} else { } else {
MGLOG_E("ReadPixels: glMapBufferRange returned nullptr"); MGLOG_E("ReadPixels: glMapBufferRange returned nullptr");
MGLOG_E("ReadPixels: glMapBufferRange returned nullptr");
} }
MGLOG_D("ReadPixels: Restoring previous pixel pack buffer binding %u", prevPixelPackBuffer); MGLOG_D("ReadPixels: Restoring previous pixel pack buffer binding %u", prevPixelPackBuffer);
g_GLESFuncs.glBindBuffer(GL_PIXEL_PACK_BUFFER, prevPixelPackBuffer); g_GLESFuncs.glBindBuffer(GL_PIXEL_PACK_BUFFER, prevPixelPackBuffer);
@@ -3132,17 +3115,15 @@ namespace MobileGL::MG_Backend::DirectGLES {
Bool usePBO; Bool usePBO;
GLuint prevPixelPackBuffer = 0; GLuint prevPixelPackBuffer = 0;
if (pixelPackBufferObject) { if (pixelPackBufferObject) {
BufferImpl::CreateAndSyncBufferObject(pixelPackBufferObject); auto* backendResource = BufferImpl::EnsureBufferResource(pixelPackBufferObject);
MGLOG_D("GetTexImage: Using PBO %u", pixelPackBufferObject->GetExternalIndex()); MGLOG_D("GetTexImage: Using PBO %u", pixelPackBufferObject->GetExternalIndex());
usePBO = true; usePBO = true;
const auto& backendBufferIt = BufferImpl::g_backendBufferObjects.find(pixelPackBufferObject.get()); if (!backendResource || backendResource->id == 0) {
if (backendBufferIt == BufferImpl::g_backendBufferObjects.end()) {
MGLOG_E("GetTexImage: No backend buffer found for PBO %u.", MGLOG_E("GetTexImage: No backend buffer found for PBO %u.",
pixelPackBufferObject ? pixelPackBufferObject->GetExternalIndex() : 0); pixelPackBufferObject ? pixelPackBufferObject->GetExternalIndex() : 0);
return; return;
} }
const auto& backendBufferObject = backendBufferIt->second; BufferImpl::BindBufferId(GL_PIXEL_PACK_BUFFER, backendResource->id);
backendBufferObject->Bind(GL_PIXEL_PACK_BUFFER);
g_GLESFuncs.glGetIntegerv(GL_PIXEL_PACK_BUFFER_BINDING, (GLint*)&prevPixelPackBuffer); g_GLESFuncs.glGetIntegerv(GL_PIXEL_PACK_BUFFER_BINDING, (GLint*)&prevPixelPackBuffer);
} else { } else {
usePBO = false; usePBO = false;
@@ -3168,8 +3149,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
if (pboMappedPtr) { if (pboMappedPtr) {
MGLOG_D("ReadPixels: Copying data from PBO to client memory"); MGLOG_D("ReadPixels: Copying data from PBO to client memory");
SizeT size = pixelPackBufferObject->GetSize(); SizeT size = pixelPackBufferObject->GetSize();
pixelPackBufferObject->UploadSubData({pboMappedPtr, size}, 0); pixelPackBufferObject->WritebackFromBackend({pboMappedPtr, size}, 0);
pixelPackBufferObject->ClearDirty();
MGLOG_D("ReadPixels: Unmapping PBO"); MGLOG_D("ReadPixels: Unmapping PBO");
g_GLESFuncs.glUnmapBuffer(GL_PIXEL_PACK_BUFFER); g_GLESFuncs.glUnmapBuffer(GL_PIXEL_PACK_BUFFER);
} else { } else {
@@ -3489,6 +3469,10 @@ namespace MobileGL::MG_Backend::DirectGLES {
return true; return true;
} }
namespace {
thread_local Bool t_backendContextCurrent = false;
}
Bool MakeCurrent() { Bool MakeCurrent() {
if (!g_EGLFuncs.eglMakeCurrent || g_Display == EGL_NO_DISPLAY || g_Surface == EGL_NO_SURFACE || if (!g_EGLFuncs.eglMakeCurrent || g_Display == EGL_NO_DISPLAY || g_Surface == EGL_NO_SURFACE ||
g_Context == EGL_NO_CONTEXT) { g_Context == EGL_NO_CONTEXT) {
@@ -3500,11 +3484,13 @@ namespace MobileGL::MG_Backend::DirectGLES {
MGLOG_E("DirectGLES::MakeCurrent failed: native eglMakeCurrent returned error 0x%04x", error); MGLOG_E("DirectGLES::MakeCurrent failed: native eglMakeCurrent returned error 0x%04x", error);
return false; return false;
} }
t_backendContextCurrent = true;
return true; return true;
} }
Bool ReleaseCurrent() { Bool ReleaseCurrent() {
if (!g_EGLFuncs.eglMakeCurrent || g_Display == EGL_NO_DISPLAY) { if (!g_EGLFuncs.eglMakeCurrent || g_Display == EGL_NO_DISPLAY) {
t_backendContextCurrent = false;
return true; return true;
} }
if (!g_EGLFuncs.eglMakeCurrent(g_Display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT)) { if (!g_EGLFuncs.eglMakeCurrent(g_Display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT)) {
@@ -3512,14 +3498,21 @@ namespace MobileGL::MG_Backend::DirectGLES {
MGLOG_E("DirectGLES::ReleaseCurrent failed: native eglMakeCurrent returned error 0x%04x", error); MGLOG_E("DirectGLES::ReleaseCurrent failed: native eglMakeCurrent returned error 0x%04x", error);
return false; return false;
} }
t_backendContextCurrent = false;
return true; return true;
} }
Bool IsBackendContextCurrentOnThisThread() {
return t_backendContextCurrent && g_Context != EGL_NO_CONTEXT;
}
void Present() { void Present() {
g_EGLFuncs.eglSwapBuffers(g_Display, g_Surface); g_EGLFuncs.eglSwapBuffers(g_Display, g_Surface);
} }
void DestroyEGLContext() { void DestroyEGLContext() {
BufferImpl::UnregisterBufferBackendOps();
t_backendContextCurrent = false;
if (g_Display != EGL_NO_DISPLAY) { if (g_Display != EGL_NO_DISPLAY) {
g_EGLFuncs.eglMakeCurrent(g_Display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); g_EGLFuncs.eglMakeCurrent(g_Display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
if (g_Context != EGL_NO_CONTEXT) { if (g_Context != EGL_NO_CONTEXT) {
@@ -95,6 +95,9 @@ namespace MobileGL::MG_Backend::DirectGLES {
Bool InitPbufferSurface(EGLint width, EGLint height); Bool InitPbufferSurface(EGLint width, EGLint height);
Bool MakeCurrent(); Bool MakeCurrent();
Bool ReleaseCurrent(); Bool ReleaseCurrent();
// True when the backend ES context is current on the calling thread, i.e.
// immediate buffer ops may issue GL calls right now.
Bool IsBackendContextCurrentOnThisThread();
void Present(); void Present();
void SetEGLFuncsTable(const MG_External::EGLFunctionsTable& eglFuncs); void SetEGLFuncsTable(const MG_External::EGLFunctionsTable& eglFuncs);
void SetGLESFuncsTable(const MG_External::GLESFunctionsTable& glesFuncs); void SetGLESFuncsTable(const MG_External::GLESFunctionsTable& glesFuncs);
+245 -172
View File
@@ -114,171 +114,254 @@ namespace MobileGL::MG_Backend::DirectGLES {
} }
namespace BufferImpl { namespace BufferImpl {
BackendBufferObject::BackendBufferObject() { namespace {
using MG_State::GLState::BackendBufferResource;
using MG_State::GLState::BufferBackendOps;
using MG_State::GLState::BufferObject;
// GL_ARRAY_BUFFER redundant-bind cache (id 0 = unknown/none).
Uint g_boundArrayBufferId = 0;
Bool g_boundArrayBufferKnown = false;
// Resources whose owning BufferObject died; ids deleted at the next
// sync point with a current ES context.
Vector<SharedPtr<BackendBufferResource>> g_deferredBufferReleases;
std::mutex g_deferredBufferReleasesMutex;
GLESBufferResource* ResourceOf(BufferObject& bufferObject) {
return static_cast<GLESBufferResource*>(bufferObject.GetBackendResource().get());
}
Bool CanTouchGLNow() {
return false && DirectGLES::IsBackendContextCurrentOnThisThread(); // BISECT-EXPERIMENT
}
// (Re)specify backend storage from the shadow copy: glBufferData.
// The orphaning point - the ES driver performs the actual rename.
void RespecifyStorageNow(GLESBufferResource& resource, BufferObject& bufferObject) {
#ifdef TRACY_ENABLE #ifdef TRACY_ENABLE
ZoneScopedC(TRACY_ZONECOLOR_BACKEND); ZoneScopedC(TRACY_ZONECOLOR_BACKEND);
#endif #endif
g_GLESFuncs.glGenBuffers(1, &m_backendBufferId); const SizeT size = bufferObject.GetSize();
if (m_backendBufferId == 0) { const GLenum usage = MG_Util::ConvertBufferUsageToGLEnum(bufferObject.GetUsage());
BindBufferId(TempBufferTarget, resource.id);
g_GLESFuncs.glBufferData(TempBufferTarget, (GLsizeiptr)size,
size > 0 ? bufferObject.GetDataReadOnly()->data() : nullptr, usage);
resource.storageSize = size;
resource.storageInitialized = true;
resource.pendingRespecify = false;
resource.pendingRanges.clear();
}
Bool StorageMatches(const GLESBufferResource& resource, const BufferObject& bufferObject) {
return resource.storageInitialized && !resource.pendingRespecify &&
resource.storageSize == bufferObject.GetSize();
}
void UploadRangeNow(GLESBufferResource& resource, BufferObject& bufferObject, SizeT start, SizeT end) {
#ifdef TRACY_ENABLE
ZoneScopedC(TRACY_ZONECOLOR_BACKEND);
#endif
if (start >= end) return;
BindBufferId(TempBufferTarget, resource.id);
g_GLESFuncs.glBufferSubData(TempBufferTarget, (GLintptr)start, (GLsizeiptr)(end - start),
bufferObject.GetDataReadOnly()->data() + start);
}
void Ops_Respecify(BufferObject& bufferObject) {
auto* resource = ResourceOf(bufferObject);
if (!resource) return; // lazy: EnsureBufferResource full-uploads on creation
if (!CanTouchGLNow() || resource->id == 0) {
resource->pendingRespecify = true;
resource->pendingRanges.clear();
return;
}
if (bufferObject.GetSize() == 0) {
resource->storageInitialized = false;
resource->storageSize = 0;
resource->pendingRespecify = false;
resource->pendingRanges.clear();
return;
}
RespecifyStorageNow(*resource, bufferObject);
}
void Ops_SubData(BufferObject& bufferObject, SizeT offset, SizeT size) {
auto* resource = ResourceOf(bufferObject);
if (!resource) return;
if (resource->pendingRespecify) return; // full re-upload pending anyway
if (!CanTouchGLNow() || resource->id == 0 || !StorageMatches(*resource, bufferObject)) {
resource->pendingRanges.Add({offset, offset + size});
return;
}
UploadRangeNow(*resource, bufferObject, offset, offset + size);
}
void Ops_FlushMappedRange(BufferObject& bufferObject, Range1D range,
Flags<BufferMappingAccessBit> appAccess) {
auto* resource = ResourceOf(bufferObject);
if (!resource) return;
if (resource->pendingRespecify) return;
if (!CanTouchGLNow() || resource->id == 0 || !StorageMatches(*resource, bufferObject)) {
resource->pendingRanges.Add(range);
return;
}
// Honour the app's real mapping flags per call: only reach for a
// mapped upload when the app allowed invalidation/unsynchronized
// access, otherwise a plain glBufferSubData carries the exact
// synchronization semantics.
const Bool invalidate = (appAccess & BufferMappingAccessBit::InvalidateRange) ||
(appAccess & BufferMappingAccessBit::InvalidateBuffer);
const Bool unsynchronized = static_cast<Bool>(appAccess & BufferMappingAccessBit::Unsynchronized);
if (PREFER_MAP_BUFFER_RANGE_FOR_BUFFER_SYNC && (invalidate || unsynchronized)) {
#ifdef TRACY_ENABLE
ZoneScopedC(TRACY_ZONECOLOR_BACKEND);
#endif
BindBufferId(TempBufferTarget, resource->id);
void* mappedData = g_GLESFuncs.glMapBufferRange(
TempBufferTarget, (GLintptr)range.start, (GLsizeiptr)(range.end - range.start),
GL_MAP_WRITE_BIT | (invalidate ? GL_MAP_INVALIDATE_RANGE_BIT : 0) |
(unsynchronized ? GL_MAP_UNSYNCHRONIZED_BIT : 0));
if (mappedData) {
Memcpy(mappedData, bufferObject.GetDataReadOnly()->data() + range.start,
range.end - range.start);
g_GLESFuncs.glUnmapBuffer(TempBufferTarget);
return;
}
MGLOG_E("Failed to map buffer with ID: %u for flush, falling back to glBufferSubData",
resource->id);
}
UploadRangeNow(*resource, bufferObject, range.start, range.end);
}
void Ops_OnDestroy(SharedPtr<BackendBufferResource>&& resource) {
if (!resource) return;
auto* glesResource = static_cast<GLESBufferResource*>(resource.get());
if (CanTouchGLNow()) {
if (glesResource->id != 0) {
if (g_boundArrayBufferKnown && g_boundArrayBufferId == glesResource->id) {
InvalidateArrayBufferBindingCache();
}
g_GLESFuncs.glDeleteBuffers(1, &glesResource->id);
glesResource->id = 0;
}
return;
}
const std::lock_guard<std::mutex> lock(g_deferredBufferReleasesMutex);
g_deferredBufferReleases.push_back(std::move(resource));
}
const BufferBackendOps g_glesBufferBackendOps = {
.Respecify = Ops_Respecify,
.SubData = Ops_SubData,
.FlushMappedRange = Ops_FlushMappedRange,
.OnDestroy = Ops_OnDestroy,
};
} // namespace
void RegisterBufferBackendOps() {
MG_State::GLState::SetBufferBackendOps(&g_glesBufferBackendOps);
}
void UnregisterBufferBackendOps() {
if (MG_State::GLState::GetBufferBackendOps() == &g_glesBufferBackendOps) {
MG_State::GLState::SetBufferBackendOps(nullptr);
}
InvalidateArrayBufferBindingCache();
const std::lock_guard<std::mutex> lock(g_deferredBufferReleasesMutex);
// The ES context owning these ids is going away; just drop the handles.
g_deferredBufferReleases.clear();
}
void ProcessDeferredBufferReleases() {
if (!CanTouchGLNow()) return;
Vector<SharedPtr<BackendBufferResource>> releases;
{
const std::lock_guard<std::mutex> lock(g_deferredBufferReleasesMutex);
releases.swap(g_deferredBufferReleases);
}
for (auto& resource : releases) {
auto* glesResource = static_cast<GLESBufferResource*>(resource.get());
if (glesResource->id != 0) {
if (g_boundArrayBufferKnown && g_boundArrayBufferId == glesResource->id) {
InvalidateArrayBufferBindingCache();
}
g_GLESFuncs.glDeleteBuffers(1, &glesResource->id);
glesResource->id = 0;
}
}
}
GLESBufferResource* GetBufferResource(MG_State::GLState::BufferObject* bufferObject) {
if (!bufferObject) return nullptr;
return static_cast<GLESBufferResource*>(bufferObject->GetBackendResource().get());
}
GLESBufferResource* EnsureBufferResource(const SharedPtr<MG_State::GLState::BufferObject>& bufferObject) {
#ifdef TRACY_ENABLE
ZoneScopedC(TRACY_ZONECOLOR_BACKEND);
#endif
if (!bufferObject) return nullptr;
auto* resource = static_cast<GLESBufferResource*>(bufferObject->GetBackendResource().get());
if (!resource) {
auto newResource = MakeShared<GLESBufferResource>();
newResource->pendingRespecify = true;
resource = newResource.get();
bufferObject->SetBackendResource(std::move(newResource));
}
if (resource->id == 0) {
g_GLESFuncs.glGenBuffers(1, &resource->id);
if (resource->id == 0) {
MGLOG_E("Failed to generate buffer object."); MGLOG_E("Failed to generate buffer object.");
MGLOG_E("ES glGetError(): %s", MG_Util::ConvertGLEnumToString(g_GLESFuncs.glGetError()).c_str()); MGLOG_E("ES glGetError(): %s", MG_Util::ConvertGLEnumToString(g_GLESFuncs.glGetError()).c_str());
} else { return resource;
MGLOG_D("Generated buffer object with ID: %u.", m_backendBufferId);
} }
resource->storageInitialized = false;
resource->pendingRespecify = true;
} }
void BackendBufferObject::SyncToBackend(const SharedPtr<MG_State::GLState::BufferObject>& stateBufferObject) { // Push persistently-mapped writes first; lands either as an immediate
// SubData (fresh storage) or as part of the full re-upload below.
bufferObject->SyncPersistentMappedRange();
if (bufferObject->GetSize() == 0) {
return resource;
}
if (resource->pendingRespecify || !resource->storageInitialized ||
resource->storageSize != bufferObject->GetSize()) {
RespecifyStorageNow(*resource, *bufferObject);
} else if (!resource->pendingRanges.empty()) {
for (const auto& range : resource->pendingRanges) {
const SizeT end = std::min(range.end, bufferObject->GetSize());
UploadRangeNow(*resource, *bufferObject, std::min(range.start, end), end);
}
resource->pendingRanges.clear();
}
return resource;
}
void BindBufferId(GLenum target, Uint id) {
#ifdef TRACY_ENABLE #ifdef TRACY_ENABLE
ZoneScopedC(TRACY_ZONECOLOR_BACKEND); ZoneScopedC(TRACY_ZONECOLOR_BACKEND);
#endif #endif
if (!stateBufferObject) { if (false && target == GL_ARRAY_BUFFER) { // BISECT2: cache disabled
MGLOG_E("State buffer object is null, cannot sync to backend."); if (g_boundArrayBufferKnown && g_boundArrayBufferId == id) {
return; return;
} }
g_boundArrayBufferId = id;
SizeT bufferSize = stateBufferObject->GetSize(); g_boundArrayBufferKnown = true;
if (bufferSize == 0) { }
MGLOG_W("Buffer size is zero, skipping sync for object with ID: %u", m_backendBufferId); g_GLESFuncs.glBindBuffer(target, id);
return;
} }
MGLOG_D("Syncing buffer object with backend ID %u to backend for state ID %u", m_backendBufferId, void InvalidateArrayBufferBindingCache() {
stateBufferObject->GetExternalIndex()); g_boundArrayBufferId = 0;
g_boundArrayBufferKnown = false;
// Decide sync method
// glBufferData
Bool needsRegeneration =
!m_isInitialized || (stateBufferObject->GetChangeBits() & BufferChangeBits::PreferReallocationBit);
if (needsRegeneration) {
MGLOG_D("Buffer size changed significantly or not initialized, regenerating buffer with ID: %u",
m_backendBufferId);
SyncToBackend_glBufferData(stateBufferObject);
m_isInitialized = true;
m_prevBufferSize = bufferSize;
stateBufferObject->ClearDirty();
return;
} }
// glMapBufferRange or glBufferSubData
Bool useInvalidationMap = !(stateBufferObject->GetChangeBits() & BufferChangeBits::ForbidInvalidationBit);
// TODO: MAY AFFECT PERFORMANCE
Bool useUnsynchronizedMap =
!(stateBufferObject->GetChangeBits() & BufferChangeBits::ForbidUnsynchronizationBit);
Bool useMapBufferRange = PREFER_MAP_BUFFER_RANGE_FOR_BUFFER_SYNC &&
(useInvalidationMap || useUnsynchronizedMap);
if (!useMapBufferRange && PREFER_MAP_BUFFER_RANGE_FOR_BUFFER_SYNC) {
auto usage = stateBufferObject->GetUsage();
if (usage == BufferUsage::DynamicDraw || usage == BufferUsage::StreamDraw ||
usage == BufferUsage::StreamCopy || usage == BufferUsage::DynamicCopy) {
useMapBufferRange = true;
}
}
if (useMapBufferRange) {
MGLOG_D("Using glMapBufferRange to sync buffer with ID: %u", m_backendBufferId);
SyncToBackend_glMapBufferRange(stateBufferObject, useInvalidationMap, useUnsynchronizedMap);
} else {
MGLOG_D("Using glBufferSubData to sync buffer with ID: %u", m_backendBufferId);
SyncToBackend_glBufferSubData(stateBufferObject);
}
// Clear dirty state
stateBufferObject->ClearDirty();
m_prevBufferSize = bufferSize;
}
void BackendBufferObject::SyncToBackend_glBufferData(
const SharedPtr<MG_State::GLState::BufferObject>& stateBufferObject) {
#ifdef TRACY_ENABLE
ZoneScopedC(TRACY_ZONECOLOR_BACKEND);
#endif
MGLOG_D("Syncing buffer data (glBufferData) for object with ID : %u", m_backendBufferId);
const void* data = stateBufferObject->GetDataReadOnly()->data();
SizeT size = stateBufferObject->GetSize();
GLenum usage = MG_Util::ConvertBufferUsageToGLEnum(stateBufferObject->GetUsage());
Bind();
g_GLESFuncs.glBufferData(TempBufferTarget, (GLsizeiptr)size, data, usage);
}
void BackendBufferObject::SyncToBackend_glBufferSubData(
const SharedPtr<MG_State::GLState::BufferObject>& stateBufferObject) {
#ifdef TRACY_ENABLE
ZoneScopedC(TRACY_ZONECOLOR_BACKEND);
#endif
MGLOG_D("Syncing buffer sub-data (glBufferSubData) for object with ID : %u", m_backendBufferId);
const void* data = stateBufferObject->GetDataReadOnly()->data();
// dirty range: [range.start, range.end)
auto& ranges = stateBufferObject->GetDirtyRanges();
if (ranges.empty()) {
MGLOG_D("No dirty range to sync for buffer with ID: %u", m_backendBufferId);
return;
}
for (const auto& range : ranges) {
Bind();
g_GLESFuncs.glBufferSubData(TempBufferTarget, (GLintptr)range.start,
(GLintptr)(range.end - range.start),
reinterpret_cast<const char*>(data) + range.start);
}
}
void BackendBufferObject::SyncToBackend_glMapBufferRange(
const SharedPtr<MG_State::GLState::BufferObject>& stateBufferObject, Bool invalidate, Bool unsynchronized) {
#ifdef TRACY_ENABLE
ZoneScopedC(TRACY_ZONECOLOR_BACKEND);
#endif
MGLOG_D("Syncing buffer map (glMapBuffer) for object with ID : %u", m_backendBufferId);
MGLOG_D("Mapping buffer with ID: %u", m_backendBufferId);
auto& ranges = stateBufferObject->GetDirtyRanges();
if (ranges.empty()) {
MGLOG_D("No dirty range to sync for buffer with ID: %u", m_backendBufferId);
return;
}
SizeT minStart = ranges.GetOverallMinStart();
SizeT maxEnd = ranges.GetOverallMaxEnd();
Bind();
void* mappedData = g_GLESFuncs.glMapBufferRange(
TempBufferTarget, (GLintptr)minStart, (GLintptr)(maxEnd - minStart),
(invalidate ? GL_MAP_INVALIDATE_RANGE_BIT : 0) | (unsynchronized ? GL_MAP_UNSYNCHRONIZED_BIT : 0) |
GL_MAP_WRITE_BIT | GL_MAP_FLUSH_EXPLICIT_BIT);
const void* data = stateBufferObject->GetDataReadOnly()->data();
if (mappedData) {
MGLOG_D("Mapped buffer data successfully for object with ID: %u", m_backendBufferId);
Memcpy(mappedData, reinterpret_cast<const char*>(data) + minStart, maxEnd - minStart);
// Explicitly flush the dirty ranges
for (const auto& range : ranges) {
g_GLESFuncs.glFlushMappedBufferRange(TempBufferTarget, (GLintptr)(range.start - minStart),
(GLintptr)(range.end - range.start));
}
g_GLESFuncs.glUnmapBuffer(TempBufferTarget);
} else {
MGLOG_E("Failed to map buffer with ID: %u", m_backendBufferId);
}
}
void BackendBufferObject::Bind(GLenum target) {
#ifdef TRACY_ENABLE
ZoneScopedC(TRACY_ZONECOLOR_BACKEND);
#endif
if (target == GL_ARRAY_BUFFER) {
if (g_boundVertexBufferObject == this) {
return;
}
g_boundVertexBufferObject = this;
}
g_GLESFuncs.glBindBuffer(target, m_backendBufferId);
}
StateBackendObjectRegistry<MG_State::GLState::BufferObject, BackendBufferObject> g_backendBufferObjects;
BackendBufferObject* g_boundVertexBufferObject = nullptr;
} // namespace BufferImpl } // namespace BufferImpl
namespace VertexArrayImpl { namespace VertexArrayImpl {
@@ -346,14 +429,13 @@ namespace MobileGL::MG_Backend::DirectGLES {
return false; return false;
} }
const auto& backendBufferIt = BufferImpl::g_backendBufferObjects.find(bufferObject.get()); auto* backendResource = BufferImpl::EnsureBufferResource(bufferObject);
if (backendBufferIt == BufferImpl::g_backendBufferObjects.end()) { if (!backendResource || backendResource->id == 0) {
MGLOG_E("No backend buffer found for attribute's buffer, cannot bind attribute."); MGLOG_E("No backend buffer found for attribute's buffer, cannot bind attribute.");
return false; return false;
} }
const auto& backendBufferObject = backendBufferIt->second;
backendBufferObject->Bind(GL_ARRAY_BUFFER); BufferImpl::BindBufferId(GL_ARRAY_BUFFER, backendResource->id);
return true; return true;
} }
@@ -416,10 +498,9 @@ namespace MobileGL::MG_Backend::DirectGLES {
const auto& indexBufferBinding = stateVAOObject->GetIndexBufferBindingSlot().GetBoundObject(); const auto& indexBufferBinding = stateVAOObject->GetIndexBufferBindingSlot().GetBoundObject();
Bool indexBufferSynced = false; Bool indexBufferSynced = false;
if (indexBufferBinding) { if (indexBufferBinding) {
const auto& backendBufferIt = BufferImpl::g_backendBufferObjects.find(indexBufferBinding.get()); auto* backendResource = BufferImpl::EnsureBufferResource(indexBufferBinding);
if (backendBufferIt != BufferImpl::g_backendBufferObjects.end()) { if (backendResource && backendResource->id != 0) {
const auto& backendBufferObject = backendBufferIt->second; BufferImpl::BindBufferId(GL_ELEMENT_ARRAY_BUFFER, backendResource->id);
backendBufferObject->Bind(GL_ELEMENT_ARRAY_BUFFER);
indexBufferSynced = true; indexBufferSynced = true;
} else { } else {
MGLOG_W("No backend buffer found for index buffer binding, cannot bind index buffer."); MGLOG_W("No backend buffer found for index buffer binding, cannot bind index buffer.");
@@ -471,7 +552,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
} }
} }
g_GLESFuncs.glBindBuffer(GL_ARRAY_BUFFER, bufferId); BufferImpl::BindBufferId(GL_ARRAY_BUFFER, bufferId);
g_GLESFuncs.glBufferData(GL_ARRAY_BUFFER, static_cast<GLsizeiptr>(uploadSize), clientData, g_GLESFuncs.glBufferData(GL_ARRAY_BUFFER, static_cast<GLsizeiptr>(uploadSize), clientData,
GL_STREAM_DRAW); GL_STREAM_DRAW);
@@ -486,7 +567,6 @@ namespace MobileGL::MG_Backend::DirectGLES {
} }
} }
BufferImpl::g_boundVertexBufferObject = nullptr;
} }
StateBackendObjectRegistry<MG_State::GLState::VertexArrayObject, BackendVertexArrayObject> StateBackendObjectRegistry<MG_State::GLState::VertexArrayObject, BackendVertexArrayObject>
@@ -1112,22 +1192,15 @@ namespace MobileGL::MG_Backend::DirectGLES {
Bool needsRegeneration = !m_isInitialized || (currentTextureInfo != m_prevTextureInfo); Bool needsRegeneration = !m_isInitialized || (currentTextureInfo != m_prevTextureInfo);
// Need to sync texture buffer if not synced yet // Need to sync texture buffer if not synced yet
auto& backendBuffers = BufferImpl::g_backendBufferObjects; auto* backendBufferResource = BufferImpl::EnsureBufferResource(buffer);
SharedPtr<BufferImpl::BackendBufferObject> backendBufferObject; if (!backendBufferResource || backendBufferResource->id == 0) {
const auto& backendBufferIt = backendBuffers.find(buffer.get()); MGLOG_E("Failed to sync backing buffer for texture buffer with ID: %u",
if (backendBufferIt == backendBuffers.end()) { stateTextureObject->GetExternalIndex());
auto& backendBufferSlot = backendBuffers.GetOrCreate(buffer); return;
if (!backendBufferSlot) {
backendBufferSlot = MakeShared<BufferImpl::BackendBufferObject>();
} }
backendBufferObject = backendBufferSlot;
} else {
backendBufferObject = backendBufferIt->second;
}
backendBufferObject->SyncToBackend(buffer);
// Bind buffer to texture // Bind buffer to texture
auto backendId = backendBufferObject->GetBackendBufferId(); auto backendId = backendBufferResource->id;
GLenum glInternalFormat, glType, glFormat; GLenum glInternalFormat, glType, glFormat;
TextureImpl::GenerateTextureFormatInfo(textureBufferObject->GetFormat(), &glInternalFormat, &glFormat, TextureImpl::GenerateTextureFormatInfo(textureBufferObject->GetFormat(), &glInternalFormat, &glFormat,
+32 -16
View File
@@ -115,26 +115,42 @@ namespace MobileGL::MG_Backend::DirectGLES {
namespace BufferImpl { namespace BufferImpl {
const GLenum TempBufferTarget = GL_ARRAY_BUFFER; const GLenum TempBufferTarget = GL_ARRAY_BUFFER;
class BackendBufferObject {
// The DirectGLES storage behind one frontend buffer. Owned (refcounted) by
// the frontend BufferObject; immediate BufferBackendOps keep it current, so
// draw-time "sync" reduces to ensuring the storage exists.
class GLESBufferResource : public MG_State::GLState::BackendBufferResource {
public: public:
BackendBufferObject(); ~GLESBufferResource() override = default;
void SyncToBackend(const SharedPtr<MG_State::GLState::BufferObject>& stateBufferObject);
Uint GetBackendBufferId() const { return m_backendBufferId; }
void Bind(GLenum target = TempBufferTarget);
private: Uint id = 0;
void SyncToBackend_glBufferData(const SharedPtr<MG_State::GLState::BufferObject>& stateBufferObject); SizeT storageSize = 0;
void SyncToBackend_glBufferSubData(const SharedPtr<MG_State::GLState::BufferObject>& stateBufferObject); Bool storageInitialized = false;
void SyncToBackend_glMapBufferRange(const SharedPtr<MG_State::GLState::BufferObject>& stateBufferObject, // Ops that arrived while no ES context was current on the calling thread
Bool invalidate = true, Bool unsynchronized = true); // (or before storage existed); replayed by EnsureBufferResource.
Bool pendingRespecify = false;
Uint m_backendBufferId = 0; VecRange1D pendingRanges;
SizeT m_prevBufferSize = 0;
Bool m_isInitialized = false;
}; };
extern BackendBufferObject* g_boundVertexBufferObject; // Registered as the frontend's BufferBackendOps at backend init.
extern StateBackendObjectRegistry<MG_State::GLState::BufferObject, BackendBufferObject> g_backendBufferObjects; void RegisterBufferBackendOps();
void UnregisterBufferBackendOps();
// Get-or-create the backend resource and bring its storage up to date
// (creates the GL buffer, replays pending ops, pushes persistent-mapped
// ranges). Requires the ES context to be current. Returns nullptr only
// for null input.
GLESBufferResource* EnsureBufferResource(const SharedPtr<MG_State::GLState::BufferObject>& bufferObject);
// Existing resource or nullptr; performs no GL calls.
GLESBufferResource* GetBufferResource(MG_State::GLState::BufferObject* bufferObject);
// Deletes GL buffers whose owning frontend objects died (possibly on a
// thread without a current ES context). Called from draw-time sync.
void ProcessDeferredBufferReleases();
// glBindBuffer with a redundant-bind cache for GL_ARRAY_BUFFER.
void BindBufferId(GLenum target, Uint id);
void InvalidateArrayBufferBindingCache();
} // namespace BufferImpl } // namespace BufferImpl
namespace VertexArrayImpl { namespace VertexArrayImpl {
@@ -229,7 +229,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
const Uint8* ResolveIndirectCommandBytes(const void* indirect, SizeT requiredBytes, const char* label) { const Uint8* ResolveIndirectCommandBytes(const void* indirect, SizeT requiredBytes, const char* label) {
auto drawBuffer = MG_State::pGLContext->GetBufferBindingSlot(BufferTarget::DrawIndirect).GetBoundObject(); auto drawBuffer = MG_State::pGLContext->GetBufferBindingSlot(BufferTarget::DrawIndirect).GetBoundObject();
if (drawBuffer) { if (drawBuffer) {
drawBuffer->MarkPersistentMappedRangeDirty(); drawBuffer->SyncPersistentMappedRange();
const auto drawData = drawBuffer->GetDataReadOnly(); const auto drawData = drawBuffer->GetDataReadOnly();
const SizeT commandOffset = reinterpret_cast<SizeT>(indirect); const SizeT commandOffset = reinterpret_cast<SizeT>(indirect);
if (!drawData || commandOffset + requiredBytes > drawData->size()) { if (!drawData || commandOffset + requiredBytes > drawData->size()) {
@@ -480,7 +480,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
return; return;
} }
parameterBuffer->MarkPersistentMappedRangeDirty(); parameterBuffer->SyncPersistentMappedRange();
const auto parameterData = parameterBuffer->GetDataReadOnly(); const auto parameterData = parameterBuffer->GetDataReadOnly();
if (!parameterData) { if (!parameterData) {
MGLOG_E("MultiDrawArraysIndirectCount skipped: CPU fallback cannot read parameter buffer"); MGLOG_E("MultiDrawArraysIndirectCount skipped: CPU fallback cannot read parameter buffer");
@@ -382,7 +382,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
} }
BufferSlice slice{}; BufferSlice slice{};
if (!m_bufferManager->SyncResidentBuffer(BufferKind::TextureBuffer, bufferObject, slice) || !slice.IsValid()) { if (!m_bufferManager->AcquireResidentSlice(BufferKind::TextureBuffer, bufferObject, slice) || !slice.IsValid()) {
MGLOG_E("ResolveTexelBufferDescriptor: failed to sync GL buffer %u for texture buffer %u", MGLOG_E("ResolveTexelBufferDescriptor: failed to sync GL buffer %u for texture buffer %u",
bufferObject->GetExternalIndex(), texture->GetExternalIndex()); bufferObject->GetExternalIndex(), texture->GetExternalIndex());
return false; return false;
@@ -457,7 +457,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
} }
BufferSlice slice{}; BufferSlice slice{};
if (!m_bufferManager->SyncResidentBuffer(BufferKind::ShaderStorage, bufferObject, slice) || !slice.IsValid()) { if (!m_bufferManager->AcquireResidentSlice(BufferKind::ShaderStorage, bufferObject, slice) || !slice.IsValid()) {
MGLOG_E("ResolveStorageBufferDescriptor: failed to sync GL buffer %u for block '%s'", MGLOG_E("ResolveStorageBufferDescriptor: failed to sync GL buffer %u for block '%s'",
bufferObject->GetExternalIndex(), programObj.storageBlockNameByBinding[binding].c_str()); bufferObject->GetExternalIndex(), programObj.storageBlockNameByBinding[binding].c_str());
return false; return false;
@@ -622,7 +622,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
MOBILEGL_ASSERT(bufferObject != nullptr, MOBILEGL_ASSERT(bufferObject != nullptr,
"ResolveUniformBufferPayload: no UBO bound at frontend binding %u for block '%s'", "ResolveUniformBufferPayload: no UBO bound at frontend binding %u for block '%s'",
frontendBinding, program.GetUniformBlockName(static_cast<Uint32>(blockIndex)).c_str()); frontendBinding, program.GetUniformBlockName(static_cast<Uint32>(blockIndex)).c_str());
bufferObject->MarkPersistentMappedRangeDirty(); bufferObject->SyncPersistentMappedRange();
const auto bufferData = bufferObject->GetDataReadOnly(); const auto bufferData = bufferObject->GetDataReadOnly();
MOBILEGL_ASSERT(bufferData != nullptr && !bufferData->empty(), MOBILEGL_ASSERT(bufferData != nullptr && !bufferData->empty(),
@@ -10,9 +10,52 @@
namespace MobileGL::MG_Backend::DirectVulkan { namespace MobileGL::MG_Backend::DirectVulkan {
namespace { namespace {
constexpr Uint32 kResidentBufferGCInterval = 60;
constexpr VmaAllocationCreateFlags kResidentBufferAllocationFlags = constexpr VmaAllocationCreateFlags kResidentBufferAllocationFlags =
VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT; VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT;
constexpr SizeT kLiveResourcePruneThreshold = 256;
using MG_State::GLState::BackendBufferResource;
using MG_State::GLState::BufferBackendOps;
using MG_State::GLState::BufferObject;
// The manager owned by the active VulkanRenderer; immediate ops route here.
VkBufferManager* g_activeBufferManager = nullptr;
void Ops_Respecify(BufferObject& bufferObject) {
if (g_activeBufferManager) {
g_activeBufferManager->OnRespecify(bufferObject);
}
}
void Ops_SubData(BufferObject& bufferObject, SizeT offset, SizeT size) {
if (g_activeBufferManager) {
g_activeBufferManager->OnSubData(bufferObject, offset, size);
}
}
void Ops_FlushMappedRange(BufferObject& bufferObject, Range1D range,
Flags<BufferMappingAccessBit> appAccess) {
if (g_activeBufferManager) {
g_activeBufferManager->OnFlushMappedRange(bufferObject, range, appAccess);
}
}
void Ops_OnDestroy(SharedPtr<BackendBufferResource>&& resource) {
if (g_activeBufferManager) {
g_activeBufferManager->OnResourceDestroyed(std::move(resource));
}
// No active manager: the device/allocator is gone or going away and
// Shutdown() already destroyed the storage; dropping the handle here
// must not touch Vulkan. VkBufferResource's dtor destroys via VMA only
// when the allocation is still valid, which Shutdown() cleared.
}
const BufferBackendOps g_vulkanBufferBackendOps = {
.Respecify = Ops_Respecify,
.SubData = Ops_SubData,
.FlushMappedRange = Ops_FlushMappedRange,
.OnDestroy = Ops_OnDestroy,
};
} // namespace } // namespace
Bool VkBufferManager::Initialize(const VkBufferManagerInitInfo& initInfo) { Bool VkBufferManager::Initialize(const VkBufferManagerInitInfo& initInfo) {
@@ -22,40 +65,81 @@ namespace MobileGL::MG_Backend::DirectVulkan {
MOBILEGL_ASSERT(initInfo.frameCount > 0, "VkBufferManager::Initialize requires non-zero frame count"); MOBILEGL_ASSERT(initInfo.frameCount > 0, "VkBufferManager::Initialize requires non-zero frame count");
m_initInfo = initInfo; m_initInfo = initInfo;
m_deferredResidentReleases.resize(initInfo.frameCount); m_deferredBufferReleases.resize(initInfo.frameCount);
m_deferredResourceReleases.resize(initInfo.frameCount);
m_currentFrameIndex = 0; m_currentFrameIndex = 0;
return InitializeTransientArenas(); m_frameSerial = 1;
m_completedSerialFloor = 0;
if (!InitializeTransientArenas()) {
return false;
}
g_activeBufferManager = this;
MG_State::GLState::SetBufferBackendOps(&g_vulkanBufferBackendOps);
return true;
} }
void VkBufferManager::Shutdown() { void VkBufferManager::Shutdown() {
if (g_activeBufferManager == this) {
g_activeBufferManager = nullptr;
if (MG_State::GLState::GetBufferBackendOps() == &g_vulkanBufferBackendOps) {
MG_State::GLState::SetBufferBackendOps(nullptr);
}
}
m_transientUploadArena.Shutdown(); m_transientUploadArena.Shutdown();
DestroyResidentBuffers(); DestroyAllDeferredReleases();
DestroyDeferredResidentReleases(); ReleaseAllLiveResources();
m_copyProvider = nullptr;
m_initInfo = {}; m_initInfo = {};
m_currentFrameIndex = 0; m_currentFrameIndex = 0;
m_residentGcTick = 0; m_frameSerial = 1;
m_completedSerialFloor = 0;
} }
Bool VkBufferManager::RecreateTransientArenas(Uint32 frameCount) { Bool VkBufferManager::RecreateTransientArenas(Uint32 frameCount) {
MOBILEGL_ASSERT(m_initInfo.allocator != nullptr, "VkBufferManager::RecreateTransientArenas requires initialized manager"); MOBILEGL_ASSERT(m_initInfo.allocator != nullptr,
"VkBufferManager::RecreateTransientArenas requires initialized manager");
MOBILEGL_ASSERT(frameCount > 0, "VkBufferManager::RecreateTransientArenas requires non-zero frame count"); MOBILEGL_ASSERT(frameCount > 0, "VkBufferManager::RecreateTransientArenas requires non-zero frame count");
// Callers guarantee the device is idle around arena recreation.
NotifyDeviceIdle();
m_transientUploadArena.Shutdown(); m_transientUploadArena.Shutdown();
m_initInfo.frameCount = frameCount; m_initInfo.frameCount = frameCount;
DestroyDeferredResidentReleases(); DestroyAllDeferredReleases();
m_deferredResidentReleases.resize(frameCount); m_deferredBufferReleases.resize(frameCount);
m_deferredResourceReleases.resize(frameCount);
m_currentFrameIndex = 0; m_currentFrameIndex = 0;
return InitializeTransientArenas(); return InitializeTransientArenas();
} }
void VkBufferManager::BeginFrame(Uint32 frameIndex) { void VkBufferManager::BeginFrame(Uint32 frameIndex) {
MOBILEGL_ASSERT(frameIndex < m_deferredResidentReleases.size(), MOBILEGL_ASSERT(frameIndex < m_deferredBufferReleases.size(),
"VkBufferManager::BeginFrame frame index out of range"); "VkBufferManager::BeginFrame frame index out of range");
m_currentFrameIndex = frameIndex; m_currentFrameIndex = frameIndex;
CollectDeferredResidentReleases(frameIndex); ++m_frameSerial;
CollectDeferredReleases(frameIndex);
m_transientUploadArena.BeginFrame(frameIndex); m_transientUploadArena.BeginFrame(frameIndex);
} }
void VkBufferManager::NotifyDeviceIdle() {
// Everything submitted so far has completed. Work recorded for the
// current frame has not been submitted yet, so the current serial
// remains busy.
if (m_frameSerial > 0) {
m_completedSerialFloor = m_frameSerial - 1;
}
}
void VkBufferManager::SetCopyCommandProvider(IBufferCopyCommandProvider* provider) {
m_copyProvider = provider;
}
Bool VkBufferManager::IsResourceBusy(const VkBufferResource& resource) const {
const Uint64 frameCount = m_initInfo.frameCount > 0 ? m_initInfo.frameCount : 1;
Uint64 completed = m_frameSerial > frameCount ? m_frameSerial - frameCount : 0;
completed = std::max(completed, m_completedSerialFloor);
return resource.lastUseSerial > completed;
}
Bool VkBufferManager::UploadTransient(BufferKind kind, Uint32 frameIndex, const void* data, Bool VkBufferManager::UploadTransient(BufferKind kind, Uint32 frameIndex, const void* data,
VkDeviceSize size, VkDeviceSize alignment, BufferSlice& outSlice) { VkDeviceSize size, VkDeviceSize alignment, BufferSlice& outSlice) {
(void)kind; (void)kind;
@@ -67,7 +151,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
.allocator = m_initInfo.allocator, .allocator = m_initInfo.allocator,
.frameCount = m_initInfo.frameCount, .frameCount = m_initInfo.frameCount,
.usage = VK_BUFFER_USAGE_VERTEX_BUFFER_BIT | VK_BUFFER_USAGE_INDEX_BUFFER_BIT | .usage = VK_BUFFER_USAGE_VERTEX_BUFFER_BIT | VK_BUFFER_USAGE_INDEX_BUFFER_BIT |
VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT | VK_BUFFER_USAGE_INDIRECT_BUFFER_BIT, VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT | VK_BUFFER_USAGE_INDIRECT_BUFFER_BIT |
VK_BUFFER_USAGE_TRANSFER_SRC_BIT,
.memoryUsage = m_initInfo.transientMemoryUsage, .memoryUsage = m_initInfo.transientMemoryUsage,
.allocationFlags = m_initInfo.transientAllocationFlags, .allocationFlags = m_initInfo.transientAllocationFlags,
.minBufferSize = m_initInfo.minUploadBytes, .minBufferSize = m_initInfo.minUploadBytes,
@@ -75,116 +160,341 @@ namespace MobileGL::MG_Backend::DirectVulkan {
}); });
} }
Bool VkBufferManager::SyncResidentBuffer(BufferKind kind, VkBufferResource* VkBufferManager::ResourceOf(MG_State::GLState::BufferObject& bufferObject) {
const SharedPtr<MG_State::GLState::BufferObject>& bufferObject, return static_cast<VkBufferResource*>(bufferObject.GetBackendResource().get());
BufferSlice& outSlice) {
const VkBufferUsageFlags requiredUsage = GetVkBufferUsage(kind);
MOBILEGL_ASSERT(requiredUsage != 0,
"VkBufferManager::SyncResidentBuffer unsupported resident buffer kind");
MOBILEGL_ASSERT(bufferObject != nullptr, "VkBufferManager::SyncResidentBuffer requires valid buffer object");
CollectResidentGarbageIfNeeded();
const auto* bufferData = bufferObject->GetDataReadOnly().get();
MOBILEGL_ASSERT(bufferData != nullptr, "VkBufferManager::SyncResidentBuffer requires frontend buffer data");
bufferObject->MarkPersistentMappedRangeDirty();
const VkDeviceSize bufferSize = static_cast<VkDeviceSize>(bufferObject->GetSize());
if (bufferSize == 0) {
MGLOG_E("VkBufferManager::SyncResidentBuffer failed: buffer size is zero");
return false;
} }
auto& entry = m_residentBuffers[bufferObject.get()]; SharedPtr<VkBufferResource> VkBufferManager::GetOrCreateResource(
entry.aliveRef = bufferObject; const SharedPtr<MG_State::GLState::BufferObject>& bufferObject) {
auto existing = std::static_pointer_cast<VkBufferResource>(bufferObject->GetBackendResource());
if (existing) {
return existing;
}
auto resource = MakeShared<VkBufferResource>();
bufferObject->SetBackendResource(resource);
TrackLiveResource(resource);
return resource;
}
const auto changeBits = bufferObject->GetChangeBits(); void VkBufferManager::TrackLiveResource(const SharedPtr<VkBufferResource>& resource) {
const Bool needsRecreate = !entry.buffer.IsValid() || entry.size != bufferSize || if (m_liveResources.size() >= kLiveResourcePruneThreshold) {
((entry.usage & requiredUsage) != requiredUsage) || std::erase_if(m_liveResources, [](const WeakPtr<VkBufferResource>& weak) { return weak.expired(); });
(changeBits & BufferChangeBits::PreferReallocationBit); }
if (needsRecreate) { m_liveResources.push_back(resource);
const VkBufferUsageFlags recreatedUsage = entry.usage | requiredUsage; }
DeferResidentRelease(std::move(entry.buffer));
const Bool created = entry.buffer.Create({ void VkBufferManager::ReleaseAllLiveResources() {
for (auto& weak : m_liveResources) {
if (auto resource = weak.lock()) {
resource->buffer.Destroy();
resource->storageSize = 0;
resource->usageFlags = 0;
resource->lastUseSerial = 0;
resource->pendingFullUpload = true;
resource->transientSlice = {};
resource->transientFrameSerial = 0;
}
}
m_liveResources.clear();
}
Bool VkBufferManager::CreateResidentStorage(VkBufferResource& resource, VkDeviceSize size,
VkBufferUsageFlags usage) {
// Staged range copies write resident storage with vkCmdCopyBuffer.
usage |= VK_BUFFER_USAGE_TRANSFER_DST_BIT;
const Bool created = resource.buffer.Create({
.allocator = m_initInfo.allocator, .allocator = m_initInfo.allocator,
.size = bufferSize, .size = size,
.usage = recreatedUsage, .usage = usage,
.memoryUsage = VMA_MEMORY_USAGE_AUTO, .memoryUsage = VMA_MEMORY_USAGE_AUTO,
.allocationFlags = kResidentBufferAllocationFlags, .allocationFlags = kResidentBufferAllocationFlags,
}); });
if (!created || entry.buffer.Map() == nullptr) { if (!created || resource.buffer.Map() == nullptr) {
MGLOG_E("VkBufferManager::SyncResidentBuffer failed: unable to create resident buffer"); MGLOG_E("VkBufferManager::CreateResidentStorage failed (size=%llu)",
entry.buffer.Destroy(); static_cast<unsigned long long>(size));
entry.size = 0; resource.buffer.Destroy();
entry.usage = 0; resource.storageSize = 0;
resource.usageFlags = 0;
return false; return false;
} }
if (!entry.buffer.Upload(bufferData->data(), bufferSize, 0)) { resource.storageSize = size;
MGLOG_E("VkBufferManager::SyncResidentBuffer failed: initial upload failed"); resource.usageFlags = usage;
entry.buffer.Destroy();
entry.size = 0;
entry.usage = 0;
return false;
}
entry.size = bufferSize;
entry.usage = recreatedUsage;
bufferObject->ClearDirty();
outSlice = entry.buffer.GetSlice(0, bufferSize);
return true; return true;
} }
if (changeBits & BufferChangeBits::DirtyBit) { Bool VkBufferManager::SwapStorageAndUploadAll(VkBufferResource& resource,
const auto& dirtyRanges = bufferObject->GetDirtyRanges(); MG_State::GLState::BufferObject& bufferObject) {
for (const auto& range : dirtyRanges) { const VkDeviceSize size = static_cast<VkDeviceSize>(bufferObject.GetSize());
const VkDeviceSize rangeOffset = static_cast<VkDeviceSize>(range.start); const VkBufferUsageFlags usage = resource.usageFlags;
const VkDeviceSize rangeSize = static_cast<VkDeviceSize>(range.end - range.start); DeferRelease(std::move(resource.buffer));
if (rangeSize == 0) { if (!CreateResidentStorage(resource, size, usage)) {
continue; resource.pendingFullUpload = true;
}
if (!entry.buffer.Upload(bufferData->data() + range.start, rangeSize, rangeOffset)) {
MGLOG_E("VkBufferManager::SyncResidentBuffer failed: dirty range upload failed");
return false; return false;
} }
if (!resource.buffer.Upload(bufferObject.GetDataReadOnly()->data(), size, 0)) {
MGLOG_E("VkBufferManager::SwapStorageAndUploadAll: upload failed");
resource.pendingFullUpload = true;
return false;
} }
bufferObject->ClearDirty(); resource.pendingFullUpload = false;
}
outSlice = entry.buffer.GetSlice(0, bufferSize);
return true; return true;
} }
void VkBufferManager::DowngradeResidentBufferToTransient(const SharedPtr<MG_State::GLState::BufferObject>& bufferObject) { Bool VkBufferManager::StagedRangeCopy(VkBufferResource& resource, MG_State::GLState::BufferObject& bufferObject,
if (bufferObject == nullptr) { SizeT offset, SizeT size) {
if (!m_copyProvider) {
return false;
}
BufferSlice staging{};
if (!m_transientUploadArena.Upload(m_currentFrameIndex, bufferObject.GetDataReadOnly()->data() + offset,
static_cast<VkDeviceSize>(size), 16, staging)) {
return false;
}
VkCommandBuffer commandBuffer = m_copyProvider->AcquireBufferCopyCommandBuffer();
if (commandBuffer == VK_NULL_HANDLE) {
return false;
}
// Order the copy after every prior read/write of this buffer, both from
// in-flight frames (submission order) and from commands already recorded
// in this frame's command buffer.
VkMemoryBarrier beforeBarrier{VK_STRUCTURE_TYPE_MEMORY_BARRIER};
beforeBarrier.srcAccessMask = VK_ACCESS_MEMORY_READ_BIT | VK_ACCESS_MEMORY_WRITE_BIT;
beforeBarrier.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
vkCmdPipelineBarrier(commandBuffer, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 1,
&beforeBarrier, 0, nullptr, 0, nullptr);
VkBufferCopy region{};
region.srcOffset = staging.offset;
region.dstOffset = static_cast<VkDeviceSize>(offset);
region.size = static_cast<VkDeviceSize>(size);
vkCmdCopyBuffer(commandBuffer, staging.buffer, resource.buffer.GetHandle(), 1, &region);
VkMemoryBarrier afterBarrier{VK_STRUCTURE_TYPE_MEMORY_BARRIER};
afterBarrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
afterBarrier.dstAccessMask = VK_ACCESS_MEMORY_READ_BIT | VK_ACCESS_MEMORY_WRITE_BIT;
vkCmdPipelineBarrier(commandBuffer, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, 0, 1,
&afterBarrier, 0, nullptr, 0, nullptr);
resource.lastUseSerial = m_frameSerial;
return true;
}
void VkBufferManager::OnRespecify(MG_State::GLState::BufferObject& bufferObject) {
auto* resource = ResourceOf(bufferObject);
if (!resource) {
return; // lazy: AcquireResidentSlice performs a full upload on creation
}
// Any cached streaming slice refers to the previous contents.
resource->transientFrameSerial = 0;
if (!resource->buffer.IsValid()) {
return; // streaming-only resource: shadow + serial are enough
}
const VkDeviceSize size = static_cast<VkDeviceSize>(bufferObject.GetSize());
if (size == 0) {
DeferRelease(std::move(resource->buffer));
resource->storageSize = 0;
resource->pendingFullUpload = false;
return; return;
} }
auto it = m_residentBuffers.find(bufferObject.get()); if (size != resource->storageSize || IsResourceBusy(*resource)) {
if (it == m_residentBuffers.end()) { // Conditional orphan: only swap the storage when the old one is
// still referenced by the GPU (or no longer fits).
SwapStorageAndUploadAll(*resource, bufferObject);
return; return;
} }
DeferResidentRelease(std::move(it->second.buffer)); if (!resource->buffer.Upload(bufferObject.GetDataReadOnly()->data(), size, 0)) {
m_residentBuffers.erase(it); MGLOG_E("VkBufferManager::OnRespecify: in-place upload failed");
resource->pendingFullUpload = true;
}
} }
void VkBufferManager::DeferResidentRelease(VkBufferObject&& buffer) { void VkBufferManager::OnSubData(MG_State::GLState::BufferObject& bufferObject, SizeT offset, SizeT size) {
auto* resource = ResourceOf(bufferObject);
if (!resource) {
return;
}
resource->transientFrameSerial = 0;
if (!resource->buffer.IsValid() || resource->pendingFullUpload) {
return;
}
if (static_cast<VkDeviceSize>(bufferObject.GetSize()) != resource->storageSize) {
resource->pendingFullUpload = true;
return;
}
if (!IsResourceBusy(*resource)) {
if (!resource->buffer.Upload(bufferObject.GetDataReadOnly()->data() + offset,
static_cast<VkDeviceSize>(size), static_cast<VkDeviceSize>(offset))) {
MGLOG_E("VkBufferManager::OnSubData: host upload failed");
resource->pendingFullUpload = true;
}
return;
}
// Busy partial write: stage + GPU copy preserves GL ordering within the
// frame and leaves bytes outside the range (possibly GPU-written, e.g.
// SSBO) intact. Fall back to a storage swap if staging is unavailable.
if (!StagedRangeCopy(*resource, bufferObject, offset, size)) {
SwapStorageAndUploadAll(*resource, bufferObject);
}
}
void VkBufferManager::OnFlushMappedRange(MG_State::GLState::BufferObject& bufferObject, Range1D range,
Flags<BufferMappingAccessBit> appAccess) {
auto* resource = ResourceOf(bufferObject);
if (!resource) {
return;
}
resource->transientFrameSerial = 0;
if (!resource->buffer.IsValid() || resource->pendingFullUpload) {
return;
}
if (static_cast<VkDeviceSize>(bufferObject.GetSize()) != resource->storageSize) {
resource->pendingFullUpload = true;
return;
}
const SizeT offset = range.start;
const SizeT size = range.end - range.start;
// GL_MAP_UNSYNCHRONIZED_BIT: the app guarantees it does not overwrite
// data the GPU is still reading; honour it with a direct host write.
if ((appAccess & BufferMappingAccessBit::Unsynchronized) || !IsResourceBusy(*resource)) {
if (!resource->buffer.Upload(bufferObject.GetDataReadOnly()->data() + offset,
static_cast<VkDeviceSize>(size), static_cast<VkDeviceSize>(offset))) {
MGLOG_E("VkBufferManager::OnFlushMappedRange: host upload failed");
resource->pendingFullUpload = true;
}
return;
}
if (!StagedRangeCopy(*resource, bufferObject, offset, size)) {
SwapStorageAndUploadAll(*resource, bufferObject);
}
}
void VkBufferManager::OnResourceDestroyed(SharedPtr<MG_State::GLState::BackendBufferResource>&& resource) {
if (!resource) {
return;
}
auto vkResource = std::static_pointer_cast<VkBufferResource>(std::move(resource));
if (!vkResource->buffer.IsValid()) {
return;
}
if (m_deferredResourceReleases.empty()) {
vkResource->buffer.Destroy();
return;
}
MOBILEGL_ASSERT(m_currentFrameIndex < m_deferredResourceReleases.size(),
"VkBufferManager::OnResourceDestroyed current frame index out of range");
// Keep the whole resource alive until this frame slot's fence has been
// waited, then the storage is destroyed with it.
m_deferredResourceReleases[m_currentFrameIndex].push_back(std::move(vkResource));
}
Bool VkBufferManager::AcquireResidentSlice(BufferKind kind,
const SharedPtr<MG_State::GLState::BufferObject>& bufferObject,
BufferSlice& outSlice) {
const VkBufferUsageFlags requiredUsage = GetVkBufferUsage(kind);
MOBILEGL_ASSERT(requiredUsage != 0, "VkBufferManager::AcquireResidentSlice unsupported buffer kind");
MOBILEGL_ASSERT(bufferObject != nullptr, "VkBufferManager::AcquireResidentSlice requires valid buffer object");
auto resource = GetOrCreateResource(bufferObject);
bufferObject->SyncPersistentMappedRange();
const VkDeviceSize size = static_cast<VkDeviceSize>(bufferObject->GetSize());
if (size == 0) {
MGLOG_E("VkBufferManager::AcquireResidentSlice failed: buffer size is zero");
return false;
}
const Bool needsRecreate = !resource->buffer.IsValid() || resource->storageSize != size ||
((resource->usageFlags & requiredUsage) != requiredUsage) ||
resource->pendingFullUpload;
if (needsRecreate) {
const VkBufferUsageFlags usage = resource->usageFlags | requiredUsage;
DeferRelease(std::move(resource->buffer));
if (!CreateResidentStorage(*resource, size, usage)) {
return false;
}
if (!resource->buffer.Upload(bufferObject->GetDataReadOnly()->data(), size, 0)) {
MGLOG_E("VkBufferManager::AcquireResidentSlice failed: initial upload failed");
resource->buffer.Destroy();
resource->storageSize = 0;
resource->usageFlags = 0;
return false;
}
resource->pendingFullUpload = false;
}
resource->lastUseSerial = m_frameSerial;
outSlice = resource->buffer.GetSlice(0, size);
return true;
}
Bool VkBufferManager::AcquireStreamedSlice(BufferKind kind,
const SharedPtr<MG_State::GLState::BufferObject>& bufferObject,
BufferSlice& outSlice) {
(void)kind;
MOBILEGL_ASSERT(bufferObject != nullptr, "VkBufferManager::AcquireStreamedSlice requires valid buffer object");
auto resource = GetOrCreateResource(bufferObject);
bufferObject->SyncPersistentMappedRange();
const VkDeviceSize size = static_cast<VkDeviceSize>(bufferObject->GetSize());
if (size == 0) {
MGLOG_E("VkBufferManager::AcquireStreamedSlice failed: buffer size is zero");
return false;
}
const Uint64 changeSerial = bufferObject->GetChangeSerial();
if (resource->transientFrameSerial == m_frameSerial && resource->transientChangeSerial == changeSerial &&
resource->transientSize == size && resource->transientSlice.IsValid()) {
outSlice = resource->transientSlice;
return true;
}
if (!m_transientUploadArena.Upload(m_currentFrameIndex, bufferObject->GetDataReadOnly()->data(), size, 16,
outSlice)) {
return false;
}
resource->transientSlice = outSlice;
resource->transientFrameSerial = m_frameSerial;
resource->transientChangeSerial = changeSerial;
resource->transientSize = size;
// Streaming path is authoritative now; release resident storage so we do
// not keep a second, stale copy alive (downgrade).
if (resource->buffer.IsValid()) {
DeferRelease(std::move(resource->buffer));
resource->storageSize = 0;
}
return true;
}
void VkBufferManager::DeferRelease(VkBufferObject&& buffer) {
if (!buffer.IsValid()) { if (!buffer.IsValid()) {
return; return;
} }
if (m_deferredResidentReleases.empty()) { if (m_deferredBufferReleases.empty()) {
buffer.Destroy(); buffer.Destroy();
return; return;
} }
MOBILEGL_ASSERT(m_currentFrameIndex < m_deferredResidentReleases.size(), MOBILEGL_ASSERT(m_currentFrameIndex < m_deferredBufferReleases.size(),
"VkBufferManager::DeferResidentRelease current frame index out of range"); "VkBufferManager::DeferRelease current frame index out of range");
m_deferredResidentReleases[m_currentFrameIndex].push_back(std::move(buffer)); m_deferredBufferReleases[m_currentFrameIndex].push_back(std::move(buffer));
} }
void VkBufferManager::CollectDeferredResidentReleases(Uint32 frameIndex) { void VkBufferManager::CollectDeferredReleases(Uint32 frameIndex) {
MOBILEGL_ASSERT(frameIndex < m_deferredResidentReleases.size(), MOBILEGL_ASSERT(frameIndex < m_deferredBufferReleases.size(),
"VkBufferManager::CollectDeferredResidentReleases frame index out of range"); "VkBufferManager::CollectDeferredReleases frame index out of range");
m_deferredResidentReleases[frameIndex].clear(); m_deferredBufferReleases[frameIndex].clear();
m_deferredResourceReleases[frameIndex].clear();
} }
VkBufferUsageFlags VkBufferManager::GetVkBufferUsage(BufferKind kind) { VkBufferUsageFlags VkBufferManager::GetVkBufferUsage(BufferKind kind) {
@@ -209,46 +519,20 @@ namespace MobileGL::MG_Backend::DirectVulkan {
} }
} }
void VkBufferManager::CollectResidentGarbageIfNeeded() { void VkBufferManager::DestroyAllDeferredReleases() {
++m_residentGcTick; for (auto& releases : m_deferredBufferReleases) {
if (m_residentGcTick < kResidentBufferGCInterval) { for (auto& buffer : releases) {
return; buffer.Destroy();
} }
CollectResidentGarbageNow(); releases.clear();
m_residentGcTick = 0;
} }
m_deferredBufferReleases.clear();
void VkBufferManager::CollectResidentGarbageNow() { for (auto& releases : m_deferredResourceReleases) {
Vector<MG_State::GLState::BufferObject*> staleBuffers; for (auto& resource : releases) {
staleBuffers.reserve(m_residentBuffers.size()); resource->buffer.Destroy();
for (const auto& [rawBuffer, entry] : m_residentBuffers) {
if (entry.aliveRef.expired()) {
staleBuffers.push_back(rawBuffer);
} }
releases.clear();
} }
m_deferredResourceReleases.clear();
for (const auto* rawBuffer : staleBuffers) {
auto it = m_residentBuffers.find(const_cast<MG_State::GLState::BufferObject*>(rawBuffer));
if (it == m_residentBuffers.end()) {
continue;
}
DeferResidentRelease(std::move(it->second.buffer));
m_residentBuffers.erase(it);
}
}
void VkBufferManager::DestroyDeferredResidentReleases() {
for (auto& deferredReleases : m_deferredResidentReleases) {
deferredReleases.clear();
}
m_deferredResidentReleases.clear();
}
void VkBufferManager::DestroyResidentBuffers() {
for (auto& [_, entry] : m_residentBuffers) {
entry.buffer.Destroy();
}
m_residentBuffers.clear();
} }
} // namespace MobileGL::MG_Backend::DirectVulkan } // namespace MobileGL::MG_Backend::DirectVulkan
@@ -33,6 +33,38 @@ namespace MobileGL::MG_Backend::DirectVulkan {
Bool transientPersistentMapping = false; Bool transientPersistentMapping = false;
}; };
// The DirectVulkan storage behind one frontend buffer (pipe_resource analogue).
// Owned (refcounted) by the frontend BufferObject; the manager holds only weak
// references (for shutdown) plus strong references on deferred-release lists.
class VkBufferResource : public MG_State::GLState::BackendBufferResource {
public:
~VkBufferResource() override = default;
// Resident storage (may be invalid for streaming-only buffers).
VkBufferObject buffer;
VkDeviceSize storageSize = 0;
VkBufferUsageFlags usageFlags = 0;
// Frame serial of the last GPU reference; drives busy tracking.
Uint64 lastUseSerial = 0;
// Set when an immediate op could not be applied; forces a full re-upload
// on the next AcquireResidentSlice.
Bool pendingFullUpload = false;
// Cached transient (streaming) slice for the current frame.
BufferSlice transientSlice{};
Uint64 transientFrameSerial = 0;
Uint64 transientChangeSerial = 0;
VkDeviceSize transientSize = 0;
};
// Supplies a command buffer that is recording and outside any render pass,
// for staged buffer-range copies. Implemented by VulkanRenderer.
class IBufferCopyCommandProvider {
public:
virtual ~IBufferCopyCommandProvider() = default;
virtual VkCommandBuffer AcquireBufferCopyCommandBuffer() = 0;
};
class VkBufferManager { class VkBufferManager {
public: public:
Bool Initialize(const VkBufferManagerInitInfo& initInfo); Bool Initialize(const VkBufferManagerInitInfo& initInfo);
@@ -41,35 +73,61 @@ namespace MobileGL::MG_Backend::DirectVulkan {
// Recreate all per-frame transient arenas // Recreate all per-frame transient arenas
Bool RecreateTransientArenas(Uint32 frameCount); Bool RecreateTransientArenas(Uint32 frameCount);
void BeginFrame(Uint32 frameIndex); void BeginFrame(Uint32 frameIndex);
// All previously submitted GPU work has completed (vkDeviceWaitIdle).
void NotifyDeviceIdle();
void SetCopyCommandProvider(IBufferCopyCommandProvider* provider);
Bool UploadTransient(BufferKind kind, Uint32 frameIndex, const void* data, VkDeviceSize size, Bool UploadTransient(BufferKind kind, Uint32 frameIndex, const void* data, VkDeviceSize size,
VkDeviceSize alignment, BufferSlice& outSlice); VkDeviceSize alignment, BufferSlice& outSlice);
Bool SyncResidentBuffer(BufferKind kind, const SharedPtr<MG_State::GLState::BufferObject>& bufferObject,
// Draw-time acquire for resident (device-storage) buffers: ensures the
// resource exists and is fully uploaded, marks it used this frame.
Bool AcquireResidentSlice(BufferKind kind, const SharedPtr<MG_State::GLState::BufferObject>& bufferObject,
BufferSlice& outSlice); BufferSlice& outSlice);
void DowngradeResidentBufferToTransient(const SharedPtr<MG_State::GLState::BufferObject>& bufferObject); // Draw-time acquire for streamed buffers: uploads the whole shadow into
// the per-frame arena (cached by change serial), releasing any resident
// storage the buffer may still own.
Bool AcquireStreamedSlice(BufferKind kind, const SharedPtr<MG_State::GLState::BufferObject>& bufferObject,
BufferSlice& outSlice);
// Immediate ops, dispatched from the frontend BufferBackendOps table.
void OnRespecify(MG_State::GLState::BufferObject& bufferObject);
void OnSubData(MG_State::GLState::BufferObject& bufferObject, SizeT offset, SizeT size);
void OnFlushMappedRange(MG_State::GLState::BufferObject& bufferObject, Range1D range,
Flags<BufferMappingAccessBit> appAccess);
void OnResourceDestroyed(SharedPtr<MG_State::GLState::BackendBufferResource>&& resource);
Uint64 GetFrameSerial() const { return m_frameSerial; }
// Busy = potentially referenced by GPU work that has not been fenced yet
// (including commands recorded for the current, unsubmitted frame).
Bool IsResourceBusy(const VkBufferResource& resource) const;
private: private:
struct ResidentBufferEntry {
WeakPtr<MG_State::GLState::BufferObject> aliveRef;
VkBufferObject buffer;
VkDeviceSize size = 0;
VkBufferUsageFlags usage = 0;
};
Bool InitializeTransientArenas(); Bool InitializeTransientArenas();
static VkBufferUsageFlags GetVkBufferUsage(BufferKind kind); static VkBufferUsageFlags GetVkBufferUsage(BufferKind kind);
void DeferResidentRelease(VkBufferObject&& buffer); SharedPtr<VkBufferResource> GetOrCreateResource(const SharedPtr<MG_State::GLState::BufferObject>& bufferObject);
void CollectDeferredResidentReleases(Uint32 frameIndex); static VkBufferResource* ResourceOf(MG_State::GLState::BufferObject& bufferObject);
void CollectResidentGarbageIfNeeded(); Bool CreateResidentStorage(VkBufferResource& resource, VkDeviceSize size, VkBufferUsageFlags usage);
void CollectResidentGarbageNow(); // Swap storage (conditional orphan) and refill it from the shadow copy.
void DestroyDeferredResidentReleases(); Bool SwapStorageAndUploadAll(VkBufferResource& resource, MG_State::GLState::BufferObject& bufferObject);
void DestroyResidentBuffers(); // Record a staging-slice copy into the resident storage, ordered against
// in-flight and already-recorded GPU work.
Bool StagedRangeCopy(VkBufferResource& resource, MG_State::GLState::BufferObject& bufferObject,
SizeT offset, SizeT size);
void DeferRelease(VkBufferObject&& buffer);
void CollectDeferredReleases(Uint32 frameIndex);
void DestroyAllDeferredReleases();
void TrackLiveResource(const SharedPtr<VkBufferResource>& resource);
void ReleaseAllLiveResources();
VkBufferManagerInitInfo m_initInfo{}; VkBufferManagerInitInfo m_initInfo{};
BufferArena m_transientUploadArena; BufferArena m_transientUploadArena;
UnorderedMap<MG_State::GLState::BufferObject*, ResidentBufferEntry> m_residentBuffers; IBufferCopyCommandProvider* m_copyProvider = nullptr;
Vector<Vector<VkBufferObject>> m_deferredResidentReleases; Vector<Vector<VkBufferObject>> m_deferredBufferReleases;
Vector<Vector<SharedPtr<VkBufferResource>>> m_deferredResourceReleases;
Vector<WeakPtr<VkBufferResource>> m_liveResources;
Uint32 m_currentFrameIndex = 0; Uint32 m_currentFrameIndex = 0;
Uint32 m_residentGcTick = 0; Uint64 m_frameSerial = 1;
Uint64 m_completedSerialFloor = 0;
}; };
} // namespace MobileGL::MG_Backend::DirectVulkan } // namespace MobileGL::MG_Backend::DirectVulkan
@@ -1792,7 +1792,7 @@ void main() {
MGLOG_E("DirectVulkan readback skipped: pixel pack buffer is too small"); MGLOG_E("DirectVulkan readback skipped: pixel pack buffer is too small");
return false; return false;
} }
pixelPackBufferObject->UploadSubData({packed.data(), packed.size()}, pboOffset); pixelPackBufferObject->WritebackFromBackend({packed.data(), packed.size()}, pboOffset);
return true; return true;
} }
@@ -1893,6 +1893,7 @@ void main() {
.transientPersistentMapping = true, .transientPersistentMapping = true,
}); });
MOBILEGL_ASSERT(succeeded, "VkBufferManager initialization failed."); MOBILEGL_ASSERT(succeeded, "VkBufferManager initialization failed.");
m_bufferManager.SetCopyCommandProvider(this);
m_textureManager = MakeUnique<VkTextureManager>(); m_textureManager = MakeUnique<VkTextureManager>();
MOBILEGL_ASSERT(m_textureManager != nullptr, "VkTextureManager creation failed."); MOBILEGL_ASSERT(m_textureManager != nullptr, "VkTextureManager creation failed.");
succeeded = m_textureManager->Initialize( succeeded = m_textureManager->Initialize(
@@ -1956,7 +1957,6 @@ void main() {
VK_VERIFY(acquireResult, "Initialize, WaitAndAcquireNextImage"); VK_VERIFY(acquireResult, "Initialize, WaitAndAcquireNextImage");
m_textureManager->BeginFrame(m_frameContext.GetCurrentFrameIndex()); m_textureManager->BeginFrame(m_frameContext.GetCurrentFrameIndex());
m_bufferManager.BeginFrame(m_frameContext.GetCurrentFrameIndex()); m_bufferManager.BeginFrame(m_frameContext.GetCurrentFrameIndex());
m_transientVertexIndexBufferSlicesThisFrame.clear();
MGLOG_D("VulkanRenderer initialized"); MGLOG_D("VulkanRenderer initialized");
} }
@@ -1986,7 +1986,6 @@ void main() {
} }
m_vertexInputStateFactory.reset(); m_vertexInputStateFactory.reset();
m_bufferManager.Shutdown(); m_bufferManager.Shutdown();
m_transientVertexIndexBufferSlicesThisFrame.clear();
if (m_device != VK_NULL_HANDLE) { if (m_device != VK_NULL_HANDLE) {
m_frameContext.Destroy(m_device, m_commandPool); m_frameContext.Destroy(m_device, m_commandPool);
@@ -2147,32 +2146,15 @@ void main() {
auto sourceBufferShared = MG_State::pGLContext->GetBufferObject(sourceBuffer->GetExternalIndex()); auto sourceBufferShared = MG_State::pGLContext->GetBufferObject(sourceBuffer->GetExternalIndex());
MOBILEGL_ASSERT(sourceBufferShared != nullptr, MOBILEGL_ASSERT(sourceBufferShared != nullptr,
"UploadAndBindVertexStreams failed to resolve shared source buffer"); "UploadAndBindVertexStreams failed to resolve shared source buffer");
sourceBufferShared->MarkPersistentMappedRangeDirty();
BufferSlice slice{}; BufferSlice slice{};
const Bool isDirty = (sourceBufferShared->GetChangeBits() & BufferChangeBits::DirtyBit);
const Uint64 changeSerial = sourceBufferShared->GetChangeSerial();
const SizeT sourceSize = sourceBufferShared->GetSize(); const SizeT sourceSize = sourceBufferShared->GetSize();
auto cachedTransient = m_transientVertexIndexBufferSlicesThisFrame.find(sourceBufferShared.get()); if (ShouldUseTransientVertexIndexBuffer(*sourceBufferShared)) {
if (cachedTransient != m_transientVertexIndexBufferSlicesThisFrame.end() && !isDirty && if (!m_bufferManager.AcquireStreamedSlice(BufferKind::Vertex, sourceBufferShared, slice)) {
cachedTransient->second.changeSerial == changeSerial && cachedTransient->second.size == sourceSize) {
slice = cachedTransient->second.slice;
} else if (ShouldUseTransientVertexIndexBuffer(*sourceBufferShared) || isDirty) {
const auto sourceData = sourceBufferShared->GetDataReadOnly();
if (!m_bufferManager.UploadTransient(BufferKind::Vertex, m_frameContext.GetCurrentFrameIndex(),
sourceData->data(), static_cast<VkDeviceSize>(sourceSize), 16,
slice)) {
MOBILEGL_ASSERT(false, "UploadAndBindVertexStreams skipped: failed to upload transient binding %zu", binding); MOBILEGL_ASSERT(false, "UploadAndBindVertexStreams skipped: failed to upload transient binding %zu", binding);
return false; return false;
} }
m_transientVertexIndexBufferSlicesThisFrame[sourceBufferShared.get()] = {
.slice = slice,
.changeSerial = changeSerial,
.size = sourceSize,
};
m_bufferManager.DowngradeResidentBufferToTransient(sourceBufferShared);
sourceBufferShared->ClearDirty();
} else { } else {
if (!m_bufferManager.SyncResidentBuffer(BufferKind::Vertex, sourceBufferShared, slice)) { if (!m_bufferManager.AcquireResidentSlice(BufferKind::Vertex, sourceBufferShared, slice)) {
MGLOG_E("UploadAndBindVertexStreams skipped: failed to sync resident binding %zu", binding); MGLOG_E("UploadAndBindVertexStreams skipped: failed to sync resident binding %zu", binding);
return false; return false;
} }
@@ -2247,7 +2229,6 @@ void main() {
const auto* indexBuffer = vao.GetIndexBufferBindingSlot().GetBoundObject().get(); const auto* indexBuffer = vao.GetIndexBufferBindingSlot().GetBoundObject().get();
MOBILEGL_ASSERT(indexBuffer != nullptr, "UploadAndBindIndexBuffer requires bound EBO"); MOBILEGL_ASSERT(indexBuffer != nullptr, "UploadAndBindIndexBuffer requires bound EBO");
const SizeT indexSize = MG_Util::GetGLTypeSize(pIndexBufferView->indexType);
const SizeT indexDataSizeBytes = pIndexBufferView->indexByteSize; const SizeT indexDataSizeBytes = pIndexBufferView->indexByteSize;
MOBILEGL_ASSERT(pIndexBufferView->indexByteOffset + indexDataSizeBytes <= indexBuffer->GetSize(), MOBILEGL_ASSERT(pIndexBufferView->indexByteOffset + indexDataSizeBytes <= indexBuffer->GetSize(),
"DrawElements index range out of bounds"); "DrawElements index range out of bounds");
@@ -2255,44 +2236,14 @@ void main() {
BufferSlice slice{}; BufferSlice slice{};
auto indexBufferShared = MG_State::pGLContext->GetBufferObject(indexBuffer->GetExternalIndex()); auto indexBufferShared = MG_State::pGLContext->GetBufferObject(indexBuffer->GetExternalIndex());
MOBILEGL_ASSERT(indexBufferShared != nullptr, "UploadAndBindIndexBuffer failed to resolve shared EBO"); MOBILEGL_ASSERT(indexBufferShared != nullptr, "UploadAndBindIndexBuffer failed to resolve shared EBO");
indexBufferShared->MarkPersistentMappedRangeDirty(); if (ShouldUseTransientVertexIndexBuffer(*indexBufferShared)) {
const Bool isDirty = (indexBufferShared->GetChangeBits() & BufferChangeBits::DirtyBit);
const Uint64 changeSerial = indexBufferShared->GetChangeSerial();
const SizeT indexBufferSize = indexBufferShared->GetSize();
auto cachedTransient = m_transientVertexIndexBufferSlicesThisFrame.find(indexBufferShared.get());
if (cachedTransient != m_transientVertexIndexBufferSlicesThisFrame.end() && !isDirty &&
cachedTransient->second.changeSerial == changeSerial && cachedTransient->second.size == indexBufferSize) {
slice = cachedTransient->second.slice;
vkCmdBindIndexBuffer(frame.commandBuffer,
slice.buffer,
slice.offset + static_cast<VkDeviceSize>(pIndexBufferView->indexByteOffset),
vkIndexType);
return true;
}
if (ShouldUseTransientVertexIndexBuffer(*indexBufferShared) || isDirty) {
const auto indexData = indexBufferShared->GetDataReadOnly(); const auto indexData = indexBufferShared->GetDataReadOnly();
MOBILEGL_ASSERT(indexData != nullptr && !indexData->empty(), "DrawElements requires non-empty EBO data"); MOBILEGL_ASSERT(indexData != nullptr && !indexData->empty(), "DrawElements requires non-empty EBO data");
if (!m_bufferManager.UploadTransient(BufferKind::Index, m_frameContext.GetCurrentFrameIndex(), if (!m_bufferManager.AcquireStreamedSlice(BufferKind::Index, indexBufferShared, slice)) {
indexData->data(),
static_cast<VkDeviceSize>(indexBufferSize), indexSize,
slice)) {
MOBILEGL_ASSERT(false, "DrawElements skipped: failed to prepare transient index buffer"); MOBILEGL_ASSERT(false, "DrawElements skipped: failed to prepare transient index buffer");
return false; return false;
} }
m_transientVertexIndexBufferSlicesThisFrame[indexBufferShared.get()] = { } else if (!m_bufferManager.AcquireResidentSlice(BufferKind::Index, indexBufferShared, slice)) {
.slice = slice,
.changeSerial = changeSerial,
.size = indexBufferSize,
};
m_bufferManager.DowngradeResidentBufferToTransient(indexBufferShared);
indexBufferShared->ClearDirty();
vkCmdBindIndexBuffer(frame.commandBuffer,
slice.buffer,
slice.offset + static_cast<VkDeviceSize>(pIndexBufferView->indexByteOffset),
vkIndexType);
return true;
}
if (!m_bufferManager.SyncResidentBuffer(BufferKind::Index, indexBufferShared, slice)) {
MGLOG_E("DrawElements skipped: failed to sync resident index buffer"); MGLOG_E("DrawElements skipped: failed to sync resident index buffer");
return false; return false;
} }
@@ -3449,10 +3400,10 @@ void main() {
MGLOG_E("DispatchComputeIndirect skipped: GL_DISPATCH_INDIRECT_BUFFER is not bound"); MGLOG_E("DispatchComputeIndirect skipped: GL_DISPATCH_INDIRECT_BUFFER is not bound");
return; return;
} }
indirectBuffer->MarkPersistentMappedRangeDirty(); indirectBuffer->SyncPersistentMappedRange();
BufferSlice slice{}; BufferSlice slice{};
if (!m_bufferManager.SyncResidentBuffer(BufferKind::Indirect, indirectBuffer, slice)) { if (!m_bufferManager.AcquireResidentSlice(BufferKind::Indirect, indirectBuffer, slice)) {
MGLOG_E("DispatchComputeIndirect skipped: failed to sync indirect dispatch buffer"); MGLOG_E("DispatchComputeIndirect skipped: failed to sync indirect dispatch buffer");
return; return;
} }
@@ -5259,16 +5210,16 @@ void main() {
return; return;
} }
drawBuffer->MarkPersistentMappedRangeDirty(); drawBuffer->SyncPersistentMappedRange();
parameterBuffer->MarkPersistentMappedRangeDirty(); parameterBuffer->SyncPersistentMappedRange();
BufferSlice drawSlice{}; BufferSlice drawSlice{};
if (!m_bufferManager.SyncResidentBuffer(BufferKind::Indirect, drawBuffer, drawSlice)) { if (!m_bufferManager.AcquireResidentSlice(BufferKind::Indirect, drawBuffer, drawSlice)) {
MGLOG_E("MultiDrawElementsIndirectCount skipped: failed to sync draw indirect buffer"); MGLOG_E("MultiDrawElementsIndirectCount skipped: failed to sync draw indirect buffer");
return; return;
} }
BufferSlice parameterSlice{}; BufferSlice parameterSlice{};
if (!m_bufferManager.SyncResidentBuffer(BufferKind::Indirect, parameterBuffer, parameterSlice)) { if (!m_bufferManager.AcquireResidentSlice(BufferKind::Indirect, parameterBuffer, parameterSlice)) {
MGLOG_E("MultiDrawElementsIndirectCount skipped: failed to sync parameter buffer"); MGLOG_E("MultiDrawElementsIndirectCount skipped: failed to sync parameter buffer");
return; return;
} }
@@ -5303,6 +5254,22 @@ void main() {
} }
} }
VkCommandBuffer VulkanRenderer::AcquireBufferCopyCommandBuffer() {
if (m_device == VK_NULL_HANDLE || m_frameContext.GetFrameCount() == 0) {
return VK_NULL_HANDLE;
}
auto& frame = m_frameContext.GetCurrent();
if (!frame.isCommandRecording) {
m_frameContext.BeginCommandRecording();
}
// vkCmdCopyBuffer must be recorded outside a render pass; draws re-begin
// their render pass lazily, matching the existing blit/clear pattern.
if (VkRenderPassManager::GetActiveRenderPass() != nullptr) {
VkRenderPassManager::EndRenderPass(frame.commandBuffer);
}
return frame.commandBuffer;
}
void VulkanRenderer::Present() { void VulkanRenderer::Present() {
MOBILEGL_ASSERT(m_imageIndexAcquired < m_swapchainObject.GetImageCount(), MOBILEGL_ASSERT(m_imageIndexAcquired < m_swapchainObject.GetImageCount(),
"Present, acquired image index out of range"); "Present, acquired image index out of range");
@@ -5469,7 +5436,6 @@ void main() {
CollectDeferredDepthMipmapCleanup(m_frameContext.GetCurrentFrameIndex()); CollectDeferredDepthMipmapCleanup(m_frameContext.GetCurrentFrameIndex());
m_textureManager->BeginFrame(m_frameContext.GetCurrentFrameIndex()); m_textureManager->BeginFrame(m_frameContext.GetCurrentFrameIndex());
m_bufferManager.BeginFrame(m_frameContext.GetCurrentFrameIndex()); m_bufferManager.BeginFrame(m_frameContext.GetCurrentFrameIndex());
m_transientVertexIndexBufferSlicesThisFrame.clear();
} }
void VulkanRenderer::CreateInstance() { void VulkanRenderer::CreateInstance() {
@@ -6171,7 +6137,6 @@ void main() {
m_textureManager->BeginFrame(m_frameContext.GetCurrentFrameIndex()); m_textureManager->BeginFrame(m_frameContext.GetCurrentFrameIndex());
} }
m_bufferManager.BeginFrame(m_frameContext.GetCurrentFrameIndex()); m_bufferManager.BeginFrame(m_frameContext.GetCurrentFrameIndex());
m_transientVertexIndexBufferSlicesThisFrame.clear();
} }
} }
@@ -101,7 +101,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
} }
}; };
class VulkanRenderer { class VulkanRenderer : public IBufferCopyCommandProvider {
public: public:
VulkanRenderer(NativeWindowType window, const VulkanRendererConfig& cfg = {}); VulkanRenderer(NativeWindowType window, const VulkanRendererConfig& cfg = {});
~VulkanRenderer(); ~VulkanRenderer();
@@ -109,6 +109,10 @@ namespace MobileGL::MG_Backend::DirectVulkan {
void Initialize(); void Initialize();
void Shutdown(); void Shutdown();
// IBufferCopyCommandProvider: recording command buffer, outside any
// render pass, for immediate staged buffer copies.
VkCommandBuffer AcquireBufferCopyCommandBuffer() override;
Bool SetupDraw(FrameContext::FrameData& frame, GLenum mode, Flags<DrawSetupAspect> aspects, Bool SetupDraw(FrameContext::FrameData& frame, GLenum mode, Flags<DrawSetupAspect> aspects,
const DrawCmdParam& drawParams, const DrawCmdParam& drawParams,
const IndexBufferView* pIndexBufferView = nullptr); const IndexBufferView* pIndexBufferView = nullptr);
@@ -233,15 +237,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
VkCommandPool m_commandPool = VK_NULL_HANDLE; VkCommandPool m_commandPool = VK_NULL_HANDLE;
struct TransientBufferSliceCacheEntry {
BufferSlice slice;
Uint64 changeSerial = 0;
SizeT size = 0;
};
VkBufferManager m_bufferManager; VkBufferManager m_bufferManager;
UnorderedMap<const MG_State::GLState::BufferObject*, TransientBufferSliceCacheEntry>
m_transientVertexIndexBufferSlicesThisFrame;
Uint m_imageIndexAcquired = 0; Uint m_imageIndexAcquired = 0;
FrameContext m_frameContext; FrameContext m_frameContext;
+2 -8
View File
@@ -834,10 +834,7 @@ namespace MobileGL::MG_Impl::GLImpl {
} }
bufferObject->SetUsage(bufferUsage); bufferObject->SetUsage(bufferUsage);
bufferObject->Resize(size); bufferObject->Respecify(size, data);
if (data) {
bufferObject->UploadData({(void*)data, (SizeT)size}, 0);
}
} }
void BufferStorage_State(GLenum target, GLsizeiptr size, const void* data, GLbitfield flags) { void BufferStorage_State(GLenum target, GLsizeiptr size, const void* data, GLbitfield flags) {
@@ -928,10 +925,7 @@ namespace MobileGL::MG_Impl::GLImpl {
} }
bufferObject->SetUsage(bufferUsage); bufferObject->SetUsage(bufferUsage);
bufferObject->Resize(size); bufferObject->Respecify(size, data);
if (data) {
bufferObject->UploadData({(void*)data, (SizeT)size}, 0);
}
} }
void NamedBufferSubData_State(GLuint buffer, GLintptr offset, GLsizeiptr size, const void* data) { void NamedBufferSubData_State(GLuint buffer, GLintptr offset, GLsizeiptr size, const void* data) {
@@ -9,24 +9,67 @@
#include "BufferObject.h" #include "BufferObject.h"
namespace MobileGL::MG_State::GLState { namespace MobileGL::MG_State::GLState {
BufferObject::BufferObject(Uint externalIndex) namespace {
: m_externalIndex(externalIndex), m_size(0), m_usage(BufferUsage::StaticDraw), m_isMapped(false), const BufferBackendOps* g_bufferBackendOps = nullptr;
m_mappingAccess(BufferMappingAccessBit::Null),
m_change(BufferChangeBits::DirtyBit | BufferChangeBits::PreferReallocationBit), m_mappedRange({0, 0}),
m_dataPtr(MakeShared<Data>()), m_ownsStagingData{} {
m_change.DirtyRanges.reserve(BufferChange::DEFAULT_RESERVED_DIRTY_RANGES_COUNT);
} }
void BufferObject::Resize(SizeT size) { void SetBufferBackendOps(const BufferBackendOps* ops) {
g_bufferBackendOps = ops;
}
const BufferBackendOps* GetBufferBackendOps() {
return g_bufferBackendOps;
}
BufferObject::BufferObject(Uint externalIndex)
: m_externalIndex(externalIndex), m_size(0), m_usage(BufferUsage::StaticDraw), m_isMapped(false),
m_mappingAccess(BufferMappingAccessBit::Null), m_mappedRange({0, 0}), m_dataPtr(MakeShared<Data>()),
m_ownsStagingData{} {}
BufferObject::~BufferObject() {
if (m_backendResource && g_bufferBackendOps && g_bufferBackendOps->OnDestroy) {
g_bufferBackendOps->OnDestroy(std::move(m_backendResource));
}
}
void BufferObject::NotifyRespecify() {
++m_changeSerial;
if (g_bufferBackendOps && g_bufferBackendOps->Respecify) {
g_bufferBackendOps->Respecify(*this);
}
}
void BufferObject::NotifySubData(SizeT offset, SizeT size) {
++m_changeSerial;
if (size == 0) return;
if (g_bufferBackendOps && g_bufferBackendOps->SubData) {
g_bufferBackendOps->SubData(*this, offset, size);
}
}
void BufferObject::NotifyFlushMappedRange(Range1D range, Flags<BufferMappingAccessBit> appAccess) {
++m_changeSerial;
if (range.start >= range.end) return;
if (g_bufferBackendOps && g_bufferBackendOps->FlushMappedRange) {
g_bufferBackendOps->FlushMappedRange(*this, range, appAccess);
}
}
void BufferObject::Respecify(SizeT size, const void* data) {
ReleaseMemory(); ReleaseMemory();
m_size = size; m_size = size;
m_dataPtr->reserve(std::bit_ceil(size)); // power-of-2 reserve m_dataPtr->reserve(std::bit_ceil(size)); // power-of-2 reserve
m_dataPtr->resize(size); m_dataPtr->resize(size);
if (data && size > 0) {
Memcpy(m_dataPtr->data(), data, size);
}
m_isImmutableStorage = false; m_isImmutableStorage = false;
m_storageFlags = 0; m_storageFlags = 0;
m_change.Bits |= BufferChangeBits::DirtyBit; NotifyRespecify();
m_change.Bits |= BufferChangeBits::PreferReallocationBit; }
++m_changeSerial;
void BufferObject::Resize(SizeT size) {
Respecify(size, nullptr);
} }
void BufferObject::AllocateImmutableStorage(SizeT size, const void* data, GLbitfield storageFlags) { void BufferObject::AllocateImmutableStorage(SizeT size, const void* data, GLbitfield storageFlags) {
@@ -41,10 +84,7 @@ namespace MobileGL::MG_State::GLState {
} }
m_isImmutableStorage = true; m_isImmutableStorage = true;
m_storageFlags = storageFlags; m_storageFlags = storageFlags;
m_change.DirtyRanges.clear(); NotifyRespecify();
m_change.DirtyRanges.Add({0, size});
m_change.Bits = BufferChangeBits::DirtyBit | BufferChangeBits::PreferReallocationBit;
++m_changeSerial;
} }
void BufferObject::UploadData(DataPtr data, SizeT atOffset) { void BufferObject::UploadData(DataPtr data, SizeT atOffset) {
@@ -54,15 +94,7 @@ namespace MobileGL::MG_State::GLState {
MOBILEGL_ASSERT(!m_isMapped || (m_mappingAccess & BufferMappingAccessBit::Persistent), MOBILEGL_ASSERT(!m_isMapped || (m_mappingAccess & BufferMappingAccessBit::Persistent),
"Cannot upload data while buffer is non-persistently mapped."); "Cannot upload data while buffer is non-persistently mapped.");
Memcpy(m_dataPtr->data() + atOffset, data.data, data.size); Memcpy(m_dataPtr->data() + atOffset, data.data, data.size);
m_change.DirtyRanges.Add({atOffset, atOffset + data.size}); NotifySubData(atOffset, data.size);
m_change.Bits |= BufferChangeBits::DirtyBit;
m_change.Bits |= BufferChangeBits::ForbidInvalidationBit;
m_change.Bits |= BufferChangeBits::ForbidUnsynchronizationBit;
++m_changeSerial;
// This function may be called by `glBufferData`, but we still set the forbid bits above,
// because when `PreferReallocationBit` is set, those bits are ignored anyway.
// The bits can fit the `glBufferSubData` semantics
// (though `glBufferSubData` calls `UploadSubData` instead).
} }
void BufferObject::SetUsage(BufferUsage usage) { void BufferObject::SetUsage(BufferUsage usage) {
@@ -78,9 +110,7 @@ namespace MobileGL::MG_State::GLState {
Memcpy(m_dataPtr->data() + m_mappedRange.start, m_stagingData.data(), Memcpy(m_dataPtr->data() + m_mappedRange.start, m_stagingData.data(),
m_mappedRange.end - m_mappedRange.start); m_mappedRange.end - m_mappedRange.start);
} }
m_change.DirtyRanges.Add({m_mappedRange.start, m_mappedRange.end}); NotifyFlushMappedRange(m_mappedRange, m_mappingAccess);
m_change.Bits |= BufferChangeBits::DirtyBit;
++m_changeSerial;
} }
m_stagingData.clear(); m_stagingData.clear();
@@ -107,20 +137,24 @@ namespace MobileGL::MG_State::GLState {
if (!(m_mappingAccess & BufferMappingAccessBit::Persistent)) { if (!(m_mappingAccess & BufferMappingAccessBit::Persistent)) {
Memcpy(m_dataPtr->data() + start, m_stagingData.data() + offset, length); Memcpy(m_dataPtr->data() + start, m_stagingData.data() + offset, length);
} }
m_change.DirtyRanges.Add({start, end}); NotifyFlushMappedRange({start, end}, m_mappingAccess);
m_change.Bits |= BufferChangeBits::DirtyBit;
++m_changeSerial;
} }
void BufferObject::MarkPersistentMappedRangeDirty() { void BufferObject::SyncPersistentMappedRange() {
if (!m_isMapped) return; if (!m_isMapped) return;
if (!(m_mappingAccess & BufferMappingAccessBit::Persistent)) return; if (!(m_mappingAccess & BufferMappingAccessBit::Persistent)) return;
if (!(m_mappingAccess & BufferMappingAccessBit::Write)) return; if (!(m_mappingAccess & BufferMappingAccessBit::Write)) return;
if (m_mappingAccess & BufferMappingAccessBit::FlushExplicit) return; if (m_mappingAccess & BufferMappingAccessBit::FlushExplicit) return;
if (m_mappedRange.start >= m_mappedRange.end) return; if (m_mappedRange.start >= m_mappedRange.end) return;
m_change.DirtyRanges.Add(m_mappedRange); NotifySubData(m_mappedRange.start, m_mappedRange.end - m_mappedRange.start);
m_change.Bits |= BufferChangeBits::DirtyBit; }
void BufferObject::WritebackFromBackend(DataPtr data, SizeT atOffset) {
MOBILEGL_ASSERT(atOffset + data.size <= m_size,
"WritebackFromBackend out of bounds: atOffset (%zu) + data.size (%zu) > m_size (%zu)", atOffset,
data.size, m_size);
Memcpy(m_dataPtr->data() + atOffset, data.data, data.size);
++m_changeSerial; ++m_changeSerial;
} }
@@ -132,11 +166,7 @@ namespace MobileGL::MG_State::GLState {
data.size, m_size); data.size, m_size);
Memcpy(m_dataPtr->data() + atOffset, data.data, data.size); Memcpy(m_dataPtr->data() + atOffset, data.data, data.size);
m_change.DirtyRanges.Add({atOffset, atOffset + data.size}); NotifySubData(atOffset, data.size);
m_change.Bits |= BufferChangeBits::DirtyBit;
m_change.Bits |= BufferChangeBits::ForbidInvalidationBit;
m_change.Bits |= BufferChangeBits::ForbidUnsynchronizationBit;
++m_changeSerial;
} }
void BufferObject::CopyDataFrom(const SharedPtr<BufferObject>& src, SizeT srcOffset, SizeT dstOffset, SizeT size) { void BufferObject::CopyDataFrom(const SharedPtr<BufferObject>& src, SizeT srcOffset, SizeT dstOffset, SizeT size) {
@@ -153,9 +183,7 @@ namespace MobileGL::MG_State::GLState {
const Uint8* srcData = src->m_dataPtr->data() + srcOffset; const Uint8* srcData = src->m_dataPtr->data() + srcOffset;
Memcpy(m_dataPtr->data() + dstOffset, srcData, size); Memcpy(m_dataPtr->data() + dstOffset, srcData, size);
m_change.DirtyRanges.Add({dstOffset, dstOffset + size}); NotifySubData(dstOffset, size);
m_change.Bits |= BufferChangeBits::DirtyBit;
++m_changeSerial;
} }
void* BufferObject::AcquireMemory(Bool markMapped, Bool read, Bool write) { void* BufferObject::AcquireMemory(Bool markMapped, Bool read, Bool write) {
@@ -164,10 +192,6 @@ namespace MobileGL::MG_State::GLState {
m_mappingAccess = (read ? BufferMappingAccessBit::Read : BufferMappingAccessBit::Null) | m_mappingAccess = (read ? BufferMappingAccessBit::Read : BufferMappingAccessBit::Null) |
(write ? BufferMappingAccessBit::Write : BufferMappingAccessBit::Null); (write ? BufferMappingAccessBit::Write : BufferMappingAccessBit::Null);
m_mappedRange = {0, m_size}; m_mappedRange = {0, m_size};
if (write) {
m_change.Bits |= BufferChangeBits::ForbidInvalidationBit;
m_change.Bits |= BufferChangeBits::ForbidUnsynchronizationBit;
}
if (m_mappingAccess & BufferMappingAccessBit::Write) { if (m_mappingAccess & BufferMappingAccessBit::Write) {
m_stagingData.resize(m_size); m_stagingData.resize(m_size);
@@ -192,21 +216,6 @@ namespace MobileGL::MG_State::GLState {
m_isMapped = true; m_isMapped = true;
m_mappingAccess = access; m_mappingAccess = access;
m_mappedRange = range; m_mappedRange = range;
m_change.Bits |=
!(access & BufferMappingAccessBit::InvalidateBuffer || access & BufferMappingAccessBit::InvalidateRange)
? BufferChangeBits::ForbidInvalidationBit
: BufferChangeBits::None;
m_change.Bits |= !(access & BufferMappingAccessBit::Unsynchronized)
? BufferChangeBits::ForbidUnsynchronizationBit
: BufferChangeBits::None;
m_change.Bits |=
!(access & BufferMappingAccessBit::InvalidateBuffer || access & BufferMappingAccessBit::InvalidateRange)
? BufferChangeBits::ForbidInvalidationBit
: BufferChangeBits::None;
m_change.Bits |= !(access & BufferMappingAccessBit::Unsynchronized)
? BufferChangeBits::ForbidUnsynchronizationBit
: BufferChangeBits::None;
if (access & BufferMappingAccessBit::Persistent) { if (access & BufferMappingAccessBit::Persistent) {
m_ownsStagingData = false; m_ownsStagingData = false;
@@ -232,11 +241,6 @@ namespace MobileGL::MG_State::GLState {
return m_dataPtr; return m_dataPtr;
} }
void BufferObject::ClearDirty() {
m_change.DirtyRanges.clear();
m_change.Bits = BufferChangeBits::None;
}
SizeT BufferObject::GetSize() const { SizeT BufferObject::GetSize() const {
return m_size; return m_size;
} }
@@ -249,18 +253,18 @@ namespace MobileGL::MG_State::GLState {
return m_usage; return m_usage;
} }
const VecRange1D& BufferObject::GetDirtyRanges() const {
return m_change.DirtyRanges;
}
Flags<BufferChangeBits> BufferObject::GetChangeBits() const {
return m_change.Bits;
}
Uint64 BufferObject::GetChangeSerial() const { Uint64 BufferObject::GetChangeSerial() const {
return m_changeSerial; return m_changeSerial;
} }
const SharedPtr<BackendBufferResource>& BufferObject::GetBackendResource() const {
return m_backendResource;
}
void BufferObject::SetBackendResource(SharedPtr<BackendBufferResource> resource) {
m_backendResource = std::move(resource);
}
Bool BufferObject::IsMapped() const { Bool BufferObject::IsMapped() const {
return m_isMapped; return m_isMapped;
} }
@@ -56,42 +56,81 @@ namespace MobileGL {
Coherent = 0x80 Coherent = 0x80
}; };
enum class BufferChangeBits : Uint8 {
None = 0,
DirtyBit = 1 << 0, // When not set, bits below are ignored and nothing should be synced to backend
PreferReallocationBit =
1 << 1, // <=> `glBufferData`; When set, ForbidInvalidationBit and ForbidUnsynchronizationBit are ignored
ForbidInvalidationBit = 1 << 2, // Indidate that invalidation flags were not used during mapping, else we're
// allowed to act as `GL_MAP_INVALIDATE_*` in backend
ForbidUnsynchronizationBit = 1 << 3, // (the same description as above, but for unsynchronization)
};
struct BufferChange {
static constexpr int DEFAULT_RESERVED_DIRTY_RANGES_COUNT = 50;
Flags<BufferChangeBits> Bits = BufferChangeBits::None;
VecRange1D DirtyRanges;
};
namespace MG_State::GLState { namespace MG_State::GLState {
class BufferObject;
// Opaque, refcounted handle to the backend's storage for one buffer
// (the pipe_resource analogue). The frontend owns the reference; the
// active backend derives from it and attaches its own payload.
class BackendBufferResource {
public:
virtual ~BackendBufferResource() = default;
};
// Immediate buffer transfer interface implemented by the active backend
// (the pipe_context buffer-op analogue). Ops are invoked at GL call time,
// right after the shadow copy has been updated; contents are always read
// from the shadow so ops carry only ranges and flags.
//
// Every op must tolerate bufferObject.GetBackendResource() == nullptr:
// resources are created lazily by the backend's draw/bind-time ensure
// path, which performs a full upload from the shadow and thereby covers
// all ops that happened before the resource existed.
struct BufferBackendOps {
// Storage (re)definition: glBufferData / glBufferStorage. The orphaning
// point - the backend decides (busy-tracking) whether to swap storage
// or write in place. Shadow already holds the new contents.
void (*Respecify)(BufferObject& bufferObject) = nullptr;
// Contents update of [offset, offset + size) from the shadow.
void (*SubData)(BufferObject& bufferObject, SizeT offset, SizeT size) = nullptr;
// Write-map flush (glUnmapBuffer / glFlushMappedBufferRange). Carries the
// app's real mapping flags so the backend can honour INVALIDATE_* /
// UNSYNCHRONIZED semantics per call instead of merging them.
void (*FlushMappedRange)(BufferObject& bufferObject, Range1D range,
Flags<BufferMappingAccessBit> appAccess) = nullptr;
// Final release of the backend resource (called from ~BufferObject).
// The backend defers actual destruction until the GPU is done with it.
void (*OnDestroy)(SharedPtr<BackendBufferResource>&& resource) = nullptr;
};
// Registered by the active backend at init, cleared at shutdown.
// Null table (unit tests, benchmarks) => shadow-only state tracking.
void SetBufferBackendOps(const BufferBackendOps* ops);
const BufferBackendOps* GetBufferBackendOps();
class BufferObject { class BufferObject {
public: public:
using TargetEnum = BufferTarget; using TargetEnum = BufferTarget;
BufferObject(Uint externalIndex); BufferObject(Uint externalIndex);
~BufferObject();
BufferObject(const BufferObject&) = delete;
BufferObject& operator=(const BufferObject&) = delete;
// Storage definition (single backend Respecify): glBufferData.
void Respecify(SizeT size, const void* data);
// Storage definition without contents; equivalent to Respecify(size, nullptr).
void Resize(SizeT size); void Resize(SizeT size);
void AllocateImmutableStorage(SizeT size, const void* data, GLbitfield storageFlags); void AllocateImmutableStorage(SizeT size, const void* data, GLbitfield storageFlags);
void UploadData(DataPtr data, SizeT atOffset);
void SetUsage(BufferUsage usage); void SetUsage(BufferUsage usage);
void UploadData(DataPtr data, SizeT atOffset);
void UploadSubData(DataPtr data, SizeT atOffset);
void CopyDataFrom(const SharedPtr<BufferObject>& src, SizeT srcOffset, SizeT dstOffset, SizeT size);
void* AcquireMemory(Bool markMapped, Bool read, Bool write); void* AcquireMemory(Bool markMapped, Bool read, Bool write);
void* AcquireMemoryRange(Range1D range, Flags<BufferMappingAccessBit> access); void* AcquireMemoryRange(Range1D range, Flags<BufferMappingAccessBit> access);
void ReleaseMemory(); void ReleaseMemory();
void FlushMemoryRange(SizeT offset, SizeT length); void FlushMemoryRange(SizeT offset, SizeT length);
void MarkPersistentMappedRangeDirty();
void UploadSubData(DataPtr data, SizeT atOffset); // Pushes the persistently-mapped write range to the backend; called by
void CopyDataFrom(const SharedPtr<BufferObject>& src, SizeT srcOffset, SizeT dstOffset, SizeT size); // backends at draw time (persistent maps mutate the shadow without API calls).
void ClearDirty(); void SyncPersistentMappedRange();
// Shadow-only write used when the backend copies GPU results (e.g. ReadPixels
// into a pixel-pack buffer) back into the frontend mirror. Does not issue a
// backend op: the backend storage already holds these bytes.
void WritebackFromBackend(DataPtr data, SizeT atOffset);
Bool IsMapped() const; Bool IsMapped() const;
Bool IsImmutableStorage() const; Bool IsImmutableStorage() const;
@@ -103,11 +142,18 @@ namespace MobileGL {
Flags<BufferMappingAccessBit> GetMappingAccess() const; Flags<BufferMappingAccessBit> GetMappingAccess() const;
GLbitfield GetStorageFlags() const; GLbitfield GetStorageFlags() const;
Uint GetExternalIndex() const; Uint GetExternalIndex() const;
const VecRange1D& GetDirtyRanges() const; // Monotonic counter bumped on every shadow mutation; backends use it to
Flags<BufferChangeBits> GetChangeBits() const; // validate cached transient slices.
Uint64 GetChangeSerial() const; Uint64 GetChangeSerial() const;
const SharedPtr<BackendBufferResource>& GetBackendResource() const;
void SetBackendResource(SharedPtr<BackendBufferResource> resource);
private: private:
void NotifyRespecify();
void NotifySubData(SizeT offset, SizeT size);
void NotifyFlushMappedRange(Range1D range, Flags<BufferMappingAccessBit> appAccess);
const Uint m_externalIndex = 0; const Uint m_externalIndex = 0;
SizeT m_size = 0; SizeT m_size = 0;
BufferUsage m_usage = BufferUsage::StaticDraw; BufferUsage m_usage = BufferUsage::StaticDraw;
@@ -116,11 +162,11 @@ namespace MobileGL {
Flags<BufferMappingAccessBit> m_mappingAccess; Flags<BufferMappingAccessBit> m_mappingAccess;
Bool m_isImmutableStorage = false; Bool m_isImmutableStorage = false;
GLbitfield m_storageFlags = 0; GLbitfield m_storageFlags = 0;
BufferChange m_change;
Uint64 m_changeSerial = 0; Uint64 m_changeSerial = 0;
Range1D m_mappedRange; Range1D m_mappedRange;
Vector<Uint8> m_stagingData; Vector<Uint8> m_stagingData;
Bool m_ownsStagingData; Bool m_ownsStagingData;
SharedPtr<BackendBufferResource> m_backendResource;
}; };
} // namespace MG_State::GLState } // namespace MG_State::GLState
} // namespace MobileGL } // namespace MobileGL
+32 -60
View File
@@ -74,10 +74,8 @@ TEST_F(BufferTest, PingPong) {
Vector<Int> bufdata(data.size()); Vector<Int> bufdata(data.size());
memcpy(bufdata.data(), p, byteSize); memcpy(bufdata.data(), p, byteSize);
ASSERT_EQ(data, bufdata); ASSERT_EQ(data, bufdata);
ASSERT_EQ(bufRead->GetDirtyRanges().size() >= 1, true); // Writes bump the change serial so backends can invalidate cached slices.
auto range = bufRead->GetDirtyRanges()[0]; ASSERT_GT(bufRead->GetChangeSerial(), 0u);
ASSERT_EQ(range.start, 0);
ASSERT_EQ(range.end, byteSize);
} }
TEST_F(BufferTest, GenerateManyNames_NoPrematureCreation) { TEST_F(BufferTest, GenerateManyNames_NoPrematureCreation) {
@@ -117,7 +115,7 @@ TEST_F(BufferTest, AcquireMemory) {
bufObj->Resize(byteSize); bufObj->Resize(byteSize);
DataPtr ptr{.data = initData.data(), .size = byteSize}; DataPtr ptr{.data = initData.data(), .size = byteSize};
bufObj->UploadData(ptr, 0); bufObj->UploadData(ptr, 0);
bufObj->ClearDirty(); const Uint64 baseSerial = bufObj->GetChangeSerial();
Int* mappedPtr = static_cast<Int*>(bufObj->AcquireMemory(true, true, true)); Int* mappedPtr = static_cast<Int*>(bufObj->AcquireMemory(true, true, true));
mappedPtr[0] = 100; mappedPtr[0] = 100;
mappedPtr[1] = 200; mappedPtr[1] = 200;
@@ -128,11 +126,8 @@ TEST_F(BufferTest, AcquireMemory) {
void* p = bufObj->AcquireMemory(false, true, false); void* p = bufObj->AcquireMemory(false, true, false);
memcpy(actual.data(), p, byteSize); memcpy(actual.data(), p, byteSize);
ASSERT_EQ(actual, expected); ASSERT_EQ(actual, expected);
ASSERT_EQ(bufObj->GetDirtyRanges().size() >= 1, true); // Unmapping a write map flushes the mapped range and bumps the serial.
auto dirty = bufObj->GetDirtyRanges()[0]; ASSERT_GT(bufObj->GetChangeSerial(), baseSerial);
ASSERT_EQ(dirty.start, 0);
ASSERT_EQ(dirty.end, sizeof(Int) * 5);
} }
TEST_F(BufferTest, AcquireMemoryRangeWithoutExplicit) { TEST_F(BufferTest, AcquireMemoryRangeWithoutExplicit) {
@@ -146,7 +141,7 @@ TEST_F(BufferTest, AcquireMemoryRangeWithoutExplicit) {
bufObj->Resize(byteSize); bufObj->Resize(byteSize);
DataPtr ptr{.data = initData.data(), .size = byteSize}; DataPtr ptr{.data = initData.data(), .size = byteSize};
bufObj->UploadData(ptr, 0); bufObj->UploadData(ptr, 0);
bufObj->ClearDirty(); const Uint64 baseSerial = bufObj->GetChangeSerial();
Range1D mapRange{.start = sizeof(Int), .end = sizeof(Int) * 4}; Range1D mapRange{.start = sizeof(Int), .end = sizeof(Int) * 4};
Int* mappedPtr = static_cast<Int*>(bufObj->AcquireMemoryRange(mapRange, BufferMappingAccessBit::Write)); Int* mappedPtr = static_cast<Int*>(bufObj->AcquireMemoryRange(mapRange, BufferMappingAccessBit::Write));
@@ -158,10 +153,7 @@ TEST_F(BufferTest, AcquireMemoryRangeWithoutExplicit) {
void* p = bufObj->AcquireMemory(false, true, false); void* p = bufObj->AcquireMemory(false, true, false);
memcpy(actual.data(), p, byteSize); memcpy(actual.data(), p, byteSize);
ASSERT_EQ(actual, expected); ASSERT_EQ(actual, expected);
ASSERT_EQ(bufObj->GetDirtyRanges().size() >= 1, true); ASSERT_GT(bufObj->GetChangeSerial(), baseSerial);
auto dirty = bufObj->GetDirtyRanges()[0];
ASSERT_EQ(dirty.start, sizeof(Int));
ASSERT_EQ(dirty.end, sizeof(Int) * 4);
} }
TEST_F(BufferTest, AcquireMemoryRangeWithExplicit) { TEST_F(BufferTest, AcquireMemoryRangeWithExplicit) {
@@ -177,7 +169,7 @@ TEST_F(BufferTest, AcquireMemoryRangeWithExplicit) {
DataPtr ptr{.data = initData.data(), .size = byteSize}; DataPtr ptr{.data = initData.data(), .size = byteSize};
bufObj->UploadData(ptr, 0); bufObj->UploadData(ptr, 0);
bufObj->ClearDirty(); const Uint64 baseSerial = bufObj->GetChangeSerial();
Range1D mapRange{.start = sizeof(Int), .end = sizeof(Int) * 4}; Range1D mapRange{.start = sizeof(Int), .end = sizeof(Int) * 4};
Int* mappedPtr = static_cast<Int*>( Int* mappedPtr = static_cast<Int*>(
@@ -186,29 +178,20 @@ TEST_F(BufferTest, AcquireMemoryRangeWithExplicit) {
mappedPtr[0] = 200; mappedPtr[0] = 200;
mappedPtr[1] = 300; mappedPtr[1] = 300;
// Only the explicitly flushed range reaches the shadow (and the backend).
bufObj->FlushMemoryRange(0, sizeof(Int)); bufObj->FlushMemoryRange(0, sizeof(Int));
ASSERT_EQ(bufObj->GetDirtyRanges().size() >= 1, true); const Uint64 flushedSerial = bufObj->GetChangeSerial();
auto dirty = bufObj->GetDirtyRanges()[0]; ASSERT_GT(flushedSerial, baseSerial);
ASSERT_EQ(dirty.start, sizeof(Int));
ASSERT_EQ(dirty.end, sizeof(Int) * 2);
// FlushExplicit unmap must not flush the rest of the mapped range.
bufObj->ReleaseMemory(); bufObj->ReleaseMemory();
ASSERT_EQ(bufObj->GetChangeSerial(), flushedSerial);
ASSERT_EQ(bufObj->GetDirtyRanges().size() >= 1, true);
dirty = bufObj->GetDirtyRanges()[0];
ASSERT_EQ(dirty.start, sizeof(Int));
ASSERT_EQ(dirty.end, sizeof(Int) * 2);
Vector<Int> expected{10, 200, 30, 40, 50}; Vector<Int> expected{10, 200, 30, 40, 50};
Vector<Int> actual(5); Vector<Int> actual(5);
void* p = bufObj->AcquireMemory(false, true, false); void* p = bufObj->AcquireMemory(false, true, false);
memcpy(actual.data(), p, byteSize); memcpy(actual.data(), p, byteSize);
ASSERT_EQ(actual, expected); ASSERT_EQ(actual, expected);
ASSERT_EQ(bufObj->GetDirtyRanges().size() >= 1, true);
dirty = bufObj->GetDirtyRanges()[0];
ASSERT_EQ(dirty.start, sizeof(Int));
ASSERT_EQ(dirty.end, sizeof(Int) * 2);
} }
TEST_F(BufferTest, CopyBufferSubData) { TEST_F(BufferTest, CopyBufferSubData) {
@@ -237,8 +220,8 @@ TEST_F(BufferTest, CopyBufferSubData) {
DataPtr dstPtr{.data = dstData.data(), .size = dstSize}; DataPtr dstPtr{.data = dstData.data(), .size = dstSize};
dstObj->UploadData(dstPtr, 0); dstObj->UploadData(dstPtr, 0);
srcObj->ClearDirty(); const Uint64 srcSerial = srcObj->GetChangeSerial();
dstObj->ClearDirty(); const Uint64 dstSerial = dstObj->GetChangeSerial();
dstObj->CopyDataFrom(srcObj, 2 * sizeof(Int), 5 * sizeof(Int), 4 * sizeof(Int)); dstObj->CopyDataFrom(srcObj, 2 * sizeof(Int), 5 * sizeof(Int), 4 * sizeof(Int));
@@ -250,10 +233,9 @@ TEST_F(BufferTest, CopyBufferSubData) {
ASSERT_EQ(actual, expected); ASSERT_EQ(actual, expected);
ASSERT_EQ(dstObj->GetDirtyRanges().size() >= 1, true); // The copy mutates only the destination.
auto dirty = dstObj->GetDirtyRanges()[0]; ASSERT_GT(dstObj->GetChangeSerial(), dstSerial);
ASSERT_EQ(dirty.start, 5 * sizeof(Int)); ASSERT_EQ(srcObj->GetChangeSerial(), srcSerial);
ASSERT_EQ(dirty.end, 9 * sizeof(Int));
} }
TEST_F(BufferTest, WriteWhileMapped) { TEST_F(BufferTest, WriteWhileMapped) {
@@ -282,10 +264,7 @@ TEST_F(BufferTest, WriteWhileMapped) {
ASSERT_EQ(actual, expected); ASSERT_EQ(actual, expected);
ASSERT_EQ(bufObj->GetDirtyRanges().size() >= 1, true); ASSERT_GT(bufObj->GetChangeSerial(), 0u);
auto dirty = bufObj->GetDirtyRanges()[0];
ASSERT_EQ(dirty.start, 0);
ASSERT_EQ(dirty.end, byteSize);
} }
TEST_F(BufferTest, PartialUpdate) { TEST_F(BufferTest, PartialUpdate) {
@@ -300,7 +279,7 @@ TEST_F(BufferTest, PartialUpdate) {
bufObj->Resize(byteSize); bufObj->Resize(byteSize);
DataPtr ptr{.data = initData.data(), .size = byteSize}; DataPtr ptr{.data = initData.data(), .size = byteSize};
bufObj->UploadData(ptr, 0); bufObj->UploadData(ptr, 0);
bufObj->ClearDirty(); const Uint64 baseSerial = bufObj->GetChangeSerial();
Vector<Int> update{999, 888}; Vector<Int> update{999, 888};
bufObj->UploadSubData({(void*)(update.data()), (SizeT)(update.size() * sizeof(Int))}, sizeof(Int)); bufObj->UploadSubData({(void*)(update.data()), (SizeT)(update.size() * sizeof(Int))}, sizeof(Int));
@@ -312,10 +291,7 @@ TEST_F(BufferTest, PartialUpdate) {
ASSERT_EQ(actual, expected); ASSERT_EQ(actual, expected);
ASSERT_EQ(bufObj->GetDirtyRanges().size() >= 1, true); ASSERT_GT(bufObj->GetChangeSerial(), baseSerial);
auto dirty = bufObj->GetDirtyRanges()[0];
ASSERT_EQ(dirty.start, sizeof(Int));
ASSERT_EQ(dirty.end, 3 * sizeof(Int));
} }
TEST_F(BufferTest, DeleteBufferObject) { TEST_F(BufferTest, DeleteBufferObject) {
@@ -662,7 +638,7 @@ TEST_F(GeneralBufferTest, General_PersistentCoherentWriteDirtyWithoutUnmap) {
auto bufferObject = MG_State::pGLContext->GetBufferObject(buffer); auto bufferObject = MG_State::pGLContext->GetBufferObject(buffer);
ASSERT_NE(bufferObject, nullptr); ASSERT_NE(bufferObject, nullptr);
bufferObject->ClearDirty(); const Uint64 baseSerial = bufferObject->GetChangeSerial();
auto* mapped = static_cast<GLint*>( auto* mapped = static_cast<GLint*>(
MapBufferRange(GL_ARRAY_BUFFER, 0, sizeof(initial), MapBufferRange(GL_ARRAY_BUFFER, 0, sizeof(initial),
@@ -670,10 +646,9 @@ TEST_F(GeneralBufferTest, General_PersistentCoherentWriteDirtyWithoutUnmap) {
ASSERT_NE(mapped, nullptr); ASSERT_NE(mapped, nullptr);
mapped[2] = 1234; mapped[2] = 1234;
bufferObject->MarkPersistentMappedRangeDirty(); // Draw-time hook: pushes the persistently mapped write range to the backend.
ASSERT_FALSE(bufferObject->GetDirtyRanges().empty()); bufferObject->SyncPersistentMappedRange();
EXPECT_EQ(bufferObject->GetDirtyRanges()[0].start, 0); EXPECT_GT(bufferObject->GetChangeSerial(), baseSerial);
EXPECT_EQ(bufferObject->GetDirtyRanges()[0].end, sizeof(initial));
const auto data = bufferObject->GetDataReadOnly(); const auto data = bufferObject->GetDataReadOnly();
EXPECT_EQ(reinterpret_cast<const GLint*>(data->data())[2], 1234); EXPECT_EQ(reinterpret_cast<const GLint*>(data->data())[2], 1234);
@@ -692,7 +667,7 @@ TEST_F(GeneralBufferTest, General_PersistentExplicitFlushOnlyDirtiesFlushedRange
auto bufferObject = MG_State::pGLContext->GetBufferObject(buffer); auto bufferObject = MG_State::pGLContext->GetBufferObject(buffer);
ASSERT_NE(bufferObject, nullptr); ASSERT_NE(bufferObject, nullptr);
bufferObject->ClearDirty(); const Uint64 baseSerial = bufferObject->GetChangeSerial();
auto* mapped = static_cast<GLint*>( auto* mapped = static_cast<GLint*>(
MapBufferRange(GL_ARRAY_BUFFER, 0, sizeof(initial), MapBufferRange(GL_ARRAY_BUFFER, 0, sizeof(initial),
@@ -701,13 +676,12 @@ TEST_F(GeneralBufferTest, General_PersistentExplicitFlushOnlyDirtiesFlushedRange
mapped[1] = 200; mapped[1] = 200;
mapped[3] = 400; mapped[3] = 400;
bufferObject->MarkPersistentMappedRangeDirty(); // FlushExplicit persistent maps only reach the backend via explicit flushes.
EXPECT_TRUE(bufferObject->GetDirtyRanges().empty()); bufferObject->SyncPersistentMappedRange();
EXPECT_EQ(bufferObject->GetChangeSerial(), baseSerial);
FlushMappedBufferRange(GL_ARRAY_BUFFER, sizeof(GLint), sizeof(GLint)); FlushMappedBufferRange(GL_ARRAY_BUFFER, sizeof(GLint), sizeof(GLint));
ASSERT_FALSE(bufferObject->GetDirtyRanges().empty()); EXPECT_GT(bufferObject->GetChangeSerial(), baseSerial);
EXPECT_EQ(bufferObject->GetDirtyRanges()[0].start, sizeof(GLint));
EXPECT_EQ(bufferObject->GetDirtyRanges()[0].end, sizeof(GLint) * 2);
EXPECT_TRUE(UnmapBuffer(GL_ARRAY_BUFFER)); EXPECT_TRUE(UnmapBuffer(GL_ARRAY_BUFFER));
EXPECT_EQ(GetError(), GL_NO_ERROR); EXPECT_EQ(GetError(), GL_NO_ERROR);
@@ -728,7 +702,7 @@ TEST_F(GeneralBufferTest, General_NamedBufferStorageMappingWrappers) {
auto bufferObject = MG_State::pGLContext->GetBufferObject(buffer); auto bufferObject = MG_State::pGLContext->GetBufferObject(buffer);
ASSERT_NE(bufferObject, nullptr); ASSERT_NE(bufferObject, nullptr);
bufferObject->ClearDirty(); const Uint64 baseSerial = bufferObject->GetChangeSerial();
auto* mapped = static_cast<GLint*>( auto* mapped = static_cast<GLint*>(
MapNamedBufferRange(buffer, 0, sizeof(initial), MapNamedBufferRange(buffer, 0, sizeof(initial),
@@ -741,9 +715,7 @@ TEST_F(GeneralBufferTest, General_NamedBufferStorageMappingWrappers) {
EXPECT_EQ(mapPointer, mapped); EXPECT_EQ(mapPointer, mapped);
FlushMappedNamedBufferRange(buffer, 0, sizeof(GLint)); FlushMappedNamedBufferRange(buffer, 0, sizeof(GLint));
ASSERT_FALSE(bufferObject->GetDirtyRanges().empty()); EXPECT_GT(bufferObject->GetChangeSerial(), baseSerial);
EXPECT_EQ(bufferObject->GetDirtyRanges()[0].start, 0);
EXPECT_EQ(bufferObject->GetDirtyRanges()[0].end, sizeof(GLint));
EXPECT_TRUE(UnmapNamedBuffer(buffer)); EXPECT_TRUE(UnmapNamedBuffer(buffer));
EXPECT_EQ(GetError(), GL_NO_ERROR); EXPECT_EQ(GetError(), GL_NO_ERROR);