Files
face_sdk/vulkan/Application.h
2026-04-25 16:42:20 +08:00

201 lines
10 KiB
C++
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#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 级别的 APIsubmit / 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 已 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);
#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;
};