mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-09-11 21:58:31 +09:00
[Perf] (MG_Backend): stage only the rects DirectVulkan actually dirtied
Consume MipmapStorage's new dirty-rect list: pack each rect tightly into the staging block and issue ONE vkCmdCopyBufferToImage with N regions instead of staging the whole union box. Offsets are computed identically in the pack and copy loops; disjoint rects mean no overlapping copy destinations; the combined depth-stencil and RGB-expand/depth-convert paths keep their single-box route (gated to the color-aspect, no-conversion case). 54% of mc_tex_stream's steady-state CPU was the one shadow->staging memmove of the union box; staged bytes drop to 4.8% (~2MB -> ~95KB per frame) and the case improves ~-49% (5945 -> 3048 ns/op, ~2.2x native to ~1.2x). Zero validation-layer findings on the 95-region copy. Unit tests 421/421.
This commit is contained in:
@@ -2305,6 +2305,12 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
IntVec3 regionLo = {0, 0, 0};
|
IntVec3 regionLo = {0, 0, 0};
|
||||||
IntVec3 regionSize = {0, 0, 0};
|
IntVec3 regionSize = {0, 0, 0};
|
||||||
SizeT texelBytes = 0;
|
SizeT texelBytes = 0;
|
||||||
|
// Scatter refinement of the single dirty box: when the storage's rect
|
||||||
|
// list reports the writes' true footprint (~100 sprites whose union box
|
||||||
|
// spans the whole atlas), each rect is staged tightly and copied with
|
||||||
|
// its own VkBufferImageCopy in ONE vkCmdCopyBufferToImage. Empty means
|
||||||
|
// "stage the one box above". Only set while subRegion.
|
||||||
|
Vector<MG_State::GLState::MipmapDirtyRegion> rects;
|
||||||
};
|
};
|
||||||
|
|
||||||
Vector<UploadItem> uploadItems;
|
Vector<UploadItem> uploadItems;
|
||||||
@@ -2368,6 +2374,23 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
static_cast<SizeT>(uploadItem.regionSize.y()) *
|
static_cast<SizeT>(uploadItem.regionSize.y()) *
|
||||||
static_cast<SizeT>(uploadItem.regionSize.z()) *
|
static_cast<SizeT>(uploadItem.regionSize.z()) *
|
||||||
uploadItem.texelBytes;
|
uploadItem.texelBytes;
|
||||||
|
// Scatter refinement: the storage only hands out its rect list
|
||||||
|
// when the rects' summed area is materially smaller than the
|
||||||
|
// union box (0 otherwise), so taking it always stages fewer
|
||||||
|
// bytes than the box - the very amplification this path exists
|
||||||
|
// to avoid paying twice.
|
||||||
|
MG_State::GLState::MipmapDirtyRegion
|
||||||
|
dirtyRects[MG_State::GLState::MipmapStorage::kMaxDirtyRects];
|
||||||
|
const SizeT dirtyRectCount = mipmapTexture.GetStorageDirtyRects(
|
||||||
|
target, level, dirtyRects, MG_State::GLState::MipmapStorage::kMaxDirtyRects);
|
||||||
|
if (dirtyRectCount >= 2) {
|
||||||
|
uploadItem.rects.assign(dirtyRects, dirtyRects + dirtyRectCount);
|
||||||
|
SizeT rectTexels = 0;
|
||||||
|
for (const auto& rect : uploadItem.rects) {
|
||||||
|
rectTexels += rect.TexelCount();
|
||||||
|
}
|
||||||
|
uploadItem.uploadByteSize = rectTexels * uploadItem.texelBytes;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (formatInfo.expandRgbToRgba) {
|
if (formatInfo.expandRgbToRgba) {
|
||||||
@@ -2534,22 +2557,37 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
std::memcpy(dst, item.source, item.uploadByteSize);
|
std::memcpy(dst, item.source, item.uploadByteSize);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
// Tight-pack the dirty box: the shadow keeps whole-level rows, the
|
// Tight-pack the dirty box(es): the shadow keeps whole-level rows, the
|
||||||
// staging slice holds only the region (bufferRowLength stays 0).
|
// staging slice holds only the region (bufferRowLength stays 0). Multi-
|
||||||
|
// rect items pack their rects back to back in list order; the copy loop
|
||||||
|
// below recomputes the same running offsets.
|
||||||
const SizeT levelRowBytes = static_cast<SizeT>(item.texelSize.x()) * item.texelBytes;
|
const SizeT levelRowBytes = static_cast<SizeT>(item.texelSize.x()) * item.texelBytes;
|
||||||
const SizeT levelSliceBytes = static_cast<SizeT>(item.texelSize.y()) * levelRowBytes;
|
const SizeT levelSliceBytes = static_cast<SizeT>(item.texelSize.y()) * levelRowBytes;
|
||||||
const SizeT regionRowBytes = static_cast<SizeT>(item.regionSize.x()) * item.texelBytes;
|
|
||||||
const Uint8* src = static_cast<const Uint8*>(item.source);
|
const Uint8* src = static_cast<const Uint8*>(item.source);
|
||||||
for (Int z = 0; z < item.regionSize.z(); ++z) {
|
const auto packBox = [&](Uint8* out, const IntVec3& lo, const IntVec3& boxSize) {
|
||||||
for (Int y = 0; y < item.regionSize.y(); ++y) {
|
const SizeT boxRowBytes = static_cast<SizeT>(boxSize.x()) * item.texelBytes;
|
||||||
const Uint8* srcRow = src +
|
for (Int z = 0; z < boxSize.z(); ++z) {
|
||||||
static_cast<SizeT>(item.regionLo.z() + z) * levelSliceBytes +
|
for (Int y = 0; y < boxSize.y(); ++y) {
|
||||||
static_cast<SizeT>(item.regionLo.y() + y) * levelRowBytes +
|
const Uint8* srcRow = src + static_cast<SizeT>(lo.z() + z) * levelSliceBytes +
|
||||||
static_cast<SizeT>(item.regionLo.x()) * item.texelBytes;
|
static_cast<SizeT>(lo.y() + y) * levelRowBytes +
|
||||||
std::memcpy(dst + (static_cast<SizeT>(z) * item.regionSize.y() + y) * regionRowBytes,
|
static_cast<SizeT>(lo.x()) * item.texelBytes;
|
||||||
srcRow, regionRowBytes);
|
std::memcpy(out + (static_cast<SizeT>(z) * static_cast<SizeT>(boxSize.y()) + y) *
|
||||||
|
boxRowBytes,
|
||||||
|
srcRow, boxRowBytes);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
return static_cast<SizeT>(boxSize.x()) * static_cast<SizeT>(boxSize.y()) *
|
||||||
|
static_cast<SizeT>(boxSize.z()) * item.texelBytes;
|
||||||
|
};
|
||||||
|
if (!item.rects.empty()) {
|
||||||
|
for (const auto& rect : item.rects) {
|
||||||
|
dst += packBox(dst, rect.lo,
|
||||||
|
IntVec3{rect.hi.x() - rect.lo.x(), rect.hi.y() - rect.lo.y(),
|
||||||
|
rect.hi.z() - rect.lo.z()});
|
||||||
|
}
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
|
packBox(dst, item.regionLo, item.regionSize);
|
||||||
}
|
}
|
||||||
|
|
||||||
const VkImageAspectFlags aspectMask = GetAspectMaskForFormat(outResource.format);
|
const VkImageAspectFlags aspectMask = GetAspectMaskForFormat(outResource.format);
|
||||||
@@ -2573,6 +2611,46 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
outResource.viewType == VK_IMAGE_VIEW_TYPE_2D_ARRAY ||
|
outResource.viewType == VK_IMAGE_VIEW_TYPE_2D_ARRAY ||
|
||||||
outResource.viewType == VK_IMAGE_VIEW_TYPE_CUBE_ARRAY;
|
outResource.viewType == VK_IMAGE_VIEW_TYPE_CUBE_ARRAY;
|
||||||
for (const auto& item : uploadItems) {
|
for (const auto& item : uploadItems) {
|
||||||
|
if (!item.rects.empty()) {
|
||||||
|
// Multi-rect item: one VkBufferImageCopy per rect, all submitted in a
|
||||||
|
// single vkCmdCopyBufferToImage. The rect list is pairwise disjoint by
|
||||||
|
// construction, so no two copies write the same texels. Multi-rect
|
||||||
|
// implies subRegion, which implies a plain color aspect - the combined
|
||||||
|
// depth-stencil split below can never see one of these.
|
||||||
|
VkBufferImageCopy rectCopies[MG_State::GLState::MipmapStorage::kMaxDirtyRects];
|
||||||
|
Uint32 rectCopyCount = 0;
|
||||||
|
VkDeviceSize runningOffset = item.offset;
|
||||||
|
for (const auto& rect : item.rects) {
|
||||||
|
const IntVec3 rectSize = {rect.hi.x() - rect.lo.x(), rect.hi.y() - rect.lo.y(),
|
||||||
|
rect.hi.z() - rect.lo.z()};
|
||||||
|
const Uint32 rectDepth = static_cast<Uint32>(std::max(rectSize.z(), 1));
|
||||||
|
VkBufferImageCopy rectCopy{};
|
||||||
|
rectCopy.bufferOffset = stagingBase + runningOffset;
|
||||||
|
rectCopy.bufferRowLength = 0;
|
||||||
|
rectCopy.bufferImageHeight = 0;
|
||||||
|
rectCopy.imageSubresource.aspectMask = aspectMask;
|
||||||
|
rectCopy.imageSubresource.mipLevel = item.level;
|
||||||
|
rectCopy.imageSubresource.baseArrayLayer = item.baseArrayLayer;
|
||||||
|
rectCopy.imageSubresource.layerCount = 1;
|
||||||
|
rectCopy.imageOffset = {rect.lo.x(), rect.lo.y(),
|
||||||
|
depthSelectsArrayLayer ? 0 : rect.lo.z()};
|
||||||
|
rectCopy.imageExtent = {static_cast<Uint32>(rectSize.x()),
|
||||||
|
static_cast<Uint32>(rectSize.y()),
|
||||||
|
depthSelectsArrayLayer ? 1u : rectDepth};
|
||||||
|
if (depthSelectsArrayLayer) {
|
||||||
|
// The GL "depth" axis addresses array layers here, so a partial
|
||||||
|
// z-range narrows the layer span rather than the extent.
|
||||||
|
rectCopy.imageSubresource.baseArrayLayer =
|
||||||
|
item.baseArrayLayer + static_cast<Uint32>(rect.lo.z());
|
||||||
|
rectCopy.imageSubresource.layerCount = rectDepth;
|
||||||
|
}
|
||||||
|
rectCopies[rectCopyCount++] = rectCopy;
|
||||||
|
runningOffset += static_cast<VkDeviceSize>(rect.TexelCount() * item.texelBytes);
|
||||||
|
}
|
||||||
|
vkCmdCopyBufferToImage(commandBuffer, stagingBuffer, outResource.image,
|
||||||
|
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, rectCopyCount, rectCopies);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
const Uint32 depthOrLayers = item.texelSize.z() > 0 ? static_cast<Uint32>(item.texelSize.z()) : 1u;
|
const Uint32 depthOrLayers = item.texelSize.z() > 0 ? static_cast<Uint32>(item.texelSize.z()) : 1u;
|
||||||
VkBufferImageCopy copy{};
|
VkBufferImageCopy copy{};
|
||||||
copy.bufferOffset = stagingBase + item.offset;
|
copy.bufferOffset = stagingBase + item.offset;
|
||||||
|
|||||||
Reference in New Issue
Block a user