修复clash的问题
This commit is contained in:
+222
-26
@@ -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 的提交),所以不会拖慢渲染主路径。
|
||||
//
|
||||
// 不持 poolQueueMtx:fence 是独立同步对象,等它不需要外部互斥。
|
||||
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, ®ion);
|
||||
|
||||
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, ®ion);
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user