mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-11-10 11:33:24 +09:00
211 lines
10 KiB
C++
211 lines
10 KiB
C++
// MobileGL - MobileGL/MG_Backend/DirectVulkan/Renderer/VkRenderPassManager.h
|
|
// Copyright (c) 2025-2026 MobileGL-Dev
|
|
// Licensed under the GNU Lesser General Public License v3.0:
|
|
// https://www.gnu.org/licenses/gpl-3.0.txt
|
|
// https://www.gnu.org/licenses/lgpl-3.0.txt
|
|
// SPDX-License-Identifier: LGPL-3.0-only
|
|
// End of Source File Header
|
|
|
|
#pragma once
|
|
|
|
#include "SwapchainObject.h"
|
|
#include "VkClearManager.h"
|
|
#include "VkTextureManager.h"
|
|
#include "../VkIncludes.h"
|
|
#include "../VulkanRendererConfig.h"
|
|
#include "MG_State/GLState/FramebufferState/FramebufferObject.h"
|
|
|
|
#include <Includes.h>
|
|
#include <unordered_map>
|
|
#include <vk_mem_alloc.h>
|
|
|
|
namespace MobileGL::MG_Backend::DirectVulkan {
|
|
struct DepthStencilAttachmentLoadInfo {
|
|
VkAttachmentLoadOp depthLoadOp = VK_ATTACHMENT_LOAD_OP_LOAD;
|
|
VkAttachmentLoadOp stencilLoadOp = VK_ATTACHMENT_LOAD_OP_LOAD;
|
|
VkImageLayout initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
|
|
};
|
|
|
|
DepthStencilAttachmentLoadInfo ResolveDepthStencilAttachmentLoadInfo(
|
|
VkImageLayout trackedLayout, Bool clearDepth, Bool clearStencil);
|
|
IntVec2 ResolveRenderPassFramebufferExtent(Bool isDefaultFbo, const TextureSize& attachmentExtent,
|
|
VkExtent2D swapchainExtent);
|
|
|
|
struct RenderPassEntry {
|
|
// Per Magma session (MagmaSession.h): each session's renderer is its own device.
|
|
static inline SessionLocal<VkDevice> s_device;
|
|
Uint64 hash = 0;
|
|
VkRenderPass renderPass = VK_NULL_HANDLE;
|
|
VkFramebuffer framebuffer = VK_NULL_HANDLE;
|
|
Uint64 compatibilityHash = 0;
|
|
Uint32 attachmentCount = 0;
|
|
Uint32 colorAttachmentCount = 0;
|
|
Bool hasDepthStencilAttachment = false;
|
|
VkSampleCountFlagBits sampleCount = VK_SAMPLE_COUNT_1_BIT;
|
|
IntVec2 extent = {0, 0};
|
|
// VkFramebufferCreateInfo::layers of the entry's framebuffer (>1 for layered GL attachments).
|
|
Uint32 layers = 1;
|
|
// Frame counter value of the last GetOrCreateRenderPass hit; drives cache eviction.
|
|
Uint64 lastUsedFrame = 0;
|
|
// P15 S0 (wire passes only): what the render pass was created from, and the render
|
|
// passes that differ from it only in loading some attachments with LOAD_OP_CLEAR. Load ops
|
|
// are not part of render-pass compatibility, so a variant begins with this entry's own
|
|
// framebuffer and runs every pipeline created against `renderPass`.
|
|
Vector<VkAttachmentDescription> wireAttachments;
|
|
Vector<VkAttachmentReference> wireColors;
|
|
VkAttachmentReference wireDepth{VK_ATTACHMENT_UNUSED, VK_IMAGE_LAYOUT_GENERAL};
|
|
Vector<std::pair<Uint64, VkRenderPass>> clearVariants;
|
|
|
|
RenderPassEntry() = default;
|
|
RenderPassEntry(const RenderPassEntry&) = delete;
|
|
RenderPassEntry(RenderPassEntry&& that) noexcept {
|
|
std::swap(hash, that.hash);
|
|
std::swap(renderPass, that.renderPass);
|
|
std::swap(framebuffer, that.framebuffer);
|
|
std::swap(compatibilityHash, that.compatibilityHash);
|
|
std::swap(attachmentCount, that.attachmentCount);
|
|
std::swap(colorAttachmentCount, that.colorAttachmentCount);
|
|
std::swap(hasDepthStencilAttachment, that.hasDepthStencilAttachment);
|
|
std::swap(sampleCount, that.sampleCount);
|
|
std::swap(extent, that.extent);
|
|
std::swap(layers, that.layers);
|
|
std::swap(lastUsedFrame, that.lastUsedFrame);
|
|
std::swap(wireAttachments, that.wireAttachments);
|
|
std::swap(wireColors, that.wireColors);
|
|
std::swap(wireDepth, that.wireDepth);
|
|
std::swap(clearVariants, that.clearVariants);
|
|
}
|
|
// Move ASSIGNMENT, not just construction. The move constructor above and the
|
|
// destructor below each independently suppress the implicit one, which left the
|
|
// type move-constructible but not move-assignable - and therefore not swappable,
|
|
// which std::swap(pair&, pair&) requires. That was invisible while UnorderedMap
|
|
// only ever move-CONSTRUCTED an element into a fresh slot. ska::flat_hash_map
|
|
// probes robin-hood: inserting swaps the entry being placed against the one
|
|
// already sitting in the slot whenever it has travelled further from its desired
|
|
// position, so the mapped type has to be swappable or the table fails to
|
|
// instantiate at all.
|
|
//
|
|
// SWAP SEMANTICS, exactly like the move constructor: this does not release the
|
|
// destination's handles, it parks them in `that`, which destroys them when it
|
|
// dies. That is correct for the only caller - std::swap, whose temporary expires
|
|
// immediately - and it is what keeps the three-move sequence from destroying a
|
|
// live render pass. It is NOT correct for a hand-written `a = std::move(b)` where
|
|
// `a` held live handles and `b` outlives the statement: those handles would then
|
|
// survive until `b` dies. There is no such caller; add a destroy-then-steal
|
|
// assignment before writing one.
|
|
RenderPassEntry& operator=(RenderPassEntry&& that) noexcept {
|
|
if (this != &that) {
|
|
std::swap(hash, that.hash);
|
|
std::swap(renderPass, that.renderPass);
|
|
std::swap(framebuffer, that.framebuffer);
|
|
std::swap(compatibilityHash, that.compatibilityHash);
|
|
std::swap(attachmentCount, that.attachmentCount);
|
|
std::swap(colorAttachmentCount, that.colorAttachmentCount);
|
|
std::swap(hasDepthStencilAttachment, that.hasDepthStencilAttachment);
|
|
std::swap(sampleCount, that.sampleCount);
|
|
std::swap(extent, that.extent);
|
|
std::swap(layers, that.layers);
|
|
std::swap(lastUsedFrame, that.lastUsedFrame);
|
|
std::swap(wireAttachments, that.wireAttachments);
|
|
std::swap(wireColors, that.wireColors);
|
|
std::swap(wireDepth, that.wireDepth);
|
|
std::swap(clearVariants, that.clearVariants);
|
|
}
|
|
return *this;
|
|
}
|
|
~RenderPassEntry() {
|
|
for (const auto& variant : clearVariants)
|
|
if (variant.second != VK_NULL_HANDLE) vkDestroyRenderPass(s_device.Get(), variant.second, nullptr);
|
|
if (renderPass != VK_NULL_HANDLE) {
|
|
vkDestroyRenderPass(s_device.Get(), renderPass, nullptr);
|
|
}
|
|
if (framebuffer != VK_NULL_HANDLE) {
|
|
vkDestroyFramebuffer(s_device.Get(), framebuffer, nullptr);
|
|
}
|
|
}
|
|
|
|
Bool CompatibleWith(const RenderPassEntry& that) const {
|
|
return this->compatibilityHash == that.compatibilityHash;
|
|
}
|
|
|
|
Bool CompatibleWith(Uint64 compatibilityHash) const {
|
|
return this->compatibilityHash == compatibilityHash;
|
|
}
|
|
};
|
|
|
|
struct ActiveRenderPassInfo {
|
|
Uint64 hash = 0;
|
|
Uint64 compatibilityHash = 0;
|
|
VkCommandBuffer commandBuffer = VK_NULL_HANDLE;
|
|
IntVec2 extent = {0, 0};
|
|
|
|
Bool CompatibleWith(const RenderPassEntry& that) const {
|
|
return compatibilityHash == that.compatibilityHash;
|
|
}
|
|
|
|
Bool CompatibleWith(Uint64 thatCompatibilityHash) const {
|
|
return compatibilityHash == thatCompatibilityHash;
|
|
}
|
|
};
|
|
|
|
class VkRenderPassManager {
|
|
public:
|
|
using HashType = Uint64;
|
|
|
|
VkRenderPassManager(VkDevice device,
|
|
VkPhysicalDevice physicalDevice, VmaAllocator allocator, const VulkanRendererConfig& config,
|
|
VkTextureManager& textureManager, SwapchainObject& swapchainObject);
|
|
~VkRenderPassManager();
|
|
|
|
Bool Initialize();
|
|
void Shutdown();
|
|
// The surface target the default framebuffer currently names (VulkanRenderer's surface
|
|
// targets): a default-framebuffer pass of one surface is not another's.
|
|
void SetDefaultFramebufferTarget(Uint64 serial) {
|
|
m_defaultFramebufferTarget = serial;
|
|
}
|
|
static Bool BeginRenderPass(VkCommandBuffer commandBuffer, RenderPassEntry& renderPassEntry);
|
|
// P15 S0: begins `renderPassEntry` through `renderPass` (one of its clear-load variants)
|
|
// with these clear values; it is active as the entry itself, so a draw continues it.
|
|
static Bool BeginRenderPass(VkCommandBuffer commandBuffer, RenderPassEntry& renderPassEntry,
|
|
VkRenderPass renderPass, const VkClearValue* clearValues, Uint32 clearValueCount);
|
|
static Bool EndRenderPass(VkCommandBuffer commandBuffer);
|
|
static ActiveRenderPassInfo* GetActiveRenderPass();
|
|
private:
|
|
VkDevice m_device = VK_NULL_HANDLE;
|
|
VkPhysicalDevice m_physicalDevice = VK_NULL_HANDLE;
|
|
VmaAllocator m_allocator = nullptr;
|
|
const VulkanRendererConfig& m_config;
|
|
VkTextureManager& m_textureManager;
|
|
SwapchainObject& m_swapchainObject;
|
|
|
|
// Bumped whenever a renderbuffer VkImage is (re)created; together with the texture
|
|
// manager's image epoch this invalidates the render-pass fast path on any attachment
|
|
// image recreation.
|
|
Uint64 m_renderbufferImageEpoch = 1;
|
|
|
|
public:
|
|
// Bumped whenever a renderbuffer backing is (re)created; consecutive-draw
|
|
// snapshots include it so an attachment respecify forces a re-resolve.
|
|
Uint64 GetRenderbufferImageEpoch() const { return m_renderbufferImageEpoch; }
|
|
|
|
private:
|
|
|
|
Uint64 m_defaultFramebufferTarget = 0;
|
|
|
|
// Supported sample counts per attachment format, so per-draw resource lookups
|
|
// do not repeat vkGetPhysicalDeviceImageFormatProperties.
|
|
UnorderedMap<VkFormat, VkSampleCountFlags> m_attachmentSampleCountsByFormat;
|
|
|
|
|
|
static inline thread_local XXH64_state_t* m_hashState = XXH64_createState();
|
|
// Per Magma session (MagmaSession.h): one session's open render pass and managers are not
|
|
// another's.
|
|
static inline SessionLocal<ActiveRenderPassInfo> s_activeRenderPass;
|
|
static inline SessionLocal<Bool> s_hasActiveRenderPass;
|
|
static inline SessionLocal<VkTextureManager*> s_textureManager;
|
|
static inline SessionLocal<SwapchainObject*> s_swapchainObject;
|
|
static inline SessionLocal<VkRenderPassManager*> s_renderPassManager;
|
|
};
|
|
} // namespace MobileGL::MG_Backend::DirectVulkan
|