/* 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 "../lib/stb_truetype.h" #ifdef CRAFTER_GRAPHICS_RENDERER_VULKAN #include #endif export module Crafter.Graphics:RenderingElement2DVulkan; #ifdef CRAFTER_GRAPHICS_RENDERER_VULKAN import Crafter.Asset; import std; import :Transform2D; import :VulkanBuffer; import :Types; import :Window; import :DescriptorHeapVulkan; import :Font; export namespace Crafter { struct RenderingElement2DVulkanBase : Transform2D { std::uint16_t index; std::uint16_t bufferX; std::uint16_t bufferY; std::array buffers; RenderingElement2DVulkanBase(Anchor2D anchor) : Transform2D(anchor) { } RenderingElement2DVulkanBase(Anchor2D anchor, std::uint16_t bufferX, std::uint16_t bufferY) : bufferX(bufferX), bufferY(bufferY), Transform2D(anchor) { } RenderingElement2DVulkanBase(Anchor2D anchor, std::uint16_t bufferX, std::uint16_t bufferY, std::array&& buffers) : bufferX(bufferX), bufferY(bufferY), buffers(std::move(buffers)), Transform2D(anchor) { } }; template struct RenderingElement2DVulkan : RenderingElement2DVulkanBase { RenderingElement2DVulkan(Anchor2D anchor) : RenderingElement2DVulkanBase(anchor) { } RenderingElement2DVulkan(Anchor2D anchor, RendertargetBase& target, Transform2D& parent) requires(Owning) : RenderingElement2DVulkanBase(anchor) { GetScale(target, parent); this->bufferX = this->scaled.size.x; this->bufferY = this->scaled.size.y; if(Single) { buffers[0] = new VulkanBuffer, Mapped>(); static_cast, Mapped>*>(buffers[0])->Create(VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_2_SHADER_DEVICE_ADDRESS_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT | VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT, bufferX * bufferY); for(std::uint8_t i = 1; i < Window::numFrames; i++) { buffers[i] = buffers[0]; } } else { for(std::uint8_t i = 0; i < Window::numFrames; i++) { buffers[i] = new VulkanBuffer, Mapped>(); static_cast, Mapped>*>(buffers[i])->Create(VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_2_SHADER_DEVICE_ADDRESS_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT | VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT, bufferX * bufferY); } } } RenderingElement2DVulkan(Anchor2D anchor, std::uint16_t bufferX, std::uint16_t bufferY) requires(Owning) : RenderingElement2DVulkanBase(anchor, bufferX, bufferY) { if constexpr(Single) { buffers[0] = new VulkanBuffer, Mapped>(); static_cast, Mapped>*>(buffers[0])->Create(VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_2_SHADER_DEVICE_ADDRESS_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT | VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT, bufferX * bufferY); for(std::uint8_t i = 1; i < Window::numFrames; i++) { buffers[i] = buffers[0]; } } else { for(std::uint8_t i = 0; i < Window::numFrames; i++) { buffers[i] = new VulkanBuffer, Mapped>(); static_cast, Mapped>*>(buffers[i])->Create(VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_2_SHADER_DEVICE_ADDRESS_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT | VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT, bufferX * bufferY); } } } RenderingElement2DVulkan(Anchor2D anchor, std::uint16_t bufferX, std::uint16_t bufferY, std::array&& buffers) requires(!Owning) : RenderingElement2DVulkanBase(anchor, bufferX, bufferY, std::move(buffers)) { } RenderingElement2DVulkan(Anchor2D anchor, const std::filesystem::path& assetPath) requires(Owning && Mapped) : RenderingElement2DVulkanBase(anchor) { TextureAssetInfo info = TextureAsset<_Float16>::LoadInfo(assetPath); this->bufferX = info.sizeX; this->bufferY = info.sizeY; if constexpr(Single) { buffers[0] = new VulkanBuffer, Mapped>(); static_cast, Mapped>*>(buffers[0])->Create(VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_2_SHADER_DEVICE_ADDRESS_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT | VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT, bufferX * bufferY); for(std::uint8_t i = 1; i < Window::numFrames; i++) { buffers[i] = buffers[0]; } TextureAsset>::Load(assetPath, static_cast, Mapped>*>(buffers[0])->value, this->bufferX, this->bufferY); } else { for(std::uint8_t i = 0; i < Window::numFrames; i++) { buffers[i] = new VulkanBuffer, Mapped>(); static_cast, Mapped>*>(buffers[i])->Create(VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_2_SHADER_DEVICE_ADDRESS_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT | VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT, bufferX * bufferY); } TextureAsset>::Load(assetPath, static_cast, Mapped>*>(buffers[0])->value, this->bufferX, this->bufferY); for(std::uint8_t i = 1; i < Window::numFrames; i++) { std::memcpy(static_cast, Mapped>*>(buffers[i])->value, static_cast, Mapped>*>(buffers[0])->value, this->bufferX * this->bufferY * sizeof(_Float16)); } } } ~RenderingElement2DVulkan() { if constexpr(Owning) { if constexpr(Single) { delete static_cast, Mapped>*>(buffers[0]); } else { for(VulkanBufferBase* buffer : buffers) { delete static_cast, Mapped>*>(buffer); } } } } RenderingElement2DVulkan(RenderingElement2DVulkan&) = delete; RenderingElement2DVulkan& operator=(RenderingElement2DVulkan&) = delete; void CreateBuffer(std::uint16_t bufferX, std::uint16_t bufferY) requires(Owning) { this->bufferX = this->scaled.size.x; this->bufferY = this->scaled.size.y; if constexpr(Single) { buffers[0] = new VulkanBuffer, Mapped>(); static_cast, Mapped>*>(buffers[0])->Create(VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_2_SHADER_DEVICE_ADDRESS_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT | VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT, bufferX * bufferY); for(std::uint8_t i = 1; i < Window::numFrames; i++) { buffers[i] = buffers[0]; } } else { for(std::uint8_t i = 0; i < Window::numFrames; i++) { buffers[i] = new VulkanBuffer, Mapped>(); static_cast, Mapped>*>(buffers[i])->Create(VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_2_SHADER_DEVICE_ADDRESS_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT | VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT, bufferX * bufferY); } } } void ResizeBuffer(RendertargetVulkan& window, DescriptorHeapVulkan& descriptorHeap, std::uint16_t bufferOffset, std::uint16_t bufferX, std::uint16_t bufferY) requires(Owning) { if constexpr(Single) { static_cast, Mapped>*>(buffers[0])->Resize(VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_2_SHADER_DEVICE_ADDRESS_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT | VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT, bufferX * bufferY); } else { for(VulkanBufferBase* buffer : buffers) { delete static_cast, Mapped>*>(buffer)->Resize(VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_2_SHADER_DEVICE_ADDRESS_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT | VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT, bufferX * bufferY); } } this->bufferX = bufferX; this->bufferY = bufferY; for(std::uint8_t frame = 0; frame < Window::numFrames; frame++) { RenderingElement2DVulkanTransformInfo* val = reinterpret_cast(reinterpret_cast(window.transformBuffer[frame].value) + sizeof(RenderingElement2DVulkanTransformInfo)); val[index].bufferX = this->bufferX; val[index].bufferY = this->bufferY; window.transformBuffer[frame].FlushDevice(); } VkHostAddressRangeEXT ranges[3] = { { .address = descriptorHeap.resourceHeap[0].value + bufferOffset + Device::descriptorHeapProperties.bufferDescriptorSize * index, .size = Device::descriptorHeapProperties.bufferDescriptorSize }, { .address = descriptorHeap.resourceHeap[1].value + bufferOffset + Device::descriptorHeapProperties.bufferDescriptorSize * index, .size = Device::descriptorHeapProperties.bufferDescriptorSize }, { .address = descriptorHeap.resourceHeap[2].value + bufferOffset + Device::descriptorHeapProperties.bufferDescriptorSize * index, .size = Device::descriptorHeapProperties.bufferDescriptorSize }, }; VkDeviceAddressRangeKHR bufferRanges[3] { { .address = buffers[0]->address, .size = buffers[0]->size }, { .address = buffers[1]->address, .size = buffers[1]->size }, { .address = buffers[2]->address, .size = buffers[2]->size }, }; VkResourceDescriptorInfoEXT infos[3] = { { .sType = VK_STRUCTURE_TYPE_RESOURCE_DESCRIPTOR_INFO_EXT, .type = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, .data = { .pAddressRange = &bufferRanges[0]} }, { .sType = VK_STRUCTURE_TYPE_RESOURCE_DESCRIPTOR_INFO_EXT, .type = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, .data = { .pAddressRange = &bufferRanges[1]} }, { .sType = VK_STRUCTURE_TYPE_RESOURCE_DESCRIPTOR_INFO_EXT, .type = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, .data = { .pAddressRange = &bufferRanges[2]} }, }; Device::vkWriteResourceDescriptorsEXT(Device::device, 3, infos, ranges); for(std::uint8_t i = 0; i < Window::numFrames; i++) { descriptorHeap.resourceHeap[i].FlushDevice(); } } void UpdatePosition(RendertargetBase& window2, Transform2D& parent) override { RendertargetVulkan& window = static_cast(window2); this->ScaleElement(parent); RenderingElement2DVulkanTransformInfo* val = reinterpret_cast(reinterpret_cast(window.transformBuffer[window.frame].value) + sizeof(RenderingElement2DVulkanTransformInfo)); val[index].scaled = this->scaled; for(Transform2D* child : this->children) { child->UpdatePosition(window, *this); } } void GetScale(RendertargetBase& window, Transform2D& parent) { this->ScaleElement(parent); for(Transform2D* child : this->children) { child->UpdatePosition(window, *this); } } int utf8_decode(const char* s, int* bytes_consumed) { unsigned char c = s[0]; if (c < 0x80) { *bytes_consumed = 1; return c; } else if ((c & 0xE0) == 0xC0) { *bytes_consumed = 2; return ((c & 0x1F) << 6) | (s[1] & 0x3F); } else if ((c & 0xF0) == 0xE0) { *bytes_consumed = 3; return ((c & 0x0F) << 12) | ((s[1] & 0x3F) << 6) | (s[2] & 0x3F); } else if ((c & 0xF8) == 0xF0) { *bytes_consumed = 4; return ((c & 0x07) << 18) | ((s[1] & 0x3F) << 12) | ((s[2] & 0x3F) << 6) | (s[3] & 0x3F); } *bytes_consumed = 1; return 0xFFFD; // replacement char } void RenderText(std::span lines, float size, Vector<_Float16, 4> color, Font& font, TextAlignment alignment = TextAlignment::Left, std::uint32_t offsetX = 0, std::uint32_t offsetY = 0, OpaqueType opaque = OpaqueType::FullyOpaque) requires(Mapped) { float scale = stbtt_ScaleForPixelHeight(&font.font, size); int baseline = (int)(font.ascent * scale); std::uint32_t lineHeight = (font.ascent - font.descent) * scale; std::uint32_t currentY = baseline; for(std::string_view line : lines) { std::uint32_t lineWidth = 0; for (const char c : line) { int advance, lsb; stbtt_GetCodepointHMetrics(&font.font, c, &advance, &lsb); lineWidth += (int)(advance * scale); } std::uint32_t x = 0; switch (alignment) { case TextAlignment::Left: x = 0; break; case TextAlignment::Center: x = (this->scaled.size.x - lineWidth) / 2; break; case TextAlignment::Right: x = this->scaled.size.x - lineWidth; break; } const char* p = line.data(); const char* end = p + line.size(); while (p < end) { int bytes; int codepoint = utf8_decode(p, &bytes); p += bytes; int ax; int lsb; stbtt_GetCodepointHMetrics(&font.font, codepoint, &ax, &lsb); int c_x1, c_y1, c_x2, c_y2; stbtt_GetCodepointBitmapBox(&font.font, codepoint, scale, scale, &c_x1, &c_y1, &c_x2, &c_y2); int w = c_x2 - c_x1; int h = c_y2 - c_y1; std::vector bitmap(w * h); stbtt_MakeCodepointBitmap(&font.font, bitmap.data(), w, h, w, scale, scale, codepoint); // Only render characters that fit within the scaled bounds switch(opaque) { case OpaqueType::FullyOpaque: { for (int j = 0; j < h; j++) { for (int i = 0; i < w; i++) { int bufferX = x + i + c_x1 + offsetX; int bufferY = currentY + j + c_y1 + offsetY; if (bufferX >= 0 && bufferX < (int)this->bufferX && bufferY >= 0 && bufferY < (int)this->bufferY) { for(std::uint8_t frame = 0; frame < Window::numFrames; frame++) { static_cast, true>*>(buffers[frame])->value[bufferY * this->bufferX + bufferX] = {color.r, color.g, color.b, static_cast<_Float16>(bitmap[j * w + i])}; } } } } break; } case OpaqueType::SemiOpaque: case OpaqueType::Transparent: { for (int j = 0; j < h; j++) { for (int i = 0; i < w; i++) { int bufferX = x + i + c_x1 + offsetX; int bufferY = currentY + j + c_y1 + offsetY; if (bufferX >= 0 && bufferX < (int)this->bufferX && bufferY >= 0 && bufferY < (int)this->bufferY) { std::uint8_t alpha = bitmap[j * w + i]; _Float16 srcA = (_Float16(alpha)/_Float16(255.0f))*color.a; for(std::uint8_t frame = 0; frame < Window::numFrames; frame++) { Vector<_Float16, 4, 4> dst = static_cast, true>*>(buffers[frame])->value[bufferY * this->bufferX + bufferX]; _Float16 outA = srcA + dst.a * (1.0f - srcA); static_cast, true>*>(buffers[frame])->value[bufferY * this->bufferX + bufferX] = Vector<_Float16, 4, 4>( (color.r * srcA + dst.r * dst.a * (1.0f - srcA)), (color.g * srcA + dst.g * dst.a * (1.0f - srcA)), (color.b * srcA + dst.b * dst.a * (1.0f - srcA)), outA ); } } } } break; } } x += (int)(ax * scale); if (p + 1 < end) { int next; x += (int)stbtt_GetGlyphKernAdvance(&font.font, codepoint, utf8_decode(p+1, &next)); } } currentY += lineHeight; } } void RenderText(std::span lines, float size, Vector<_Float16, 4> color, Font& font, std::uint8_t frame, TextAlignment alignment = TextAlignment::Left, std::uint32_t offsetX = 0, std::uint32_t offsetY = 0, OpaqueType opaque = OpaqueType::FullyOpaque) requires(Mapped) { float scale = stbtt_ScaleForPixelHeight(&font.font, size); int baseline = (int)(font.ascent * scale); std::uint32_t lineHeight = (font.ascent - font.descent) * scale; std::uint32_t currentY = baseline; for(std::string_view line : lines) { std::uint32_t lineWidth = 0; for (const char c : line) { int advance, lsb; stbtt_GetCodepointHMetrics(&font.font, c, &advance, &lsb); lineWidth += (int)(advance * scale); } std::uint32_t x = 0; switch (alignment) { case TextAlignment::Left: x = 0; break; case TextAlignment::Center: x = (this->scaled.size.x - lineWidth) / 2; break; case TextAlignment::Right: x = this->scaled.size.x - lineWidth; break; } const char* p = line.data(); const char* end = p + line.size(); while (p < end) { int bytes; int codepoint = utf8_decode(p, &bytes); p += bytes; int ax; int lsb; stbtt_GetCodepointHMetrics(&font.font, codepoint, &ax, &lsb); int c_x1, c_y1, c_x2, c_y2; stbtt_GetCodepointBitmapBox(&font.font, codepoint, scale, scale, &c_x1, &c_y1, &c_x2, &c_y2); int w = c_x2 - c_x1; int h = c_y2 - c_y1; std::vector bitmap(w * h); stbtt_MakeCodepointBitmap(&font.font, bitmap.data(), w, h, w, scale, scale, codepoint); // Only render characters that fit within the scaled bounds switch(opaque) { case OpaqueType::FullyOpaque: { for (int j = 0; j < h; j++) { for (int i = 0; i < w; i++) { int bufferX = x + i + c_x1 + offsetX; int bufferY = currentY + j + c_y1 + offsetY; if (bufferX >= 0 && bufferX < (int)this->bufferX && bufferY >= 0 && bufferY < (int)this->bufferY) { static_cast, true>*>(buffers[frame])->value[bufferY * this->bufferX + bufferX] = {color.r, color.g, color.b, static_cast<_Float16>(bitmap[j * w + i])}; } } } break; } case OpaqueType::SemiOpaque: case OpaqueType::Transparent: { for (int j = 0; j < h; j++) { for (int i = 0; i < w; i++) { int bufferX = x + i + c_x1 + offsetX; int bufferY = currentY + j + c_y1 + offsetY; if (bufferX >= 0 && bufferX < (int)this->bufferX && bufferY >= 0 && bufferY < (int)this->bufferY) { std::uint8_t alpha = bitmap[j * w + i]; _Float16 srcA = (_Float16(alpha)/_Float16(255.0f))*color.a; Vector<_Float16, 4, 4> dst = static_cast, true>*>(buffers[frame])->value[bufferY * this->bufferX + bufferX]; _Float16 outA = srcA + dst.a * (1.0f - srcA); static_cast, true>*>(buffers[frame])->value[bufferY * this->bufferX + bufferX] = Vector<_Float16, 4, 4>( (color.r * srcA + dst.r * dst.a * (1.0f - srcA)), (color.g * srcA + dst.g * dst.a * (1.0f - srcA)), (color.b * srcA + dst.b * dst.a * (1.0f - srcA)), outA ); } } } break; } } x += (int)(ax * scale); if (p + 1 < end) { int next; x += (int)stbtt_GetGlyphKernAdvance(&font.font, codepoint, utf8_decode(p+1, &next)); } } currentY += lineHeight; } } }; } #endif