修复clash的问题

This commit is contained in:
xsl
2026-04-25 16:42:20 +08:00
parent 2066b3b124
commit 50680ee854
8 changed files with 893 additions and 64 deletions
+222 -26
View File
@@ -110,6 +110,11 @@ void Application::initVulkan()
createCommandPool(); // 创建命令池
createCommandBuffer(); // 创建命令缓冲区
createSyncObjects(); // 创建同步对象
// 复用式 single-time-command 资源:从 commandPool_ex 预分配
// kTransferSlotCount 个 cmdbuf + 同数 signaled fence
// 之后所有 copyBuffer / updateTexture 走 runTransferCommand
// 不再每次 vkAllocate/vkFree。
createTransferResources();
_lastDrawFrameTime = getCurrentTimeMillis();
_applicationInited = true;
// The first branch above already built all of the window-dependent
@@ -790,7 +795,14 @@ void Application::drawFrame(long long frameTime)
submitInfo.signalSemaphoreCount = 1;
submitInfo.pSignalSemaphores = signalSemaphores;
VkResult submitRes = vkQueueSubmit(graphicsQueue, 1, &submitInfo, inFlightFences[currentFrame]);
// 串行化 graphicsQueue/presentQueue 的 submit 与 present
// 确保与 copyBuffer / updateTexture 等其它线程的队列提交互斥,
// 避免驱动内部 pthread_mutex 在长时间并发下被破坏。
VkResult submitRes;
{
std::lock_guard<std::mutex> poolLock(poolQueueMtx);
submitRes = vkQueueSubmit(graphicsQueue, 1, &submitInfo, inFlightFences[currentFrame]);
}
if (submitRes != VK_SUCCESS) {
#ifndef _WIN32
DebugLog::log_throttled("drawFrame.submit",
@@ -810,7 +822,10 @@ void Application::drawFrame(long long frameTime)
presentInfo.swapchainCount = 1;
presentInfo.pSwapchains = swapChains;
presentInfo.pImageIndices = &imageIndex;
result = vkQueuePresentKHR(presentQueue, &presentInfo);
{
std::lock_guard<std::mutex> poolLock(poolQueueMtx);
result = vkQueuePresentKHR(presentQueue, &presentInfo);
}
if (result != VK_SUCCESS) {
#ifndef _WIN32
@@ -1121,6 +1136,184 @@ void Application::endSingleTimeCommands(VkDevice device, VkCommandPool commandPo
vkFreeCommandBuffers(device, commandPool, 1, &commandBuffer);
}
void Application::createTransferResources()
{
if (m_xferInited) {
#ifndef _WIN32
DebugLog::log("createTransferResources: already inited, skip");
#endif
return;
}
if (commandPool_ex == VK_NULL_HANDLE) {
#ifndef _WIN32
DebugLog::log("createTransferResources: commandPool_ex is null, abort");
#endif
throw std::runtime_error("createTransferResources: commandPool_ex not created yet");
}
VkCommandBufferAllocateInfo allocInfo{};
allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
allocInfo.commandPool = commandPool_ex;
allocInfo.commandBufferCount = kTransferSlotCount;
if (vkAllocateCommandBuffers(device, &allocInfo, m_xferCmd) != VK_SUCCESS) {
throw std::runtime_error("createTransferResources: vkAllocateCommandBuffers failed");
}
// fence 创建为 SIGNALED:第一次 runTransferCommand 的 vkWaitForFences
// 会立刻返回,避免冷启动卡顿。
VkFenceCreateInfo fenceInfo{};
fenceInfo.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO;
fenceInfo.flags = VK_FENCE_CREATE_SIGNALED_BIT;
for (uint32_t i = 0; i < kTransferSlotCount; ++i) {
if (vkCreateFence(device, &fenceInfo, nullptr, &m_xferFence[i]) != VK_SUCCESS) {
for (uint32_t j = 0; j < i; ++j) {
vkDestroyFence(device, m_xferFence[j], nullptr);
m_xferFence[j] = VK_NULL_HANDLE;
}
vkFreeCommandBuffers(device, commandPool_ex, kTransferSlotCount, m_xferCmd);
for (uint32_t j = 0; j < kTransferSlotCount; ++j) m_xferCmd[j] = VK_NULL_HANDLE;
throw std::runtime_error("createTransferResources: vkCreateFence failed");
}
}
m_xferIdx = 0;
m_xferInited = true;
#ifndef _WIN32
DebugLog::log("createTransferResources: done, slots=%u pool=commandPool_ex",
(unsigned)kTransferSlotCount);
#endif
}
void Application::destroyTransferResources()
{
if (!m_xferInited) {
return;
}
// 调用方必须保证 GPU 已 idle 且没有线程正在 runTransferCommand。
// 这里再加一道 m_xferMtx,串行化潜在的最后一次 transfer。
std::lock_guard<std::mutex> xferLock(m_xferMtx);
for (uint32_t i = 0; i < kTransferSlotCount; ++i) {
if (m_xferFence[i] != VK_NULL_HANDLE) {
vkDestroyFence(device, m_xferFence[i], nullptr);
m_xferFence[i] = VK_NULL_HANDLE;
}
}
if (m_xferCmd[0] != VK_NULL_HANDLE && commandPool_ex != VK_NULL_HANDLE) {
vkFreeCommandBuffers(device, commandPool_ex, kTransferSlotCount, m_xferCmd);
}
for (uint32_t i = 0; i < kTransferSlotCount; ++i) {
m_xferCmd[i] = VK_NULL_HANDLE;
}
m_xferIdx = 0;
m_xferInited = false;
#ifndef _WIN32
DebugLog::log("destroyTransferResources: done");
#endif
}
void Application::runTransferCommand(const std::function<void(VkCommandBuffer)>& record)
{
if (!m_xferInited) {
#ifndef _WIN32
DebugLog::log_throttled("runTransferCommand.notInited",
"runTransferCommand: not inited, skip");
#endif
return;
}
// 串行化所有 transfer 调用:
// - 保证 m_xferIdx 推进 / m_xferCmd[idx] 录制 / m_xferFence[idx] 等待
// 形成一组原子动作;
// - 同时也保证 commandPool_ex 的「外部同步」语义(同一时刻只允许一个
// 线程对它做 record/reset)。
std::lock_guard<std::mutex> xferLock(m_xferMtx);
const uint32_t idx = m_xferIdx;
VkFence fence = m_xferFence[idx];
VkCommandBuffer cmd = m_xferCmd[idx];
// 等上一次该 slot 的提交真正完成。fence 是独立同步对象,不需要持
// poolQueueMtx 就可以等待,drawFrame 的 submit 不会被阻塞。
VkResult wr = vkWaitForFences(device, 1, &fence, VK_TRUE, UINT64_MAX);
if (wr != VK_SUCCESS) {
#ifndef _WIN32
DebugLog::log_throttled("runTransferCommand.wait",
"runTransferCommand: vkWaitForFences slot=%u -> %d",
idx, (int)wr);
#endif
}
vkResetFences(device, 1, &fence);
vkResetCommandBuffer(cmd, 0);
VkCommandBufferBeginInfo beginInfo{};
beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
beginInfo.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;
if (vkBeginCommandBuffer(cmd, &beginInfo) != VK_SUCCESS) {
#ifndef _WIN32
DebugLog::log_throttled("runTransferCommand.begin",
"runTransferCommand: vkBeginCommandBuffer slot=%u failed", idx);
#endif
return;
}
// 调用方在这里 record 命令:vkCmdCopyBuffer / vkCmdCopyBufferToImage /
// transitionImageLayout 等。这些是纯 record 操作,在 m_xferMtx 持有
// 且 cmd 独占的前提下不需要再额外加锁。
record(cmd);
if (vkEndCommandBuffer(cmd) != VK_SUCCESS) {
#ifndef _WIN32
DebugLog::log_throttled("runTransferCommand.end",
"runTransferCommand: vkEndCommandBuffer slot=%u failed", idx);
#endif
return;
}
VkSubmitInfo submitInfo{};
submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
submitInfo.commandBufferCount = 1;
submitInfo.pCommandBuffers = &cmd;
// vkQueueSubmit 必须和 drawFrame 的 vkQueueSubmit / vkQueuePresentKHR
// 互斥(队列要求外部同步)。其它步骤只占 m_xferMtx 即可。
{
std::lock_guard<std::mutex> poolLock(poolQueueMtx);
VkResult sr = vkQueueSubmit(graphicsQueue, 1, &submitInfo, fence);
if (sr != VK_SUCCESS) {
#ifndef _WIN32
DebugLog::log_throttled("runTransferCommand.submit",
"runTransferCommand: vkQueueSubmit slot=%u -> %d",
idx, (int)sr);
#endif
}
}
// ★ 关键:调用方代码(FaceApp::uploadVertexData / Application::updateTexture
// 在 runTransferCommand 之后会立刻 vkMapMemory + memcpy 覆写共享的
// staging buffer,去做下一次拷贝。所以本接口必须像旧的 vkQueueWaitIdle
// 一样保证 GPU **已读完** staging 才能返回,否则覆写会 race 上 GPU
// 还在执行的 vkCmdCopyBuffer / vkCmdCopyBufferToImage,导致顶点 / 纹理
// 数据被错位拼接(外观就是模型畸形 / 纹理花屏)。
//
// 这里只等自己这一次的 fence,不像旧实现 vkQueueWaitIdle 那样等整个
// graphicsQueue(包括 drawFrame 的提交),所以不会拖慢渲染主路径。
//
// 不持 poolQueueMtxfence 是独立同步对象,等它不需要外部互斥。
VkResult er = vkWaitForFences(device, 1, &fence, VK_TRUE, UINT64_MAX);
if (er != VK_SUCCESS) {
#ifndef _WIN32
DebugLog::log_throttled("runTransferCommand.endWait",
"runTransferCommand: end vkWaitForFences slot=%u -> %d",
idx, (int)er);
#endif
}
m_xferIdx = (idx + 1) % kTransferSlotCount;
}
void Application::updateTexture(VkDevice device, VkPhysicalDevice physicalDevice,
VkCommandPool commandPool, VkQueue queue,
@@ -1153,33 +1346,36 @@ void Application::updateTexture(VkDevice device, VkPhysicalDevice physicalDevice
vkUnmapMemory(device, texture.stagingBufferMemory);
VkCommandBuffer commandBuffer = beginSingleTimeCommands(device, commandPool);
// 走 runTransferCommand:复用 commandPool_ex 上预分配的 cmdbuf + fence
// 不再每帧 vkAllocate/vkFree,也不再 vkQueueWaitIdle。
// 注意:传入的 commandPool / queue 参数保留只是为了兼容旧接口,
// 实际命令池一律换成 commandPool_ex(与渲染主管线物理隔离)。
(void)commandPool;
(void)queue;
runTransferCommand([&](VkCommandBuffer commandBuffer) {
transitionImageLayout(commandBuffer, texture.image,
VK_IMAGE_LAYOUT_UNDEFINED,
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL);
transitionImageLayout(commandBuffer, texture.image,
VK_IMAGE_LAYOUT_UNDEFINED,
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL);
VkBufferImageCopy region = {};
region.bufferOffset = 0;
region.bufferRowLength = 0;
region.bufferImageHeight = 0;
region.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
region.imageSubresource.mipLevel = 0;
region.imageSubresource.baseArrayLayer = 0;
region.imageSubresource.layerCount = 1;
region.imageOffset = { 0, 0, 0 };
region.imageExtent = { static_cast<uint32_t>(width),
static_cast<uint32_t>(height), 1 };
VkBufferImageCopy region = {};
region.bufferOffset = 0;
region.bufferRowLength = 0;
region.bufferImageHeight = 0;
region.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
region.imageSubresource.mipLevel = 0;
region.imageSubresource.baseArrayLayer = 0;
region.imageSubresource.layerCount = 1;
region.imageOffset = { 0, 0, 0 };
region.imageExtent = { static_cast<uint32_t>(width),
static_cast<uint32_t>(height), 1 };
vkCmdCopyBufferToImage(commandBuffer, texture.stagingBuffer, texture.image,
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, &region);
transitionImageLayout(commandBuffer, texture.image,
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
endSingleTimeCommands(device, commandPool, queue, commandBuffer);
vkCmdCopyBufferToImage(commandBuffer, texture.stagingBuffer, texture.image,
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, &region);
transitionImageLayout(commandBuffer, texture.image,
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
});
texture.image_layout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
}
+76
View File
@@ -5,6 +5,7 @@
#include "AppBase.h"
#include <thread>
#include <mutex>
#include <functional>
struct Texture
{
@@ -48,8 +49,68 @@ public:
virtual void render(VkCommandBuffer commandBuffer, long long frameTime);
virtual bool isInited() { return _applicationInited; }
std::mutex createTextureMtx;
// 全局 GPU 提交互斥锁:任何对 graphicsQueue / presentQueue 的提交
// vkQueueSubmit / vkQueuePresentKHR / vkQueueWaitIdle)以及共享
// commandPool 的 vkAllocateCommandBuffers / vkFreeCommandBuffers
// 都必须在持有此锁的期间执行,否则驱动内部维护命令池与队列的
// pthread_mutex 在长时间多线程并发下会被破坏(FORTIFY: pthread_mutex_lock
// called on a destroyed mutex)。
// 需要覆盖的 3 条并发线路:
// 1) 渲染线程 drawFrame
// 2) processImageNative → updateTexture (single-time commands)
// 3) passDataToNative → update_face_vertex_buffer → copyBuffer
std::mutex poolQueueMtx;
void processWithVulkan(uint8_t* data, int width, int height, int rowStride, size_t dataSize, Texture& texture, bool srgb, VkCommandPool pool, std::string tex_path);
// ---------------------------------------------------------------------
// 复用式 single-time-command 接口(替代每帧 allocate+submit+waitIdle+free
//
// ★ 同步语义(重要):
// 本接口 **同步**,返回时 GPU 已经读完 record 里访问的源数据。
// 行为上等价于旧的 vkQueueSubmit + vkQueueWaitIdle,但只等自己这次
// 提交的 fence,不阻塞渲染线程在 graphicsQueue 上的其它提交。
//
// 为什么必须同步:调用方 (FaceApp::uploadVertexData /
// Application::updateTexture) 紧接着会复用同一个 staging buffer
//
// vkMapMemory(staging); memcpy(staging, A); vkUnmapMemory;
// runTransferCommand([&](cmd){ vkCmdCopyBuffer(cmd, staging, dstA); });
// vkMapMemory(staging); memcpy(staging, B); ← 必须等 dstA 拷完!
// vkUnmapMemory;
// runTransferCommand([&](cmd){ vkCmdCopyBuffer(cmd, staging, dstB); });
//
// 如果 runTransferCommand 异步返回,第二次 memcpy 会在 GPU 还没读完
// staging 里的 A 时就把它覆盖成 B —— 顶点 / 纹理立刻畸形。
//
// 行为:
// - 共 kTransferSlotCount 个 VkCommandBuffer + 同数 VkFence
// 从 commandPool_ex 一次性分配,在 cleanup 时一次性释放。
// - 每次 runTransferCommand:
// 1) m_xferMtx.lock() (串行所有 transfer 提交)
// 2) 当前 slot 上 vkWaitForFences (等上一次该 slot 的提交完成)
// 3) vkResetFences + vkResetCommandBuffer + vkBegin
// 4) 调用 record(cmd) 录制命令 (vkCmdCopy* 等)
// 5) vkEndCommandBuffer
// 6) poolQueueMtx 内 vkQueueSubmit(graphicsQueue, fence)
// 7) vkWaitForFences(fence) (★ 等本次 GPU 完成才返回)
// 8) m_xferIdx 推进到下一个 slot
//
// 为什么这样设计:
// - 完全消除每帧 vkAllocateCommandBuffers / vkFreeCommandBuffers
// 根除驱动 per-pool mutex 在长时间高频压力下被破坏导致的
// FORTIFY: pthread_mutex_lock called on a destroyed mutex 崩溃。
// - 用 fence 替代 vkQueueWaitIdle,等待粒度只到自己这一次提交,
// 不会 stall 整条 graphicsQueue(包括渲染主路径的提交)。
// - 固定使用 commandPool_ex(与渲染主用的 commandPool 物理隔离),
// 即使驱动还有内部 contention,也不会波及渲染主管线。
//
// 调用约束:
// - 调用方 **绝不能** 提前持有 poolQueueMtx;本接口内部按需短暂获取。
// - record 函数体内只允许 vkCmd*(命令录制)这类操作,不要在里面调
// queue / pool 级别的 APIsubmit / present / pool reset 等)。
static constexpr uint32_t kTransferSlotCount = 3;
void runTransferCommand(const std::function<void(VkCommandBuffer)>& record);
protected:
VkPhysicalDevice physicalDevice = VK_NULL_HANDLE; // 物理设备
@@ -104,6 +165,21 @@ public:
VkExtent2D _prevSwapChainExtent = {0, 0};
VkFormat _prevSwapChainImageFormat = VK_FORMAT_UNDEFINED;
protected:
// Transfer command resources(详见 runTransferCommand 上方注释)。
// 命令缓冲来自 commandPool_ex,与渲染主用的 commandPool 物理隔离。
VkCommandBuffer m_xferCmd[kTransferSlotCount] = { VK_NULL_HANDLE, VK_NULL_HANDLE, VK_NULL_HANDLE };
VkFence m_xferFence[kTransferSlotCount] = { VK_NULL_HANDLE, VK_NULL_HANDLE, VK_NULL_HANDLE };
uint32_t m_xferIdx = 0;
bool m_xferInited = false;
std::mutex m_xferMtx;
// 创建/销毁 transfer 资源。createTransferResources 必须在 createCommandPool
// 之后调用;destroyTransferResources 必须在销毁 commandPool_ex 之前调用,
// 且 GPU 已 idlevkDeviceWaitIdle 或确认所有 fence 已 signaled)。
void createTransferResources();
void destroyTransferResources();
protected:
void loadTexture(std::string path, Texture& tex, bool srgb, VkCommandPool pool);
void loadTexture(std::vector<unsigned char>& image_data, size_t image_size, int w, int h, Texture& tex, bool srgb, VkCommandPool pool, std::string path);
+23 -35
View File
@@ -889,6 +889,12 @@ void FaceApp::onWindowLost()
std::unique_lock<std::mutex> lk_point(mtx_point);
std::unique_lock<std::mutex> lk_motion(changeMotionMtx);
std::unique_lock<std::mutex> lk_tex(createTextureMtx);
// 同时阻塞所有并发的 GPU 提交(drawFrame / copyBuffer / updateTexture)。
// cleanupForWindowLost() 会执行 vkDeviceWaitIdle 并销毁 swapchain / surface /
// semaphores / fences 等窗口相关资源,如果此时有其它线程正在 vkQueueSubmit
// 或 vkQueuePresentKHR,会触发 FORTIFY: pthread_mutex_lock called on a
// destroyed mutex。这里持锁确保销毁与提交是互斥的。
std::unique_lock<std::mutex> lk_pool(poolQueueMtx);
Application::cleanupForWindowLost();
_secondfaceAppInited = false;
@@ -1053,41 +1059,20 @@ void FaceApp::update_face_vertex_buffer(float* pos, int pointCount)
void FaceApp::copyBuffer(VkBuffer srcBuffer, VkBuffer dstBuffer, VkDeviceSize size)
{
VkCommandBuffer commandBuffer = beginSingleTimeCommands();
VkBufferCopy copyRegion = {};
copyRegion.size = size;
vkCmdCopyBuffer(commandBuffer, srcBuffer, dstBuffer, 1, &copyRegion);
endSingleTimeCommands(commandBuffer);
}
VkCommandBuffer FaceApp::beginSingleTimeCommands() {
VkCommandBufferAllocateInfo allocInfo = { VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO };
allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
allocInfo.commandPool = commandPool;
allocInfo.commandBufferCount = 1;
VkCommandBuffer commandBuffer;
vkAllocateCommandBuffers(device, &allocInfo, &commandBuffer);
VkCommandBufferBeginInfo beginInfo = { VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO };
beginInfo.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;
vkBeginCommandBuffer(commandBuffer, &beginInfo);
return commandBuffer;
}
void FaceApp::endSingleTimeCommands(VkCommandBuffer commandBuffer) {
vkEndCommandBuffer(commandBuffer);
VkSubmitInfo submitInfo = { VK_STRUCTURE_TYPE_SUBMIT_INFO };
submitInfo.commandBufferCount = 1;
submitInfo.pCommandBuffers = &commandBuffer;
vkQueueSubmit(graphicsQueue, 1, &submitInfo, VK_NULL_HANDLE);
vkQueueWaitIdle(graphicsQueue);
vkFreeCommandBuffers(device, commandPool, 1, &commandBuffer);
// 改造说明:
// 旧实现是 vkAllocate(commandPool) + vkBegin + vkCmdCopyBuffer + vkEnd +
// vkQueueSubmit(graphicsQueue) + vkQueueWaitIdle + vkFree(commandPool)
// 每帧 update_face_vertex_buffer 会调两次 copyBuffer(顶点 + 索引),
// 长期高频 allocate/free 会把驱动 per-pool mutex 玩坏,触发
// FORTIFY: pthread_mutex_lock called on a destroyed mutex。
//
// 现在改走基类的 runTransferCommand,复用 commandPool_ex 上预分配的
// 3 个 cmdbuf + fence,并发安全由 m_xferMtx + poolQueueMtx 联合保证。
runTransferCommand([&](VkCommandBuffer commandBuffer) {
VkBufferCopy copyRegion = {};
copyRegion.size = size;
vkCmdCopyBuffer(commandBuffer, srcBuffer, dstBuffer, 1, &copyRegion);
});
}
void FaceApp::createUniformBuffer()
@@ -1458,6 +1443,9 @@ void FaceApp::cleanup()
//}
destroyTexture(device, tex_bg);
// 必须在销毁 commandPool_ex 之前释放从它分配的 transfer cmdbuf + fence。
// vkDeviceWaitIdle 已在本函数开头调过,提交不会再有 in-flight。
destroyTransferResources();
vkDestroyCommandPool(device, commandPool, nullptr);
vkDestroyCommandPool(device, commandPool_ex, nullptr);
Application::cleanup();
-2
View File
@@ -112,8 +112,6 @@ private:
const bool kThick = false;
void copyBuffer(VkBuffer srcBuffer, VkBuffer dstBuffer, VkDeviceSize size);
VkCommandBuffer beginSingleTimeCommands();
void endSingleTimeCommands(VkCommandBuffer commandBuffer);
// 顶点缓冲区相关
VkBuffer m_vertexBuffer = VK_NULL_HANDLE;