mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-09-07 19:58:32 +09:00
[Fix] (DirectGLES): flush queued buffer ranges through a range-invalidating map first, so a partial write into a busy store is priced by the range
This commit is contained in:
@@ -176,6 +176,14 @@ namespace MobileGL::MG_Config {
|
|||||||
// upload ring (negative control / driver-bug escape hatch; the immediate
|
// upload ring (negative control / driver-bug escape hatch; the immediate
|
||||||
// upload stalls on drivers that resolve the WAR hazard on the CPU, e.g. Mali).
|
// upload stalls on drivers that resolve the WAR hazard on the CPU, e.g. Mali).
|
||||||
Bool DisableUploadRing = false;
|
Bool DisableUploadRing = false;
|
||||||
|
// MOBILEGL_DISABLE_INVALIDATE_FLUSH: skip the glMapBufferRange(WRITE |
|
||||||
|
// INVALIDATE_RANGE) tier of the DirectGLES pending-range flush and go straight
|
||||||
|
// to the upload ring's staged glCopyBufferSubData (negative control / escape
|
||||||
|
// hatch for a driver whose range-invalidating map misbehaves). The map tier is
|
||||||
|
// what keeps a partial write into a large in-flight buffer priced by the RANGE:
|
||||||
|
// on Mali both the immediate glBufferSubData and a staged copy into a busy
|
||||||
|
// mutable store ghost the whole destination on the CPU.
|
||||||
|
Bool DisableInvalidateFlush = false;
|
||||||
// MOBILEGL_ESPRYT_FORCE_DS_READBACK_EMULATION: make DirectGLES skip the native ES
|
// MOBILEGL_ESPRYT_FORCE_DS_READBACK_EMULATION: make DirectGLES skip the native ES
|
||||||
// depth/stencil reads and always go through the shader-sampling emulation. Core GL
|
// depth/stencil reads and always go through the shader-sampling emulation. Core GL
|
||||||
// ES has no depth or stencil readback, but some drivers accept it anyway (Mesa does,
|
// ES has no depth or stencil readback, but some drivers accept it anyway (Mesa does,
|
||||||
|
|||||||
@@ -186,6 +186,7 @@ namespace MobileGL::MG_ConfigLoader {
|
|||||||
features.DisableUboRing = QueryEnvFlag("MOBILEGL_DISABLE_UBO_RING");
|
features.DisableUboRing = QueryEnvFlag("MOBILEGL_DISABLE_UBO_RING");
|
||||||
features.DisableUnpackRing = QueryEnvFlag("MOBILEGL_DISABLE_UNPACK_RING");
|
features.DisableUnpackRing = QueryEnvFlag("MOBILEGL_DISABLE_UNPACK_RING");
|
||||||
features.DisableUploadRing = QueryEnvFlag("MOBILEGL_DISABLE_UPLOAD_RING");
|
features.DisableUploadRing = QueryEnvFlag("MOBILEGL_DISABLE_UPLOAD_RING");
|
||||||
|
features.DisableInvalidateFlush = QueryEnvFlag("MOBILEGL_DISABLE_INVALIDATE_FLUSH");
|
||||||
features.EsprytForceDepthStencilReadbackEmulation =
|
features.EsprytForceDepthStencilReadbackEmulation =
|
||||||
QueryEnvFlag("MOBILEGL_ESPRYT_FORCE_DS_READBACK_EMULATION");
|
QueryEnvFlag("MOBILEGL_ESPRYT_FORCE_DS_READBACK_EMULATION");
|
||||||
features.RelaxedSemantics = QueryEnvFlag("MOBILEGL_RELAXED_SEMANTICS");
|
features.RelaxedSemantics = QueryEnvFlag("MOBILEGL_RELAXED_SEMANTICS");
|
||||||
|
|||||||
@@ -829,10 +829,32 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Push every queued range of `resource` from the shadow into the backend
|
// Push every queued range of `resource` from the shadow into the backend
|
||||||
// store. Ranges go through the staging ring + glCopyBufferSubData when the
|
// store, without ever letting a driver resolve the WAR hazard against
|
||||||
// ring is usable - the copy resolves the WAR hazard against in-flight
|
// in-flight frames at the WHOLE BUFFER's expense. Three tiers:
|
||||||
// frames on the GPU timeline, where it is mere job ordering - and fall
|
//
|
||||||
// back to the direct (potentially stalling) glBufferSubData otherwise.
|
// 1. glMapBufferRange(WRITE | INVALIDATE_RANGE) + memcpy. The entire
|
||||||
|
// mapped range is rewritten from the authoritative shadow, so
|
||||||
|
// declaring its old bytes dead is exact - and it lets the driver
|
||||||
|
// swap fresh pages in for JUST that range. This is the only tier
|
||||||
|
// whose cost scales with the RANGE on this Mali driver: both the
|
||||||
|
// immediate glBufferSubData (pre-queueing) and a staged
|
||||||
|
// glCopyBufferSubData into a busy MUTABLE store ghost the whole
|
||||||
|
// destination with a worker-thread memcpy - Minecraft 26.3 streams
|
||||||
|
// ~1MB section meshes into 128MB arenas about nine times a frame
|
||||||
|
// during a camera pan, and 9 x 128MB of ghosting per frame is
|
||||||
|
// ~380ms, the measured 2-4 fps. (Backing the arenas with immutable
|
||||||
|
// stores also kills the ghost, but eagerly commits every arena's
|
||||||
|
// full extent - +hundreds of MB - which LMK'd the whole device.)
|
||||||
|
// 2. The staging ring + glCopyBufferSubData: the copy is ordered on
|
||||||
|
// the GPU timeline, no CPU wait (MOBILEGL_DISABLE_INVALIDATE_FLUSH
|
||||||
|
// forces this tier as the map path's negative control).
|
||||||
|
// 3. Direct glBufferSubData (potentially stalling) when neither the
|
||||||
|
// map entry points nor the ring exist.
|
||||||
|
//
|
||||||
|
// The ranges are flushed AS QUEUED (VecRange1D::Add already merges
|
||||||
|
// near-adjacent ones): bytes, not flush calls, are the cost axis here,
|
||||||
|
// and collapsing a scattered flush into its union re-copied nearly whole
|
||||||
|
// chunk-mesh arenas every frame.
|
||||||
// The caller owns syncedChangeSerial; this only drains the queue.
|
// The caller owns syncedChangeSerial; this only drains the queue.
|
||||||
void FlushPendingRangesNow(GLESBufferResource& resource, BufferObject& bufferObject) {
|
void FlushPendingRangesNow(GLESBufferResource& resource, BufferObject& bufferObject) {
|
||||||
#ifdef TRACY_ENABLE
|
#ifdef TRACY_ENABLE
|
||||||
@@ -848,19 +870,26 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
|||||||
// Clamp against BOTH extents: the readback flush may run while the
|
// Clamp against BOTH extents: the readback flush may run while the
|
||||||
// frontend size and the backend store disagree (a pending respecify
|
// frontend size and the backend store disagree (a pending respecify
|
||||||
// resolves that later; bytes past either end have nowhere to land).
|
// resolves that later; bytes past either end have nowhere to land).
|
||||||
//
|
|
||||||
// The ranges are copied AS QUEUED (VecRange1D::Add already merges
|
|
||||||
// near-adjacent ones): this driver runs buffer copies as worker-thread
|
|
||||||
// memcpys, so BYTES are the cost axis - collapsing a scattered flush
|
|
||||||
// into its union re-copied nearly whole chunk-mesh arenas every frame
|
|
||||||
// and saturated the copy worker during camera pans.
|
|
||||||
const SizeT limit = std::min(bufferObject.GetSize(), resource.storageSize);
|
const SizeT limit = std::min(bufferObject.GetSize(), resource.storageSize);
|
||||||
|
const Bool mapUsable = !MG_Config::Features.DisableInvalidateFlush &&
|
||||||
|
g_GLESFuncs.glMapBufferRange && g_GLESFuncs.glUnmapBuffer;
|
||||||
const Bool ringUsable = UploadRingUsableNow();
|
const Bool ringUsable = UploadRingUsableNow();
|
||||||
for (const auto& range : ranges) {
|
for (const auto& range : ranges) {
|
||||||
const SizeT end = std::min(range.end, limit);
|
const SizeT end = std::min(range.end, limit);
|
||||||
const SizeT start = std::min(range.start, end);
|
const SizeT start = std::min(range.start, end);
|
||||||
const SizeT size = end - start;
|
const SizeT size = end - start;
|
||||||
if (size == 0) continue;
|
if (size == 0) continue;
|
||||||
|
if (mapUsable) {
|
||||||
|
BindBufferId(TempBufferTarget, resource.id);
|
||||||
|
void* dst = g_GLESFuncs.glMapBufferRange(TempBufferTarget, (GLintptr)start,
|
||||||
|
(GLsizeiptr)size,
|
||||||
|
GL_MAP_WRITE_BIT | GL_MAP_INVALIDATE_RANGE_BIT);
|
||||||
|
if (dst) {
|
||||||
|
Memcpy(dst, bufferObject.MappedData() + start, size);
|
||||||
|
g_GLESFuncs.glUnmapBuffer(TempBufferTarget);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
SizeT ringOffset = 0;
|
SizeT ringOffset = 0;
|
||||||
if (ringUsable && size <= kUploadRingMaxBytes &&
|
if (ringUsable && size <= kUploadRingMaxBytes &&
|
||||||
RingAllocate(g_uploadRing, size, ringOffset)) {
|
RingAllocate(g_uploadRing, size, ringOffset)) {
|
||||||
|
|||||||
Reference in New Issue
Block a user