#pragma once #include "AppBase.h" #include #include #include 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& record); protected: VkPhysicalDevice physicalDevice = VK_NULL_HANDLE; // 物理设备 VkDevice device; // 逻辑设备 VkQueue graphicsQueue; // 图形队列 VkSurfaceKHR surface; // 窗口表面 VkSwapchainKHR swapChain; // 交换链 std::vector swapChainImages; // 交换链图像 VkFormat swapChainImageFormat; // 交换链图像格式 VkExtent2D swapChainExtent; // 交换链图像分辨率 std::vector swapChainImageViews; // 交换链图像视图 VkRenderPass renderPass; // 渲染流程 VkPipelineLayout pipelineLayout; // 管线布局 VkPipeline graphicsPipeline; // 图形管线 std::vector swapChainFramebuffers; // 帧缓冲区 VkQueue presentQueue; // 呈现队列 bool _sceondInited = true; public: VkCommandPool commandPool; VkCommandPool commandPool_ex; std::vector commandBuffers; std::vector imageAvailableSemaphores; // 每个交换链图像一个 std::vector renderFinishedSemaphores; // 每个交换链图像一个 std::vector inFlightFences; // 每个帧在飞行一个 std::vector 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& 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; };