Fix Windows ARM64 build: disable x86 asm in StormLib's libtomcrypt

StormLib's bundled libtomcrypt uses x86 inline assembly (bswapl/movl)
gated by __MINGW32__, which is defined on CLANGARM64 too. Pass
-DLTC_NO_BSWAP to force portable C byte-swap fallback.
This commit is contained in:
Kelsi 2026-02-25 03:06:06 -08:00
parent 9b90ab0429
commit 7ca9caa212
6 changed files with 26 additions and 89 deletions

View file

@ -195,10 +195,14 @@ jobs:
shell: msys2 {0}
run: |
git clone --depth 1 https://github.com/ladislav-zezula/StormLib.git /tmp/StormLib
# Disable x86 inline asm in bundled libtomcrypt (bswapl/movl) —
# __MINGW32__ is defined on CLANGARM64 which incorrectly enables x86 asm
cmake -S /tmp/StormLib -B /tmp/StormLib/build -G Ninja \
-DCMAKE_BUILD_TYPE=Release \
-DCMAKE_INSTALL_PREFIX="$MINGW_PREFIX" \
-DBUILD_SHARED_LIBS=OFF
-DBUILD_SHARED_LIBS=OFF \
-DCMAKE_C_FLAGS="-DLTC_NO_BSWAP" \
-DCMAKE_CXX_FLAGS="-DLTC_NO_BSWAP"
cmake --build /tmp/StormLib/build --parallel $(nproc)
cmake --install /tmp/StormLib/build

View file

@ -256,10 +256,14 @@ jobs:
shell: msys2 {0}
run: |
git clone --depth 1 https://github.com/ladislav-zezula/StormLib.git /tmp/StormLib
# Disable x86 inline asm in bundled libtomcrypt (bswapl/movl) —
# __MINGW32__ is defined on CLANGARM64 which incorrectly enables x86 asm
cmake -S /tmp/StormLib -B /tmp/StormLib/build -G Ninja \
-DCMAKE_BUILD_TYPE=Release \
-DCMAKE_INSTALL_PREFIX="$MINGW_PREFIX" \
-DBUILD_SHARED_LIBS=OFF
-DBUILD_SHARED_LIBS=OFF \
-DCMAKE_C_FLAGS="-DLTC_NO_BSWAP" \
-DCMAKE_CXX_FLAGS="-DLTC_NO_BSWAP"
cmake --build /tmp/StormLib/build --parallel $(nproc)
cmake --install /tmp/StormLib/build

View file

@ -478,7 +478,6 @@ private:
// Helper to allocate descriptor sets
VkDescriptorSet allocateMaterialSet();
VkDescriptorSet allocateBoneSet();
void preallocateBoneBuffers(M2Instance& instance);
// Helper to destroy model GPU resources
void destroyModelGPU(M2ModelGPU& model);

View file

@ -657,9 +657,9 @@ private:
bool wireframeMode = false;
bool frustumCulling = true;
bool portalCulling = false; // Disabled by default - needs debugging
bool distanceCulling = true; // Enabled with active-group exemption to prevent floor disappearing
float maxGroupDistance = 800.0f;
float maxGroupDistanceSq = 640000.0f; // maxGroupDistance^2
bool distanceCulling = false; // Disabled - causes ground to disappear
float maxGroupDistance = 500.0f;
float maxGroupDistanceSq = 250000.0f; // maxGroupDistance^2
uint32_t lastDrawCalls = 0;
mutable uint32_t lastPortalCulledGroups = 0;
mutable uint32_t lastDistanceCulledGroups = 0;

View file

@ -767,38 +767,6 @@ VkDescriptorSet M2Renderer::allocateBoneSet() {
return set;
}
void M2Renderer::preallocateBoneBuffers(M2Instance& instance) {
if (!vkCtx_) return;
for (int fi = 0; fi < 2; fi++) {
if (instance.boneBuffer[fi]) continue; // already allocated
VkBufferCreateInfo bci{VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO};
bci.size = 128 * sizeof(glm::mat4); // max 128 bones
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(vkCtx_->getAllocator(), &bci, &aci,
&instance.boneBuffer[fi], &instance.boneAlloc[fi], &allocInfo);
instance.boneMapped[fi] = allocInfo.pMappedData;
instance.boneSet[fi] = allocateBoneSet();
if (instance.boneSet[fi]) {
VkDescriptorBufferInfo bufInfo{};
bufInfo.buffer = instance.boneBuffer[fi];
bufInfo.offset = 0;
bufInfo.range = bci.size;
VkWriteDescriptorSet write{VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET};
write.dstSet = instance.boneSet[fi];
write.dstBinding = 0;
write.descriptorCount = 1;
write.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER;
write.pBufferInfo = &bufInfo;
vkUpdateDescriptorSets(vkCtx_->getDevice(), 1, &write, 0, nullptr);
}
}
}
// ---------------------------------------------------------------------------
// M2 collision mesh: build spatial grid + classify triangles
// ---------------------------------------------------------------------------
@ -1647,11 +1615,6 @@ uint32_t M2Renderer::createInstance(uint32_t modelId, const glm::vec3& position,
instance.variationTimer = 3000.0f + static_cast<float>(rand() % 8000);
}
// Pre-allocate bone SSBOs so first render frame doesn't hitch
if (mdlRef.hasAnimation && !mdlRef.disableAnimation) {
preallocateBoneBuffers(instance);
}
instances.push_back(instance);
size_t idx = instances.size() - 1;
instanceIndexById[instance.id] = idx;
@ -1685,8 +1648,6 @@ uint32_t M2Renderer::createInstanceWithMatrix(uint32_t modelId, const glm::mat4&
}
}
const auto& mdlRef = models[modelId];
M2Instance instance;
instance.id = nextInstanceId++;
instance.modelId = modelId;
@ -1696,24 +1657,20 @@ uint32_t M2Renderer::createInstanceWithMatrix(uint32_t modelId, const glm::mat4&
instance.modelMatrix = modelMatrix;
instance.invModelMatrix = glm::inverse(modelMatrix);
glm::vec3 localMin, localMax;
getTightCollisionBounds(mdlRef, localMin, localMax);
getTightCollisionBounds(models[modelId], localMin, localMax);
transformAABB(instance.modelMatrix, localMin, localMax, instance.worldBoundsMin, instance.worldBoundsMax);
// Initialize animation
if (mdlRef.hasAnimation && !mdlRef.disableAnimation && !mdlRef.sequences.empty()) {
const auto& mdl2 = models[modelId];
if (mdl2.hasAnimation && !mdl2.disableAnimation && !mdl2.sequences.empty()) {
instance.currentSequenceIndex = 0;
instance.idleSequenceIndex = 0;
instance.animDuration = static_cast<float>(mdlRef.sequences[0].duration);
instance.animTime = static_cast<float>(rand() % std::max(1u, mdlRef.sequences[0].duration));
instance.animDuration = static_cast<float>(mdl2.sequences[0].duration);
instance.animTime = static_cast<float>(rand() % std::max(1u, mdl2.sequences[0].duration));
instance.variationTimer = 3000.0f + static_cast<float>(rand() % 8000);
} else {
instance.animTime = static_cast<float>(rand()) / RAND_MAX * 10000.0f;
}
// Pre-allocate bone SSBOs so first render frame doesn't hitch
if (mdlRef.hasAnimation && !mdlRef.disableAnimation) {
preallocateBoneBuffers(instance);
}
instances.push_back(instance);
size_t idx = instances.size() - 1;
instanceIndexById[instance.id] = idx;
@ -1854,11 +1811,7 @@ void M2Renderer::update(float deltaTime, const glm::vec3& cameraPos, const glm::
// Cache camera state for frustum-culling bone computation
cachedCamPos_ = cameraPos;
const size_t animInstCount = instances.size();
const float maxRenderDistance = (animInstCount > 3000) ? 600.0f
: (animInstCount > 2000) ? 800.0f
: (animInstCount > 1000) ? 1400.0f
: 2800.0f;
const float maxRenderDistance = (instances.size() > 2000) ? 800.0f : 2800.0f;
cachedMaxRenderDistSq_ = maxRenderDistance * maxRenderDistance;
// Build frustum for culling bones
@ -2128,12 +2081,8 @@ void M2Renderer::render(VkCommandBuffer cmd, VkDescriptorSet perFrameSet, const
lastDrawCallCount = 0;
// Adaptive render distance: tiered for performance without excessive pop-in
const size_t instCount = instances.size();
const float maxRenderDistance = (instCount > 3000) ? 250.0f
: (instCount > 2000) ? 400.0f
: (instCount > 1000) ? 600.0f
: 1000.0f;
// Adaptive render distance: balanced for performance without excessive pop-in
const float maxRenderDistance = (instances.size() > 2000) ? 350.0f : 1000.0f;
const float maxRenderDistanceSq = maxRenderDistance * maxRenderDistance;
const float fadeStartFraction = 0.75f;
const glm::vec3 camPos = camera.getPosition();

View file

@ -1319,9 +1319,6 @@ void WMORenderer::render(VkCommandBuffer cmd, VkDescriptorSet perFrameSet, const
bool doFrustumCull = false; // Temporarily disabled: can over-cull world WMOs
bool doDistanceCull = distanceCulling;
// Cache active group info for distance-cull exemption (player's current WMO group)
const auto activeGroupCopy = activeGroup_;
auto cullInstance = [&](size_t instIdx) -> InstanceDrawList {
if (instIdx >= instances.size()) return InstanceDrawList{};
const auto& instance = instances[instIdx];
@ -1332,9 +1329,6 @@ void WMORenderer::render(VkCommandBuffer cmd, VkDescriptorSet perFrameSet, const
InstanceDrawList result;
result.instanceIndex = instIdx;
// Check if this instance is the one the player is standing in
bool isActiveInstance = activeGroupCopy.isValid() && activeGroupCopy.instanceIdx == instIdx;
// Portal-based visibility
std::unordered_set<uint32_t> portalVisibleGroups;
bool usePortalCulling = doPortalCull && !model.portals.empty() && !model.portalRefs.empty();
@ -1355,26 +1349,13 @@ void WMORenderer::render(VkCommandBuffer cmd, VkDescriptorSet perFrameSet, const
const auto& [gMin, gMax] = instance.worldGroupBounds[gi];
if (doDistanceCull) {
// Never cull the group the player is standing in or its portal neighbors
bool isExempt = false;
if (isActiveInstance) {
if (static_cast<int32_t>(gi) == activeGroupCopy.groupIdx) {
isExempt = true;
} else {
for (uint32_t ng : activeGroupCopy.neighborGroups) {
if (ng == static_cast<uint32_t>(gi)) { isExempt = true; break; }
}
}
}
if (!isExempt) {
glm::vec3 closestPoint = glm::clamp(camPos, gMin, gMax);
float distSq = glm::dot(closestPoint - camPos, closestPoint - camPos);
if (distSq > maxGroupDistanceSq) {
if (distSq > 250000.0f) {
result.distanceCulled++;
continue;
}
}
}
if (doFrustumCull && !frustum.intersectsAABB(gMin, gMax))
continue;