201 lines
10 KiB
C++
201 lines
10 KiB
C++
|
||
#pragma once
|
||
|
||
|
||
#include "AppBase.h"
|
||
#include <thread>
|
||
#include <mutex>
|
||
#include <functional>
|
||
|
||
struct Texture
|
||
{
|
||
VkSampler sampler = VK_NULL_HANDLE;
|
||
VkImage image = VK_NULL_HANDLE;
|
||
VkImageLayout image_layout = VkImageLayout::VK_IMAGE_LAYOUT_UNDEFINED;
|
||
VkDeviceMemory device_memory = VK_NULL_HANDLE;
|
||
VkImageView view = VK_NULL_HANDLE;
|
||
uint32_t width = 0;
|
||
uint32_t height = 0;
|
||
uint32_t mip_levels = 0;
|
||
VkBuffer stagingBuffer = VK_NULL_HANDLE;
|
||
VkDeviceMemory stagingBufferMemory = VK_NULL_HANDLE;
|
||
VkDevice device = VK_NULL_HANDLE;
|
||
std::string texture_path;
|
||
};
|
||
|
||
class Application : public AppBase
|
||
{
|
||
public:
|
||
|
||
virtual void initVulkan();
|
||
void initWindow();
|
||
void createSurface();
|
||
void mainLoop();
|
||
virtual void cleanup();
|
||
|
||
void createImageViews();
|
||
void createFramebuffers();
|
||
void createCommandPool();
|
||
void createCommandBuffer();
|
||
|
||
void createLogicalDevice();
|
||
void createSwapChain();
|
||
void createPipelineLayout();
|
||
void createGraphicsPipeline();
|
||
void createRenderPass();
|
||
void recordCommandBuffer(VkCommandBuffer commandBuffer, uint32_t imageIndex, long long frameTime);
|
||
void createSyncObjects();
|
||
virtual void drawFrame(long long frameTime);
|
||
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 级别的 API(submit / present / pool reset 等)。
|
||
static constexpr uint32_t kTransferSlotCount = 3;
|
||
void runTransferCommand(const std::function<void(VkCommandBuffer)>& record);
|
||
|
||
protected:
|
||
|
||
VkPhysicalDevice physicalDevice = VK_NULL_HANDLE; // 物理设备
|
||
VkDevice device; // 逻辑设备
|
||
VkQueue graphicsQueue; // 图形队列
|
||
VkSurfaceKHR surface; // 窗口表面
|
||
VkSwapchainKHR swapChain; // 交换链
|
||
std::vector<VkImage> swapChainImages; // 交换链图像
|
||
VkFormat swapChainImageFormat; // 交换链图像格式
|
||
VkExtent2D swapChainExtent; // 交换链图像分辨率
|
||
std::vector<VkImageView> swapChainImageViews; // 交换链图像视图
|
||
VkRenderPass renderPass; // 渲染流程
|
||
VkPipelineLayout pipelineLayout; // 管线布局
|
||
VkPipeline graphicsPipeline; // 图形管线
|
||
std::vector<VkFramebuffer> swapChainFramebuffers; // 帧缓冲区
|
||
VkQueue presentQueue; // 呈现队列
|
||
bool _sceondInited = true;
|
||
|
||
public:
|
||
VkCommandPool commandPool;
|
||
VkCommandPool commandPool_ex;
|
||
std::vector<VkCommandBuffer> commandBuffers;
|
||
std::vector<VkSemaphore> imageAvailableSemaphores; // 每个交换链图像一个
|
||
std::vector<VkSemaphore> renderFinishedSemaphores; // 每个交换链图像一个
|
||
std::vector<VkFence> inFlightFences; // 每个帧在飞行一个
|
||
std::vector<VkFence> imagesInFlight; // 跟踪每个图像的使用状态
|
||
int currentFrame = 0;
|
||
int MAX_FRAMES_IN_FLIGHT = 2;
|
||
bool _applicationInited = false;
|
||
void cleanupSecondInit();
|
||
|
||
// Tear down only the window-dependent Vulkan objects so we can survive an
|
||
// Android APP_CMD_TERM_WINDOW (screen off / background / rotate).
|
||
// Keeps renderPass / pipelines / VMA / textures / FaceApp resources intact,
|
||
// so pipelines created by FaceApp remain valid for the new swapchain.
|
||
// Caller MUST hold any app-level mutexes that serialize JNI -> Vulkan access.
|
||
void cleanupForWindowLost();
|
||
|
||
// Rebuild the window-dependent Vulkan objects torn down above.
|
||
// Safe to call only after cleanupForWindowLost().
|
||
// Returns true when the new swapchain's extent or format differs from the
|
||
// one in use before cleanupForWindowLost(). When true, any pipeline baked
|
||
// with a static viewport/scissor from swapChainExtent -- including the
|
||
// FaceApp pipelines -- must be destroyed and recreated against the new
|
||
// renderPass / extent. Application's own renderPass + graphicsPipeline are
|
||
// already handled internally.
|
||
bool reinitForNewWindow();
|
||
|
||
// Extent / format captured by the most recent cleanupForWindowLost().
|
||
// Used by reinitForNewWindow() to decide whether renderPass / pipelines
|
||
// must be rebuilt against the new swapchain.
|
||
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 已 idle(vkDeviceWaitIdle 或确认所有 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);
|
||
#ifdef _WIN32
|
||
void loadTextureExample(std::wstring path, Texture& tex, bool srgb, VkCommandPool pool);
|
||
#endif
|
||
|
||
uint32_t findMemoryType(VkPhysicalDevice physicalDevice, uint32_t typeFilter,
|
||
VkMemoryPropertyFlags properties);
|
||
void createTexture(VkDevice device, VkPhysicalDevice physicalDevice, int width, int height, Texture& texture, bool srgb, std::string tex_path, size_t dataSize);
|
||
void updateTexture(VkDevice device, VkPhysicalDevice physicalDevice, VkCommandPool commandPool, VkQueue queue, uint8_t* data, int width, int height, int rowStride, size_t dataSize, Texture& texture);
|
||
VkCommandBuffer beginSingleTimeCommands(VkDevice device, VkCommandPool commandPool);
|
||
void endSingleTimeCommands(VkDevice device, VkCommandPool commandPool, VkQueue queue, VkCommandBuffer commandBuffer);
|
||
void transitionImageLayout(VkCommandBuffer commandBuffer, VkImage image, VkImageLayout oldLayout, VkImageLayout newLayout);
|
||
void destroy_texture(Texture texture);
|
||
|
||
long long _lastDrawFrameTime;
|
||
|
||
}; |