Batch GPU uploads to eliminate per-upload fence waits (stutter fix)

Every uploadBuffer/VkTexture::upload called immediateSubmit which did a
separate vkQueueSubmit + vkWaitForFences. Loading a single creature model
with textures caused 4-8+ fence waits; terrain chunks caused 80+ per batch.

Added beginUploadBatch/endUploadBatch to VkContext: records all upload
commands into a single command buffer, submits once with one fence wait.
Staging buffers are deferred for cleanup after the batch completes.

Wrapped in batch mode:
- CharacterRenderer::loadModel (creature VB/IB + textures)
- M2Renderer::loadModel (doodad VB/IB + textures)
- TerrainRenderer::loadTerrain/loadTerrainIncremental (chunk geometry + textures)
- TerrainRenderer::uploadPreloadedTextures
- WMORenderer::loadModel (group geometry + textures)
This commit is contained in:
Kelsi 2026-03-07 12:19:59 -08:00
parent 884b72bc1c
commit 16b4336700
8 changed files with 97 additions and 4 deletions

View file

@ -326,6 +326,8 @@ bool TerrainRenderer::loadTerrain(const pipeline::TerrainMesh& mesh,
}
LOG_DEBUG("Loading terrain mesh: ", mesh.validChunkCount, " chunks");
vkCtx->beginUploadBatch();
for (int y = 0; y < 16; y++) {
for (int x = 0; x < 16; x++) {
const auto& chunk = mesh.getChunk(x, y);
@ -405,6 +407,8 @@ bool TerrainRenderer::loadTerrain(const pipeline::TerrainMesh& mesh,
}
}
vkCtx->endUploadBatch();
LOG_DEBUG("Loaded ", chunks.size(), " terrain chunks to GPU");
return !chunks.empty();
}
@ -413,6 +417,10 @@ bool TerrainRenderer::loadTerrainIncremental(const pipeline::TerrainMesh& mesh,
const std::vector<std::string>& texturePaths,
int tileX, int tileY,
int& chunkIndex, int maxChunksPerCall) {
// Batch all GPU uploads (VBs, IBs, textures) into a single command buffer
// submission with one fence wait, instead of one per buffer/texture.
vkCtx->beginUploadBatch();
int uploaded = 0;
while (chunkIndex < 256 && uploaded < maxChunksPerCall) {
int cy = chunkIndex / 16;
@ -490,6 +498,8 @@ bool TerrainRenderer::loadTerrainIncremental(const pipeline::TerrainMesh& mesh,
uploaded++;
}
vkCtx->endUploadBatch();
return chunkIndex >= 256;
}
@ -580,6 +590,9 @@ void TerrainRenderer::uploadPreloadedTextures(
[](unsigned char c) { return static_cast<char>(std::tolower(c)); });
return key;
};
// Batch all texture uploads into a single command buffer submission
vkCtx->beginUploadBatch();
for (const auto& [path, blp] : textures) {
std::string key = normalizeKey(path);
if (textureCache.find(key) != textureCache.end()) continue;
@ -599,6 +612,8 @@ void TerrainRenderer::uploadPreloadedTextures(
textureCacheBytes_ += e.approxBytes;
textureCache[key] = std::move(e);
}
vkCtx->endUploadBatch();
}
VkTexture* TerrainRenderer::createAlphaTexture(const std::vector<uint8_t>& alphaData) {