mirror of
https://github.com/Kelsidavis/WoWee.git
synced 2026-03-22 23:30:14 +00:00
Revert terrain_manager to original finalizeTile to fix water rendering
The incremental advanceFinalization state machine broke water rendering in ways that couldn't be resolved. Reverted to the original monolithic finalizeTile approach. The other performance optimizations (bone SSBO pre-allocation, WMO distance culling, M2 adaptive distance tiers) are kept.
This commit is contained in:
parent
7dd78e2c0a
commit
9b90ab0429
2 changed files with 255 additions and 326 deletions
|
|
@ -123,41 +123,6 @@ struct PendingTile {
|
|||
std::unordered_map<std::string, pipeline::BLPImage> preloadedTextures;
|
||||
};
|
||||
|
||||
/**
|
||||
* Phases for incremental tile finalization (one bounded unit of work per call)
|
||||
*/
|
||||
enum class FinalizationPhase {
|
||||
TERRAIN, // Upload terrain mesh + textures
|
||||
M2_MODELS, // Upload ONE M2 model per call
|
||||
M2_INSTANCES, // Create all M2 instances (lightweight struct allocation)
|
||||
WMO_MODELS, // Upload ONE WMO model per call
|
||||
WMO_INSTANCES, // Create all WMO instances + load WMO liquids
|
||||
WMO_DOODADS, // Upload ONE WMO doodad M2 per call
|
||||
WATER, // Upload water from terrain
|
||||
AMBIENT, // Register ambient emitters
|
||||
DONE // Commit to loadedTiles
|
||||
};
|
||||
|
||||
/**
|
||||
* In-progress tile finalization state — tracks progress across frames
|
||||
*/
|
||||
struct FinalizingTile {
|
||||
std::shared_ptr<PendingTile> pending;
|
||||
FinalizationPhase phase = FinalizationPhase::TERRAIN;
|
||||
|
||||
// Progress indices within current phase
|
||||
size_t m2ModelIndex = 0; // Next M2 model to upload
|
||||
size_t wmoModelIndex = 0; // Next WMO model to upload
|
||||
size_t wmoDoodadIndex = 0; // Next WMO doodad to upload
|
||||
|
||||
// Accumulated results (built up across phases)
|
||||
std::vector<uint32_t> m2InstanceIds;
|
||||
std::vector<uint32_t> wmoInstanceIds;
|
||||
std::vector<uint32_t> tileUniqueIds;
|
||||
std::vector<uint32_t> tileWmoUniqueIds;
|
||||
std::unordered_set<uint32_t> uploadedM2ModelIds;
|
||||
};
|
||||
|
||||
/**
|
||||
* Terrain manager for multi-tile terrain streaming
|
||||
*
|
||||
|
|
@ -254,8 +219,8 @@ public:
|
|||
int getLoadedTileCount() const { return static_cast<int>(loadedTiles.size()); }
|
||||
int getPendingTileCount() const { return static_cast<int>(pendingTiles.size()); }
|
||||
int getReadyQueueCount() const { return static_cast<int>(readyQueue.size()); }
|
||||
/** Total unfinished tiles (worker threads + ready queue + finalizing) */
|
||||
int getRemainingTileCount() const { return static_cast<int>(pendingTiles.size() + readyQueue.size() + finalizingTiles_.size()); }
|
||||
/** Total unfinished tiles (worker threads + ready queue) */
|
||||
int getRemainingTileCount() const { return static_cast<int>(pendingTiles.size() + readyQueue.size()); }
|
||||
TileCoord getCurrentTile() const { return currentTile; }
|
||||
|
||||
/** Process all ready tiles immediately (use during loading screens) */
|
||||
|
|
@ -289,10 +254,9 @@ private:
|
|||
std::shared_ptr<PendingTile> prepareTile(int x, int y);
|
||||
|
||||
/**
|
||||
* Advance incremental finalization of a tile (one bounded unit of work).
|
||||
* Returns true when the tile is fully finalized (phase == DONE).
|
||||
* Main thread: upload prepared tile data to GPU
|
||||
*/
|
||||
bool advanceFinalization(FinalizingTile& ft);
|
||||
void finalizeTile(const std::shared_ptr<PendingTile>& pending);
|
||||
|
||||
/**
|
||||
* Background worker thread loop
|
||||
|
|
@ -377,8 +341,16 @@ private:
|
|||
// Dedup set for WMO placements across tile boundaries (prevents rendering Stormwind 16x)
|
||||
std::unordered_set<uint32_t> placedWmoIds;
|
||||
|
||||
// Tiles currently being incrementally finalized across frames
|
||||
std::deque<FinalizingTile> finalizingTiles_;
|
||||
// Progressive M2 upload queue (spread heavy uploads across frames)
|
||||
struct PendingM2Upload {
|
||||
uint32_t modelId;
|
||||
pipeline::M2Model model;
|
||||
std::string path;
|
||||
};
|
||||
std::queue<PendingM2Upload> m2UploadQueue_;
|
||||
static constexpr int MAX_M2_UPLOADS_PER_FRAME = 5; // Upload up to 5 models per frame
|
||||
|
||||
void processM2UploadQueue();
|
||||
|
||||
struct GroundEffectEntry {
|
||||
std::array<uint32_t, 4> doodadIds{{0, 0, 0, 0}};
|
||||
|
|
|
|||
|
|
@ -226,9 +226,7 @@ bool TerrainManager::loadTile(int x, int y) {
|
|||
return false;
|
||||
}
|
||||
|
||||
FinalizingTile ft;
|
||||
ft.pending = std::move(pending);
|
||||
while (!advanceFinalization(ft)) {}
|
||||
finalizeTile(pending);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
@ -650,160 +648,176 @@ void TerrainManager::logMissingAdtOnce(const std::string& adtPath) {
|
|||
}
|
||||
}
|
||||
|
||||
bool TerrainManager::advanceFinalization(FinalizingTile& ft) {
|
||||
auto& pending = ft.pending;
|
||||
void TerrainManager::finalizeTile(const std::shared_ptr<PendingTile>& pending) {
|
||||
int x = pending->coord.x;
|
||||
int y = pending->coord.y;
|
||||
TileCoord coord = pending->coord;
|
||||
|
||||
switch (ft.phase) {
|
||||
LOG_DEBUG("Finalizing tile [", x, ",", y, "] (GPU upload)");
|
||||
|
||||
case FinalizationPhase::TERRAIN: {
|
||||
// Check if tile was already loaded or failed
|
||||
if (loadedTiles.find(coord) != loadedTiles.end() || failedTiles.find(coord) != failedTiles.end()) {
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(queueMutex);
|
||||
pendingTiles.erase(coord);
|
||||
}
|
||||
ft.phase = FinalizationPhase::DONE;
|
||||
return true;
|
||||
}
|
||||
|
||||
LOG_DEBUG("Finalizing tile [", x, ",", y, "] (incremental)");
|
||||
|
||||
// Upload pre-loaded textures
|
||||
if (!pending->preloadedTextures.empty()) {
|
||||
terrainRenderer->uploadPreloadedTextures(pending->preloadedTextures);
|
||||
}
|
||||
|
||||
// Upload terrain mesh to GPU
|
||||
if (!terrainRenderer->loadTerrain(pending->mesh, pending->terrain.textures, x, y)) {
|
||||
LOG_ERROR("Failed to upload terrain to GPU for tile [", x, ",", y, "]");
|
||||
failedTiles[coord] = true;
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(queueMutex);
|
||||
pendingTiles.erase(coord);
|
||||
}
|
||||
ft.phase = FinalizationPhase::DONE;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Load water immediately after terrain (same frame) — water vertex positions
|
||||
// depend on terrain chunk data and must be uploaded before the terrain renders
|
||||
// without water, otherwise vertices appear at origin.
|
||||
if (waterRenderer) {
|
||||
waterRenderer->loadFromTerrain(pending->terrain, true, x, y);
|
||||
}
|
||||
|
||||
// Ensure M2 renderer has asset manager
|
||||
if (m2Renderer && assetManager) {
|
||||
m2Renderer->initialize(nullptr, VK_NULL_HANDLE, assetManager);
|
||||
}
|
||||
|
||||
ft.phase = FinalizationPhase::M2_MODELS;
|
||||
return false;
|
||||
// Check if tile was already loaded (race condition guard) or failed
|
||||
if (loadedTiles.find(coord) != loadedTiles.end()) {
|
||||
return;
|
||||
}
|
||||
if (failedTiles.find(coord) != failedTiles.end()) {
|
||||
return;
|
||||
}
|
||||
|
||||
case FinalizationPhase::M2_MODELS: {
|
||||
// Upload ONE M2 model per call
|
||||
if (m2Renderer && ft.m2ModelIndex < pending->m2Models.size()) {
|
||||
auto& m2Ready = pending->m2Models[ft.m2ModelIndex];
|
||||
if (m2Renderer->loadModel(m2Ready.model, m2Ready.modelId)) {
|
||||
ft.uploadedM2ModelIds.insert(m2Ready.modelId);
|
||||
}
|
||||
ft.m2ModelIndex++;
|
||||
// Stay in this phase until all models uploaded
|
||||
if (ft.m2ModelIndex < pending->m2Models.size()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (!ft.uploadedM2ModelIds.empty()) {
|
||||
LOG_DEBUG(" Uploaded ", ft.uploadedM2ModelIds.size(), " M2 models for tile [", x, ",", y, "]");
|
||||
}
|
||||
ft.phase = FinalizationPhase::M2_INSTANCES;
|
||||
return false;
|
||||
// Upload pre-loaded textures to the GL cache so loadTerrain avoids file I/O
|
||||
if (!pending->preloadedTextures.empty()) {
|
||||
terrainRenderer->uploadPreloadedTextures(pending->preloadedTextures);
|
||||
}
|
||||
|
||||
case FinalizationPhase::M2_INSTANCES: {
|
||||
// Create all M2 instances (lightweight struct allocation, no GPU work)
|
||||
if (m2Renderer) {
|
||||
int loadedDoodads = 0;
|
||||
int skippedDedup = 0;
|
||||
for (const auto& p : pending->m2Placements) {
|
||||
if (p.uniqueId != 0 && placedDoodadIds.count(p.uniqueId)) {
|
||||
skippedDedup++;
|
||||
continue;
|
||||
}
|
||||
uint32_t instId = m2Renderer->createInstance(p.modelId, p.position, p.rotation, p.scale);
|
||||
if (instId) {
|
||||
ft.m2InstanceIds.push_back(instId);
|
||||
if (p.uniqueId != 0) {
|
||||
placedDoodadIds.insert(p.uniqueId);
|
||||
ft.tileUniqueIds.push_back(p.uniqueId);
|
||||
// Upload terrain to GPU
|
||||
if (!terrainRenderer->loadTerrain(pending->mesh, pending->terrain.textures, x, y)) {
|
||||
LOG_ERROR("Failed to upload terrain to GPU for tile [", x, ",", y, "]");
|
||||
failedTiles[coord] = true;
|
||||
return;
|
||||
}
|
||||
|
||||
// Load water
|
||||
if (waterRenderer) {
|
||||
waterRenderer->loadFromTerrain(pending->terrain, true, x, y);
|
||||
}
|
||||
|
||||
// Register water surface ambient sound emitters
|
||||
if (ambientSoundManager) {
|
||||
// Scan ADT water data for water surfaces
|
||||
int waterEmitterCount = 0;
|
||||
for (size_t chunkIdx = 0; chunkIdx < pending->terrain.waterData.size(); chunkIdx++) {
|
||||
const auto& chunkWater = pending->terrain.waterData[chunkIdx];
|
||||
if (!chunkWater.hasWater()) continue;
|
||||
|
||||
// Calculate chunk position in world coordinates
|
||||
int chunkX = chunkIdx % 16;
|
||||
int chunkY = chunkIdx / 16;
|
||||
|
||||
// WoW coordinates: Each ADT tile is 533.33 units, each chunk is 533.33/16 = 33.333 units
|
||||
// Tile origin in GL space
|
||||
float tileOriginX = (32.0f - x) * 533.33333f;
|
||||
float tileOriginY = (32.0f - y) * 533.33333f;
|
||||
|
||||
// Chunk center position
|
||||
float chunkCenterX = tileOriginX + (chunkX + 0.5f) * 33.333333f;
|
||||
float chunkCenterY = tileOriginY + (chunkY + 0.5f) * 33.333333f;
|
||||
|
||||
// Use first layer for height and type detection
|
||||
if (!chunkWater.layers.empty()) {
|
||||
const auto& layer = chunkWater.layers[0];
|
||||
float waterHeight = layer.minHeight;
|
||||
|
||||
// Determine water type and register appropriate emitter
|
||||
// liquidType: 0=water/lake, 1=ocean, 2=magma, 3=slime
|
||||
if (layer.liquidType == 0) {
|
||||
// Lake/river water - add water surface emitter every 32 chunks to avoid spam
|
||||
if (chunkIdx % 32 == 0) {
|
||||
PendingTile::AmbientEmitter emitter;
|
||||
emitter.position = glm::vec3(chunkCenterX, chunkCenterY, waterHeight);
|
||||
emitter.type = 4; // WATER_SURFACE
|
||||
pending->ambientEmitters.push_back(emitter);
|
||||
waterEmitterCount++;
|
||||
}
|
||||
} else if (layer.liquidType == 1) {
|
||||
// Ocean - add ocean emitter every 64 chunks (oceans are very large)
|
||||
if (chunkIdx % 64 == 0) {
|
||||
PendingTile::AmbientEmitter emitter;
|
||||
emitter.position = glm::vec3(chunkCenterX, chunkCenterY, waterHeight);
|
||||
emitter.type = 4; // WATER_SURFACE (could add separate OCEAN type later)
|
||||
pending->ambientEmitters.push_back(emitter);
|
||||
waterEmitterCount++;
|
||||
}
|
||||
loadedDoodads++;
|
||||
}
|
||||
}
|
||||
LOG_DEBUG(" Loaded doodads for tile [", x, ",", y, "]: ",
|
||||
loadedDoodads, " instances (", ft.uploadedM2ModelIds.size(), " new models, ",
|
||||
skippedDedup, " dedup skipped)");
|
||||
}
|
||||
ft.phase = FinalizationPhase::WMO_MODELS;
|
||||
return false;
|
||||
}
|
||||
|
||||
case FinalizationPhase::WMO_MODELS: {
|
||||
// Upload ONE WMO model per call
|
||||
if (wmoRenderer && assetManager) {
|
||||
wmoRenderer->initialize(nullptr, VK_NULL_HANDLE, assetManager);
|
||||
|
||||
if (ft.wmoModelIndex < pending->wmoModels.size()) {
|
||||
auto& wmoReady = pending->wmoModels[ft.wmoModelIndex];
|
||||
// Deduplicate
|
||||
if (wmoReady.uniqueId != 0 && placedWmoIds.count(wmoReady.uniqueId)) {
|
||||
// Skip this one, advance
|
||||
ft.wmoModelIndex++;
|
||||
if (ft.wmoModelIndex < pending->wmoModels.size()) return false;
|
||||
} else {
|
||||
wmoRenderer->loadModel(wmoReady.model, wmoReady.modelId);
|
||||
ft.wmoModelIndex++;
|
||||
if (ft.wmoModelIndex < pending->wmoModels.size()) return false;
|
||||
}
|
||||
// Skip magma and slime for now (no ambient sounds for those)
|
||||
}
|
||||
}
|
||||
ft.wmoModelIndex = 0; // Reset for WMO_INSTANCES phase iteration
|
||||
ft.phase = FinalizationPhase::WMO_INSTANCES;
|
||||
return false;
|
||||
if (waterEmitterCount > 0) {
|
||||
}
|
||||
}
|
||||
|
||||
case FinalizationPhase::WMO_INSTANCES: {
|
||||
// Create all WMO instances + load WMO liquids
|
||||
if (wmoRenderer) {
|
||||
int loadedWMOs = 0;
|
||||
int loadedLiquids = 0;
|
||||
int skippedWmoDedup = 0;
|
||||
for (auto& wmoReady : pending->wmoModels) {
|
||||
if (wmoReady.uniqueId != 0 && placedWmoIds.count(wmoReady.uniqueId)) {
|
||||
skippedWmoDedup++;
|
||||
continue;
|
||||
}
|
||||
std::vector<uint32_t> m2InstanceIds;
|
||||
std::vector<uint32_t> wmoInstanceIds;
|
||||
std::vector<uint32_t> tileUniqueIds;
|
||||
std::vector<uint32_t> tileWmoUniqueIds;
|
||||
|
||||
// Upload M2 models to GPU and create instances
|
||||
if (m2Renderer && assetManager) {
|
||||
// Always pass the latest asset manager. initialize() is idempotent and updates
|
||||
// the pointer even when the renderer was initialized earlier without assets.
|
||||
m2Renderer->initialize(nullptr, VK_NULL_HANDLE, assetManager);
|
||||
|
||||
// Upload M2 models immediately (batching was causing hangs)
|
||||
// The 5ms time budget in processReadyTiles() limits the spike
|
||||
std::unordered_set<uint32_t> uploadedModelIds;
|
||||
for (auto& m2Ready : pending->m2Models) {
|
||||
if (m2Renderer->loadModel(m2Ready.model, m2Ready.modelId)) {
|
||||
uploadedModelIds.insert(m2Ready.modelId);
|
||||
}
|
||||
}
|
||||
if (!uploadedModelIds.empty()) {
|
||||
LOG_DEBUG(" Uploaded ", uploadedModelIds.size(), " M2 models for tile [", x, ",", y, "]");
|
||||
}
|
||||
|
||||
// Create instances (deduplicate by uniqueId across tile boundaries)
|
||||
int loadedDoodads = 0;
|
||||
int skippedDedup = 0;
|
||||
for (const auto& p : pending->m2Placements) {
|
||||
// Skip if this doodad was already placed by a neighboring tile
|
||||
if (p.uniqueId != 0 && placedDoodadIds.count(p.uniqueId)) {
|
||||
skippedDedup++;
|
||||
continue;
|
||||
}
|
||||
uint32_t instId = m2Renderer->createInstance(p.modelId, p.position, p.rotation, p.scale);
|
||||
if (instId) {
|
||||
m2InstanceIds.push_back(instId);
|
||||
if (p.uniqueId != 0) {
|
||||
placedDoodadIds.insert(p.uniqueId);
|
||||
tileUniqueIds.push_back(p.uniqueId);
|
||||
}
|
||||
loadedDoodads++;
|
||||
}
|
||||
}
|
||||
|
||||
LOG_DEBUG(" Loaded doodads for tile [", x, ",", y, "]: ",
|
||||
loadedDoodads, " instances (", uploadedModelIds.size(), " new models, ",
|
||||
skippedDedup, " dedup skipped)");
|
||||
}
|
||||
|
||||
// Upload WMO models to GPU and create instances
|
||||
if (wmoRenderer && assetManager) {
|
||||
// WMORenderer may be initialized before assets are ready; always re-pass assets.
|
||||
wmoRenderer->initialize(nullptr, VK_NULL_HANDLE, assetManager);
|
||||
|
||||
int loadedWMOs = 0;
|
||||
int loadedLiquids = 0;
|
||||
int skippedWmoDedup = 0;
|
||||
for (auto& wmoReady : pending->wmoModels) {
|
||||
// Deduplicate by placement uniqueId when available.
|
||||
// Some ADTs use uniqueId=0, which is not safe for dedup.
|
||||
if (wmoReady.uniqueId != 0 && placedWmoIds.count(wmoReady.uniqueId)) {
|
||||
skippedWmoDedup++;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (wmoRenderer->loadModel(wmoReady.model, wmoReady.modelId)) {
|
||||
uint32_t wmoInstId = wmoRenderer->createInstance(wmoReady.modelId, wmoReady.position, wmoReady.rotation);
|
||||
if (wmoInstId) {
|
||||
ft.wmoInstanceIds.push_back(wmoInstId);
|
||||
wmoInstanceIds.push_back(wmoInstId);
|
||||
if (wmoReady.uniqueId != 0) {
|
||||
placedWmoIds.insert(wmoReady.uniqueId);
|
||||
ft.tileWmoUniqueIds.push_back(wmoReady.uniqueId);
|
||||
tileWmoUniqueIds.push_back(wmoReady.uniqueId);
|
||||
}
|
||||
loadedWMOs++;
|
||||
|
||||
// Load WMO liquids (canals, pools, etc.)
|
||||
if (waterRenderer) {
|
||||
// Compute the same model matrix as WMORenderer uses
|
||||
glm::mat4 modelMatrix = glm::mat4(1.0f);
|
||||
modelMatrix = glm::translate(modelMatrix, wmoReady.position);
|
||||
modelMatrix = glm::rotate(modelMatrix, wmoReady.rotation.z, glm::vec3(0.0f, 0.0f, 1.0f));
|
||||
modelMatrix = glm::rotate(modelMatrix, wmoReady.rotation.y, glm::vec3(0.0f, 1.0f, 0.0f));
|
||||
modelMatrix = glm::rotate(modelMatrix, wmoReady.rotation.x, glm::vec3(1.0f, 0.0f, 0.0f));
|
||||
|
||||
// Load liquids from each WMO group
|
||||
for (const auto& group : wmoReady.model.groups) {
|
||||
if (group.liquid.hasLiquid()) {
|
||||
waterRenderer->loadFromWMO(group.liquid, modelMatrix, wmoInstId);
|
||||
|
|
@ -813,109 +827,57 @@ bool TerrainManager::advanceFinalization(FinalizingTile& ft) {
|
|||
}
|
||||
}
|
||||
}
|
||||
if (loadedWMOs > 0 || skippedWmoDedup > 0) {
|
||||
LOG_DEBUG(" Loaded WMOs for tile [", x, ",", y, "]: ",
|
||||
loadedWMOs, " instances, ", skippedWmoDedup, " dedup skipped");
|
||||
}
|
||||
if (loadedLiquids > 0) {
|
||||
LOG_DEBUG(" Loaded WMO liquids for tile [", x, ",", y, "]: ", loadedLiquids);
|
||||
}
|
||||
}
|
||||
ft.phase = FinalizationPhase::WMO_DOODADS;
|
||||
return false;
|
||||
}
|
||||
|
||||
case FinalizationPhase::WMO_DOODADS: {
|
||||
// Upload ONE WMO doodad M2 per call
|
||||
if (m2Renderer && ft.wmoDoodadIndex < pending->wmoDoodads.size()) {
|
||||
auto& doodad = pending->wmoDoodads[ft.wmoDoodadIndex];
|
||||
m2Renderer->loadModel(doodad.model, doodad.modelId);
|
||||
uint32_t wmoDoodadInstId = m2Renderer->createInstanceWithMatrix(
|
||||
doodad.modelId, doodad.modelMatrix, doodad.worldPosition);
|
||||
if (wmoDoodadInstId) ft.m2InstanceIds.push_back(wmoDoodadInstId);
|
||||
ft.wmoDoodadIndex++;
|
||||
if (ft.wmoDoodadIndex < pending->wmoDoodads.size()) return false;
|
||||
if (loadedWMOs > 0 || skippedWmoDedup > 0) {
|
||||
LOG_DEBUG(" Loaded WMOs for tile [", x, ",", y, "]: ",
|
||||
loadedWMOs, " instances, ", skippedWmoDedup, " dedup skipped");
|
||||
}
|
||||
if (loadedLiquids > 0) {
|
||||
LOG_DEBUG(" Loaded WMO liquids for tile [", x, ",", y, "]: ", loadedLiquids);
|
||||
}
|
||||
ft.phase = FinalizationPhase::WATER;
|
||||
return false;
|
||||
}
|
||||
|
||||
case FinalizationPhase::WATER: {
|
||||
// Terrain water was already loaded in TERRAIN phase.
|
||||
// Generate water ambient emitters here.
|
||||
if (ambientSoundManager) {
|
||||
for (size_t chunkIdx = 0; chunkIdx < pending->terrain.waterData.size(); chunkIdx++) {
|
||||
const auto& chunkWater = pending->terrain.waterData[chunkIdx];
|
||||
if (!chunkWater.hasWater()) continue;
|
||||
|
||||
int chunkX = chunkIdx % 16;
|
||||
int chunkY = chunkIdx / 16;
|
||||
float tileOriginX = (32.0f - x) * 533.33333f;
|
||||
float tileOriginY = (32.0f - y) * 533.33333f;
|
||||
float chunkCenterX = tileOriginX + (chunkX + 0.5f) * 33.333333f;
|
||||
float chunkCenterY = tileOriginY + (chunkY + 0.5f) * 33.333333f;
|
||||
|
||||
if (!chunkWater.layers.empty()) {
|
||||
const auto& layer = chunkWater.layers[0];
|
||||
float waterHeight = layer.minHeight;
|
||||
if (layer.liquidType == 0 && chunkIdx % 32 == 0) {
|
||||
PendingTile::AmbientEmitter emitter;
|
||||
emitter.position = glm::vec3(chunkCenterX, chunkCenterY, waterHeight);
|
||||
emitter.type = 4;
|
||||
pending->ambientEmitters.push_back(emitter);
|
||||
} else if (layer.liquidType == 1 && chunkIdx % 64 == 0) {
|
||||
PendingTile::AmbientEmitter emitter;
|
||||
emitter.position = glm::vec3(chunkCenterX, chunkCenterY, waterHeight);
|
||||
emitter.type = 4;
|
||||
pending->ambientEmitters.push_back(emitter);
|
||||
}
|
||||
}
|
||||
// Upload WMO doodad M2 models
|
||||
if (m2Renderer) {
|
||||
for (auto& doodad : pending->wmoDoodads) {
|
||||
m2Renderer->loadModel(doodad.model, doodad.modelId);
|
||||
uint32_t wmoDoodadInstId = m2Renderer->createInstanceWithMatrix(
|
||||
doodad.modelId, doodad.modelMatrix, doodad.worldPosition);
|
||||
if (wmoDoodadInstId) m2InstanceIds.push_back(wmoDoodadInstId);
|
||||
}
|
||||
}
|
||||
|
||||
ft.phase = FinalizationPhase::AMBIENT;
|
||||
return false;
|
||||
}
|
||||
|
||||
case FinalizationPhase::AMBIENT: {
|
||||
// Register ambient sound emitters
|
||||
if (ambientSoundManager && !pending->ambientEmitters.empty()) {
|
||||
for (const auto& emitter : pending->ambientEmitters) {
|
||||
auto type = static_cast<audio::AmbientSoundManager::AmbientType>(emitter.type);
|
||||
ambientSoundManager->addEmitter(emitter.position, type);
|
||||
}
|
||||
if (loadedWMOs > 0) {
|
||||
LOG_DEBUG(" Loaded WMOs for tile [", x, ",", y, "]: ", loadedWMOs);
|
||||
}
|
||||
}
|
||||
|
||||
// Commit tile to loadedTiles
|
||||
auto tile = std::make_unique<TerrainTile>();
|
||||
tile->coord = coord;
|
||||
tile->terrain = std::move(pending->terrain);
|
||||
tile->mesh = std::move(pending->mesh);
|
||||
tile->loaded = true;
|
||||
tile->m2InstanceIds = std::move(ft.m2InstanceIds);
|
||||
tile->wmoInstanceIds = std::move(ft.wmoInstanceIds);
|
||||
tile->wmoUniqueIds = std::move(ft.tileWmoUniqueIds);
|
||||
tile->doodadUniqueIds = std::move(ft.tileUniqueIds);
|
||||
getTileBounds(coord, tile->minX, tile->minY, tile->maxX, tile->maxY);
|
||||
loadedTiles[coord] = std::move(tile);
|
||||
putCachedTile(pending);
|
||||
|
||||
// Now safe to remove from pendingTiles (tile is in loadedTiles)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(queueMutex);
|
||||
pendingTiles.erase(coord);
|
||||
// Register ambient sound emitters with ambient sound manager
|
||||
if (ambientSoundManager && !pending->ambientEmitters.empty()) {
|
||||
for (const auto& emitter : pending->ambientEmitters) {
|
||||
// Cast uint32_t type to AmbientSoundManager::AmbientType enum
|
||||
auto type = static_cast<audio::AmbientSoundManager::AmbientType>(emitter.type);
|
||||
ambientSoundManager->addEmitter(emitter.position, type);
|
||||
}
|
||||
|
||||
LOG_DEBUG(" Finalized tile [", x, ",", y, "]");
|
||||
|
||||
ft.phase = FinalizationPhase::DONE;
|
||||
return true;
|
||||
}
|
||||
|
||||
case FinalizationPhase::DONE:
|
||||
return true;
|
||||
}
|
||||
return true;
|
||||
// Create tile entry
|
||||
auto tile = std::make_unique<TerrainTile>();
|
||||
tile->coord = coord;
|
||||
tile->terrain = std::move(pending->terrain);
|
||||
tile->mesh = std::move(pending->mesh);
|
||||
tile->loaded = true;
|
||||
tile->m2InstanceIds = std::move(m2InstanceIds);
|
||||
tile->wmoInstanceIds = std::move(wmoInstanceIds);
|
||||
tile->wmoUniqueIds = std::move(tileWmoUniqueIds);
|
||||
tile->doodadUniqueIds = std::move(tileUniqueIds);
|
||||
|
||||
// Calculate world bounds
|
||||
getTileBounds(coord, tile->minX, tile->minY, tile->maxX, tile->maxY);
|
||||
|
||||
loadedTiles[coord] = std::move(tile);
|
||||
putCachedTile(pending);
|
||||
|
||||
LOG_DEBUG(" Finalized tile [", x, ",", y, "]");
|
||||
}
|
||||
|
||||
void TerrainManager::workerLoop() {
|
||||
|
|
@ -965,59 +927,80 @@ void TerrainManager::processReadyTiles() {
|
|||
// Taxi mode gets a slightly larger budget to avoid visible late-pop terrain/models.
|
||||
const float timeBudgetMs = taxiStreamingMode_ ? 8.0f : 5.0f;
|
||||
auto startTime = std::chrono::high_resolution_clock::now();
|
||||
int processed = 0;
|
||||
|
||||
// Move newly ready tiles into the finalizing deque.
|
||||
// Keep them in pendingTiles so streamTiles() won't re-enqueue them.
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(queueMutex);
|
||||
while (!readyQueue.empty()) {
|
||||
auto pending = readyQueue.front();
|
||||
readyQueue.pop();
|
||||
if (pending) {
|
||||
FinalizingTile ft;
|
||||
ft.pending = std::move(pending);
|
||||
finalizingTiles_.push_back(std::move(ft));
|
||||
while (true) {
|
||||
std::shared_ptr<PendingTile> pending;
|
||||
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(queueMutex);
|
||||
if (readyQueue.empty()) {
|
||||
break;
|
||||
}
|
||||
pending = readyQueue.front();
|
||||
readyQueue.pop();
|
||||
}
|
||||
}
|
||||
|
||||
// Finalize one complete tile per iteration, check budget between tiles.
|
||||
// Each tile runs through all phases before moving to the next — this
|
||||
// avoids subtle state issues from spreading GPU uploads across frames.
|
||||
while (!finalizingTiles_.empty()) {
|
||||
auto& ft = finalizingTiles_.front();
|
||||
while (!advanceFinalization(ft)) {}
|
||||
finalizingTiles_.pop_front();
|
||||
if (pending) {
|
||||
TileCoord coord = pending->coord;
|
||||
|
||||
auto now = std::chrono::high_resolution_clock::now();
|
||||
float elapsedMs = std::chrono::duration<float, std::milli>(now - startTime).count();
|
||||
if (elapsedMs >= timeBudgetMs) {
|
||||
break;
|
||||
finalizeTile(pending);
|
||||
|
||||
auto now = std::chrono::high_resolution_clock::now();
|
||||
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(queueMutex);
|
||||
pendingTiles.erase(coord);
|
||||
}
|
||||
processed++;
|
||||
|
||||
// Check if we've exceeded time budget
|
||||
float elapsedMs = std::chrono::duration<float, std::milli>(now - startTime).count();
|
||||
if (elapsedMs >= timeBudgetMs) {
|
||||
if (processed > 1) {
|
||||
LOG_DEBUG("Processed ", processed, " tiles in ", elapsedMs, "ms (budget: ", timeBudgetMs, "ms)");
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void TerrainManager::processM2UploadQueue() {
|
||||
// Upload up to MAX_M2_UPLOADS_PER_FRAME models per frame
|
||||
int uploaded = 0;
|
||||
while (!m2UploadQueue_.empty() && uploaded < MAX_M2_UPLOADS_PER_FRAME) {
|
||||
auto& upload = m2UploadQueue_.front();
|
||||
if (m2Renderer) {
|
||||
m2Renderer->loadModel(upload.model, upload.modelId);
|
||||
}
|
||||
m2UploadQueue_.pop();
|
||||
uploaded++;
|
||||
}
|
||||
|
||||
if (uploaded > 0) {
|
||||
LOG_DEBUG("Uploaded ", uploaded, " M2 models (", m2UploadQueue_.size(), " remaining in queue)");
|
||||
}
|
||||
}
|
||||
|
||||
void TerrainManager::processAllReadyTiles() {
|
||||
// Move all ready tiles into finalizing deque
|
||||
// Keep in pendingTiles until committed (same as processReadyTiles)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(queueMutex);
|
||||
while (!readyQueue.empty()) {
|
||||
auto pending = readyQueue.front();
|
||||
while (true) {
|
||||
std::shared_ptr<PendingTile> pending;
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(queueMutex);
|
||||
if (readyQueue.empty()) break;
|
||||
pending = readyQueue.front();
|
||||
readyQueue.pop();
|
||||
if (pending) {
|
||||
FinalizingTile ft;
|
||||
ft.pending = std::move(pending);
|
||||
finalizingTiles_.push_back(std::move(ft));
|
||||
}
|
||||
if (pending) {
|
||||
TileCoord coord = pending->coord;
|
||||
finalizeTile(pending);
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(queueMutex);
|
||||
pendingTiles.erase(coord);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Finalize all tiles completely (no time budget — used for loading screens)
|
||||
while (!finalizingTiles_.empty()) {
|
||||
auto& ft = finalizingTiles_.front();
|
||||
while (!advanceFinalization(ft)) {}
|
||||
finalizingTiles_.pop_front();
|
||||
}
|
||||
}
|
||||
|
||||
std::shared_ptr<PendingTile> TerrainManager::getCachedTile(const TileCoord& coord) {
|
||||
|
|
@ -1116,31 +1099,6 @@ void TerrainManager::unloadTile(int x, int y) {
|
|||
pendingTiles.erase(coord);
|
||||
}
|
||||
|
||||
// Remove from finalizingTiles_ if it's being incrementally finalized.
|
||||
// Water may have already been loaded in TERRAIN phase, so clean it up.
|
||||
for (auto fit = finalizingTiles_.begin(); fit != finalizingTiles_.end(); ++fit) {
|
||||
if (fit->pending && fit->pending->coord == coord) {
|
||||
// If past TERRAIN phase, water was already loaded — remove it
|
||||
if (fit->phase != FinalizationPhase::TERRAIN && waterRenderer) {
|
||||
waterRenderer->removeTile(x, y);
|
||||
}
|
||||
// Clean up any M2/WMO instances that were already created
|
||||
if (m2Renderer && !fit->m2InstanceIds.empty()) {
|
||||
m2Renderer->removeInstances(fit->m2InstanceIds);
|
||||
}
|
||||
if (wmoRenderer && !fit->wmoInstanceIds.empty()) {
|
||||
for (uint32_t id : fit->wmoInstanceIds) {
|
||||
if (waterRenderer) waterRenderer->removeWMO(id);
|
||||
}
|
||||
wmoRenderer->removeInstances(fit->wmoInstanceIds);
|
||||
}
|
||||
for (uint32_t uid : fit->tileUniqueIds) placedDoodadIds.erase(uid);
|
||||
for (uint32_t uid : fit->tileWmoUniqueIds) placedWmoIds.erase(uid);
|
||||
finalizingTiles_.erase(fit);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
auto it = loadedTiles.find(coord);
|
||||
if (it == loadedTiles.end()) {
|
||||
return;
|
||||
|
|
@ -1209,7 +1167,6 @@ void TerrainManager::unloadAll() {
|
|||
while (!readyQueue.empty()) readyQueue.pop();
|
||||
}
|
||||
pendingTiles.clear();
|
||||
finalizingTiles_.clear();
|
||||
placedDoodadIds.clear();
|
||||
|
||||
LOG_INFO("Unloading all terrain tiles");
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue