mirror of
https://github.com/Kelsidavis/WoWee.git
synced 2026-05-02 15:53:51 +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;
|
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
|
* Terrain manager for multi-tile terrain streaming
|
||||||
*
|
*
|
||||||
|
|
@ -254,8 +219,8 @@ public:
|
||||||
int getLoadedTileCount() const { return static_cast<int>(loadedTiles.size()); }
|
int getLoadedTileCount() const { return static_cast<int>(loadedTiles.size()); }
|
||||||
int getPendingTileCount() const { return static_cast<int>(pendingTiles.size()); }
|
int getPendingTileCount() const { return static_cast<int>(pendingTiles.size()); }
|
||||||
int getReadyQueueCount() const { return static_cast<int>(readyQueue.size()); }
|
int getReadyQueueCount() const { return static_cast<int>(readyQueue.size()); }
|
||||||
/** Total unfinished tiles (worker threads + ready queue + finalizing) */
|
/** Total unfinished tiles (worker threads + ready queue) */
|
||||||
int getRemainingTileCount() const { return static_cast<int>(pendingTiles.size() + readyQueue.size() + finalizingTiles_.size()); }
|
int getRemainingTileCount() const { return static_cast<int>(pendingTiles.size() + readyQueue.size()); }
|
||||||
TileCoord getCurrentTile() const { return currentTile; }
|
TileCoord getCurrentTile() const { return currentTile; }
|
||||||
|
|
||||||
/** Process all ready tiles immediately (use during loading screens) */
|
/** Process all ready tiles immediately (use during loading screens) */
|
||||||
|
|
@ -289,10 +254,9 @@ private:
|
||||||
std::shared_ptr<PendingTile> prepareTile(int x, int y);
|
std::shared_ptr<PendingTile> prepareTile(int x, int y);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Advance incremental finalization of a tile (one bounded unit of work).
|
* Main thread: upload prepared tile data to GPU
|
||||||
* Returns true when the tile is fully finalized (phase == DONE).
|
|
||||||
*/
|
*/
|
||||||
bool advanceFinalization(FinalizingTile& ft);
|
void finalizeTile(const std::shared_ptr<PendingTile>& pending);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Background worker thread loop
|
* Background worker thread loop
|
||||||
|
|
@ -377,8 +341,16 @@ private:
|
||||||
// Dedup set for WMO placements across tile boundaries (prevents rendering Stormwind 16x)
|
// Dedup set for WMO placements across tile boundaries (prevents rendering Stormwind 16x)
|
||||||
std::unordered_set<uint32_t> placedWmoIds;
|
std::unordered_set<uint32_t> placedWmoIds;
|
||||||
|
|
||||||
// Tiles currently being incrementally finalized across frames
|
// Progressive M2 upload queue (spread heavy uploads across frames)
|
||||||
std::deque<FinalizingTile> finalizingTiles_;
|
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 {
|
struct GroundEffectEntry {
|
||||||
std::array<uint32_t, 4> doodadIds{{0, 0, 0, 0}};
|
std::array<uint32_t, 4> doodadIds{{0, 0, 0, 0}};
|
||||||
|
|
|
||||||
|
|
@ -226,9 +226,7 @@ bool TerrainManager::loadTile(int x, int y) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
FinalizingTile ft;
|
finalizeTile(pending);
|
||||||
ft.pending = std::move(pending);
|
|
||||||
while (!advanceFinalization(ft)) {}
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -650,160 +648,176 @@ void TerrainManager::logMissingAdtOnce(const std::string& adtPath) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool TerrainManager::advanceFinalization(FinalizingTile& ft) {
|
void TerrainManager::finalizeTile(const std::shared_ptr<PendingTile>& pending) {
|
||||||
auto& pending = ft.pending;
|
|
||||||
int x = pending->coord.x;
|
int x = pending->coord.x;
|
||||||
int y = pending->coord.y;
|
int y = pending->coord.y;
|
||||||
TileCoord coord = pending->coord;
|
TileCoord coord = pending->coord;
|
||||||
|
|
||||||
switch (ft.phase) {
|
LOG_DEBUG("Finalizing tile [", x, ",", y, "] (GPU upload)");
|
||||||
|
|
||||||
case FinalizationPhase::TERRAIN: {
|
// Check if tile was already loaded (race condition guard) or failed
|
||||||
// Check if tile was already loaded or failed
|
if (loadedTiles.find(coord) != loadedTiles.end()) {
|
||||||
if (loadedTiles.find(coord) != loadedTiles.end() || failedTiles.find(coord) != failedTiles.end()) {
|
return;
|
||||||
{
|
|
||||||
std::lock_guard<std::mutex> lock(queueMutex);
|
|
||||||
pendingTiles.erase(coord);
|
|
||||||
}
|
}
|
||||||
ft.phase = FinalizationPhase::DONE;
|
if (failedTiles.find(coord) != failedTiles.end()) {
|
||||||
return true;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
LOG_DEBUG("Finalizing tile [", x, ",", y, "] (incremental)");
|
// Upload pre-loaded textures to the GL cache so loadTerrain avoids file I/O
|
||||||
|
|
||||||
// Upload pre-loaded textures
|
|
||||||
if (!pending->preloadedTextures.empty()) {
|
if (!pending->preloadedTextures.empty()) {
|
||||||
terrainRenderer->uploadPreloadedTextures(pending->preloadedTextures);
|
terrainRenderer->uploadPreloadedTextures(pending->preloadedTextures);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Upload terrain mesh to GPU
|
// Upload terrain to GPU
|
||||||
if (!terrainRenderer->loadTerrain(pending->mesh, pending->terrain.textures, x, y)) {
|
if (!terrainRenderer->loadTerrain(pending->mesh, pending->terrain.textures, x, y)) {
|
||||||
LOG_ERROR("Failed to upload terrain to GPU for tile [", x, ",", y, "]");
|
LOG_ERROR("Failed to upload terrain to GPU for tile [", x, ",", y, "]");
|
||||||
failedTiles[coord] = true;
|
failedTiles[coord] = true;
|
||||||
{
|
return;
|
||||||
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
|
// Load water
|
||||||
// depend on terrain chunk data and must be uploaded before the terrain renders
|
|
||||||
// without water, otherwise vertices appear at origin.
|
|
||||||
if (waterRenderer) {
|
if (waterRenderer) {
|
||||||
waterRenderer->loadFromTerrain(pending->terrain, true, x, y);
|
waterRenderer->loadFromTerrain(pending->terrain, true, x, y);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ensure M2 renderer has asset manager
|
// 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++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Skip magma and slime for now (no ambient sounds for those)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (waterEmitterCount > 0) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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) {
|
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);
|
m2Renderer->initialize(nullptr, VK_NULL_HANDLE, assetManager);
|
||||||
}
|
|
||||||
|
|
||||||
ft.phase = FinalizationPhase::M2_MODELS;
|
// Upload M2 models immediately (batching was causing hangs)
|
||||||
return false;
|
// The 5ms time budget in processReadyTiles() limits the spike
|
||||||
}
|
std::unordered_set<uint32_t> uploadedModelIds;
|
||||||
|
for (auto& m2Ready : pending->m2Models) {
|
||||||
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)) {
|
if (m2Renderer->loadModel(m2Ready.model, m2Ready.modelId)) {
|
||||||
ft.uploadedM2ModelIds.insert(m2Ready.modelId);
|
uploadedModelIds.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()) {
|
if (!uploadedModelIds.empty()) {
|
||||||
LOG_DEBUG(" Uploaded ", ft.uploadedM2ModelIds.size(), " M2 models for tile [", x, ",", y, "]");
|
LOG_DEBUG(" Uploaded ", uploadedModelIds.size(), " M2 models for tile [", x, ",", y, "]");
|
||||||
}
|
|
||||||
ft.phase = FinalizationPhase::M2_INSTANCES;
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
case FinalizationPhase::M2_INSTANCES: {
|
// Create instances (deduplicate by uniqueId across tile boundaries)
|
||||||
// Create all M2 instances (lightweight struct allocation, no GPU work)
|
|
||||||
if (m2Renderer) {
|
|
||||||
int loadedDoodads = 0;
|
int loadedDoodads = 0;
|
||||||
int skippedDedup = 0;
|
int skippedDedup = 0;
|
||||||
for (const auto& p : pending->m2Placements) {
|
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)) {
|
if (p.uniqueId != 0 && placedDoodadIds.count(p.uniqueId)) {
|
||||||
skippedDedup++;
|
skippedDedup++;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
uint32_t instId = m2Renderer->createInstance(p.modelId, p.position, p.rotation, p.scale);
|
uint32_t instId = m2Renderer->createInstance(p.modelId, p.position, p.rotation, p.scale);
|
||||||
if (instId) {
|
if (instId) {
|
||||||
ft.m2InstanceIds.push_back(instId);
|
m2InstanceIds.push_back(instId);
|
||||||
if (p.uniqueId != 0) {
|
if (p.uniqueId != 0) {
|
||||||
placedDoodadIds.insert(p.uniqueId);
|
placedDoodadIds.insert(p.uniqueId);
|
||||||
ft.tileUniqueIds.push_back(p.uniqueId);
|
tileUniqueIds.push_back(p.uniqueId);
|
||||||
}
|
}
|
||||||
loadedDoodads++;
|
loadedDoodads++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
LOG_DEBUG(" Loaded doodads for tile [", x, ",", y, "]: ",
|
LOG_DEBUG(" Loaded doodads for tile [", x, ",", y, "]: ",
|
||||||
loadedDoodads, " instances (", ft.uploadedM2ModelIds.size(), " new models, ",
|
loadedDoodads, " instances (", uploadedModelIds.size(), " new models, ",
|
||||||
skippedDedup, " dedup skipped)");
|
skippedDedup, " dedup skipped)");
|
||||||
}
|
}
|
||||||
ft.phase = FinalizationPhase::WMO_MODELS;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
case FinalizationPhase::WMO_MODELS: {
|
// Upload WMO models to GPU and create instances
|
||||||
// Upload ONE WMO model per call
|
|
||||||
if (wmoRenderer && assetManager) {
|
if (wmoRenderer && assetManager) {
|
||||||
|
// WMORenderer may be initialized before assets are ready; always re-pass assets.
|
||||||
wmoRenderer->initialize(nullptr, VK_NULL_HANDLE, 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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ft.wmoModelIndex = 0; // Reset for WMO_INSTANCES phase iteration
|
|
||||||
ft.phase = FinalizationPhase::WMO_INSTANCES;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
case FinalizationPhase::WMO_INSTANCES: {
|
|
||||||
// Create all WMO instances + load WMO liquids
|
|
||||||
if (wmoRenderer) {
|
|
||||||
int loadedWMOs = 0;
|
int loadedWMOs = 0;
|
||||||
int loadedLiquids = 0;
|
int loadedLiquids = 0;
|
||||||
int skippedWmoDedup = 0;
|
int skippedWmoDedup = 0;
|
||||||
for (auto& wmoReady : pending->wmoModels) {
|
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)) {
|
if (wmoReady.uniqueId != 0 && placedWmoIds.count(wmoReady.uniqueId)) {
|
||||||
skippedWmoDedup++;
|
skippedWmoDedup++;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (wmoRenderer->loadModel(wmoReady.model, wmoReady.modelId)) {
|
||||||
uint32_t wmoInstId = wmoRenderer->createInstance(wmoReady.modelId, wmoReady.position, wmoReady.rotation);
|
uint32_t wmoInstId = wmoRenderer->createInstance(wmoReady.modelId, wmoReady.position, wmoReady.rotation);
|
||||||
if (wmoInstId) {
|
if (wmoInstId) {
|
||||||
ft.wmoInstanceIds.push_back(wmoInstId);
|
wmoInstanceIds.push_back(wmoInstId);
|
||||||
if (wmoReady.uniqueId != 0) {
|
if (wmoReady.uniqueId != 0) {
|
||||||
placedWmoIds.insert(wmoReady.uniqueId);
|
placedWmoIds.insert(wmoReady.uniqueId);
|
||||||
ft.tileWmoUniqueIds.push_back(wmoReady.uniqueId);
|
tileWmoUniqueIds.push_back(wmoReady.uniqueId);
|
||||||
}
|
}
|
||||||
loadedWMOs++;
|
loadedWMOs++;
|
||||||
|
|
||||||
// Load WMO liquids (canals, pools, etc.)
|
// Load WMO liquids (canals, pools, etc.)
|
||||||
if (waterRenderer) {
|
if (waterRenderer) {
|
||||||
|
// Compute the same model matrix as WMORenderer uses
|
||||||
glm::mat4 modelMatrix = glm::mat4(1.0f);
|
glm::mat4 modelMatrix = glm::mat4(1.0f);
|
||||||
modelMatrix = glm::translate(modelMatrix, wmoReady.position);
|
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.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.y, glm::vec3(0.0f, 1.0f, 0.0f));
|
||||||
modelMatrix = glm::rotate(modelMatrix, wmoReady.rotation.x, glm::vec3(1.0f, 0.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) {
|
for (const auto& group : wmoReady.model.groups) {
|
||||||
if (group.liquid.hasLiquid()) {
|
if (group.liquid.hasLiquid()) {
|
||||||
waterRenderer->loadFromWMO(group.liquid, modelMatrix, wmoInstId);
|
waterRenderer->loadFromWMO(group.liquid, modelMatrix, wmoInstId);
|
||||||
|
|
@ -813,6 +827,7 @@ bool TerrainManager::advanceFinalization(FinalizingTile& ft) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
if (loadedWMOs > 0 || skippedWmoDedup > 0) {
|
if (loadedWMOs > 0 || skippedWmoDedup > 0) {
|
||||||
LOG_DEBUG(" Loaded WMOs for tile [", x, ",", y, "]: ",
|
LOG_DEBUG(" Loaded WMOs for tile [", x, ",", y, "]: ",
|
||||||
loadedWMOs, " instances, ", skippedWmoDedup, " dedup skipped");
|
loadedWMOs, " instances, ", skippedWmoDedup, " dedup skipped");
|
||||||
|
|
@ -820,102 +835,49 @@ bool TerrainManager::advanceFinalization(FinalizingTile& ft) {
|
||||||
if (loadedLiquids > 0) {
|
if (loadedLiquids > 0) {
|
||||||
LOG_DEBUG(" Loaded WMO liquids for tile [", x, ",", y, "]: ", loadedLiquids);
|
LOG_DEBUG(" Loaded WMO liquids for tile [", x, ",", y, "]: ", loadedLiquids);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
ft.phase = FinalizationPhase::WMO_DOODADS;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
case FinalizationPhase::WMO_DOODADS: {
|
// Upload WMO doodad M2 models
|
||||||
// Upload ONE WMO doodad M2 per call
|
if (m2Renderer) {
|
||||||
if (m2Renderer && ft.wmoDoodadIndex < pending->wmoDoodads.size()) {
|
for (auto& doodad : pending->wmoDoodads) {
|
||||||
auto& doodad = pending->wmoDoodads[ft.wmoDoodadIndex];
|
|
||||||
m2Renderer->loadModel(doodad.model, doodad.modelId);
|
m2Renderer->loadModel(doodad.model, doodad.modelId);
|
||||||
uint32_t wmoDoodadInstId = m2Renderer->createInstanceWithMatrix(
|
uint32_t wmoDoodadInstId = m2Renderer->createInstanceWithMatrix(
|
||||||
doodad.modelId, doodad.modelMatrix, doodad.worldPosition);
|
doodad.modelId, doodad.modelMatrix, doodad.worldPosition);
|
||||||
if (wmoDoodadInstId) ft.m2InstanceIds.push_back(wmoDoodadInstId);
|
if (wmoDoodadInstId) m2InstanceIds.push_back(wmoDoodadInstId);
|
||||||
ft.wmoDoodadIndex++;
|
|
||||||
if (ft.wmoDoodadIndex < pending->wmoDoodads.size()) return false;
|
|
||||||
}
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ft.phase = FinalizationPhase::AMBIENT;
|
if (loadedWMOs > 0) {
|
||||||
return false;
|
LOG_DEBUG(" Loaded WMOs for tile [", x, ",", y, "]: ", loadedWMOs);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
case FinalizationPhase::AMBIENT: {
|
// Register ambient sound emitters with ambient sound manager
|
||||||
// Register ambient sound emitters
|
|
||||||
if (ambientSoundManager && !pending->ambientEmitters.empty()) {
|
if (ambientSoundManager && !pending->ambientEmitters.empty()) {
|
||||||
for (const auto& emitter : pending->ambientEmitters) {
|
for (const auto& emitter : pending->ambientEmitters) {
|
||||||
|
// Cast uint32_t type to AmbientSoundManager::AmbientType enum
|
||||||
auto type = static_cast<audio::AmbientSoundManager::AmbientType>(emitter.type);
|
auto type = static_cast<audio::AmbientSoundManager::AmbientType>(emitter.type);
|
||||||
ambientSoundManager->addEmitter(emitter.position, type);
|
ambientSoundManager->addEmitter(emitter.position, type);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Commit tile to loadedTiles
|
// Create tile entry
|
||||||
auto tile = std::make_unique<TerrainTile>();
|
auto tile = std::make_unique<TerrainTile>();
|
||||||
tile->coord = coord;
|
tile->coord = coord;
|
||||||
tile->terrain = std::move(pending->terrain);
|
tile->terrain = std::move(pending->terrain);
|
||||||
tile->mesh = std::move(pending->mesh);
|
tile->mesh = std::move(pending->mesh);
|
||||||
tile->loaded = true;
|
tile->loaded = true;
|
||||||
tile->m2InstanceIds = std::move(ft.m2InstanceIds);
|
tile->m2InstanceIds = std::move(m2InstanceIds);
|
||||||
tile->wmoInstanceIds = std::move(ft.wmoInstanceIds);
|
tile->wmoInstanceIds = std::move(wmoInstanceIds);
|
||||||
tile->wmoUniqueIds = std::move(ft.tileWmoUniqueIds);
|
tile->wmoUniqueIds = std::move(tileWmoUniqueIds);
|
||||||
tile->doodadUniqueIds = std::move(ft.tileUniqueIds);
|
tile->doodadUniqueIds = std::move(tileUniqueIds);
|
||||||
|
|
||||||
|
// Calculate world bounds
|
||||||
getTileBounds(coord, tile->minX, tile->minY, tile->maxX, tile->maxY);
|
getTileBounds(coord, tile->minX, tile->minY, tile->maxX, tile->maxY);
|
||||||
|
|
||||||
loadedTiles[coord] = std::move(tile);
|
loadedTiles[coord] = std::move(tile);
|
||||||
putCachedTile(pending);
|
putCachedTile(pending);
|
||||||
|
|
||||||
// Now safe to remove from pendingTiles (tile is in loadedTiles)
|
|
||||||
{
|
|
||||||
std::lock_guard<std::mutex> lock(queueMutex);
|
|
||||||
pendingTiles.erase(coord);
|
|
||||||
}
|
|
||||||
|
|
||||||
LOG_DEBUG(" Finalized tile [", x, ",", y, "]");
|
LOG_DEBUG(" Finalized tile [", x, ",", y, "]");
|
||||||
|
|
||||||
ft.phase = FinalizationPhase::DONE;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
case FinalizationPhase::DONE:
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void TerrainManager::workerLoop() {
|
void TerrainManager::workerLoop() {
|
||||||
|
|
@ -965,59 +927,80 @@ void TerrainManager::processReadyTiles() {
|
||||||
// Taxi mode gets a slightly larger budget to avoid visible late-pop terrain/models.
|
// Taxi mode gets a slightly larger budget to avoid visible late-pop terrain/models.
|
||||||
const float timeBudgetMs = taxiStreamingMode_ ? 8.0f : 5.0f;
|
const float timeBudgetMs = taxiStreamingMode_ ? 8.0f : 5.0f;
|
||||||
auto startTime = std::chrono::high_resolution_clock::now();
|
auto startTime = std::chrono::high_resolution_clock::now();
|
||||||
|
int processed = 0;
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
std::shared_ptr<PendingTile> pending;
|
||||||
|
|
||||||
// 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);
|
std::lock_guard<std::mutex> lock(queueMutex);
|
||||||
while (!readyQueue.empty()) {
|
if (readyQueue.empty()) {
|
||||||
auto pending = readyQueue.front();
|
break;
|
||||||
|
}
|
||||||
|
pending = readyQueue.front();
|
||||||
readyQueue.pop();
|
readyQueue.pop();
|
||||||
if (pending) {
|
|
||||||
FinalizingTile ft;
|
|
||||||
ft.pending = std::move(pending);
|
|
||||||
finalizingTiles_.push_back(std::move(ft));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Finalize one complete tile per iteration, check budget between tiles.
|
if (pending) {
|
||||||
// Each tile runs through all phases before moving to the next — this
|
TileCoord coord = pending->coord;
|
||||||
// avoids subtle state issues from spreading GPU uploads across frames.
|
|
||||||
while (!finalizingTiles_.empty()) {
|
finalizeTile(pending);
|
||||||
auto& ft = finalizingTiles_.front();
|
|
||||||
while (!advanceFinalization(ft)) {}
|
|
||||||
finalizingTiles_.pop_front();
|
|
||||||
|
|
||||||
auto now = std::chrono::high_resolution_clock::now();
|
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();
|
float elapsedMs = std::chrono::duration<float, std::milli>(now - startTime).count();
|
||||||
if (elapsedMs >= timeBudgetMs) {
|
if (elapsedMs >= timeBudgetMs) {
|
||||||
|
if (processed > 1) {
|
||||||
|
LOG_DEBUG("Processed ", processed, " tiles in ", elapsedMs, "ms (budget: ", timeBudgetMs, "ms)");
|
||||||
|
}
|
||||||
break;
|
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() {
|
void TerrainManager::processAllReadyTiles() {
|
||||||
// Move all ready tiles into finalizing deque
|
while (true) {
|
||||||
// Keep in pendingTiles until committed (same as processReadyTiles)
|
std::shared_ptr<PendingTile> pending;
|
||||||
{
|
{
|
||||||
std::lock_guard<std::mutex> lock(queueMutex);
|
std::lock_guard<std::mutex> lock(queueMutex);
|
||||||
while (!readyQueue.empty()) {
|
if (readyQueue.empty()) break;
|
||||||
auto pending = readyQueue.front();
|
pending = readyQueue.front();
|
||||||
readyQueue.pop();
|
readyQueue.pop();
|
||||||
|
}
|
||||||
if (pending) {
|
if (pending) {
|
||||||
FinalizingTile ft;
|
TileCoord coord = pending->coord;
|
||||||
ft.pending = std::move(pending);
|
finalizeTile(pending);
|
||||||
finalizingTiles_.push_back(std::move(ft));
|
{
|
||||||
|
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) {
|
std::shared_ptr<PendingTile> TerrainManager::getCachedTile(const TileCoord& coord) {
|
||||||
|
|
@ -1116,31 +1099,6 @@ void TerrainManager::unloadTile(int x, int y) {
|
||||||
pendingTiles.erase(coord);
|
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);
|
auto it = loadedTiles.find(coord);
|
||||||
if (it == loadedTiles.end()) {
|
if (it == loadedTiles.end()) {
|
||||||
return;
|
return;
|
||||||
|
|
@ -1209,7 +1167,6 @@ void TerrainManager::unloadAll() {
|
||||||
while (!readyQueue.empty()) readyQueue.pop();
|
while (!readyQueue.empty()) readyQueue.pop();
|
||||||
}
|
}
|
||||||
pendingTiles.clear();
|
pendingTiles.clear();
|
||||||
finalizingTiles_.clear();
|
|
||||||
placedDoodadIds.clear();
|
placedDoodadIds.clear();
|
||||||
|
|
||||||
LOG_INFO("Unloading all terrain tiles");
|
LOG_INFO("Unloading all terrain tiles");
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue