mirror of
https://github.com/Kelsidavis/WoWee.git
synced 2026-03-22 23:30:14 +00:00
make start on ubuntu intel video cards
This commit is contained in:
parent
7565019dc9
commit
027640189a
8 changed files with 170 additions and 44 deletions
|
|
@ -399,9 +399,10 @@ enum class MovementFlags : uint32_t {
|
|||
WATER_WALK = 0x00008000, // Walk on water surface
|
||||
SWIMMING = 0x00200000,
|
||||
ASCENDING = 0x00400000,
|
||||
CAN_FLY = 0x00800000,
|
||||
FLYING = 0x01000000,
|
||||
HOVER = 0x02000000,
|
||||
DESCENDING = 0x00800000,
|
||||
CAN_FLY = 0x01000000,
|
||||
FLYING = 0x02000000,
|
||||
HOVER = 0x40000000,
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -416,6 +416,13 @@ private:
|
|||
static constexpr uint32_t MAX_MATERIAL_SETS = 8192;
|
||||
static constexpr uint32_t MAX_BONE_SETS = 8192;
|
||||
|
||||
// Dummy identity bone buffer + descriptor set for non-animated models.
|
||||
// The pipeline layout declares set 2 (bones) and some drivers (Intel ANV)
|
||||
// require all declared sets to be bound even when the shader doesn't access them.
|
||||
::VkBuffer dummyBoneBuffer_ = VK_NULL_HANDLE;
|
||||
VmaAllocation dummyBoneAlloc_ = VK_NULL_HANDLE;
|
||||
VkDescriptorSet dummyBoneSet_ = VK_NULL_HANDLE;
|
||||
|
||||
// Dynamic ribbon vertex buffer (CPU-written triangle strip)
|
||||
static constexpr size_t MAX_RIBBON_VERTS = 2048; // 9 floats each
|
||||
::VkBuffer ribbonVB_ = VK_NULL_HANDLE;
|
||||
|
|
|
|||
|
|
@ -394,6 +394,11 @@ private:
|
|||
std::unordered_set<uint32_t> uploadedM2Ids_;
|
||||
std::mutex uploadedM2IdsMutex_;
|
||||
|
||||
// Cross-tile dedup for WMO doodad preparation on background workers
|
||||
// (prevents re-parsing thousands of doodads when same WMO spans multiple tiles)
|
||||
std::unordered_set<uint32_t> preparedWmoUniqueIds_;
|
||||
std::mutex preparedWmoUniqueIdsMutex_;
|
||||
|
||||
// Dedup set for doodad placements across tile boundaries
|
||||
std::unordered_set<uint32_t> placedDoodadIds;
|
||||
|
||||
|
|
|
|||
|
|
@ -3671,13 +3671,13 @@ void Application::spawnPlayerCharacter() {
|
|||
uint32_t raceId = charSectionsDbc->getUInt32(r, csL ? (*csL)["RaceID"] : 1);
|
||||
uint32_t sexId = charSectionsDbc->getUInt32(r, csL ? (*csL)["SexID"] : 2);
|
||||
uint32_t baseSection = charSectionsDbc->getUInt32(r, csL ? (*csL)["BaseSection"] : 3);
|
||||
uint32_t variationIndex = charSectionsDbc->getUInt32(r, csL ? (*csL)["VariationIndex"] : 4);
|
||||
uint32_t colorIndex = charSectionsDbc->getUInt32(r, csL ? (*csL)["ColorIndex"] : 5);
|
||||
uint32_t variationIndex = charSectionsDbc->getUInt32(r, csL ? (*csL)["VariationIndex"] : 8);
|
||||
uint32_t colorIndex = charSectionsDbc->getUInt32(r, csL ? (*csL)["ColorIndex"] : 9);
|
||||
|
||||
if (raceId != targetRaceId || sexId != targetSexId) continue;
|
||||
|
||||
// Section 0 = skin: match by colorIndex = skin byte
|
||||
const uint32_t csTex1 = csL ? (*csL)["Texture1"] : 6;
|
||||
const uint32_t csTex1 = csL ? (*csL)["Texture1"] : 4;
|
||||
if (baseSection == 0 && !foundSkin && colorIndex == charSkinId) {
|
||||
std::string tex1 = charSectionsDbc->getString(r, csTex1);
|
||||
if (!tex1.empty()) {
|
||||
|
|
@ -5353,9 +5353,9 @@ void Application::buildCharSectionsCache() {
|
|||
uint32_t raceF = csL ? (*csL)["RaceID"] : 1;
|
||||
uint32_t sexF = csL ? (*csL)["SexID"] : 2;
|
||||
uint32_t secF = csL ? (*csL)["BaseSection"] : 3;
|
||||
uint32_t varF = csL ? (*csL)["VariationIndex"] : 4;
|
||||
uint32_t colF = csL ? (*csL)["ColorIndex"] : 5;
|
||||
uint32_t tex1F = csL ? (*csL)["Texture1"] : 6;
|
||||
uint32_t varF = csL ? (*csL)["VariationIndex"] : 8;
|
||||
uint32_t colF = csL ? (*csL)["ColorIndex"] : 9;
|
||||
uint32_t tex1F = csL ? (*csL)["Texture1"] : 4;
|
||||
for (uint32_t r = 0; r < dbc->getRecordCount(); r++) {
|
||||
uint32_t race = dbc->getUInt32(r, raceF);
|
||||
uint32_t sex = dbc->getUInt32(r, sexF);
|
||||
|
|
@ -5962,9 +5962,9 @@ void Application::spawnOnlineCreature(uint64_t guid, uint32_t displayId, float x
|
|||
if (rId != npcRace || sId != npcSex) continue;
|
||||
|
||||
uint32_t section = csDbc->getUInt32(r, csL ? (*csL)["BaseSection"] : 3);
|
||||
uint32_t variation = csDbc->getUInt32(r, csL ? (*csL)["VariationIndex"] : 4);
|
||||
uint32_t color = csDbc->getUInt32(r, csL ? (*csL)["ColorIndex"] : 5);
|
||||
uint32_t tex1F = csL ? (*csL)["Texture1"] : 6;
|
||||
uint32_t variation = csDbc->getUInt32(r, csL ? (*csL)["VariationIndex"] : 8);
|
||||
uint32_t color = csDbc->getUInt32(r, csL ? (*csL)["ColorIndex"] : 9);
|
||||
uint32_t tex1F = csL ? (*csL)["Texture1"] : 4;
|
||||
|
||||
if (section == 0 && def.basePath.empty() && color == npcSkin) {
|
||||
def.basePath = csDbc->getString(r, tex1F);
|
||||
|
|
@ -6080,11 +6080,11 @@ void Application::spawnOnlineCreature(uint64_t guid, uint32_t displayId, float x
|
|||
if (raceId != targetRace || sexId != targetSex) continue;
|
||||
uint32_t section = csDbc->getUInt32(r, csL ? (*csL)["BaseSection"] : 3);
|
||||
if (section != 3) continue;
|
||||
uint32_t variation = csDbc->getUInt32(r, csL ? (*csL)["VariationIndex"] : 4);
|
||||
uint32_t colorIdx = csDbc->getUInt32(r, csL ? (*csL)["ColorIndex"] : 5);
|
||||
uint32_t variation = csDbc->getUInt32(r, csL ? (*csL)["VariationIndex"] : 8);
|
||||
uint32_t colorIdx = csDbc->getUInt32(r, csL ? (*csL)["ColorIndex"] : 9);
|
||||
if (variation != static_cast<uint32_t>(extraCopy.hairStyleId)) continue;
|
||||
if (colorIdx != static_cast<uint32_t>(extraCopy.hairColorId)) continue;
|
||||
def.hairTexturePath = csDbc->getString(r, csL ? (*csL)["Texture1"] : 6);
|
||||
def.hairTexturePath = csDbc->getString(r, csL ? (*csL)["Texture1"] : 4);
|
||||
break;
|
||||
}
|
||||
|
||||
|
|
@ -7193,7 +7193,7 @@ void Application::spawnOnlinePlayer(uint64_t guid,
|
|||
const auto* csL = pipeline::getActiveDBCLayout() ? pipeline::getActiveDBCLayout()->getLayout("CharSections") : nullptr;
|
||||
uint32_t targetRaceId = raceId;
|
||||
uint32_t targetSexId = genderId;
|
||||
const uint32_t csTex1 = csL ? (*csL)["Texture1"] : 6;
|
||||
const uint32_t csTex1 = csL ? (*csL)["Texture1"] : 4;
|
||||
|
||||
bool foundSkin = false;
|
||||
bool foundUnderwear = false;
|
||||
|
|
@ -7204,8 +7204,8 @@ void Application::spawnOnlinePlayer(uint64_t guid,
|
|||
uint32_t rRace = charSectionsDbc->getUInt32(r, csL ? (*csL)["RaceID"] : 1);
|
||||
uint32_t rSex = charSectionsDbc->getUInt32(r, csL ? (*csL)["SexID"] : 2);
|
||||
uint32_t baseSection = charSectionsDbc->getUInt32(r, csL ? (*csL)["BaseSection"] : 3);
|
||||
uint32_t variationIndex = charSectionsDbc->getUInt32(r, csL ? (*csL)["VariationIndex"] : 4);
|
||||
uint32_t colorIndex = charSectionsDbc->getUInt32(r, csL ? (*csL)["ColorIndex"] : 5);
|
||||
uint32_t variationIndex = charSectionsDbc->getUInt32(r, csL ? (*csL)["VariationIndex"] : 8);
|
||||
uint32_t colorIndex = charSectionsDbc->getUInt32(r, csL ? (*csL)["ColorIndex"] : 9);
|
||||
|
||||
if (rRace != targetRaceId || rSex != targetSexId) continue;
|
||||
|
||||
|
|
@ -8189,9 +8189,9 @@ void Application::processCreatureSpawnQueue(bool unlimited) {
|
|||
uint32_t sId = csDbc->getUInt32(r, csL ? (*csL)["SexID"] : 2);
|
||||
if (rId != nRace || sId != nSex) continue;
|
||||
uint32_t section = csDbc->getUInt32(r, csL ? (*csL)["BaseSection"] : 3);
|
||||
uint32_t variation = csDbc->getUInt32(r, csL ? (*csL)["VariationIndex"] : 4);
|
||||
uint32_t color = csDbc->getUInt32(r, csL ? (*csL)["ColorIndex"] : 5);
|
||||
uint32_t tex1F = csL ? (*csL)["Texture1"] : 6;
|
||||
uint32_t variation = csDbc->getUInt32(r, csL ? (*csL)["VariationIndex"] : 8);
|
||||
uint32_t color = csDbc->getUInt32(r, csL ? (*csL)["ColorIndex"] : 9);
|
||||
uint32_t tex1F = csL ? (*csL)["Texture1"] : 4;
|
||||
if (section == 0 && color == nSkin) {
|
||||
std::string t = csDbc->getString(r, tex1F);
|
||||
if (!t.empty()) displaySkinPaths.push_back(t);
|
||||
|
|
|
|||
|
|
@ -832,7 +832,7 @@ void MovementPacket::writeMovementPayload(network::Packet& packet, const Movemen
|
|||
packet.writeUInt8(static_cast<uint8_t>(info.transportSeat));
|
||||
|
||||
// Optional second transport time for interpolated movement.
|
||||
if (info.flags2 & 0x0200) {
|
||||
if (info.flags2 & 0x0400) { // MOVEMENTFLAG2_INTERPOLATED_MOVEMENT
|
||||
packet.writeUInt32(info.transportTime2);
|
||||
}
|
||||
}
|
||||
|
|
@ -994,26 +994,27 @@ bool UpdateObjectParser::parseMovementBlock(network::Packet& packet, UpdateBlock
|
|||
LOG_DEBUG(" OnTransport: guid=0x", std::hex, block.transportGuid, std::dec,
|
||||
" offset=(", block.transportX, ", ", block.transportY, ", ", block.transportZ, ")");
|
||||
|
||||
if (moveFlags2 & 0x0200) { // MOVEMENTFLAG2_INTERPOLATED_MOVEMENT
|
||||
if (moveFlags2 & 0x0400) { // MOVEMENTFLAG2_INTERPOLATED_MOVEMENT
|
||||
if (rem() < 4) return false;
|
||||
/*uint32_t tTime2 =*/ packet.readUInt32();
|
||||
}
|
||||
}
|
||||
|
||||
// Swimming/flying pitch
|
||||
// WotLK 3.3.5a movement flags relevant here:
|
||||
// WotLK 3.3.5a movement flags (wire format):
|
||||
// SWIMMING = 0x00200000
|
||||
// FLYING = 0x01000000 (player/creature actively flying)
|
||||
// SPLINE_ELEVATION = 0x02000000 (smooth vertical spline offset — no pitch field)
|
||||
// CAN_FLY = 0x01000000 (ability to fly — no pitch field)
|
||||
// FLYING = 0x02000000 (actively flying — has pitch field)
|
||||
// SPLINE_ELEVATION = 0x04000000 (smooth vertical spline offset)
|
||||
// MovementFlags2:
|
||||
// MOVEMENTFLAG2_ALWAYS_ALLOW_PITCHING = 0x0010
|
||||
// MOVEMENTFLAG2_ALWAYS_ALLOW_PITCHING = 0x0020
|
||||
//
|
||||
// Pitch is present when SWIMMING or FLYING are set, or the always-allow flag is set.
|
||||
// The original code checked 0x02000000 (SPLINE_ELEVATION) which neither covers SWIMMING
|
||||
// nor FLYING, causing misaligned reads for swimming/flying entities in SMSG_UPDATE_OBJECT.
|
||||
// Note: CAN_FLY (0x01000000) does NOT gate pitch; only FLYING (0x02000000) does.
|
||||
// (TBC uses 0x01000000 for FLYING — see TbcMoveFlags in packet_parsers_tbc.cpp.)
|
||||
if ((moveFlags & 0x00200000) /* SWIMMING */ ||
|
||||
(moveFlags & 0x01000000) /* FLYING */ ||
|
||||
(moveFlags2 & 0x0010) /* MOVEMENTFLAG2_ALWAYS_ALLOW_PITCHING */) {
|
||||
(moveFlags & 0x02000000) /* FLYING */ ||
|
||||
(moveFlags2 & 0x0020) /* MOVEMENTFLAG2_ALWAYS_ALLOW_PITCHING */) {
|
||||
if (rem() < 4) return false;
|
||||
/*float pitch =*/ packet.readFloat();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -366,6 +366,41 @@ bool M2Renderer::initialize(VkContext* ctx, VkDescriptorSetLayout perFrameLayout
|
|||
vkCreateDescriptorPool(device, &ci, nullptr, &boneDescPool_);
|
||||
}
|
||||
|
||||
// Create a small identity-bone SSBO + descriptor set so that non-animated
|
||||
// draws always have a valid set 2 bound. The Intel ANV driver segfaults
|
||||
// on vkCmdDrawIndexed when a declared descriptor set slot is unbound.
|
||||
{
|
||||
// Single identity matrix (bone 0 = identity)
|
||||
glm::mat4 identity(1.0f);
|
||||
VkBufferCreateInfo bci{VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO};
|
||||
bci.size = sizeof(glm::mat4);
|
||||
bci.usage = VK_BUFFER_USAGE_STORAGE_BUFFER_BIT;
|
||||
VmaAllocationCreateInfo aci{};
|
||||
aci.usage = VMA_MEMORY_USAGE_CPU_TO_GPU;
|
||||
aci.flags = VMA_ALLOCATION_CREATE_MAPPED_BIT;
|
||||
VmaAllocationInfo allocInfo{};
|
||||
vmaCreateBuffer(ctx->getAllocator(), &bci, &aci,
|
||||
&dummyBoneBuffer_, &dummyBoneAlloc_, &allocInfo);
|
||||
if (allocInfo.pMappedData) {
|
||||
memcpy(allocInfo.pMappedData, &identity, sizeof(identity));
|
||||
}
|
||||
|
||||
dummyBoneSet_ = allocateBoneSet();
|
||||
if (dummyBoneSet_) {
|
||||
VkDescriptorBufferInfo bufInfo{};
|
||||
bufInfo.buffer = dummyBoneBuffer_;
|
||||
bufInfo.offset = 0;
|
||||
bufInfo.range = sizeof(glm::mat4);
|
||||
VkWriteDescriptorSet write{VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET};
|
||||
write.dstSet = dummyBoneSet_;
|
||||
write.dstBinding = 0;
|
||||
write.descriptorCount = 1;
|
||||
write.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER;
|
||||
write.pBufferInfo = &bufInfo;
|
||||
vkUpdateDescriptorSets(device, 1, &write, 0, nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
// --- Pipeline layouts ---
|
||||
|
||||
// Main M2 pipeline layout: set 0 = perFrame, set 1 = material, set 2 = bones
|
||||
|
|
@ -746,6 +781,9 @@ void M2Renderer::shutdown() {
|
|||
if (ribbonPipelineLayout_) { vkDestroyPipelineLayout(device, ribbonPipelineLayout_, nullptr); ribbonPipelineLayout_ = VK_NULL_HANDLE; }
|
||||
|
||||
// Destroy descriptor pools and layouts
|
||||
if (dummyBoneBuffer_) { vmaDestroyBuffer(alloc, dummyBoneBuffer_, dummyBoneAlloc_); dummyBoneBuffer_ = VK_NULL_HANDLE; }
|
||||
// dummyBoneSet_ is freed implicitly when boneDescPool_ is destroyed
|
||||
dummyBoneSet_ = VK_NULL_HANDLE;
|
||||
if (materialDescPool_) { vkDestroyDescriptorPool(device, materialDescPool_, nullptr); materialDescPool_ = VK_NULL_HANDLE; }
|
||||
if (boneDescPool_) { vkDestroyDescriptorPool(device, boneDescPool_, nullptr); boneDescPool_ = VK_NULL_HANDLE; }
|
||||
if (materialSetLayout_) { vkDestroyDescriptorSetLayout(device, materialSetLayout_, nullptr); materialSetLayout_ = VK_NULL_HANDLE; }
|
||||
|
|
@ -812,7 +850,11 @@ VkDescriptorSet M2Renderer::allocateMaterialSet() {
|
|||
ai.descriptorSetCount = 1;
|
||||
ai.pSetLayouts = &materialSetLayout_;
|
||||
VkDescriptorSet set = VK_NULL_HANDLE;
|
||||
vkAllocateDescriptorSets(vkCtx_->getDevice(), &ai, &set);
|
||||
VkResult result = vkAllocateDescriptorSets(vkCtx_->getDevice(), &ai, &set);
|
||||
if (result != VK_SUCCESS) {
|
||||
LOG_ERROR("M2Renderer: material descriptor set allocation failed (", result, ")");
|
||||
return VK_NULL_HANDLE;
|
||||
}
|
||||
return set;
|
||||
}
|
||||
|
||||
|
|
@ -822,7 +864,11 @@ VkDescriptorSet M2Renderer::allocateBoneSet() {
|
|||
ai.descriptorSetCount = 1;
|
||||
ai.pSetLayouts = &boneSetLayout_;
|
||||
VkDescriptorSet set = VK_NULL_HANDLE;
|
||||
vkAllocateDescriptorSets(vkCtx_->getDevice(), &ai, &set);
|
||||
VkResult result = vkAllocateDescriptorSets(vkCtx_->getDevice(), &ai, &set);
|
||||
if (result != VK_SUCCESS) {
|
||||
LOG_ERROR("M2Renderer: bone descriptor set allocation failed (", result, ")");
|
||||
return VK_NULL_HANDLE;
|
||||
}
|
||||
return set;
|
||||
}
|
||||
|
||||
|
|
@ -1303,6 +1349,10 @@ bool M2Renderer::loadModel(const pipeline::M2Model& model, uint32_t modelId) {
|
|||
gpuModel.indexBuffer = buf.buffer;
|
||||
gpuModel.indexAlloc = buf.allocation;
|
||||
}
|
||||
|
||||
if (!gpuModel.vertexBuffer || !gpuModel.indexBuffer) {
|
||||
LOG_ERROR("M2Renderer::loadModel: GPU buffer upload failed for model ", modelId);
|
||||
}
|
||||
}
|
||||
|
||||
// Load ALL textures from the model into a local vector.
|
||||
|
|
@ -1751,6 +1801,7 @@ bool M2Renderer::loadModel(const pipeline::M2Model& model, uint32_t modelId) {
|
|||
}
|
||||
|
||||
models[modelId] = std::move(gpuModel);
|
||||
spatialIndexDirty_ = true; // Map may have rehashed — refresh cachedModel pointers
|
||||
|
||||
LOG_DEBUG("Loaded M2 model: ", model.name, " (", models[modelId].vertexCount, " vertices, ",
|
||||
models[modelId].indexCount / 3, " triangles, ", models[modelId].batches.size(), " batches)");
|
||||
|
|
@ -2504,6 +2555,7 @@ void M2Renderer::render(VkCommandBuffer cmd, VkDescriptorSet perFrameSet, const
|
|||
|
||||
uint32_t currentModelId = UINT32_MAX;
|
||||
const M2ModelGPU* currentModel = nullptr;
|
||||
bool currentModelValid = false;
|
||||
|
||||
// State tracking
|
||||
VkPipeline currentPipeline = VK_NULL_HANDLE;
|
||||
|
|
@ -2519,6 +2571,12 @@ void M2Renderer::render(VkCommandBuffer cmd, VkDescriptorSet perFrameSet, const
|
|||
float fadeAlpha;
|
||||
};
|
||||
|
||||
// Validate per-frame descriptor set before any Vulkan commands
|
||||
if (!perFrameSet) {
|
||||
LOG_ERROR("M2Renderer::render: perFrameSet is VK_NULL_HANDLE — skipping M2 render");
|
||||
return;
|
||||
}
|
||||
|
||||
// Bind per-frame descriptor set (set 0) — shared across all draws
|
||||
vkCmdBindDescriptorSets(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS,
|
||||
pipelineLayout_, 0, 1, &perFrameSet, 0, nullptr);
|
||||
|
|
@ -2528,6 +2586,13 @@ void M2Renderer::render(VkCommandBuffer cmd, VkDescriptorSet perFrameSet, const
|
|||
currentPipeline = opaquePipeline_;
|
||||
bool opaquePass = true; // Pass 1 = opaque, pass 2 = transparent (set below for second pass)
|
||||
|
||||
// Bind dummy bone set (set 2) so non-animated draws have a valid binding.
|
||||
// Animated instances override this with their real bone set per-instance.
|
||||
if (dummyBoneSet_) {
|
||||
vkCmdBindDescriptorSets(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS,
|
||||
pipelineLayout_, 2, 1, &dummyBoneSet_, 0, nullptr);
|
||||
}
|
||||
|
||||
for (const auto& entry : sortedVisible_) {
|
||||
if (entry.index >= instances.size()) continue;
|
||||
auto& instance = instances[entry.index];
|
||||
|
|
@ -2535,14 +2600,17 @@ void M2Renderer::render(VkCommandBuffer cmd, VkDescriptorSet perFrameSet, const
|
|||
// Bind vertex + index buffers once per model group
|
||||
if (entry.modelId != currentModelId) {
|
||||
currentModelId = entry.modelId;
|
||||
currentModelValid = false;
|
||||
auto mdlIt = models.find(currentModelId);
|
||||
if (mdlIt == models.end()) continue;
|
||||
currentModel = &mdlIt->second;
|
||||
if (!currentModel->vertexBuffer) continue;
|
||||
if (!currentModel->vertexBuffer || !currentModel->indexBuffer) continue;
|
||||
currentModelValid = true;
|
||||
VkDeviceSize offset = 0;
|
||||
vkCmdBindVertexBuffers(cmd, 0, 1, ¤tModel->vertexBuffer, &offset);
|
||||
vkCmdBindIndexBuffer(cmd, currentModel->indexBuffer, 0, VK_INDEX_TYPE_UINT16);
|
||||
}
|
||||
if (!currentModelValid) continue;
|
||||
|
||||
const M2ModelGPU& model = *currentModel;
|
||||
|
||||
|
|
@ -2785,7 +2853,6 @@ void M2Renderer::render(VkCommandBuffer cmd, VkDescriptorSet perFrameSet, const
|
|||
continue;
|
||||
}
|
||||
vkCmdPushConstants(cmd, pipelineLayout_, VK_SHADER_STAGE_VERTEX_BIT, 0, sizeof(pc), &pc);
|
||||
|
||||
vkCmdDrawIndexed(cmd, batch.indexCount, 1, batch.indexStart, 0, 0);
|
||||
lastDrawCallCount++;
|
||||
}
|
||||
|
|
@ -2799,6 +2866,7 @@ void M2Renderer::render(VkCommandBuffer cmd, VkDescriptorSet perFrameSet, const
|
|||
|
||||
currentModelId = UINT32_MAX;
|
||||
currentModel = nullptr;
|
||||
currentModelValid = false;
|
||||
// Reset pipeline to opaque so the first transparent bind always sets explicitly
|
||||
currentPipeline = opaquePipeline_;
|
||||
|
||||
|
|
@ -2817,14 +2885,17 @@ void M2Renderer::render(VkCommandBuffer cmd, VkDescriptorSet perFrameSet, const
|
|||
// `!opaquePass && !rawTransparent → continue` handles opaque skipping)
|
||||
if (entry.modelId != currentModelId) {
|
||||
currentModelId = entry.modelId;
|
||||
currentModelValid = false;
|
||||
auto mdlIt = models.find(currentModelId);
|
||||
if (mdlIt == models.end()) continue;
|
||||
currentModel = &mdlIt->second;
|
||||
if (!currentModel->vertexBuffer) continue;
|
||||
if (!currentModel->vertexBuffer || !currentModel->indexBuffer) continue;
|
||||
currentModelValid = true;
|
||||
VkDeviceSize offset = 0;
|
||||
vkCmdBindVertexBuffers(cmd, 0, 1, ¤tModel->vertexBuffer, &offset);
|
||||
vkCmdBindIndexBuffer(cmd, currentModel->indexBuffer, 0, VK_INDEX_TYPE_UINT16);
|
||||
}
|
||||
if (!currentModelValid) continue;
|
||||
|
||||
const M2ModelGPU& model = *currentModel;
|
||||
|
||||
|
|
@ -4168,6 +4239,21 @@ void M2Renderer::clear() {
|
|||
}
|
||||
if (boneDescPool_) {
|
||||
vkResetDescriptorPool(device, boneDescPool_, 0);
|
||||
// Re-allocate the dummy bone set (invalidated by pool reset)
|
||||
dummyBoneSet_ = allocateBoneSet();
|
||||
if (dummyBoneSet_ && dummyBoneBuffer_) {
|
||||
VkDescriptorBufferInfo bufInfo{};
|
||||
bufInfo.buffer = dummyBoneBuffer_;
|
||||
bufInfo.offset = 0;
|
||||
bufInfo.range = sizeof(glm::mat4);
|
||||
VkWriteDescriptorSet write{VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET};
|
||||
write.dstSet = dummyBoneSet_;
|
||||
write.dstBinding = 0;
|
||||
write.descriptorCount = 1;
|
||||
write.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER;
|
||||
write.pBufferInfo = &bufInfo;
|
||||
vkUpdateDescriptorSets(device, 1, &write, 0, nullptr);
|
||||
}
|
||||
}
|
||||
}
|
||||
models.clear();
|
||||
|
|
|
|||
|
|
@ -562,7 +562,17 @@ std::shared_ptr<PendingTile> TerrainManager::prepareTile(int x, int y) {
|
|||
|
||||
// Pre-load WMO doodads (M2 models inside WMO)
|
||||
if (!workerRunning.load()) return nullptr;
|
||||
if (!wmoModel.doodadSets.empty() && !wmoModel.doodads.empty()) {
|
||||
|
||||
// Skip WMO doodads if this placement was already prepared by another tile's worker.
|
||||
// This prevents 15+ copies of Stormwind's ~6000 doodads from being parsed
|
||||
// simultaneously, which was the primary cause of OOM during world load.
|
||||
bool wmoAlreadyPrepared = false;
|
||||
if (placement.uniqueId != 0) {
|
||||
std::lock_guard<std::mutex> lock(preparedWmoUniqueIdsMutex_);
|
||||
wmoAlreadyPrepared = !preparedWmoUniqueIds_.insert(placement.uniqueId).second;
|
||||
}
|
||||
|
||||
if (!wmoAlreadyPrepared && !wmoModel.doodadSets.empty() && !wmoModel.doodads.empty()) {
|
||||
glm::mat4 wmoMatrix(1.0f);
|
||||
wmoMatrix = glm::translate(wmoMatrix, pos);
|
||||
wmoMatrix = glm::rotate(wmoMatrix, rot.z, glm::vec3(0, 0, 1));
|
||||
|
|
@ -575,6 +585,7 @@ std::shared_ptr<PendingTile> TerrainManager::prepareTile(int x, int y) {
|
|||
setsToLoad.push_back(placement.doodadSet);
|
||||
}
|
||||
std::unordered_set<uint32_t> loadedDoodadIndices;
|
||||
std::unordered_set<uint32_t> wmoPreparedModelIds; // within-WMO model dedup
|
||||
for (uint32_t setIdx : setsToLoad) {
|
||||
const auto& doodadSet = wmoModel.doodadSets[setIdx];
|
||||
for (uint32_t di = 0; di < doodadSet.count; di++) {
|
||||
|
|
@ -599,15 +610,16 @@ std::shared_ptr<PendingTile> TerrainManager::prepareTile(int x, int y) {
|
|||
|
||||
uint32_t doodadModelId = static_cast<uint32_t>(std::hash<std::string>{}(m2Path));
|
||||
|
||||
// Skip file I/O if model already uploaded from a previous tile
|
||||
// Skip file I/O if model already uploaded or already prepared within this WMO
|
||||
bool modelAlreadyUploaded = false;
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(uploadedM2IdsMutex_);
|
||||
modelAlreadyUploaded = uploadedM2Ids_.count(doodadModelId) > 0;
|
||||
}
|
||||
bool modelAlreadyPreparedInWmo = !wmoPreparedModelIds.insert(doodadModelId).second;
|
||||
|
||||
pipeline::M2Model m2Model;
|
||||
if (!modelAlreadyUploaded) {
|
||||
if (!modelAlreadyUploaded && !modelAlreadyPreparedInWmo) {
|
||||
std::vector<uint8_t> m2Data = assetManager->readFile(m2Path);
|
||||
if (m2Data.empty()) continue;
|
||||
|
||||
|
|
@ -1404,7 +1416,11 @@ void TerrainManager::unloadTile(int x, int y) {
|
|||
wmoRenderer->removeInstances(fit->wmoInstanceIds);
|
||||
}
|
||||
for (uint32_t uid : fit->tileUniqueIds) placedDoodadIds.erase(uid);
|
||||
for (uint32_t uid : fit->tileWmoUniqueIds) placedWmoIds.erase(uid);
|
||||
for (uint32_t uid : fit->tileWmoUniqueIds) {
|
||||
placedWmoIds.erase(uid);
|
||||
std::lock_guard<std::mutex> lock(preparedWmoUniqueIdsMutex_);
|
||||
preparedWmoUniqueIds_.erase(uid);
|
||||
}
|
||||
finalizingTiles_.erase(fit);
|
||||
return;
|
||||
}
|
||||
|
|
@ -1425,6 +1441,8 @@ void TerrainManager::unloadTile(int x, int y) {
|
|||
}
|
||||
for (uint32_t uid : tile->wmoUniqueIds) {
|
||||
placedWmoIds.erase(uid);
|
||||
std::lock_guard<std::mutex> lock(preparedWmoUniqueIdsMutex_);
|
||||
preparedWmoUniqueIds_.erase(uid);
|
||||
}
|
||||
|
||||
// Remove M2 doodad instances
|
||||
|
|
@ -1509,6 +1527,10 @@ void TerrainManager::unloadAll() {
|
|||
std::lock_guard<std::mutex> lock(uploadedM2IdsMutex_);
|
||||
uploadedM2Ids_.clear();
|
||||
}
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(preparedWmoUniqueIdsMutex_);
|
||||
preparedWmoUniqueIds_.clear();
|
||||
}
|
||||
|
||||
LOG_INFO("Unloading all terrain tiles");
|
||||
loadedTiles.clear();
|
||||
|
|
@ -1561,6 +1583,10 @@ void TerrainManager::softReset() {
|
|||
std::lock_guard<std::mutex> lock(uploadedM2IdsMutex_);
|
||||
uploadedM2Ids_.clear();
|
||||
}
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(preparedWmoUniqueIdsMutex_);
|
||||
preparedWmoUniqueIds_.clear();
|
||||
}
|
||||
|
||||
// Clear tile cache — keys are (x,y) without map name, so stale entries from
|
||||
// a different map with overlapping coordinates would produce wrong geometry.
|
||||
|
|
|
|||
|
|
@ -257,8 +257,8 @@ void CharacterCreateScreen::updateAppearanceRanges() {
|
|||
if (raceId != targetRaceId || sexId != targetSexId) continue;
|
||||
|
||||
uint32_t baseSection = dbc->getUInt32(r, csL ? (*csL)["BaseSection"] : 3);
|
||||
uint32_t variationIndex = dbc->getUInt32(r, csL ? (*csL)["VariationIndex"] : 4);
|
||||
uint32_t colorIndex = dbc->getUInt32(r, csL ? (*csL)["ColorIndex"] : 5);
|
||||
uint32_t variationIndex = dbc->getUInt32(r, csL ? (*csL)["VariationIndex"] : 8);
|
||||
uint32_t colorIndex = dbc->getUInt32(r, csL ? (*csL)["ColorIndex"] : 9);
|
||||
|
||||
if (baseSection == 0 && variationIndex == 0) {
|
||||
skinMax = std::max(skinMax, static_cast<int>(colorIndex));
|
||||
|
|
@ -284,8 +284,8 @@ void CharacterCreateScreen::updateAppearanceRanges() {
|
|||
if (raceId != targetRaceId || sexId != targetSexId) continue;
|
||||
|
||||
uint32_t baseSection = dbc->getUInt32(r, csL ? (*csL)["BaseSection"] : 3);
|
||||
uint32_t variationIndex = dbc->getUInt32(r, csL ? (*csL)["VariationIndex"] : 4);
|
||||
uint32_t colorIndex = dbc->getUInt32(r, csL ? (*csL)["ColorIndex"] : 5);
|
||||
uint32_t variationIndex = dbc->getUInt32(r, csL ? (*csL)["VariationIndex"] : 8);
|
||||
uint32_t colorIndex = dbc->getUInt32(r, csL ? (*csL)["ColorIndex"] : 9);
|
||||
|
||||
if (baseSection == 1 && colorIndex == static_cast<uint32_t>(skin)) {
|
||||
faceMax = std::max(faceMax, static_cast<int>(variationIndex));
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue