From 1c1a142f52567613983e4584f4d64ed9aa64d9eb Mon Sep 17 00:00:00 2001 From: Jorijn van der Graaf Date: Sat, 11 Apr 2026 18:48:00 +0200 Subject: [PATCH 1/6] rendertargetvulkan --- examples/VulkanUI/main.cpp | 11 +- .../Crafter.Graphics-Rendertarget.cpp | 160 ++++++++++++++++++ .../Crafter.Graphics-ForwardDeclarations.cppm | 2 - interfaces/Crafter.Graphics-GridElement.cppm | 9 +- .../Crafter.Graphics-RenderingElement2D.cppm | 4 +- ...ter.Graphics-RenderingElement2DVulkan.cppm | 130 ++------------ interfaces/Crafter.Graphics-Rendertarget.cppm | 43 ++++- interfaces/Crafter.Graphics-Transform2D.cppm | 9 +- project.json | 3 +- 9 files changed, 223 insertions(+), 148 deletions(-) create mode 100644 implementations/Crafter.Graphics-Rendertarget.cpp diff --git a/examples/VulkanUI/main.cpp b/examples/VulkanUI/main.cpp index 05dfbaf..325c38a 100644 --- a/examples/VulkanUI/main.cpp +++ b/examples/VulkanUI/main.cpp @@ -54,7 +54,6 @@ int main() { window.FinishInit(); - RendertargetBase<3> rendertargetBase(1280, 720); RenderingElement2DVulkan element( { 0.5, //anchorX: relative position where this elements x anchor (top-left) is placed to its parent x anchor @@ -73,13 +72,7 @@ int main() { reinterpret_cast, true>*>(element.buffers[i])->value[1] = {0, 1, 0, 1}; reinterpret_cast, true>*>(element.buffers[i])->FlushDevice(); } - - InitializeRenderingElement2DVulkanBuffer(); - element.UpdatePosition(rendertargetBase, rendertargetBase.transform); - - renderingElement2DVulkanTransformBuffer[0].FlushDevice(); - renderingElement2DVulkanTransformBuffer[1].FlushDevice(); - renderingElement2DVulkanTransformBuffer[2].FlushDevice(); + RendertargetVulkan rendertarget(1280, 720, {&element}); VkImageDescriptorInfoEXT imageInfo0 = { .sType = VK_STRUCTURE_TYPE_IMAGE_DESCRIPTOR_INFO_EXT, @@ -130,7 +123,7 @@ int main() { .size = Device::descriptorHeapProperties.imageDescriptorSize }, - WriteRenderingElement2DVulkanDescriptors(infos, ranges, 3, descriptorHeap.bufferStartOffset, descriptorHeap); + rendertarget.WriteDescriptors(infos, ranges, 3, descriptorHeap.bufferStartOffset, descriptorHeap); window.pipeline = &pipeline; window.descriptorHeap = &descriptorHeap; diff --git a/implementations/Crafter.Graphics-Rendertarget.cpp b/implementations/Crafter.Graphics-Rendertarget.cpp new file mode 100644 index 0000000..f453ca3 --- /dev/null +++ b/implementations/Crafter.Graphics-Rendertarget.cpp @@ -0,0 +1,160 @@ +/* +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; +#ifdef CRAFTER_GRAPHICS_RENDERER_VULKAN +#include +#endif +module Crafter.Graphics:Rendertarget_impl; +#ifdef CRAFTER_GRAPHICS_RENDERER_VULKAN +import :Rendertarget; +import :Window; +import :DescriptorHeapVulkan; +import :RenderingElement2DVulkan; +import std; +using namespace Crafter; + +RendertargetVulkan::RendertargetVulkan(std::uint16_t sizeX, std::uint16_t sizeY, std::vector&& elementss) : RendertargetBase(sizeX, sizeY), elements(std::move(elementss)) { + for(Transform2D* child : transform.children) { + SetOrderResursive(child); + } + for(std::uint8_t frame = 0; frame < Window::numFrames; frame++) { + transformBuffer[frame].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, elements.size()+1); + RenderingElement2DVulkanTransformInfo* val = reinterpret_cast(reinterpret_cast(transformBuffer[frame].value) + sizeof(RenderingElement2DVulkanTransformInfo)); + std::uint16_t* sizePtr = reinterpret_cast(transformBuffer[frame].value); + *sizePtr = static_cast(elements.size()); + for(std::uint16_t i = 0; i < elements.size(); i++) { + elements[i]->ScaleElement(transform); + val[i].scaled = elements[i]->scaled; + val[i].bufferX = elements[i]->bufferX; + val[i].bufferY = elements[i]->bufferY; + } + transformBuffer[frame].FlushDevice(); + } +} + +RendertargetVulkan::RendertargetVulkan(std::uint16_t sizeX, std::uint16_t sizeY) : RendertargetBase(sizeX, sizeY) { + +} + +void RendertargetVulkan::UpdateBuffer(std::uint8_t frame) { + elements.clear(); + for(Transform2D* child : transform.children) { + SetOrderResursive(child); + } + transformBuffer[frame].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, elements.size()+1); + RenderingElement2DVulkanTransformInfo* val = reinterpret_cast(reinterpret_cast(transformBuffer[frame].value) + sizeof(RenderingElement2DVulkanTransformInfo)); + std::uint16_t* sizePtr = reinterpret_cast(transformBuffer[frame].value); + *sizePtr = static_cast(elements.size()); + for(std::uint16_t i = 0; i < elements.size(); i++) { + elements[i]->ScaleElement(transform); + val[i].scaled = elements[i]->scaled; + val[i].bufferX = elements[i]->bufferX; + val[i].bufferY = elements[i]->bufferY; + } + transformBuffer[frame].FlushDevice(); +} + +void RendertargetVulkan::ReorderBuffer(std::uint8_t frame) { + RenderingElement2DVulkanTransformInfo* val = reinterpret_cast(reinterpret_cast(transformBuffer[frame].value) + sizeof(RenderingElement2DVulkanTransformInfo)); + elements.clear(); + for(Transform2D* child : transform.children) { + SetOrderResursive(child); + } + for(std::uint16_t i = 0; i < elements.size(); i++) { + val[i].scaled = elements[i]->scaled; + val[i].bufferX = elements[i]->bufferX; + val[i].bufferY = elements[i]->bufferY; + } +} + +void RendertargetVulkan::WriteDescriptors(std::span infos, std::span ranges, std::uint16_t start, std::uint32_t bufferOffset, DescriptorHeapVulkan& descriptorHeap) { + VkDeviceAddressRangeKHR transformRanges[Window::numFrames] = { + { + .address = transformBuffer[0].address, + .size = transformBuffer[0].size + }, + { + .address = transformBuffer[1].address, + .size = transformBuffer[1].size + }, + { + .address = transformBuffer[2].address, + .size = transformBuffer[2].size + } + }; + + for(std::uint8_t i = 0; i < Window::numFrames; i++) { + ranges[start + i] = { + .address = descriptorHeap.resourceHeap[i].value + bufferOffset, + .size = Device::descriptorHeapProperties.bufferDescriptorSize + }; + infos[start + i] = { + .sType = VK_STRUCTURE_TYPE_RESOURCE_DESCRIPTOR_INFO_EXT, + .type = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, + .data = { .pAddressRange = &transformRanges[i]} + }; + } + + start += 3; + bufferOffset += Device::descriptorHeapProperties.bufferDescriptorSize; + + std::vector bufferRanges(elements.size() * Window::numFrames); + + std::uint16_t rangeOffset = 0; + + for(std::uint8_t i2 = 0; i2 < Window::numFrames; i2++) { + for(std::uint16_t i = 0; i < elements.size(); i++) { + ranges[start + i] = { + .address = descriptorHeap.resourceHeap[i2].value + bufferOffset + Device::descriptorHeapProperties.bufferDescriptorSize * i, + .size = Device::descriptorHeapProperties.bufferDescriptorSize + }; + infos[start + i] = { + .sType = VK_STRUCTURE_TYPE_RESOURCE_DESCRIPTOR_INFO_EXT, + .type = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, + .data = { .pAddressRange = &bufferRanges[i]} + }; + bufferRanges[rangeOffset + i] = { + .address = elements[i]->buffers[i2]->address, + .size = elements[i]->buffers[i2]->size + }; + } + start += elements.size(); + rangeOffset += elements.size(); + } + + Device::vkWriteResourceDescriptorsEXT(Device::device, start, infos.data(), ranges.data()); + + for(std::uint8_t i = 0; i < Window::numFrames; i++) { + descriptorHeap.resourceHeap[i].FlushDevice(); + } +} + +void RendertargetVulkan::SetOrderResursive(Transform2D* elementTransform) { + std::sort(elementTransform->children.begin(), elementTransform->children.end(), [](Transform2D* a, Transform2D* b){ return a->anchor.z < b->anchor.z; }); + for(Transform2D* childTransform : elementTransform->children) { + RenderingElement2DVulkanBase* renderer = dynamic_cast(childTransform); + if(renderer) { + renderer->index = elements.size(); + elements.push_back(renderer); + } + SetOrderResursive(childTransform); + } +} +#endif \ No newline at end of file diff --git a/interfaces/Crafter.Graphics-ForwardDeclarations.cppm b/interfaces/Crafter.Graphics-ForwardDeclarations.cppm index 9a5adca..3534166 100644 --- a/interfaces/Crafter.Graphics-ForwardDeclarations.cppm +++ b/interfaces/Crafter.Graphics-ForwardDeclarations.cppm @@ -21,9 +21,7 @@ export module Crafter.Graphics:ForwardDeclarations; import std; export namespace Crafter { - template struct RendertargetBase; - struct GridElement; struct Window; } \ No newline at end of file diff --git a/interfaces/Crafter.Graphics-GridElement.cppm b/interfaces/Crafter.Graphics-GridElement.cppm index 7465b5e..30565bf 100644 --- a/interfaces/Crafter.Graphics-GridElement.cppm +++ b/interfaces/Crafter.Graphics-GridElement.cppm @@ -33,8 +33,7 @@ export namespace Crafter { GridElement(std::uint32_t columns, std::uint32_t rows, std::int32_t spacingX, std::int32_t spacingY, std::int32_t paddingX, std::int32_t paddingY, Anchor2D anchor) : Transform2D(anchor), columns(columns), rows(rows), spacingX(spacingX), spacingY(spacingY), paddingX(paddingX), paddingY(paddingY) { } - template - void UpdatePositionImpl(RendertargetBase& window, Transform2D& parent) { + void UpdatePosition(RendertargetBase& window, Transform2D& parent) override { ScaleElement(parent); std::int32_t cellWidth = (paddingX * 2) - (spacingX * (columns - 1)) / columns; std::int32_t cellHeight = (paddingY * 2) - (spacingY * (rows - 1)) / rows; @@ -61,11 +60,5 @@ export namespace Crafter { } } } - void UpdatePosition(RendertargetBase<1>& window, Transform2D& parent) override { - UpdatePositionImpl<1>(window, parent); - } - void UpdatePosition(RendertargetBase<3>& window, Transform2D& parent) override { - UpdatePositionImpl<3>(window, parent); - } }; } \ No newline at end of file diff --git a/interfaces/Crafter.Graphics-RenderingElement2D.cppm b/interfaces/Crafter.Graphics-RenderingElement2D.cppm index 22c04f0..dc2c184 100644 --- a/interfaces/Crafter.Graphics-RenderingElement2D.cppm +++ b/interfaces/Crafter.Graphics-RenderingElement2D.cppm @@ -130,7 +130,7 @@ export namespace Crafter { } - void UpdatePosition(RendertargetBase& window, Transform2D& parent) override { + void UpdatePosition(RendertargetBase& window, Transform2D& parent) override { ScaleData2D oldScale = this->scaled; this->ScaleElement(parent); if constexpr(Scaling && !Rotating) { @@ -153,7 +153,7 @@ export namespace Crafter { } } - std::vector ResizeText(RendertargetBase& window, Transform2D& parent, const std::string_view text, float& size, Font& font, TextOverflowMode overflowMode = TextOverflowMode::Clip, TextScaleMode scaleMode = TextScaleMode::None) { + std::vector ResizeText(RendertargetBase& window, Transform2D& parent, const std::string_view text, float& size, Font& font, TextOverflowMode overflowMode = TextOverflowMode::Clip, TextScaleMode scaleMode = TextScaleMode::None) { float scale = stbtt_ScaleForPixelHeight(&font.font, size); int baseline = (int)(font.ascent * scale); diff --git a/interfaces/Crafter.Graphics-RenderingElement2DVulkan.cppm b/interfaces/Crafter.Graphics-RenderingElement2DVulkan.cppm index 5827287..f8faf00 100644 --- a/interfaces/Crafter.Graphics-RenderingElement2DVulkan.cppm +++ b/interfaces/Crafter.Graphics-RenderingElement2DVulkan.cppm @@ -33,13 +33,6 @@ import :Window; import :DescriptorHeapVulkan; export namespace Crafter { - struct __attribute__((packed)) RenderingElement2DVulkanTransformInfo { - ScaleData2D scaled; // 0 - 8 bytes - std::uint16_t bufferX; // 8 - 2 bytes - std::uint16_t bufferY; // 10 - 2 bytes - //12 bytes total; - }; - struct RenderingElement2DVulkanBase : Transform2D { std::uint16_t index; std::uint16_t bufferX; @@ -56,14 +49,10 @@ export namespace Crafter { } }; - std::vector renderingElement2DVulkans; - VulkanBuffer renderingElement2DVulkanTransformBuffer[Window::numFrames]; - template struct RenderingElement2DVulkan : RenderingElement2DVulkanBase { - RenderingElement2DVulkan(Anchor2D anchor, RendertargetBase& target, Transform2D& parent) requires(Owning) : RenderingElement2DVulkanBase(anchor) { - renderingElement2DVulkans.push_back(this); - UpdatePosition(target, parent); + 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; for(std::uint8_t i = 0; i < Window::numFrames; i++) { @@ -73,7 +62,6 @@ export namespace Crafter { } RenderingElement2DVulkan(Anchor2D anchor, std::uint16_t bufferX, std::uint16_t bufferY) requires(Owning) : RenderingElement2DVulkanBase(anchor, bufferX, bufferY) { - renderingElement2DVulkans.push_back(this); 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); @@ -81,7 +69,7 @@ export namespace Crafter { } RenderingElement2DVulkan(Anchor2D anchor, std::uint16_t bufferX, std::uint16_t bufferY, std::array&& buffers) requires(!Owning) : RenderingElement2DVulkanBase(anchor, bufferX, bufferY, std::move(buffers)) { - renderingElement2DVulkans.push_back(this); + } RenderingElement2DVulkan(Anchor2D anchor, const std::filesystem::path& assetPath, bool single) requires(Owning && Mapped) : RenderingElement2DVulkanBase(anchor) { @@ -94,15 +82,15 @@ export namespace Crafter { for(std::uint8_t i = 1; i < Window::numFrames; i++) { buffers[i] = buffers[0]; } - TextureAsset<_Float16>::LoadInfo(assetPath, buffers[0].value, this->bufferX, this->bufferY); + TextureAsset<_Float16>::Load(assetPath, reinterpret_cast<_Float16*>(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<_Float16>::LoadInfo(assetPath, buffers[0].value, this->bufferX, this->bufferY); + TextureAsset<_Float16>::Load(assetPath, reinterpret_cast<_Float16*>(static_cast, Mapped>*>(buffers[0])->value), this->bufferX, this->bufferY); for(std::uint8_t i = 1; i < Window::numFrames; i++) { - std::memcpy(buffers[i].value, buffers[0].value, this->bufferX * this->bufferY * sizeof(_Float16)); + std::memcpy(static_cast, Mapped>*>(buffers[i])->value, static_cast, Mapped>*>(buffers[0])->value, this->bufferX * this->bufferY * sizeof(_Float16)); } } } @@ -113,113 +101,27 @@ export namespace Crafter { delete static_cast, Mapped>*>(buffer); } } - auto it = std::find(renderingElement2DVulkans.begin(), renderingElement2DVulkans.end(), this); - if (it != renderingElement2DVulkans.end()) { - renderingElement2DVulkans.erase(it); - } } RenderingElement2DVulkan(RenderingElement2DVulkan&) = delete; RenderingElement2DVulkan& operator=(RenderingElement2DVulkan&) = delete; - void UpdatePosition(RendertargetBase& window, Transform2D& parent) override { + void UpdatePosition(RendertargetBase& window2, Transform2D& parent) override { + RendertargetVulkan& window = static_cast(window2); this->ScaleElement(parent); - RenderingElement2DVulkanTransformInfo* val = reinterpret_cast(reinterpret_cast(renderingElement2DVulkanTransformBuffer[window.frame].value) + sizeof(RenderingElement2DVulkanTransformInfo)); + 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); + } + } }; - - void WriteRenderingElement2DVulkanDescriptors(std::span infos, std::span ranges, std::uint16_t start, std::uint32_t bufferOffset, DescriptorHeapVulkan& descriptorHeap) { - VkDeviceAddressRangeKHR transformRanges[Window::numFrames] = { - { - .address = renderingElement2DVulkanTransformBuffer[0].address, - .size = renderingElement2DVulkanTransformBuffer[0].size - }, - { - .address = renderingElement2DVulkanTransformBuffer[1].address, - .size = renderingElement2DVulkanTransformBuffer[1].size - }, - { - .address = renderingElement2DVulkanTransformBuffer[2].address, - .size = renderingElement2DVulkanTransformBuffer[2].size - } - }; - - for(std::uint8_t i = 0; i < Window::numFrames; i++) { - ranges[start + i] = { - .address = descriptorHeap.resourceHeap[i].value + bufferOffset, - .size = Device::descriptorHeapProperties.bufferDescriptorSize - }; - infos[start + i] = { - .sType = VK_STRUCTURE_TYPE_RESOURCE_DESCRIPTOR_INFO_EXT, - .type = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, - .data = { .pAddressRange = &transformRanges[i]} - }; - } - - start += 3; - bufferOffset += Device::descriptorHeapProperties.bufferDescriptorSize; - - std::vector bufferRanges(renderingElement2DVulkans.size() * Window::numFrames); - - std::uint16_t rangeOffset = 0; - - for(std::uint8_t i2 = 0; i2 < Window::numFrames; i2++) { - for(std::uint16_t i = 0; i < renderingElement2DVulkans.size(); i++) { - ranges[start + i] = { - .address = descriptorHeap.resourceHeap[i2].value + bufferOffset + Device::descriptorHeapProperties.bufferDescriptorSize * i, - .size = Device::descriptorHeapProperties.bufferDescriptorSize - }; - infos[start + i] = { - .sType = VK_STRUCTURE_TYPE_RESOURCE_DESCRIPTOR_INFO_EXT, - .type = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, - .data = { .pAddressRange = &bufferRanges[i]} - }; - bufferRanges[rangeOffset + i] = { - .address = renderingElement2DVulkans[i]->buffers[i2]->address, - .size = renderingElement2DVulkans[i]->buffers[i2]->size - }; - } - start += renderingElement2DVulkans.size(); - rangeOffset += renderingElement2DVulkans.size(); - } - - Device::vkWriteResourceDescriptorsEXT(Device::device, start, infos.data(), ranges.data()); - - for(std::uint8_t i = 0; i < Window::numFrames; i++) { - descriptorHeap.resourceHeap[i].FlushDevice(); - } - } - - void InitializeRenderingElement2DVulkanBuffer() { - for(std::uint8_t frame = 0; frame < Window::numFrames; frame++) { - renderingElement2DVulkanTransformBuffer[frame].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, renderingElement2DVulkans.size()+1); - RenderingElement2DVulkanTransformInfo* val = reinterpret_cast(reinterpret_cast(renderingElement2DVulkanTransformBuffer[frame].value) + sizeof(RenderingElement2DVulkanTransformInfo)); - std::uint16_t* sizePtr = reinterpret_cast(renderingElement2DVulkanTransformBuffer[frame].value); - *sizePtr = renderingElement2DVulkans.size(); - for(std::uint16_t i = 0; i < renderingElement2DVulkans.size(); i++) { - val[i].scaled = renderingElement2DVulkans[i]->scaled; - val[i].bufferX = renderingElement2DVulkans[i]->bufferX; - val[i].bufferY = renderingElement2DVulkans[i]->bufferY; - renderingElement2DVulkans[i]->index = i; - } - } - } - - void UpdateRenderingElement2DVulkanBuffer(std::uint8_t frame) { - renderingElement2DVulkanTransformBuffer[frame].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, renderingElement2DVulkans.size()+1); - RenderingElement2DVulkanTransformInfo* val = reinterpret_cast(reinterpret_cast(renderingElement2DVulkanTransformBuffer[frame].value) + sizeof(RenderingElement2DVulkanTransformInfo)); - std::uint16_t* sizePtr = reinterpret_cast(renderingElement2DVulkanTransformBuffer[frame].value); - *sizePtr = renderingElement2DVulkans.size(); - for(std::uint16_t i = 0; i < renderingElement2DVulkans.size(); i++) { - val[i].scaled = renderingElement2DVulkans[i]->scaled; - val[i].bufferX = renderingElement2DVulkans[i]->bufferX; - val[i].bufferY = renderingElement2DVulkans[i]->bufferY; - renderingElement2DVulkans[i]->index = i + 1; - } - } } #endif \ No newline at end of file diff --git a/interfaces/Crafter.Graphics-Rendertarget.cppm b/interfaces/Crafter.Graphics-Rendertarget.cppm index da850ef..8ca5c6f 100644 --- a/interfaces/Crafter.Graphics-Rendertarget.cppm +++ b/interfaces/Crafter.Graphics-Rendertarget.cppm @@ -16,6 +16,12 @@ 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:Rendertarget; import Crafter.Math; import Crafter.Asset; @@ -23,9 +29,12 @@ import std; import :Types; import :Transform2D; import :RenderingElement2DBase; +#ifdef CRAFTER_GRAPHICS_RENDERER_VULKAN +import :Device; +import :VulkanBuffer; +#endif export namespace Crafter { - template struct RendertargetBase { #ifdef CRAFTER_TIMING std::vector> renderTimings; @@ -33,7 +42,6 @@ export namespace Crafter { Transform2D transform; std::uint16_t sizeX; std::uint16_t sizeY; - std::uint8_t frame; RendertargetBase() = default; RendertargetBase(std::uint16_t sizeX, std::uint16_t sizeY) : sizeX(sizeX), sizeY(sizeY), transform({0, 0, 1, 1, 0, 0, 0}){ transform.scaled.size.x = sizeX; @@ -42,12 +50,39 @@ export namespace Crafter { transform.scaled.position.y = 0; } }; + + #ifdef CRAFTER_GRAPHICS_RENDERER_VULKAN + struct RenderingElement2DVulkanBase; + + struct __attribute__((packed)) RenderingElement2DVulkanTransformInfo { + ScaleData2D scaled; // 0 - 8 bytes + std::uint16_t bufferX; // 8 - 2 bytes + std::uint16_t bufferY; // 10 - 2 bytes + //12 bytes total; + }; + + + struct DescriptorHeapVulkan; + struct RendertargetVulkan : RendertargetBase { + std::uint8_t frame; + std::vector elements; + VulkanBuffer transformBuffer[3]; + + RendertargetVulkan() = default; + RendertargetVulkan(std::uint16_t sizeX, std::uint16_t sizeY, std::vector&& elements); + RendertargetVulkan(std::uint16_t sizeX, std::uint16_t sizeY); + void UpdateBuffer(std::uint8_t frame); + void ReorderBuffer(std::uint8_t frame); + void WriteDescriptors(std::span infos, std::span ranges, std::uint16_t start, std::uint32_t bufferOffset, DescriptorHeapVulkan& descriptorHeap); + void SetOrderResursive(Transform2D* elementTransform); + }; + #endif template - struct Rendertarget : RendertargetBase { + struct Rendertarget : RendertargetBase { Vector* buffer[Frames]; Rendertarget() = default; - Rendertarget(std::uint16_t sizeX, std::uint16_t sizeY) : RendertargetBase(sizeX, sizeY) { + Rendertarget(std::uint16_t sizeX, std::uint16_t sizeY) : RendertargetBase(sizeX, sizeY) { } void RenderElement(Transform2D* elementTransform, std::uint8_t frame, std::vector&& dirtyRects) { diff --git a/interfaces/Crafter.Graphics-Transform2D.cppm b/interfaces/Crafter.Graphics-Transform2D.cppm index bab45e5..f24bb6a 100644 --- a/interfaces/Crafter.Graphics-Transform2D.cppm +++ b/interfaces/Crafter.Graphics-Transform2D.cppm @@ -49,14 +49,7 @@ export namespace Crafter { Transform2D& operator=(Transform2D&) = delete; virtual ~Transform2D() = default; - virtual void UpdatePosition(RendertargetBase<1>& window, Transform2D& parent) { - ScaleElement(parent); - for(Transform2D* child : children) { - child->UpdatePosition(window, *this); - } - } - - virtual void UpdatePosition(RendertargetBase<3>& window, Transform2D& parent) { + virtual void UpdatePosition(RendertargetBase& window, Transform2D& parent) { ScaleElement(parent); for(Transform2D* child : children) { child->UpdatePosition(window, *this); diff --git a/project.json b/project.json index c19bdf0..752dc42 100644 --- a/project.json +++ b/project.json @@ -10,7 +10,8 @@ "implementations/Crafter.Graphics-Transform2D", "implementations/Crafter.Graphics-Device", "implementations/Crafter.Graphics-Mesh", - "implementations/Crafter.Graphics-RenderingElement3D" + "implementations/Crafter.Graphics-RenderingElement3D", + "implementations/Crafter.Graphics-Rendertarget" ], "interfaces": [ "interfaces/Crafter.Graphics-Window", From ea18f32300bb69754ab1591356e8e241838ec393 Mon Sep 17 00:00:00 2001 From: Jorijn van der Graaf Date: Sat, 11 Apr 2026 23:18:41 +0200 Subject: [PATCH 2/6] vulkan2d fixes --- examples/VulkanUI/raygen.glsl | 6 +- .../Crafter.Graphics-Rendertarget.cpp | 26 +- ...ter.Graphics-RenderingElement2DVulkan.cppm | 262 +++++++++++++++++- interfaces/Crafter.Graphics-Rendertarget.cppm | 21 +- 4 files changed, 279 insertions(+), 36 deletions(-) diff --git a/examples/VulkanUI/raygen.glsl b/examples/VulkanUI/raygen.glsl index 364122e..849b983 100644 --- a/examples/VulkanUI/raygen.glsl +++ b/examples/VulkanUI/raygen.glsl @@ -38,9 +38,9 @@ void main() for (uint16_t i = 1us; i < UITransformBuffer[bufferStart].count+1; i++) { if(pixel.x > UITransformBuffer[bufferStart].data[i].x && pixel.x < UITransformBuffer[bufferStart].data[i].x + UITransformBuffer[bufferStart].data[i].sizeX && pixel.y > UITransformBuffer[bufferStart].data[i].y && pixel.y < UITransformBuffer[bufferStart].data[i].y + UITransformBuffer[bufferStart].data[i].sizeY) { - int16_t srcX = int16_t((pixel.x - UITransformBuffer[bufferStart].data[i].x) * UITransformBuffer[bufferStart].data[i].bufferX / UITransformBuffer[bufferStart].data[i].sizeX); - int16_t srcY = int16_t((pixel.y - UITransformBuffer[bufferStart].data[i].y) * UITransformBuffer[bufferStart].data[i].bufferY / UITransformBuffer[bufferStart].data[i].sizeY); - hitValue = vec4(UIPixelBuffer[bufferStart + UITransformBuffer[bufferStart].count].pixels[srcY * UITransformBuffer[bufferStart].data[i].bufferX + srcX]); + int16_t srcX = int16_t(float(pixel.x - UITransformBuffer[bufferStart].data[i].x) * float(UITransformBuffer[bufferStart].data[i].bufferX) / float(UITransformBuffer[bufferStart].data[i].sizeX)); + int16_t srcY = int16_t(float(pixel.y - UITransformBuffer[bufferStart].data[i].y) * float(UITransformBuffer[bufferStart].data[i].bufferY) / float(UITransformBuffer[bufferStart].data[i].sizeY)); + hitValue = vec4(UIPixelBuffer[bufferStart + 1].pixels[srcY * UITransformBuffer[bufferStart].data[i].bufferX + srcX]); } } diff --git a/implementations/Crafter.Graphics-Rendertarget.cpp b/implementations/Crafter.Graphics-Rendertarget.cpp index f453ca3..c01fb3b 100644 --- a/implementations/Crafter.Graphics-Rendertarget.cpp +++ b/implementations/Crafter.Graphics-Rendertarget.cpp @@ -53,17 +53,23 @@ RendertargetVulkan::RendertargetVulkan(std::uint16_t sizeX, std::uint16_t sizeY) } -void RendertargetVulkan::UpdateBuffer(std::uint8_t frame) { +void RendertargetVulkan::UpdateElements() { elements.clear(); + std::sort(transform->children.begin(), transform->children.end(), [](Transform2D* a, Transform2D* b){ return a->anchor.z < b->anchor.z; }); for(Transform2D* child : transform.children) { SetOrderResursive(child); } + for(std::uint16_t i = 0; i < elements.size(); i++) { + elements[i]->ScaleElement(transform); + } +} + +void RendertargetVulkan::UpdateBuffer(std::uint8_t frame) { transformBuffer[frame].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, elements.size()+1); RenderingElement2DVulkanTransformInfo* val = reinterpret_cast(reinterpret_cast(transformBuffer[frame].value) + sizeof(RenderingElement2DVulkanTransformInfo)); std::uint16_t* sizePtr = reinterpret_cast(transformBuffer[frame].value); *sizePtr = static_cast(elements.size()); for(std::uint16_t i = 0; i < elements.size(); i++) { - elements[i]->ScaleElement(transform); val[i].scaled = elements[i]->scaled; val[i].bufferX = elements[i]->bufferX; val[i].bufferY = elements[i]->bufferY; @@ -73,10 +79,6 @@ void RendertargetVulkan::UpdateBuffer(std::uint8_t frame) { void RendertargetVulkan::ReorderBuffer(std::uint8_t frame) { RenderingElement2DVulkanTransformInfo* val = reinterpret_cast(reinterpret_cast(transformBuffer[frame].value) + sizeof(RenderingElement2DVulkanTransformInfo)); - elements.clear(); - for(Transform2D* child : transform.children) { - SetOrderResursive(child); - } for(std::uint16_t i = 0; i < elements.size(); i++) { val[i].scaled = elements[i]->scaled; val[i].bufferX = elements[i]->bufferX; @@ -128,7 +130,7 @@ void RendertargetVulkan::WriteDescriptors(std::span infos[start + i] = { .sType = VK_STRUCTURE_TYPE_RESOURCE_DESCRIPTOR_INFO_EXT, .type = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, - .data = { .pAddressRange = &bufferRanges[i]} + .data = { .pAddressRange = &bufferRanges[i]} }; bufferRanges[rangeOffset + i] = { .address = elements[i]->buffers[i2]->address, @@ -147,13 +149,13 @@ void RendertargetVulkan::WriteDescriptors(std::span } void RendertargetVulkan::SetOrderResursive(Transform2D* elementTransform) { + RenderingElement2DVulkanBase* renderer = dynamic_cast(elementTransform); + if(renderer) { + renderer->index = elements.size(); + elements.push_back(renderer); + } std::sort(elementTransform->children.begin(), elementTransform->children.end(), [](Transform2D* a, Transform2D* b){ return a->anchor.z < b->anchor.z; }); for(Transform2D* childTransform : elementTransform->children) { - RenderingElement2DVulkanBase* renderer = dynamic_cast(childTransform); - if(renderer) { - renderer->index = elements.size(); - elements.push_back(renderer); - } SetOrderResursive(childTransform); } } diff --git a/interfaces/Crafter.Graphics-RenderingElement2DVulkan.cppm b/interfaces/Crafter.Graphics-RenderingElement2DVulkan.cppm index f8faf00..d177978 100644 --- a/interfaces/Crafter.Graphics-RenderingElement2DVulkan.cppm +++ b/interfaces/Crafter.Graphics-RenderingElement2DVulkan.cppm @@ -31,6 +31,7 @@ import :VulkanBuffer; import :Types; import :Window; import :DescriptorHeapVulkan; +import :Font; export namespace Crafter { struct RenderingElement2DVulkanBase : Transform2D { @@ -51,20 +52,36 @@ export namespace Crafter { template struct RenderingElement2DVulkan : RenderingElement2DVulkanBase { - RenderingElement2DVulkan(Anchor2D anchor, RendertargetBase& target, Transform2D& parent) requires(Owning) : RenderingElement2DVulkanBase(anchor) { + RenderingElement2DVulkan(Anchor2D anchor, RendertargetBase& target, Transform2D& parent, bool single = false) requires(Owning) : RenderingElement2DVulkanBase(anchor) { GetScale(target, parent); this->bufferX = this->scaled.size.x; this->bufferY = this->scaled.size.y; - 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); + 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) { - 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, bool single = false) requires(Owning) : RenderingElement2DVulkanBase(anchor, bufferX, bufferY) { + 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); + } } } @@ -72,7 +89,7 @@ export namespace Crafter { } - RenderingElement2DVulkan(Anchor2D anchor, const std::filesystem::path& assetPath, bool single) requires(Owning && Mapped) : RenderingElement2DVulkanBase(anchor) { + RenderingElement2DVulkan(Anchor2D anchor, const std::filesystem::path& assetPath, bool single = false) requires(Owning && Mapped) : RenderingElement2DVulkanBase(anchor) { TextureAssetInfo info = TextureAsset<_Float16>::LoadInfo(assetPath); this->bufferX = info.sizeX; this->bufferY = info.sizeY; @@ -82,13 +99,13 @@ export namespace Crafter { for(std::uint8_t i = 1; i < Window::numFrames; i++) { buffers[i] = buffers[0]; } - TextureAsset<_Float16>::Load(assetPath, reinterpret_cast<_Float16*>(static_cast, Mapped>*>(buffers[0])->value), this->bufferX, this->bufferY); + 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<_Float16>::Load(assetPath, reinterpret_cast<_Float16*>(static_cast, Mapped>*>(buffers[0])->value), this->bufferX, this->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)); } @@ -122,6 +139,229 @@ export namespace Crafter { 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 \ No newline at end of file diff --git a/interfaces/Crafter.Graphics-Rendertarget.cppm b/interfaces/Crafter.Graphics-Rendertarget.cppm index 8ca5c6f..db0b9b0 100644 --- a/interfaces/Crafter.Graphics-Rendertarget.cppm +++ b/interfaces/Crafter.Graphics-Rendertarget.cppm @@ -71,6 +71,7 @@ export namespace Crafter { RendertargetVulkan() = default; RendertargetVulkan(std::uint16_t sizeX, std::uint16_t sizeY, std::vector&& elements); RendertargetVulkan(std::uint16_t sizeX, std::uint16_t sizeY); + void UpdateElements(); void UpdateBuffer(std::uint8_t frame); void ReorderBuffer(std::uint8_t frame); void WriteDescriptors(std::span infos, std::span ranges, std::uint16_t start, std::uint32_t bufferOffset, DescriptorHeapVulkan& descriptorHeap); @@ -183,16 +184,16 @@ export namespace Crafter { void AddOldRects(Transform2D* elementTransform, std::uint8_t frame, std::vector& clipRects) { RenderingElement2DBase* element = dynamic_cast*>(elementTransform); if(element) { - // if(element->scaled.position.x != element->oldScale[frame].position.x || element->scaled.position.y != element->oldScale[frame].position.y || element->scaled.size.x != element->oldScale[frame].size.x || element->scaled.size.y != element->oldScale[frame].size.y || element->redraw[frame]) { - // clipRects.emplace_back(std::max(element->scaled.position.x, std::uint16_t(0)), std::min(std::uint16_t(element->scaled.position.x + element->scaled.size.x), this->sizeX), std::max(element->scaled.position.y, std::uint16_t(0)), std::min(std::uint16_t(element->scaled.position.y + element->scaled.size.y), this->sizeY)); - // clipRects.emplace_back(std::max(element->oldScale[frame].position.x, std::uint16_t(0)), std::min(std::uint16_t(element->oldScale[frame].position.x + element->oldScale[frame].size.x), this->sizeX), std::max(element->oldScale[frame].position.y, std::uint16_t(0)), std::min(std::uint16_t(element->oldScale[frame].position.y + element->oldScale[frame].size.y), this->sizeY)); - // element->oldScale[frame] = element->scaled; - // element->redraw[frame] = false; - // } else if(element->redraw[frame]) { - // clipRects.emplace_back(std::max(element->scaled.position.x, std::uint16_t(0)), std::min(std::uint16_t(element->scaled.position.x + element->scaled.size.x), this->sizeX), std::max(element->scaled.position.y, std::uint16_t(0)), std::min(std::uint16_t(element->scaled.position.y + element->scaled.size.y), this->sizeY)); - // element->oldScale[frame] = element->scaled; - // element->redraw[frame] = false; - // } + if(element->scaled.position.x != element->oldScale[frame].position.x || element->scaled.position.y != element->oldScale[frame].position.y || element->scaled.size.x != element->oldScale[frame].size.x || element->scaled.size.y != element->oldScale[frame].size.y || element->redraw[frame]) { + clipRects.emplace_back(std::max(element->scaled.position.x, std::int16_t(0)), std::min(std::int16_t(element->scaled.position.x + element->scaled.size.x), std::int16_t(this->sizeX)), std::max(element->scaled.position.y, std::int16_t(0)), std::min(std::int16_t(element->scaled.position.y + element->scaled.size.y), std::int16_t(this->sizeY))); + clipRects.emplace_back(std::max(element->oldScale[frame].position.x, std::int16_t(0)), std::min(std::int16_t(element->oldScale[frame].position.x + element->oldScale[frame].size.x), std::int16_t(this->sizeX)), std::max(element->oldScale[frame].position.y, std::int16_t(0)), std::min(std::int16_t(element->oldScale[frame].position.y + element->oldScale[frame].size.y), std::int16_t(this->sizeY))); + element->oldScale[frame] = element->scaled; + element->redraw[frame] = false; + } else if(element->redraw[frame]) { + clipRects.emplace_back(std::max(element->scaled.position.x, std::int16_t(0)), std::min(std::int16_t(element->scaled.position.x + element->scaled.size.x), std::int16_t(this->sizeX)), std::max(element->scaled.position.y, std::int16_t(0)), std::min(std::int16_t(element->scaled.position.y + element->scaled.size.y), std::int16_t(this->sizeY))); + element->oldScale[frame] = element->scaled; + element->redraw[frame] = false; + } } for(Transform2D* child : elementTransform->children) { AddOldRects(child, frame, clipRects); From 4c93c5535ed5437343dc0632df758197ba9e3204 Mon Sep 17 00:00:00 2001 From: Jorijn van der Graaf Date: Sat, 11 Apr 2026 23:22:52 +0200 Subject: [PATCH 3/6] typo --- implementations/Crafter.Graphics-Rendertarget.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/implementations/Crafter.Graphics-Rendertarget.cpp b/implementations/Crafter.Graphics-Rendertarget.cpp index c01fb3b..ac755d8 100644 --- a/implementations/Crafter.Graphics-Rendertarget.cpp +++ b/implementations/Crafter.Graphics-Rendertarget.cpp @@ -55,7 +55,7 @@ RendertargetVulkan::RendertargetVulkan(std::uint16_t sizeX, std::uint16_t sizeY) void RendertargetVulkan::UpdateElements() { elements.clear(); - std::sort(transform->children.begin(), transform->children.end(), [](Transform2D* a, Transform2D* b){ return a->anchor.z < b->anchor.z; }); + std::sort(transform.children.begin(), transform.children.end(), [](Transform2D* a, Transform2D* b){ return a->anchor.z < b->anchor.z; }); for(Transform2D* child : transform.children) { SetOrderResursive(child); } From 5ffe1404fcc8e46b977ce5accd1586a1cc70748e Mon Sep 17 00:00:00 2001 From: Jorijn van der Graaf Date: Mon, 13 Apr 2026 18:36:07 +0200 Subject: [PATCH 4/6] vulkan2d fixes --- .../Crafter.Graphics-Rendertarget.cpp | 25 +--- ...ter.Graphics-RenderingElement2DVulkan.cppm | 113 ++++++++++++++++-- interfaces/Crafter.Graphics-Rendertarget.cppm | 3 +- 3 files changed, 107 insertions(+), 34 deletions(-) diff --git a/implementations/Crafter.Graphics-Rendertarget.cpp b/implementations/Crafter.Graphics-Rendertarget.cpp index ac755d8..52de2ee 100644 --- a/implementations/Crafter.Graphics-Rendertarget.cpp +++ b/implementations/Crafter.Graphics-Rendertarget.cpp @@ -30,24 +30,6 @@ import :RenderingElement2DVulkan; import std; using namespace Crafter; -RendertargetVulkan::RendertargetVulkan(std::uint16_t sizeX, std::uint16_t sizeY, std::vector&& elementss) : RendertargetBase(sizeX, sizeY), elements(std::move(elementss)) { - for(Transform2D* child : transform.children) { - SetOrderResursive(child); - } - for(std::uint8_t frame = 0; frame < Window::numFrames; frame++) { - transformBuffer[frame].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, elements.size()+1); - RenderingElement2DVulkanTransformInfo* val = reinterpret_cast(reinterpret_cast(transformBuffer[frame].value) + sizeof(RenderingElement2DVulkanTransformInfo)); - std::uint16_t* sizePtr = reinterpret_cast(transformBuffer[frame].value); - *sizePtr = static_cast(elements.size()); - for(std::uint16_t i = 0; i < elements.size(); i++) { - elements[i]->ScaleElement(transform); - val[i].scaled = elements[i]->scaled; - val[i].bufferX = elements[i]->bufferX; - val[i].bufferY = elements[i]->bufferY; - } - transformBuffer[frame].FlushDevice(); - } -} RendertargetVulkan::RendertargetVulkan(std::uint16_t sizeX, std::uint16_t sizeY) : RendertargetBase(sizeX, sizeY) { @@ -59,18 +41,14 @@ void RendertargetVulkan::UpdateElements() { for(Transform2D* child : transform.children) { SetOrderResursive(child); } - for(std::uint16_t i = 0; i < elements.size(); i++) { - elements[i]->ScaleElement(transform); - } } -void RendertargetVulkan::UpdateBuffer(std::uint8_t frame) { +void RendertargetVulkan::CreateBuffer(std::uint8_t frame) { transformBuffer[frame].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, elements.size()+1); RenderingElement2DVulkanTransformInfo* val = reinterpret_cast(reinterpret_cast(transformBuffer[frame].value) + sizeof(RenderingElement2DVulkanTransformInfo)); std::uint16_t* sizePtr = reinterpret_cast(transformBuffer[frame].value); *sizePtr = static_cast(elements.size()); for(std::uint16_t i = 0; i < elements.size(); i++) { - val[i].scaled = elements[i]->scaled; val[i].bufferX = elements[i]->bufferX; val[i].bufferY = elements[i]->bufferY; } @@ -84,6 +62,7 @@ void RendertargetVulkan::ReorderBuffer(std::uint8_t frame) { val[i].bufferX = elements[i]->bufferX; val[i].bufferY = elements[i]->bufferY; } + transformBuffer[frame].FlushDevice(); } void RendertargetVulkan::WriteDescriptors(std::span infos, std::span ranges, std::uint16_t start, std::uint32_t bufferOffset, DescriptorHeapVulkan& descriptorHeap) { diff --git a/interfaces/Crafter.Graphics-RenderingElement2DVulkan.cppm b/interfaces/Crafter.Graphics-RenderingElement2DVulkan.cppm index d177978..620a328 100644 --- a/interfaces/Crafter.Graphics-RenderingElement2DVulkan.cppm +++ b/interfaces/Crafter.Graphics-RenderingElement2DVulkan.cppm @@ -50,13 +50,16 @@ export namespace Crafter { } }; - template + template struct RenderingElement2DVulkan : RenderingElement2DVulkanBase { - RenderingElement2DVulkan(Anchor2D anchor, RendertargetBase& target, Transform2D& parent, bool single = false) requires(Owning) : RenderingElement2DVulkanBase(anchor) { + 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) { + 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++) { @@ -70,8 +73,8 @@ export namespace Crafter { } } - RenderingElement2DVulkan(Anchor2D anchor, std::uint16_t bufferX, std::uint16_t bufferY, bool single = false) requires(Owning) : RenderingElement2DVulkanBase(anchor, bufferX, bufferY) { - if(single) { + 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++) { @@ -89,11 +92,11 @@ export namespace Crafter { } - RenderingElement2DVulkan(Anchor2D anchor, const std::filesystem::path& assetPath, bool single = false) requires(Owning && Mapped) : RenderingElement2DVulkanBase(anchor) { + 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(single) { + 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++) { @@ -114,8 +117,12 @@ export namespace Crafter { ~RenderingElement2DVulkan() { if constexpr(Owning) { - for(VulkanBufferBase* buffer : buffers) { - delete static_cast, Mapped>*>(buffer); + if constexpr(Single) { + delete static_cast, Mapped>*>(buffers[0]); + } else { + for(VulkanBufferBase* buffer : buffers) { + delete static_cast, Mapped>*>(buffer); + } } } } @@ -123,6 +130,94 @@ export namespace Crafter { 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); diff --git a/interfaces/Crafter.Graphics-Rendertarget.cppm b/interfaces/Crafter.Graphics-Rendertarget.cppm index db0b9b0..fb00861 100644 --- a/interfaces/Crafter.Graphics-Rendertarget.cppm +++ b/interfaces/Crafter.Graphics-Rendertarget.cppm @@ -69,10 +69,9 @@ export namespace Crafter { VulkanBuffer transformBuffer[3]; RendertargetVulkan() = default; - RendertargetVulkan(std::uint16_t sizeX, std::uint16_t sizeY, std::vector&& elements); RendertargetVulkan(std::uint16_t sizeX, std::uint16_t sizeY); void UpdateElements(); - void UpdateBuffer(std::uint8_t frame); + void CreateBuffer(std::uint8_t frame); void ReorderBuffer(std::uint8_t frame); void WriteDescriptors(std::span infos, std::span ranges, std::uint16_t start, std::uint32_t bufferOffset, DescriptorHeapVulkan& descriptorHeap); void SetOrderResursive(Transform2D* elementTransform); From ef8d6235250064117484915bb1608342cac1c740 Mon Sep 17 00:00:00 2001 From: Jorijn van der Graaf Date: Wed, 15 Apr 2026 19:30:21 +0200 Subject: [PATCH 5/6] text rendering fixes --- ...after.Graphics-RenderingElement2DBase.cppm | 6 +++ ...ter.Graphics-RenderingElement2DVulkan.cppm | 44 +++++++++++++------ 2 files changed, 37 insertions(+), 13 deletions(-) diff --git a/interfaces/Crafter.Graphics-RenderingElement2DBase.cppm b/interfaces/Crafter.Graphics-RenderingElement2DBase.cppm index b4cf182..37c80ec 100644 --- a/interfaces/Crafter.Graphics-RenderingElement2DBase.cppm +++ b/interfaces/Crafter.Graphics-RenderingElement2DBase.cppm @@ -30,6 +30,12 @@ export namespace Crafter { Right }; + enum class TextVerticalAlignment { + Top, + Center, + Bottom + }; + enum class TextOverflowMode { Clip, Wrap diff --git a/interfaces/Crafter.Graphics-RenderingElement2DVulkan.cppm b/interfaces/Crafter.Graphics-RenderingElement2DVulkan.cppm index 620a328..aad409a 100644 --- a/interfaces/Crafter.Graphics-RenderingElement2DVulkan.cppm +++ b/interfaces/Crafter.Graphics-RenderingElement2DVulkan.cppm @@ -358,30 +358,39 @@ export namespace Crafter { } } - 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) { + void RenderText(std::span lines, float size, Vector<_Float16, 4> color, Font& font, std::uint8_t frame, TextAlignment alignment = TextAlignment::Left, TextVerticalAlignment verticalAlignment = TextVerticalAlignment::Top, std::int32_t offsetX = 0, std::int32_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; + std::int32_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; + offsetX -= lineWidth / 2; break; case TextAlignment::Right: - x = this->scaled.size.x - lineWidth; + offsetX += lineWidth; + break; + } + + switch (verticalAlignment) { + case TextVerticalAlignment::Top: + break; + case TextVerticalAlignment::Center: + offsetY += (lineHeight / 2) - (size); + break; + case TextVerticalAlignment::Bottom: + offsetY += lineHeight; break; } @@ -411,7 +420,7 @@ export namespace Crafter { 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 bufferX = offsetX + i + c_x1; int bufferY = currentY + j + c_y1 + offsetY; if (bufferX >= 0 && bufferX < (int)this->bufferX && bufferY >= 0 && bufferY < (int)this->bufferY) { @@ -425,7 +434,7 @@ export namespace Crafter { 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 bufferX = offsetX + i + c_x1; int bufferY = currentY + j + c_y1 + offsetY; if (bufferX >= 0 && bufferX < (int)this->bufferX && bufferY >= 0 && bufferY < (int)this->bufferY) { @@ -447,12 +456,21 @@ export namespace Crafter { } } - x += (int)(ax * scale); + if(alignment != TextAlignment::Right) { + offsetX += (int)(ax * scale); - if (p + 1 < end) { - int next; - x += (int)stbtt_GetGlyphKernAdvance(&font.font, codepoint, utf8_decode(p+1, &next)); - } + if (p + 1 < end) { + int next; + offsetX += (int)stbtt_GetGlyphKernAdvance(&font.font, codepoint, utf8_decode(p+1, &next)); + } + } else { + offsetX -= (int)(ax * scale); + + if (p + 1 < end) { + int next; + offsetX -= (int)stbtt_GetGlyphKernAdvance(&font.font, codepoint, utf8_decode(p+1, &next)); + } + } } currentY += lineHeight; } From c9ebd448f90a48522d8b2575e012e7268609b352 Mon Sep 17 00:00:00 2001 From: Jorijn van der Graaf Date: Thu, 16 Apr 2026 23:03:24 +0200 Subject: [PATCH 6/6] update --- implementations/Crafter.Graphics-Device.cpp | 72 ++++++--- implementations/Crafter.Graphics-Font.cpp | 11 ++ implementations/Crafter.Graphics-Window.cpp | 86 +++++----- interfaces/Crafter.Graphics-Font.cppm | 3 +- ...ter.Graphics-RenderingElement2DVulkan.cppm | 29 ++-- interfaces/Crafter.Graphics-Types.cppm | 147 +++++++++++++++++- interfaces/Crafter.Graphics-Window.cppm | 1 + 7 files changed, 277 insertions(+), 72 deletions(-) diff --git a/implementations/Crafter.Graphics-Device.cpp b/implementations/Crafter.Graphics-Device.cpp index b027a26..d65d583 100644 --- a/implementations/Crafter.Graphics-Device.cpp +++ b/implementations/Crafter.Graphics-Device.cpp @@ -211,6 +211,33 @@ constexpr CrafterKeys keysym_to_crafter_key(xkb_keysym_t sym) case XKB_KEY_y: return CrafterKeys::Y; case XKB_KEY_z: return CrafterKeys::Z; + case XKB_KEY_A: return CrafterKeys::A; + case XKB_KEY_B: return CrafterKeys::B; + case XKB_KEY_C: return CrafterKeys::C; + case XKB_KEY_D: return CrafterKeys::D; + case XKB_KEY_E: return CrafterKeys::E; + case XKB_KEY_F: return CrafterKeys::F; + case XKB_KEY_G: return CrafterKeys::G; + case XKB_KEY_H: return CrafterKeys::H; + case XKB_KEY_I: return CrafterKeys::I; + case XKB_KEY_J: return CrafterKeys::J; + case XKB_KEY_K: return CrafterKeys::K; + case XKB_KEY_L: return CrafterKeys::L; + case XKB_KEY_M: return CrafterKeys::M; + case XKB_KEY_N: return CrafterKeys::N; + case XKB_KEY_O: return CrafterKeys::O; + case XKB_KEY_P: return CrafterKeys::P; + case XKB_KEY_Q: return CrafterKeys::Q; + case XKB_KEY_R: return CrafterKeys::R; + case XKB_KEY_S: return CrafterKeys::S; + case XKB_KEY_T: return CrafterKeys::T; + case XKB_KEY_U: return CrafterKeys::U; + case XKB_KEY_V: return CrafterKeys::V; + case XKB_KEY_W: return CrafterKeys::W; + case XKB_KEY_X: return CrafterKeys::X; + case XKB_KEY_Y: return CrafterKeys::Y; + case XKB_KEY_Z: return CrafterKeys::Z; + // Numbers case XKB_KEY_0: return CrafterKeys::_0; case XKB_KEY_1: return CrafterKeys::_1; @@ -300,8 +327,7 @@ constexpr CrafterKeys keysym_to_crafter_key(xkb_keysym_t sym) case XKB_KEY_period: return CrafterKeys::period; case XKB_KEY_slash: return CrafterKeys::slash; - default: - throw std::runtime_error(std::format("Unkown XKB_KEY: {}", sym)); + default: return CrafterKeys::CrafterKeysMax; } } @@ -461,31 +487,39 @@ void Device::keyboard_leave(void *data, wl_keyboard *keyboard, uint32_t serial, } - - void Device::keyboard_key(void *data, wl_keyboard *keyboard, uint32_t serial, uint32_t time, uint32_t key, uint32_t state) { - xkb_keycode_t keycode = key + 8; - xkb_keysym_t keysym = xkb_state_key_get_one_sym(xkb_state, keycode); + xkb_keycode_t keycode = key + 8; + xkb_keysym_t keysym = xkb_state_key_get_one_sym(xkb_state, keycode); CrafterKeys crafterKey = keysym_to_crafter_key(keysym); - if(state == WL_KEYBOARD_KEY_STATE_PRESSED) { - if(Device::focusedWindow->heldkeys[static_cast(crafterKey)]) { - Device::focusedWindow->onKeyHold[static_cast(crafterKey)].Invoke(); - Device::focusedWindow->onAnyKeyHold.Invoke(crafterKey); - } else{ - Device::focusedWindow->heldkeys[static_cast(crafterKey)] = true; - Device::focusedWindow->onKeyDown[static_cast(crafterKey)].Invoke(); - Device::focusedWindow->onAnyKeyDown.Invoke(crafterKey); + if (state == WL_KEYBOARD_KEY_STATE_PRESSED) { + if (focusedWindow->heldkeys[(std::uint8_t)crafterKey]) { + focusedWindow->onKeyHold[(std::uint8_t)crafterKey].Invoke(); + focusedWindow->onAnyKeyHold.Invoke(crafterKey); + } else { + focusedWindow->heldkeys[(std::uint8_t)crafterKey] = true; + focusedWindow->onKeyDown[(std::uint8_t)crafterKey].Invoke(); + focusedWindow->onAnyKeyDown.Invoke(crafterKey); } - } else{ - Device::focusedWindow->heldkeys[static_cast(crafterKey)] = false; - Device::focusedWindow->onKeyUp[static_cast(crafterKey)].Invoke(); - Device::focusedWindow->onAnyKeyUp.Invoke(crafterKey); + + std::string buf; + buf.resize(16); + int n = xkb_state_key_get_utf8(xkb_state, keycode, buf.data(), 16); + if (n > 0) { + if ((unsigned char)buf[0] >= 0x20 && buf[0] != 0x7f) { + buf.resize(n); + focusedWindow->onTextInput.Invoke(buf); + } + } + } else { + focusedWindow->heldkeys[(std::uint8_t)crafterKey] = false; + focusedWindow->onKeyUp[(std::uint8_t)crafterKey].Invoke(); + focusedWindow->onAnyKeyUp.Invoke(crafterKey); } } void Device::keyboard_modifiers(void *data, wl_keyboard *keyboard, uint32_t serial, uint32_t mods_depressed, uint32_t mods_latched, uint32_t mods_locked, uint32_t group) { - + xkb_state_update_mask(xkb_state, mods_depressed, mods_latched, mods_locked, 0, 0, group); } void Device::keyboard_repeat_info(void *data, wl_keyboard *keyboard, int32_t rate, int32_t delay) { diff --git a/implementations/Crafter.Graphics-Font.cpp b/implementations/Crafter.Graphics-Font.cpp index e101528..7d7c3ca 100644 --- a/implementations/Crafter.Graphics-Font.cpp +++ b/implementations/Crafter.Graphics-Font.cpp @@ -56,4 +56,15 @@ Font::Font(const std::filesystem::path& fontFilePath) { this->ascent = ascent; this->descent = descent; this->lineGap = lineGap; +} + +std::uint32_t Font::GetLineWidth(const std::string_view text, float size) { + float scale = stbtt_ScaleForPixelHeight(&font, size); + std::uint32_t lineWidth = 0; + for (const char c : text) { + int advance, lsb; + stbtt_GetCodepointHMetrics(&font, c, &advance, &lsb); + lineWidth += (int)(advance * scale); + } + return lineWidth; } \ No newline at end of file diff --git a/implementations/Crafter.Graphics-Window.cpp b/implementations/Crafter.Graphics-Window.cpp index 7b795ad..57a63ee 100644 --- a/implementations/Crafter.Graphics-Window.cpp +++ b/implementations/Crafter.Graphics-Window.cpp @@ -272,49 +272,65 @@ LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { PostQuitMessage(0); break; } - case WM_KEYDOWN:{ - if ((lParam & (1 << 30)) == 0) { // only first press - CrafterKeys crafterKey = vk_to_crafter_key(wParam); - if(window->heldkeys[static_cast(crafterKey)]) { - window->onKeyHold[static_cast(crafterKey)].Invoke(); - window->onAnyKeyHold.Invoke(crafterKey); - } else{ - window->heldkeys[static_cast(crafterKey)] = true; - window->onKeyDown[static_cast(crafterKey)].Invoke(); - window->onAnyKeyDown.Invoke(crafterKey); - } + case WM_KEYDOWN: + case WM_SYSKEYDOWN: { // SYSKEYDOWN catches Alt combos, F10, etc. + CrafterKeys crafterKey = vk_to_crafter_key(wParam); + bool isRepeat = (lParam & (1 << 30)) != 0; + + if (isRepeat) { + window->onKeyHold[(uint8_t)crafterKey].Invoke(); + window->onAnyKeyHold.Invoke(crafterKey); + } else { + window->heldkeys[(uint8_t)crafterKey] = true; + window->onKeyDown[(uint8_t)crafterKey].Invoke(); + window->onAnyKeyDown.Invoke(crafterKey); } break; } - case WM_KEYUP: { + + case WM_KEYUP: + case WM_SYSKEYUP: { CrafterKeys crafterKey = vk_to_crafter_key(wParam); - window->heldkeys[static_cast(crafterKey)] = false; - window->onKeyUp[static_cast(crafterKey)].Invoke(); + window->heldkeys[(uint8_t)crafterKey] = false; + window->onKeyUp[(uint8_t)crafterKey].Invoke(); window->onAnyKeyUp.Invoke(crafterKey); break; } - case WM_MOUSEMOVE: { - int x = LOWORD(lParam); - int y = HIWORD(lParam); - Vector pos(x, y); - window->currentMousePos = pos; - window->onMouseMove.Invoke(); - for(MouseElement* element : window->mouseElements) { - if(element) { - if(window->currentMousePos.x >= element->scaled.position.x && window->currentMousePos.x <= element->scaled.position.x+element->scaled.size.x && window->currentMousePos.y > element->scaled.position.y && window->currentMousePos.y < element->scaled.position.y+element->scaled.size.y) { - element->onMouseMove.Invoke(); - if(!element->mouseHover) { - element->mouseHover = true; - element->onMouseEnter.Invoke(); - } - } else if(element->mouseHover) { - element->mouseHover = false; - element->onMouseLeave.Invoke(); - } - } - } - window->mouseElements.erase(std::remove(window->mouseElements.begin(), window->mouseElements.end(), static_cast(nullptr)), window->mouseElements.end()); + case WM_CHAR: { + // wParam is a UTF-16 code unit. May be a surrogate — buffer until we have a pair. + wchar_t wc = (wchar_t)wParam; + + // Filter control characters (backspace=0x08, tab=0x09, enter=0x0D, escape=0x1B, etc.) + if (wc < 0x20 || wc == 0x7f) break; + + // Handle UTF-16 surrogate pairs (characters outside the BMP, e.g. emoji). + static wchar_t highSurrogate = 0; + wchar_t utf16[2]; + int utf16Len; + + if (wc >= 0xD800 && wc <= 0xDBFF) { + // High surrogate — stash it and wait for the low surrogate. + highSurrogate = wc; + break; + } else if (wc >= 0xDC00 && wc <= 0xDFFF) { + // Low surrogate — pair with the stashed high surrogate. + if (highSurrogate == 0) break; // orphaned low surrogate, ignore + utf16[0] = highSurrogate; + utf16[1] = wc; + utf16Len = 2; + highSurrogate = 0; + } else { + utf16[0] = wc; + utf16Len = 1; + } + + // Convert UTF-16 to UTF-8. + char utf8[8]; + int n = WideCharToMultiByte(CP_UTF8, 0, utf16, utf16Len, utf8, sizeof(utf8), nullptr, nullptr); + if (n > 0) { + window->onTextInput.Invoke(std::string(utf8, n)); + } break; } case WM_LBUTTONDOWN: { diff --git a/interfaces/Crafter.Graphics-Font.cppm b/interfaces/Crafter.Graphics-Font.cppm index 77d14dd..3b98b35 100644 --- a/interfaces/Crafter.Graphics-Font.cppm +++ b/interfaces/Crafter.Graphics-Font.cppm @@ -33,6 +33,7 @@ namespace Crafter { std::int_fast32_t descent; std::int_fast32_t lineGap; stbtt_fontinfo font; - Font(const std::filesystem::path& font); + Font(const std::filesystem::path& font); + std::uint32_t GetLineWidth(const std::string_view text, float size); }; } \ No newline at end of file diff --git a/interfaces/Crafter.Graphics-RenderingElement2DVulkan.cppm b/interfaces/Crafter.Graphics-RenderingElement2DVulkan.cppm index aad409a..81c5543 100644 --- a/interfaces/Crafter.Graphics-RenderingElement2DVulkan.cppm +++ b/interfaces/Crafter.Graphics-RenderingElement2DVulkan.cppm @@ -363,8 +363,14 @@ export namespace Crafter { 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 ogOffsetX = offsetX; + std::uint32_t ogOffsetY = offsetY; + + for(std::string_view line : lines) { + offsetX = ogOffsetX; + offsetY = ogOffsetY; + std::int32_t lineWidth = 0; for (const char c : line) { int advance, lsb; @@ -379,7 +385,7 @@ export namespace Crafter { offsetX -= lineWidth / 2; break; case TextAlignment::Right: - offsetX += lineWidth; + offsetX -= lineWidth; break; } @@ -456,21 +462,12 @@ export namespace Crafter { } } - if(alignment != TextAlignment::Right) { - offsetX += (int)(ax * scale); + offsetX += (int)(ax * scale); - if (p + 1 < end) { - int next; - offsetX += (int)stbtt_GetGlyphKernAdvance(&font.font, codepoint, utf8_decode(p+1, &next)); - } - } else { - offsetX -= (int)(ax * scale); - - if (p + 1 < end) { - int next; - offsetX -= (int)stbtt_GetGlyphKernAdvance(&font.font, codepoint, utf8_decode(p+1, &next)); - } - } + if (p + 1 < end) { + int next; + offsetX += (int)stbtt_GetGlyphKernAdvance(&font.font, codepoint, utf8_decode(p+1, &next)); + } } currentY += lineHeight; } diff --git a/interfaces/Crafter.Graphics-Types.cppm b/interfaces/Crafter.Graphics-Types.cppm index 8a55f2e..5ae0b8d 100644 --- a/interfaces/Crafter.Graphics-Types.cppm +++ b/interfaces/Crafter.Graphics-Types.cppm @@ -49,7 +49,7 @@ export namespace Crafter { std::chrono::duration delta; }; - enum class CrafterKeys { + enum class CrafterKeys : std::uint8_t { // Alphabetic keys A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, @@ -92,6 +92,151 @@ export namespace Crafter { CrafterKeysMax }; + constexpr std::string CrafterKeyToString(CrafterKeys key) { + switch (key) { + // Alphabetic keys + case CrafterKeys::A: return "A"; + case CrafterKeys::B: return "B"; + case CrafterKeys::C: return "C"; + case CrafterKeys::D: return "D"; + case CrafterKeys::E: return "E"; + case CrafterKeys::F: return "F"; + case CrafterKeys::G: return "G"; + case CrafterKeys::H: return "H"; + case CrafterKeys::I: return "I"; + case CrafterKeys::J: return "J"; + case CrafterKeys::K: return "K"; + case CrafterKeys::L: return "L"; + case CrafterKeys::M: return "M"; + case CrafterKeys::N: return "N"; + case CrafterKeys::O: return "O"; + case CrafterKeys::P: return "P"; + case CrafterKeys::Q: return "Q"; + case CrafterKeys::R: return "R"; + case CrafterKeys::S: return "S"; + case CrafterKeys::T: return "T"; + case CrafterKeys::U: return "U"; + case CrafterKeys::V: return "V"; + case CrafterKeys::W: return "W"; + case CrafterKeys::X: return "X"; + case CrafterKeys::Y: return "Y"; + case CrafterKeys::Z: return "Z"; + + // Numeric keys + case CrafterKeys::_0: return "0"; + case CrafterKeys::_1: return "1"; + case CrafterKeys::_2: return "2"; + case CrafterKeys::_3: return "3"; + case CrafterKeys::_4: return "4"; + case CrafterKeys::_5: return "5"; + case CrafterKeys::_6: return "6"; + case CrafterKeys::_7: return "7"; + case CrafterKeys::_8: return "8"; + case CrafterKeys::_9: return "9"; + + // Function keys + case CrafterKeys::F1: return "F1"; + case CrafterKeys::F2: return "F2"; + case CrafterKeys::F3: return "F3"; + case CrafterKeys::F4: return "F4"; + case CrafterKeys::F5: return "F5"; + case CrafterKeys::F6: return "F6"; + case CrafterKeys::F7: return "F7"; + case CrafterKeys::F8: return "F8"; + case CrafterKeys::F9: return "F9"; + case CrafterKeys::F10: return "F10"; + case CrafterKeys::F11: return "F11"; + case CrafterKeys::F12: return "F12"; + + // Control keys + case CrafterKeys::Escape: return "Escape"; + case CrafterKeys::Tab: return "Tab"; + case CrafterKeys::Enter: return "Enter"; + case CrafterKeys::Space: return "Space"; + case CrafterKeys::Backspace: return "Backspace"; + case CrafterKeys::Delete: return "Delete"; + case CrafterKeys::Insert: return "Insert"; + case CrafterKeys::Home: return "Home"; + case CrafterKeys::End: return "End"; + case CrafterKeys::PageUp: return "PageUp"; + case CrafterKeys::PageDown: return "PageDown"; + case CrafterKeys::CapsLock: return "CapsLock"; + case CrafterKeys::NumLock: return "NumLock"; + case CrafterKeys::ScrollLock: return "ScrollLock"; + + // Modifier keys + case CrafterKeys::LeftShift: return "LeftShift"; + case CrafterKeys::RightShift: return "RightShift"; + case CrafterKeys::LeftCtrl: return "LeftCtrl"; + case CrafterKeys::RightCtrl: return "RightCtrl"; + case CrafterKeys::LeftAlt: return "LeftAlt"; + case CrafterKeys::RightAlt: return "RightAlt"; + case CrafterKeys::LeftSuper: return "LeftSuper"; + case CrafterKeys::RightSuper: return "RightSuper"; + + // Arrow keys + case CrafterKeys::Up: return "Up"; + case CrafterKeys::Down: return "Down"; + case CrafterKeys::Left: return "Left"; + case CrafterKeys::Right: return "Right"; + + // Keypad keys + case CrafterKeys::keypad_0: return "Keypad0"; + case CrafterKeys::keypad_1: return "Keypad1"; + case CrafterKeys::keypad_2: return "Keypad2"; + case CrafterKeys::keypad_3: return "Keypad3"; + case CrafterKeys::keypad_4: return "Keypad4"; + case CrafterKeys::keypad_5: return "Keypad5"; + case CrafterKeys::keypad_6: return "Keypad6"; + case CrafterKeys::keypad_7: return "Keypad7"; + case CrafterKeys::keypad_8: return "Keypad8"; + case CrafterKeys::keypad_9: return "Keypad9"; + case CrafterKeys::keypad_enter: return "KeypadEnter"; + case CrafterKeys::keypad_plus: return "KeypadPlus"; + case CrafterKeys::keypad_minus: return "KeypadMinus"; + case CrafterKeys::keypad_multiply: return "KeypadMultiply"; + case CrafterKeys::keypad_divide: return "KeypadDivide"; + case CrafterKeys::keypad_decimal: return "KeypadDecimal"; + + // Punctuation and special keys + case CrafterKeys::grave: return "Grave"; + case CrafterKeys::minus: return "Minus"; + case CrafterKeys::equal: return "Equal"; + case CrafterKeys::bracket_left: return "BracketLeft"; + case CrafterKeys::bracket_right: return "BracketRight"; + case CrafterKeys::backslash: return "Backslash"; + case CrafterKeys::semicolon: return "Semicolon"; + case CrafterKeys::quote: return "Quote"; + case CrafterKeys::comma: return "Comma"; + case CrafterKeys::period: return "Period"; + case CrafterKeys::slash: return "Slash"; + case CrafterKeys::print_screen: return "PrintScreen"; + case CrafterKeys::pause: return "Pause"; + case CrafterKeys::menu: return "Menu"; + + // Additional keys + case CrafterKeys::volume_up: return "VolumeUp"; + case CrafterKeys::volume_down: return "VolumeDown"; + case CrafterKeys::volume_mute: return "VolumeMute"; + case CrafterKeys::media_play: return "MediaPlay"; + case CrafterKeys::media_stop: return "MediaStop"; + case CrafterKeys::media_prev: return "MediaPrev"; + case CrafterKeys::media_next: return "MediaNext"; + case CrafterKeys::browser_back: return "BrowserBack"; + case CrafterKeys::browser_forward: return "BrowserForward"; + case CrafterKeys::browser_refresh: return "BrowserRefresh"; + case CrafterKeys::browser_stop: return "BrowserStop"; + case CrafterKeys::browser_search: return "BrowserSearch"; + case CrafterKeys::browser_home: return "BrowserHome"; + case CrafterKeys::launch_mail: return "LaunchMail"; + case CrafterKeys::launch_calculator: return "LaunchCalculator"; + case CrafterKeys::launch_media_player:return "LaunchMediaPlayer"; + + case CrafterKeys::CrafterKeysMax: return "Unknown"; + } + return "Unknown"; + } + template constexpr T AlignUp(T value, T2 alignment) { return (value + alignment - 1) & ~(alignment - 1); diff --git a/interfaces/Crafter.Graphics-Window.cppm b/interfaces/Crafter.Graphics-Window.cppm index 0bcb47a..2a8f7ab 100644 --- a/interfaces/Crafter.Graphics-Window.cppm +++ b/interfaces/Crafter.Graphics-Window.cppm @@ -82,6 +82,7 @@ export namespace Crafter { Event onAnyKeyDown; Event onAnyKeyHold; Event onAnyKeyUp; + Event onTextInput; Event onMouseRightClick; Event onMouseLeftClick; Event onMouseRightHold;