2026-04-05 22:53:59 +02:00
|
|
|
/*
|
|
|
|
|
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"
|
2026-04-09 00:15:09 +02:00
|
|
|
#ifdef CRAFTER_GRAPHICS_RENDERER_VULKAN
|
|
|
|
|
#include <vulkan/vulkan.h>
|
|
|
|
|
#endif
|
2026-04-05 22:53:59 +02:00
|
|
|
export module Crafter.Graphics:RenderingElement2DVulkan;
|
2026-04-09 00:15:09 +02:00
|
|
|
#ifdef CRAFTER_GRAPHICS_RENDERER_VULKAN
|
2026-04-05 22:53:59 +02:00
|
|
|
import Crafter.Asset;
|
|
|
|
|
import std;
|
|
|
|
|
import :Transform2D;
|
2026-04-09 00:15:09 +02:00
|
|
|
import :VulkanBuffer;
|
2026-04-05 22:53:59 +02:00
|
|
|
import :Types;
|
|
|
|
|
import :Window;
|
2026-04-10 22:26:15 +02:00
|
|
|
import :DescriptorHeapVulkan;
|
2026-04-11 23:18:41 +02:00
|
|
|
import :Font;
|
2026-04-05 22:53:59 +02:00
|
|
|
|
|
|
|
|
export namespace Crafter {
|
2026-04-09 00:15:09 +02:00
|
|
|
struct RenderingElement2DVulkanBase : Transform2D {
|
|
|
|
|
std::uint16_t index;
|
|
|
|
|
std::uint16_t bufferX;
|
|
|
|
|
std::uint16_t bufferY;
|
2026-04-10 22:26:15 +02:00
|
|
|
std::array<VulkanBufferBase*, Window::numFrames> buffers;
|
2026-04-10 22:57:53 +02:00
|
|
|
RenderingElement2DVulkanBase(Anchor2D anchor) : Transform2D(anchor) {
|
|
|
|
|
|
|
|
|
|
}
|
2026-04-09 00:15:09 +02:00
|
|
|
RenderingElement2DVulkanBase(Anchor2D anchor, std::uint16_t bufferX, std::uint16_t bufferY) : bufferX(bufferX), bufferY(bufferY), Transform2D(anchor) {
|
|
|
|
|
|
2026-04-10 22:26:15 +02:00
|
|
|
}
|
|
|
|
|
RenderingElement2DVulkanBase(Anchor2D anchor, std::uint16_t bufferX, std::uint16_t bufferY, std::array<VulkanBufferBase*, Window::numFrames>&& buffers) : bufferX(bufferX), bufferY(bufferY), buffers(std::move(buffers)), Transform2D(anchor) {
|
|
|
|
|
|
2026-04-09 00:15:09 +02:00
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
2026-04-13 18:36:07 +02:00
|
|
|
template<bool Owning, bool Mapped, bool Single = false>
|
2026-04-09 00:15:09 +02:00
|
|
|
struct RenderingElement2DVulkan : RenderingElement2DVulkanBase {
|
2026-04-13 18:36:07 +02:00
|
|
|
RenderingElement2DVulkan(Anchor2D anchor) : RenderingElement2DVulkanBase(anchor) {
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
RenderingElement2DVulkan(Anchor2D anchor, RendertargetBase& target, Transform2D& parent) requires(Owning) : RenderingElement2DVulkanBase(anchor) {
|
2026-04-11 18:48:00 +02:00
|
|
|
GetScale(target, parent);
|
2026-04-10 22:57:53 +02:00
|
|
|
this->bufferX = this->scaled.size.x;
|
|
|
|
|
this->bufferY = this->scaled.size.y;
|
2026-04-13 18:36:07 +02:00
|
|
|
if(Single) {
|
2026-04-11 23:18:41 +02:00
|
|
|
buffers[0] = new VulkanBuffer<Vector<_Float16, 4, 4>, Mapped>();
|
|
|
|
|
static_cast<VulkanBuffer<Vector<_Float16, 4, 4>, 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<Vector<_Float16, 4, 4>, Mapped>();
|
|
|
|
|
static_cast<VulkanBuffer<Vector<_Float16, 4, 4>, 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);
|
|
|
|
|
}
|
2026-04-10 22:57:53 +02:00
|
|
|
}
|
|
|
|
|
}
|
2026-04-11 13:54:17 +02:00
|
|
|
|
2026-04-13 18:36:07 +02:00
|
|
|
RenderingElement2DVulkan(Anchor2D anchor, std::uint16_t bufferX, std::uint16_t bufferY) requires(Owning) : RenderingElement2DVulkanBase(anchor, bufferX, bufferY) {
|
|
|
|
|
if constexpr(Single) {
|
2026-04-11 23:18:41 +02:00
|
|
|
buffers[0] = new VulkanBuffer<Vector<_Float16, 4, 4>, Mapped>();
|
|
|
|
|
static_cast<VulkanBuffer<Vector<_Float16, 4, 4>, 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<Vector<_Float16, 4, 4>, Mapped>();
|
|
|
|
|
static_cast<VulkanBuffer<Vector<_Float16, 4, 4>, 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);
|
|
|
|
|
}
|
2026-04-09 00:15:09 +02:00
|
|
|
}
|
2026-04-05 22:53:59 +02:00
|
|
|
}
|
2026-04-11 13:54:17 +02:00
|
|
|
|
2026-04-10 22:26:15 +02:00
|
|
|
RenderingElement2DVulkan(Anchor2D anchor, std::uint16_t bufferX, std::uint16_t bufferY, std::array<VulkanBufferBase*, Window::numFrames>&& buffers) requires(!Owning) : RenderingElement2DVulkanBase(anchor, bufferX, bufferY, std::move(buffers)) {
|
2026-04-11 18:48:00 +02:00
|
|
|
|
2026-04-05 22:53:59 +02:00
|
|
|
}
|
|
|
|
|
|
2026-04-13 18:36:07 +02:00
|
|
|
RenderingElement2DVulkan(Anchor2D anchor, const std::filesystem::path& assetPath) requires(Owning && Mapped) : RenderingElement2DVulkanBase(anchor) {
|
2026-04-11 13:54:17 +02:00
|
|
|
TextureAssetInfo info = TextureAsset<_Float16>::LoadInfo(assetPath);
|
|
|
|
|
this->bufferX = info.sizeX;
|
|
|
|
|
this->bufferY = info.sizeY;
|
2026-04-13 18:36:07 +02:00
|
|
|
if constexpr(Single) {
|
2026-04-11 13:54:17 +02:00
|
|
|
buffers[0] = new VulkanBuffer<Vector<_Float16, 4, 4>, Mapped>();
|
|
|
|
|
static_cast<VulkanBuffer<Vector<_Float16, 4, 4>, 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];
|
|
|
|
|
}
|
2026-04-11 23:18:41 +02:00
|
|
|
TextureAsset<Vector<_Float16, 4, 4>>::Load(assetPath, static_cast<VulkanBuffer<Vector<_Float16, 4, 4>, Mapped>*>(buffers[0])->value, this->bufferX, this->bufferY);
|
2026-04-11 13:54:17 +02:00
|
|
|
} else {
|
|
|
|
|
for(std::uint8_t i = 0; i < Window::numFrames; i++) {
|
|
|
|
|
buffers[i] = new VulkanBuffer<Vector<_Float16, 4, 4>, Mapped>();
|
|
|
|
|
static_cast<VulkanBuffer<Vector<_Float16, 4, 4>, 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);
|
|
|
|
|
}
|
2026-04-11 23:18:41 +02:00
|
|
|
TextureAsset<Vector<_Float16, 4, 4>>::Load(assetPath, static_cast<VulkanBuffer<Vector<_Float16, 4, 4>, Mapped>*>(buffers[0])->value, this->bufferX, this->bufferY);
|
2026-04-11 13:54:17 +02:00
|
|
|
for(std::uint8_t i = 1; i < Window::numFrames; i++) {
|
2026-04-11 18:48:00 +02:00
|
|
|
std::memcpy(static_cast<VulkanBuffer<Vector<_Float16, 4, 4>, Mapped>*>(buffers[i])->value, static_cast<VulkanBuffer<Vector<_Float16, 4, 4>, Mapped>*>(buffers[0])->value, this->bufferX * this->bufferY * sizeof(_Float16));
|
2026-04-11 13:54:17 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-05 22:53:59 +02:00
|
|
|
~RenderingElement2DVulkan() {
|
|
|
|
|
if constexpr(Owning) {
|
2026-04-13 18:36:07 +02:00
|
|
|
if constexpr(Single) {
|
|
|
|
|
delete static_cast<VulkanBuffer<Vector<_Float16, 4, 4>, Mapped>*>(buffers[0]);
|
|
|
|
|
} else {
|
|
|
|
|
for(VulkanBufferBase* buffer : buffers) {
|
|
|
|
|
delete static_cast<VulkanBuffer<Vector<_Float16, 4, 4>, Mapped>*>(buffer);
|
|
|
|
|
}
|
2026-04-05 22:53:59 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
RenderingElement2DVulkan(RenderingElement2DVulkan&) = delete;
|
|
|
|
|
RenderingElement2DVulkan& operator=(RenderingElement2DVulkan&) = delete;
|
|
|
|
|
|
2026-04-13 18:36:07 +02:00
|
|
|
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<Vector<_Float16, 4, 4>, Mapped>();
|
|
|
|
|
static_cast<VulkanBuffer<Vector<_Float16, 4, 4>, 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<Vector<_Float16, 4, 4>, Mapped>();
|
|
|
|
|
static_cast<VulkanBuffer<Vector<_Float16, 4, 4>, 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<VulkanBuffer<Vector<_Float16, 4, 4>, 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<VulkanBuffer<Vector<_Float16, 4, 4>, 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<RenderingElement2DVulkanTransformInfo*>(reinterpret_cast<char*>(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();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-11 18:48:00 +02:00
|
|
|
void UpdatePosition(RendertargetBase& window2, Transform2D& parent) override {
|
|
|
|
|
RendertargetVulkan& window = static_cast<RendertargetVulkan&>(window2);
|
2026-04-05 22:53:59 +02:00
|
|
|
this->ScaleElement(parent);
|
2026-04-11 18:48:00 +02:00
|
|
|
RenderingElement2DVulkanTransformInfo* val = reinterpret_cast<RenderingElement2DVulkanTransformInfo*>(reinterpret_cast<char*>(window.transformBuffer[window.frame].value) + sizeof(RenderingElement2DVulkanTransformInfo));
|
2026-04-09 00:15:09 +02:00
|
|
|
val[index].scaled = this->scaled;
|
2026-04-05 22:53:59 +02:00
|
|
|
for(Transform2D* child : this->children) {
|
|
|
|
|
child->UpdatePosition(window, *this);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-11 18:48:00 +02:00
|
|
|
void GetScale(RendertargetBase& window, Transform2D& parent) {
|
|
|
|
|
this->ScaleElement(parent);
|
|
|
|
|
for(Transform2D* child : this->children) {
|
|
|
|
|
child->UpdatePosition(window, *this);
|
2026-04-09 00:15:09 +02:00
|
|
|
}
|
|
|
|
|
}
|
2026-04-11 23:18:41 +02:00
|
|
|
|
|
|
|
|
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<const std::string_view> 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<unsigned char> 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<VulkanBuffer<Vector<_Float16, 4, 4>, 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<VulkanBuffer<Vector<_Float16, 4, 4>, true>*>(buffers[frame])->value[bufferY * this->bufferX + bufferX];
|
|
|
|
|
|
|
|
|
|
_Float16 outA = srcA + dst.a * (1.0f - srcA);
|
|
|
|
|
static_cast<VulkanBuffer<Vector<_Float16, 4, 4>, 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<const std::string_view> 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<unsigned char> 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<VulkanBuffer<Vector<_Float16, 4, 4>, 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<VulkanBuffer<Vector<_Float16, 4, 4>, true>*>(buffers[frame])->value[bufferY * this->bufferX + bufferX];
|
|
|
|
|
|
|
|
|
|
_Float16 outA = srcA + dst.a * (1.0f - srcA);
|
|
|
|
|
static_cast<VulkanBuffer<Vector<_Float16, 4, 4>, 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;
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-04-11 18:48:00 +02:00
|
|
|
};
|
2026-04-09 00:15:09 +02:00
|
|
|
}
|
|
|
|
|
#endif
|