[Perf] (MG_Backend): upload only the rects DirectGLES actually dirtied

Consume MipmapStorage's new dirty-rect list: when a level offers a
profitable rect list, the sync path issues one glTexSubImage2D/3D per
rect under a single UNPACK_ROW_LENGTH set/reset instead of one call
covering the union box. Striding is the exact scheme the single-box
path already uses (UNPACK_ALIGNMENT pinned to 1 by
ScopedDefaultUnpackState, so every bpp is stride-exact); levels
without a profitable list take the old path unchanged.

On the atlas-streaming case this trades one ~2MB upload for ~95 small
ones totalling ~95KB - roughly a wash in driver-call overhead on
desktop NVIDIA GL (mc_tex_stream ~-3%), a clear byte-volume win for
tiled/mobile GLES where the driver shadow-copies every upload. Unit
tests 421/421.
This commit is contained in:
BZLZHH
2026-08-06 20:17:19 -04:00
parent 7db5b35a3e
commit 6a02c5fea0
+52 -2
View File
@@ -2440,10 +2440,45 @@ namespace MobileGL::MG_Backend::DirectGLES {
static_cast<SizeT>(dirtyRegion.lo.z()) * levelSliceBytes +
static_cast<SizeT>(dirtyRegion.lo.y()) * levelRowBytes +
static_cast<SizeT>(dirtyRegion.lo.x()) * bpp;
// Scatter refinement behind the union box: ~100 sprite
// writes into an atlas leave a box that spans nearly the
// whole level while the touched texels are a few percent of
// it. The storage's bounded rect list recovers the true
// footprint; each rect is uploaded with the same
// ROW_LENGTH striding into the level shadow as the box
// path. The storage only hands the list out when its
// summed area is materially smaller than the box (0
// otherwise), so the extra calls always move fewer bytes.
MG_State::GLState::MipmapDirtyRegion
dirtyRects[MG_State::GLState::MipmapStorage::kMaxDirtyRects];
SizeT dirtyRectCount = 0;
if (subRectEligible) {
dirtyRectCount = textureMipmapObject->GetStorageDirtyRects(
uploadTarget, level, dirtyRects,
MG_State::GLState::MipmapStorage::kMaxDirtyRects);
}
const auto rectShadowPtr = [&](const MG_State::GLState::MipmapDirtyRegion& rect) {
return static_cast<const Uint8*>(uploadData) +
static_cast<SizeT>(rect.lo.z()) * levelSliceBytes +
static_cast<SizeT>(rect.lo.y()) * levelRowBytes +
static_cast<SizeT>(rect.lo.x()) * bpp;
};
switch (MapToBackendTextureTarget(stateTextureObject->GetTarget())) {
case TextureTarget::Texture2D:
case TextureTarget::TextureCubeMap:
if (subRectEligible) {
if (subRectEligible && dirtyRectCount >= 2) {
g_GLESFuncs.glPixelStorei(GL_UNPACK_ROW_LENGTH, texelSize.x());
for (SizeT r = 0; r < dirtyRectCount; ++r) {
const auto& rect = dirtyRects[r];
g_GLESFuncs.glTexSubImage2D(
glUploadTarget, static_cast<GLint>(level), rect.lo.x(),
rect.lo.y(), static_cast<GLsizei>(rect.hi.x() - rect.lo.x()),
static_cast<GLsizei>(rect.hi.y() - rect.lo.y()), glFormat,
glType, rectShadowPtr(rect));
}
// The surrounding ScopedDefaultUnpackState shadow says 0.
g_GLESFuncs.glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
} else if (subRectEligible) {
g_GLESFuncs.glPixelStorei(GL_UNPACK_ROW_LENGTH, texelSize.x());
g_GLESFuncs.glTexSubImage2D(
glUploadTarget, static_cast<GLint>(level), dirtyRegion.lo.x(),
@@ -2463,7 +2498,22 @@ namespace MobileGL::MG_Backend::DirectGLES {
// ES 3.2 has GL_TEXTURE_CUBE_MAP_ARRAY natively and it stores exactly
// like a 2D array whose depth is 6 * the cube count.
case TextureTarget::TextureCubeMapArray:
if (subRectEligible) {
if (subRectEligible && dirtyRectCount >= 2) {
g_GLESFuncs.glPixelStorei(GL_UNPACK_ROW_LENGTH, texelSize.x());
g_GLESFuncs.glPixelStorei(GL_UNPACK_IMAGE_HEIGHT, texelSize.y());
for (SizeT r = 0; r < dirtyRectCount; ++r) {
const auto& rect = dirtyRects[r];
g_GLESFuncs.glTexSubImage3D(
glUploadTarget, static_cast<GLint>(level), rect.lo.x(),
rect.lo.y(), rect.lo.z(),
static_cast<GLsizei>(rect.hi.x() - rect.lo.x()),
static_cast<GLsizei>(rect.hi.y() - rect.lo.y()),
static_cast<GLsizei>(rect.hi.z() - rect.lo.z()), glFormat,
glType, rectShadowPtr(rect));
}
g_GLESFuncs.glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
g_GLESFuncs.glPixelStorei(GL_UNPACK_IMAGE_HEIGHT, 0);
} else if (subRectEligible) {
g_GLESFuncs.glPixelStorei(GL_UNPACK_ROW_LENGTH, texelSize.x());
g_GLESFuncs.glPixelStorei(GL_UNPACK_IMAGE_HEIGHT, texelSize.y());
g_GLESFuncs.glTexSubImage3D(