Modules/Graphics/public/VulkanContext.hpp
Шурупов Илья Викторович acc4be39be Stop rebuilding the swapchain every frame on rotated displays
When the surface is rotated, the swapchain is created with identity
transform and swapped width/height, so its extent never matched the
window size and Android keeps reporting VK_SUBOPTIMAL_KHR. Both made
the swapchain be recreated on almost every frame in that orientation.
The extent check now compares against the size the swapchain was
requested for, and a suboptimal present only recreates it when the
surface transform or extent actually changed.
2026-10-17 20:12:04 +03:00

135 lines
4.6 KiB
C++

#pragma once
#include <vulkan/vulkan.h>
#include <cstdint>
#include <memory>
#include <vector>
namespace tp {
class GpuTimer;
class VulkanSurfaceSource {
public:
virtual ~VulkanSurfaceSource() = default;
[[nodiscard]] virtual std::vector<const char*> requiredInstanceExtensions() const = 0;
[[nodiscard]] virtual VkSurfaceKHR createSurface(VkInstance instance) const = 0;
[[nodiscard]] virtual VkExtent2D framebufferExtent() const = 0;
[[nodiscard]] virtual bool wantsTranslucency() const = 0;
virtual void onTranslucencyResolved(bool translucent) = 0;
};
class VulkanContext {
public:
explicit VulkanContext(VulkanSurfaceSource& surfaceSource);
~VulkanContext();
VulkanContext(const VulkanContext&) = delete;
VulkanContext& operator=(const VulkanContext&) = delete;
VkInstance instance() const { return mInstance; }
VkDevice device() const { return mDevice; }
VkPhysicalDevice physicalDevice() const { return mPhysicalDevice; }
VkQueue graphicsQueue() const { return mGraphicsQueue; }
VkRenderPass renderPass() const { return mRenderPass; }
uint32_t swapchainImageCount() const { return static_cast<uint32_t>(mSwapchainImages.size()); }
VkExtent2D swapchainExtent() const { return mSwapchainExtent; }
VkCommandBuffer* commandBufferArray() { return mCommandBuffers.data(); }
VkCommandBuffer activeCommandBuffer() const { return mCommandBuffers[mCurrentFrame]; }
uint32_t* currentFrameSlot() { return &mCurrentFrame; }
uint32_t framesInFlight() const { return kFramesInFlight; }
bool isTranslucent() const { return mTranslucent; }
bool beginFrame(int framebufferWidth, int framebufferHeight);
[[nodiscard]] uint64_t swapchainRecreations() const { return mSwapchainRecreations; }
void endFrame();
void waitIdle();
uint32_t graphicsQueueFamily() const { return mGraphicsQueueFamily; }
VkCommandPool commandPool() const { return mCommandPool; }
uint32_t findMemoryType(uint32_t typeFilter, VkMemoryPropertyFlags properties) const;
VkCommandBuffer beginOneShotCommands() const;
void endOneShotCommands(VkCommandBuffer commandBuffer) const;
void syncSwapchainToWindow();
void releaseSurface();
void recreateSurface();
[[nodiscard]] bool hasSurface() const { return mSurface != VK_NULL_HANDLE; }
static VulkanContext* current();
private:
uint64_t mSwapchainRecreations = 0;
VkSurfaceTransformFlagBitsKHR mSurfaceTransform = VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR;
VkExtent2D mSurfaceExtent{ 0, 0 };
VkExtent2D mRequestedExtent{ 0, 0 };
void createInstance();
void createSurface();
void selectCompositeAlpha();
void selectPhysicalDevice();
void createLogicalDevice();
void createCommandResources();
void createSyncObjects();
void createRenderPass();
bool swapchainMatches(int framebufferWidth, int framebufferHeight) const;
VkPresentModeKHR selectPresentMode() const;
void createSwapchain(int width, int height, VkSwapchainKHR previous = VK_NULL_HANDLE);
void destroySwapchainResources();
void destroySwapchain();
void recreateSwapchain(int width, int height);
[[nodiscard]] bool surfaceChanged() const;
VkFormat selectDepthStencilFormat() const;
void createDepthStencil();
void destroyDepthStencil();
VulkanSurfaceSource& mSurfaceSource;
VkCompositeAlphaFlagBitsKHR mCompositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR;
bool mTranslucent = false;
VkInstance mInstance = VK_NULL_HANDLE;
VkSurfaceKHR mSurface = VK_NULL_HANDLE;
VkPhysicalDevice mPhysicalDevice = VK_NULL_HANDLE;
VkDevice mDevice = VK_NULL_HANDLE;
uint32_t mGraphicsQueueFamily = UINT32_MAX;
uint32_t mPresentQueueFamily = UINT32_MAX;
VkQueue mGraphicsQueue = VK_NULL_HANDLE;
VkQueue mPresentQueue = VK_NULL_HANDLE;
VkSwapchainKHR mSwapchain = VK_NULL_HANDLE;
VkPresentModeKHR mPresentMode = VK_PRESENT_MODE_FIFO_KHR;
VkFormat mSwapchainFormat{};
VkExtent2D mSwapchainExtent{};
std::vector<VkImage> mSwapchainImages;
std::vector<VkImageView> mSwapchainImageViews;
std::vector<VkFramebuffer> mSwapchainFramebuffers;
VkRenderPass mRenderPass = VK_NULL_HANDLE;
VkFormat mDepthStencilFormat{};
VkImage mDepthStencilImage = VK_NULL_HANDLE;
VkDeviceMemory mDepthStencilMemory = VK_NULL_HANDLE;
VkImageView mDepthStencilView = VK_NULL_HANDLE;
static constexpr uint32_t kFramesInFlight = 2;
VkCommandPool mCommandPool = VK_NULL_HANDLE;
std::vector<VkCommandBuffer> mCommandBuffers;
std::vector<VkSemaphore> mImageAvailableSemaphores;
std::vector<VkSemaphore> mRenderFinishedSemaphores;
std::vector<VkFence> mInFlightFences;
uint32_t mCurrentFrame = 0;
uint32_t mAcquiredImageIndex = 0;
bool mFrameActive = false;
std::unique_ptr<GpuTimer> mFrameGpuTimer;
};
}