/* Crafter®.Graphics Copyright (C) 2026 Catcrafts® catcrafts.net This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License version 3.0 as published by the Free Software Foundation; This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ module; #include "vulkan/vulkan.h" export module Crafter.Graphics:UIRenderer; import std; import :Device; import :Window; import :RenderPass; import :DescriptorHeapVulkan; import :VulkanBuffer; import :SamplerVulkan; import :ShaderVulkan; import :ImageVulkan; import :UIDrawList; import :UIAtlas; export namespace Crafter::UI { // The compute-pass-side renderer. Owns the compute pipeline, per-frame // item buffers, the SDF glyph atlas, and the descriptor-heap slot // allocations. Implements RenderPass so it plugs into Window::passes. // // Lifecycle: // - Initialize(window, shaderPath) — once, after the window has a // descriptor heap. Allocates slots, creates pipeline, atlas image. // - SetItems(span) per frame, before Window::Render runs. // - Record(...) — invoked by Window::Render's pass loop. class UIRenderer : public RenderPass { public: // Defaulted bindless slot capacity — covers most game UIs without // descriptor heap pressure. Override in Initialize. static constexpr std::uint16_t kDefaultBindlessImageCount = 256; FontAtlas atlas; // Initialize. `initCmd` must be a command buffer in recording // state — used to transition the atlas image. Window must already // have a non-null descriptorHeap with enough free slots for // (numFrames + 1 + bindlessImageCount) images, numFrames buffers, // and 1 sampler. void Initialize(Window& window, VkCommandBuffer initCmd, const std::filesystem::path& spvPath = "ui.comp.spv", std::uint16_t bindlessImageCount = kDefaultBindlessImageCount); // Stage `items` into the next-frame mapped buffer. Must be called // BEFORE Window::Render so the buffer is flushed before the // dispatch reads it. void SetItems(std::span items); // RenderPass impl — invoked from Window::Render's pass loop. void Record(VkCommandBuffer cmd, std::uint32_t frameIdx, Window& window) override; // Heap slot accessors — UIScene reads these to populate DrawList. std::uint32_t BindlessBaseHeapIdx() const { return bindlessBase_; } FontAtlas& Atlas() { return atlas; } // The frame currently being staged. Window::Render advances // `currentBuffer` before passes record; SetItems writes to // (currentBuffer + 1) so the previous frame's buffer is still in // flight on the GPU. For V1 we ride on Window's currentBuffer // directly since vkQueueWaitIdle gates each frame. std::uint32_t pendingItemCount = 0; private: Window* window_ = nullptr; VkPipeline pipeline_ = VK_NULL_HANDLE; VulkanBuffer itemBufs_[Window::numFrames]; std::uint16_t itemCapacity_ = 0; // Heap slot allocations (resource heap unless noted). std::uint16_t outImageBase_ = 0; // images[outImageBase_ + frame] = swapchain view std::uint16_t atlasImageSlot_ = 0; // sampled atlas image slot std::uint16_t bindlessBase_ = 0; // first user-image slot std::uint16_t bindlessCount_ = 0; // user-image slot count std::uint16_t itemBufBase_ = 0; // SSBO slot base; per-frame at base + i std::uint16_t linearSamplerSlot_ = 0; // sampler heap // Stable VkImageViewCreateInfo for the atlas — descriptor heap // writes need a pointer to one, so we keep it on the renderer. VkImageViewCreateInfo atlasViewCreateInfo_{}; // Helpers. void GrowItemBuffersIfNeeded(std::uint32_t needed); void WriteSwapchainDescriptors(); void WriteAtlasDescriptor(); void WriteSamplerDescriptors(); void WriteItemBufferDescriptors(); void CreatePipeline(const std::filesystem::path& spvPath); void CreateLinearSampler(); }; }