new UI system
This commit is contained in:
parent
d840a81448
commit
216972e73a
82 changed files with 4837 additions and 3243 deletions
|
|
@ -20,11 +20,9 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
|||
|
||||
module;
|
||||
|
||||
#ifdef CRAFTER_GRAPHICS_RENDERER_VULKAN
|
||||
#include "vulkan/vulkan.h"
|
||||
#include "vulkan/vk_enum_string_helper.h"
|
||||
#define GET_EXTENSION_FUNCTION(_id) ((PFN_##_id)(vkGetInstanceProcAddr(instance, #_id)))
|
||||
#endif
|
||||
|
||||
#ifdef CRAFTER_GRAPHICS_WINDOW_WAYLAND
|
||||
#include <linux/input-event-codes.h>
|
||||
|
|
@ -45,12 +43,10 @@ module;
|
|||
module Crafter.Graphics:Device_impl;
|
||||
import :Device;
|
||||
import :Window;
|
||||
import :MouseElement;
|
||||
import :Types;
|
||||
import std;
|
||||
using namespace Crafter;
|
||||
|
||||
#ifdef CRAFTER_GRAPHICS_RENDERER_VULKAN
|
||||
const char* const instanceExtensionNames[] = {
|
||||
"VK_EXT_debug_utils",
|
||||
"VK_KHR_surface",
|
||||
|
|
@ -175,7 +171,6 @@ VkBool32 onError(VkDebugUtilsMessageSeverityFlagBitsEXT severity, VkDebugUtilsMe
|
|||
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
#ifdef CRAFTER_GRAPHICS_WINDOW_WAYLAND
|
||||
|
|
@ -364,50 +359,19 @@ void Device::pointer_handle_button(void* data, wl_pointer* pointer, std::uint32_
|
|||
if(state == WL_POINTER_BUTTON_STATE_PRESSED) {
|
||||
Device::focusedWindow->mouseLeftHeld = true;
|
||||
Device::focusedWindow->onMouseLeftClick.Invoke();
|
||||
for(MouseElement* element : Device::focusedWindow->mouseElements) {
|
||||
if(element) {
|
||||
if(Device::focusedWindow->currentMousePos.x >= element->scaled.position.x && Device::focusedWindow->currentMousePos.x <= element->scaled.position.x+element->scaled.size.x && Device::focusedWindow->currentMousePos.y > element->scaled.position.y && Device::focusedWindow->currentMousePos.y < element->scaled.position.y+element->scaled.size.y) {
|
||||
element->onMouseLeftClick.Invoke();
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Device::focusedWindow->mouseLeftHeld = false;
|
||||
Device::focusedWindow->onMouseLeftRelease.Invoke();
|
||||
for(MouseElement* element : Device::focusedWindow->mouseElements) {
|
||||
if(element) {
|
||||
if(Device::focusedWindow->currentMousePos.x >= element->scaled.position.x && Device::focusedWindow->currentMousePos.x <= element->scaled.position.x+element->scaled.size.x && Device::focusedWindow->currentMousePos.y > element->scaled.position.y && Device::focusedWindow->currentMousePos.y < element->scaled.position.y+element->scaled.size.y) {
|
||||
element->onMouseLeftRelease.Invoke();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if(button == BTN_RIGHT){
|
||||
if(state == WL_POINTER_BUTTON_STATE_PRESSED) {
|
||||
Device::focusedWindow->mouseRightHeld = true;
|
||||
Device::focusedWindow->onMouseRightClick.Invoke();
|
||||
for(MouseElement* element : Device::focusedWindow->mouseElements) {
|
||||
if(element) {
|
||||
if(Device::focusedWindow->currentMousePos.x >= element->scaled.position.x && Device::focusedWindow->currentMousePos.x <= element->scaled.position.x+element->scaled.size.x && Device::focusedWindow->currentMousePos.y > element->scaled.position.y && Device::focusedWindow->currentMousePos.y < element->scaled.position.y+element->scaled.size.y) {
|
||||
element->onMouseRightClick.Invoke();
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Device::focusedWindow->mouseRightHeld = false;
|
||||
Device::focusedWindow->onMouseRightRelease.Invoke();
|
||||
for(MouseElement* element : Device::focusedWindow->mouseElements) {
|
||||
if(element) {
|
||||
if(Device::focusedWindow->currentMousePos.x >= element->scaled.position.x && Device::focusedWindow->currentMousePos.x <= element->scaled.position.x+element->scaled.size.x && Device::focusedWindow->currentMousePos.y > element->scaled.position.y && Device::focusedWindow->currentMousePos.y < element->scaled.position.y+element->scaled.size.y) {
|
||||
element->onMouseRightRelease.Invoke();
|
||||
}
|
||||
}
|
||||
}
|
||||
Device::focusedWindow->onMouseRightRelease.Invoke();
|
||||
}
|
||||
}
|
||||
Device::focusedWindow->mouseElements.erase(std::remove(Device::focusedWindow->mouseElements.begin(), Device::focusedWindow->mouseElements.end(), static_cast<MouseElement*>(nullptr)), Device::focusedWindow->mouseElements.end());
|
||||
Device::focusedWindow->mouseElements.insert(Device::focusedWindow->mouseElements.end(), Device::focusedWindow->pendingMouseElements.begin(), Device::focusedWindow->pendingMouseElements.end());
|
||||
Device::focusedWindow->pendingMouseElements.clear();
|
||||
}
|
||||
|
||||
void Device::PointerListenerHandleMotion(void* data, wl_pointer* wl_pointer, std::uint32_t time, wl_fixed_t surface_x, wl_fixed_t surface_y) {
|
||||
|
|
@ -416,21 +380,6 @@ void Device::PointerListenerHandleMotion(void* data, wl_pointer* wl_pointer, std
|
|||
Device::focusedWindow->currentMousePos = pos * Device::focusedWindow->scale;
|
||||
//Device::focusedWindow->mouseDelta = {Device::focusedWindow->currentMousePos.x-Device::focusedWindow->lastMousePos.x, Device::focusedWindow->currentMousePos.y-Device::focusedWindow->lastMousePos.y};
|
||||
Device::focusedWindow->onMouseMove.Invoke();
|
||||
for(MouseElement* element : Device::focusedWindow->mouseElements) {
|
||||
if(element) {
|
||||
if(Device::focusedWindow->currentMousePos.x >= element->scaled.position.x && Device::focusedWindow->currentMousePos.x <= element->scaled.position.x+element->scaled.size.x && Device::focusedWindow->currentMousePos.y > element->scaled.position.y && Device::focusedWindow->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();
|
||||
}
|
||||
}
|
||||
}
|
||||
Device::focusedWindow->mouseElements.erase(std::remove(Device::focusedWindow->mouseElements.begin(), Device::focusedWindow->mouseElements.end(), static_cast<MouseElement*>(nullptr)), Device::focusedWindow->mouseElements.end());
|
||||
}
|
||||
|
||||
void Device::PointerListenerHandleEnter(void* data, wl_pointer* wl_pointer, std::uint32_t serial, wl_surface* surface, wl_fixed_t surface_x, wl_fixed_t surface_y) {
|
||||
|
|
@ -559,7 +508,6 @@ void Device::Initialize() {
|
|||
}
|
||||
#endif
|
||||
|
||||
#ifdef CRAFTER_GRAPHICS_RENDERER_VULKAN
|
||||
VkApplicationInfo app{VK_STRUCTURE_TYPE_APPLICATION_INFO};
|
||||
app.pApplicationName = "";
|
||||
app.pEngineName = "Crafter.Graphics";
|
||||
|
|
@ -727,8 +675,15 @@ void Device::Initialize() {
|
|||
.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_2_FEATURES,
|
||||
.pNext = &bit16,
|
||||
.shaderFloat16 = VK_TRUE,
|
||||
// Bindless / runtime descriptor array indexing — needed for the
|
||||
// descriptor_heap shader path.
|
||||
.shaderUniformBufferArrayNonUniformIndexing = VK_TRUE,
|
||||
.shaderSampledImageArrayNonUniformIndexing = VK_TRUE,
|
||||
.shaderStorageBufferArrayNonUniformIndexing = VK_TRUE,
|
||||
.shaderStorageImageArrayNonUniformIndexing = VK_TRUE,
|
||||
.runtimeDescriptorArray = VK_TRUE,
|
||||
.bufferDeviceAddress = VK_TRUE
|
||||
.scalarBlockLayout = VK_TRUE,
|
||||
.bufferDeviceAddress = VK_TRUE
|
||||
};
|
||||
|
||||
VkPhysicalDeviceRayTracingPipelineFeaturesKHR physicalDeviceRayTracingPipelineFeatures{
|
||||
|
|
@ -747,8 +702,17 @@ void Device::Initialize() {
|
|||
.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2,
|
||||
.pNext = &deviceAccelerationStructureFeature,
|
||||
.features = {
|
||||
.samplerAnisotropy = VK_TRUE,
|
||||
.shaderInt16 = VK_TRUE
|
||||
// Order matches VkPhysicalDeviceFeatures declaration so the
|
||||
// designated-initializer-order warning stays quiet.
|
||||
.samplerAnisotropy = VK_TRUE,
|
||||
.shaderStorageImageReadWithoutFormat = VK_TRUE,
|
||||
.shaderStorageImageWriteWithoutFormat = VK_TRUE,
|
||||
// Bindless dynamic indexing — required to index `images[]`,
|
||||
// `textures[]`, `samplers[]`, `itemHeap[]` with a runtime value.
|
||||
.shaderSampledImageArrayDynamicIndexing = VK_TRUE,
|
||||
.shaderStorageBufferArrayDynamicIndexing = VK_TRUE,
|
||||
.shaderStorageImageArrayDynamicIndexing = VK_TRUE,
|
||||
.shaderInt16 = VK_TRUE
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -806,12 +770,12 @@ void Device::Initialize() {
|
|||
vkCmdBindResourceHeapEXT = reinterpret_cast<PFN_vkCmdBindResourceHeapEXT>(vkGetInstanceProcAddr(instance, "vkCmdBindResourceHeapEXT"));
|
||||
vkCmdBindSamplerHeapEXT = reinterpret_cast<PFN_vkCmdBindSamplerHeapEXT>(vkGetInstanceProcAddr(instance, "vkCmdBindSamplerHeapEXT"));
|
||||
vkWriteResourceDescriptorsEXT = reinterpret_cast<PFN_vkWriteResourceDescriptorsEXT>(vkGetInstanceProcAddr(instance, "vkWriteResourceDescriptorsEXT"));
|
||||
vkWriteSamplerDescriptorsEXT = reinterpret_cast<PFN_vkWriteSamplerDescriptorsEXT>(vkGetInstanceProcAddr(instance, "vkWriteSamplerDescriptorsEXT"));
|
||||
vkCmdPushDataEXT = reinterpret_cast<PFN_vkCmdPushDataEXT>(vkGetInstanceProcAddr(instance, "vkCmdPushDataEXT"));
|
||||
vkGetPhysicalDeviceDescriptorSizeEXT = reinterpret_cast<PFN_vkGetPhysicalDeviceDescriptorSizeEXT>(vkGetInstanceProcAddr(instance, "vkGetPhysicalDeviceDescriptorSizeEXT"));
|
||||
vkGetDeviceFaultInfoEXT = reinterpret_cast<PFN_vkGetDeviceFaultInfoEXT>(vkGetInstanceProcAddr(instance, "vkGetDeviceFaultInfoEXT"));
|
||||
#endif
|
||||
}
|
||||
|
||||
#ifdef CRAFTER_GRAPHICS_RENDERER_VULKAN
|
||||
std::uint32_t Device::GetMemoryType(uint32_t typeBits, VkMemoryPropertyFlags properties) {
|
||||
for (uint32_t i = 0; i < memoryProperties.memoryTypeCount; i++)
|
||||
{
|
||||
|
|
@ -826,5 +790,4 @@ std::uint32_t Device::GetMemoryType(uint32_t typeBits, VkMemoryPropertyFlags pro
|
|||
}
|
||||
|
||||
throw std::runtime_error("Could not find a matching memory type");
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
|
@ -61,10 +61,26 @@ Font::Font(const std::filesystem::path& fontFilePath) {
|
|||
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) {
|
||||
std::size_t i = 0;
|
||||
while (i < text.size()) {
|
||||
std::uint32_t cp = DecodeUtf8(text, i);
|
||||
if (cp == 0) break;
|
||||
int advance, lsb;
|
||||
stbtt_GetCodepointHMetrics(&font, c, &advance, &lsb);
|
||||
stbtt_GetCodepointHMetrics(&font, static_cast<int>(cp), &advance, &lsb);
|
||||
lineWidth += (int)(advance * scale);
|
||||
}
|
||||
return lineWidth;
|
||||
}
|
||||
|
||||
float Font::LineHeight(float size) {
|
||||
float scale = stbtt_ScaleForPixelHeight(&font, size);
|
||||
return (ascent - descent + lineGap) * scale;
|
||||
}
|
||||
|
||||
float Font::AscentPx(float size) {
|
||||
return ascent * stbtt_ScaleForPixelHeight(&font, size);
|
||||
}
|
||||
|
||||
float Font::ScaleForSize(float size) {
|
||||
return stbtt_ScaleForPixelHeight(&font, size);
|
||||
}
|
||||
|
|
@ -18,9 +18,7 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
|||
*/
|
||||
|
||||
module;
|
||||
#ifdef CRAFTER_GRAPHICS_RENDERER_VULKAN
|
||||
#include "vulkan/vulkan.h"
|
||||
#endif
|
||||
module Crafter.Graphics:Mesh_impl;
|
||||
import Crafter.Math;
|
||||
import :Mesh;
|
||||
|
|
@ -30,8 +28,6 @@ import std;
|
|||
|
||||
using namespace Crafter;
|
||||
|
||||
#ifdef CRAFTER_GRAPHICS_RENDERER_VULKAN
|
||||
|
||||
void Mesh::Build(std::span<Vector<float, 3, 3>> verticies, std::span<std::uint32_t> indicies, VkCommandBuffer cmd) {
|
||||
vertexBuffer.Resize(VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT | VK_BUFFER_USAGE_2_ACCELERATION_STRUCTURE_BUILD_INPUT_READ_ONLY_BIT_KHR, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT, verticies.size());
|
||||
indexBuffer.Resize(VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT | VK_BUFFER_USAGE_2_ACCELERATION_STRUCTURE_BUILD_INPUT_READ_ONLY_BIT_KHR | VK_BUFFER_USAGE_STORAGE_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT, indicies.size());
|
||||
|
|
@ -129,5 +125,4 @@ void Mesh::Build(std::span<Vector<float, 3, 3>> verticies, std::span<std::uint32
|
|||
.accelerationStructure = accelerationStructure
|
||||
};
|
||||
blasAddr = Device::vkGetAccelerationStructureDeviceAddressKHR(Device::device, &addrInfo);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
|
@ -1,44 +0,0 @@
|
|||
/*
|
||||
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 as published by the Free Software Foundation; either
|
||||
version 3.0 of the License, or (at your option) any later version.
|
||||
|
||||
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 Crafter.Graphics:MouseElement_impl;
|
||||
import :MouseElement;
|
||||
import :Window;
|
||||
import :Types;
|
||||
import :Font;
|
||||
import std;
|
||||
|
||||
using namespace Crafter;
|
||||
|
||||
MouseElement::MouseElement(Anchor2D anchor, Window& window) : Transform2D(anchor) {
|
||||
window.mouseElements.push_back(this);
|
||||
}
|
||||
|
||||
MouseElement::MouseElement(Anchor2D anchor) : Transform2D(anchor) {
|
||||
|
||||
}
|
||||
|
||||
MouseElement::MouseElement(Window& window) : Transform2D({0, 0, 1, 1, 0, 0, 0}) {
|
||||
window.mouseElements.push_back(this);
|
||||
}
|
||||
|
||||
MouseElement::MouseElement() : Transform2D({0, 0, 1, 1, 0, 0, 0}) {
|
||||
|
||||
}
|
||||
|
|
@ -18,14 +18,11 @@ 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 <vulkan/vulkan_core.h>
|
||||
#endif
|
||||
module Crafter.Graphics:RenderingElement3D_impl;
|
||||
import :RenderingElement3D;
|
||||
import std;
|
||||
|
||||
#ifdef CRAFTER_GRAPHICS_RENDERER_VULKAN
|
||||
using namespace Crafter;
|
||||
|
||||
|
||||
|
|
@ -132,6 +129,4 @@ void RenderingElement3D::BuildTLAS(VkCommandBuffer cmd, std::uint32_t index) {
|
|||
.accelerationStructure = tlases[index].accelerationStructure
|
||||
};
|
||||
tlases[index].address = Device::vkGetAccelerationStructureDeviceAddressKHR(Device::device, &addrInfo);
|
||||
}
|
||||
|
||||
#endif
|
||||
}
|
||||
|
|
@ -1,141 +0,0 @@
|
|||
/*
|
||||
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 <vulkan/vulkan.h>
|
||||
#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) : RendertargetBase(sizeX, 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; });
|
||||
for(Transform2D* child : transform.children) {
|
||||
SetOrderResursive(child);
|
||||
}
|
||||
}
|
||||
|
||||
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<RenderingElement2DVulkanTransformInfo*>(reinterpret_cast<char*>(transformBuffer[frame].value) + sizeof(RenderingElement2DVulkanTransformInfo));
|
||||
std::uint16_t* sizePtr = reinterpret_cast<std::uint16_t*>(transformBuffer[frame].value);
|
||||
*sizePtr = static_cast<std::uint16_t>(elements.size());
|
||||
for(std::uint16_t i = 0; i < elements.size(); i++) {
|
||||
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<RenderingElement2DVulkanTransformInfo*>(reinterpret_cast<char*>(transformBuffer[frame].value) + sizeof(RenderingElement2DVulkanTransformInfo));
|
||||
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;
|
||||
}
|
||||
transformBuffer[frame].FlushDevice();
|
||||
}
|
||||
|
||||
void RendertargetVulkan::WriteDescriptors(std::span<VkResourceDescriptorInfoEXT> infos, std::span<VkHostAddressRangeEXT> 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<VkDeviceAddressRangeKHR> 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) {
|
||||
RenderingElement2DVulkanBase* renderer = dynamic_cast<RenderingElement2DVulkanBase*>(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) {
|
||||
SetOrderResursive(childTransform);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
|
@ -1,76 +0,0 @@
|
|||
/*
|
||||
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 <stdio.h>
|
||||
#include <unistd.h>
|
||||
#include <string.h>
|
||||
#include <errno.h>
|
||||
#include <sys/stat.h>
|
||||
#include <fcntl.h>
|
||||
#include <sys/mman.h>
|
||||
#include <time.h>
|
||||
module Crafter.Graphics:Shm_impl;
|
||||
import :Shm;
|
||||
import std;
|
||||
|
||||
using namespace Crafter;
|
||||
|
||||
void Crafter::randname(char *buf) {
|
||||
struct timespec ts;
|
||||
clock_gettime(CLOCK_REALTIME, &ts);
|
||||
long r = ts.tv_nsec;
|
||||
for (int i = 0; i < 6; ++i) {
|
||||
buf[i] = 'A'+(r&15)+(r&16)*2;
|
||||
r >>= 5;
|
||||
}
|
||||
}
|
||||
|
||||
int Crafter::anonymous_shm_open(void) {
|
||||
char name[] = "/hello-wayland-XXXXXX";
|
||||
int retries = 100;
|
||||
|
||||
do {
|
||||
randname(name + strlen(name) - 6);
|
||||
|
||||
--retries;
|
||||
// shm_open guarantees that O_CLOEXEC is set
|
||||
int fd = shm_open(name, O_RDWR | O_CREAT | O_EXCL, 0600);
|
||||
if (fd >= 0) {
|
||||
shm_unlink(name);
|
||||
return fd;
|
||||
}
|
||||
} while (retries > 0 && errno == EEXIST);
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
int Crafter::create_shm_file(off_t size) {
|
||||
int fd = anonymous_shm_open();
|
||||
if (fd < 0) {
|
||||
return fd;
|
||||
}
|
||||
|
||||
if (ftruncate(fd, size) < 0) {
|
||||
close(fd);
|
||||
return -1;
|
||||
}
|
||||
|
||||
return fd;
|
||||
}
|
||||
|
|
@ -1,33 +0,0 @@
|
|||
/*
|
||||
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 as published by the Free Software Foundation; either
|
||||
version 3.0 of the License, or (at your option) any later version.
|
||||
|
||||
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 Crafter.Graphics:Transform2D_impl;
|
||||
import :Transform2D;
|
||||
import :Rendertarget;
|
||||
import :Types;
|
||||
import :Font;
|
||||
import std;
|
||||
|
||||
using namespace Crafter;
|
||||
|
||||
|
||||
Anchor2D::Anchor2D(float x, float y, float width, float height, float offsetX, float offsetY, std::uint8_t z, bool maintainAspectRatio): x(x), y(y), width(width), height(height), offsetX(offsetX), offsetY(offsetY), z(z), maintainAspectRatio(maintainAspectRatio) {
|
||||
|
||||
}
|
||||
130
implementations/Crafter.Graphics-UIAtlas.cpp
Normal file
130
implementations/Crafter.Graphics-UIAtlas.cpp
Normal file
|
|
@ -0,0 +1,130 @@
|
|||
/*
|
||||
Crafter®.Graphics
|
||||
Copyright (C) 2026 Catcrafts®
|
||||
catcrafts.net
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License version 3.0 as published by the Free Software Foundation;
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
module;
|
||||
#include "vulkan/vulkan.h"
|
||||
#include "../lib/stb_truetype.h"
|
||||
module Crafter.Graphics:UIAtlas_impl;
|
||||
import :UIAtlas;
|
||||
import :Font;
|
||||
import :ImageVulkan;
|
||||
import :Device;
|
||||
import std;
|
||||
|
||||
using namespace Crafter;
|
||||
using namespace Crafter::UI;
|
||||
|
||||
void FontAtlas::Initialize(VkCommandBuffer cmd) {
|
||||
image.Create(
|
||||
kAtlasSize, kAtlasSize, /*mipLevels*/ 1, cmd,
|
||||
VK_FORMAT_R8_UNORM,
|
||||
VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT,
|
||||
VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL
|
||||
);
|
||||
// Staging buffer is mapped; clear it so empty atlas regions sample as
|
||||
// distance < onedge (i.e. fully outside any glyph).
|
||||
std::memset(image.buffer.value, 0, kAtlasSize * kAtlasSize);
|
||||
dirty = true;
|
||||
}
|
||||
|
||||
bool FontAtlas::ShelfPlace(int w, int h, int& outX, int& outY) {
|
||||
// Try existing shelves first — same height heuristic keeps fragmentation low.
|
||||
for (Shelf& s : shelves_) {
|
||||
if (h <= s.height && s.cursorX + w <= kAtlasSize) {
|
||||
outX = s.cursorX;
|
||||
outY = s.y;
|
||||
s.cursorX += w;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
// New shelf below current ones.
|
||||
if (nextShelfY_ + h > kAtlasSize) return false;
|
||||
Shelf s{};
|
||||
s.y = nextShelfY_;
|
||||
s.height = h;
|
||||
s.cursorX = w;
|
||||
outX = 0;
|
||||
outY = s.y;
|
||||
shelves_.push_back(s);
|
||||
nextShelfY_ += h;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool FontAtlas::Ensure(Font& font, std::uint32_t codepoint) {
|
||||
Key key{&font, codepoint};
|
||||
if (cache_.contains(key)) return true;
|
||||
|
||||
float fontScale = stbtt_ScaleForPixelHeight(&font.font, kBaseSize);
|
||||
|
||||
// Advance is always present, even for empty glyphs (e.g. space).
|
||||
int advanceUnits = 0, lsb = 0;
|
||||
stbtt_GetCodepointHMetrics(&font.font, static_cast<int>(codepoint), &advanceUnits, &lsb);
|
||||
|
||||
int sw = 0, sh = 0, xoff = 0, yoff = 0;
|
||||
unsigned char* sdf = stbtt_GetCodepointSDF(
|
||||
&font.font, fontScale, static_cast<int>(codepoint),
|
||||
kPadding, static_cast<unsigned char>(kOnEdgeValue), kPixelDistScale,
|
||||
&sw, &sh, &xoff, &yoff
|
||||
);
|
||||
|
||||
Glyph g{};
|
||||
g.advance = advanceUnits * fontScale;
|
||||
g.xoff = static_cast<float>(xoff);
|
||||
g.yoff = static_cast<float>(yoff);
|
||||
|
||||
if (sdf && sw > 0 && sh > 0) {
|
||||
int px = 0, py = 0;
|
||||
if (!ShelfPlace(sw, sh, px, py)) {
|
||||
stbtt_FreeSDF(sdf, nullptr);
|
||||
return false; // V1: silently drop overflow; V2: grow atlas
|
||||
}
|
||||
// Blit row-by-row into the mapped staging buffer.
|
||||
for (int row = 0; row < sh; ++row) {
|
||||
std::memcpy(
|
||||
image.buffer.value + (py + row) * kAtlasSize + px,
|
||||
sdf + row * sw,
|
||||
static_cast<std::size_t>(sw)
|
||||
);
|
||||
}
|
||||
stbtt_FreeSDF(sdf, nullptr);
|
||||
|
||||
g.w = static_cast<float>(sw);
|
||||
g.h = static_cast<float>(sh);
|
||||
g.u0 = static_cast<float>(px) / kAtlasSize;
|
||||
g.v0 = static_cast<float>(py) / kAtlasSize;
|
||||
g.u1 = static_cast<float>(px + sw) / kAtlasSize;
|
||||
g.v1 = static_cast<float>(py + sh) / kAtlasSize;
|
||||
dirty = true;
|
||||
}
|
||||
// For empty glyphs (whitespace) we still cache the entry — the size-0
|
||||
// fields tell the emitter to skip the quad but advance the cursor.
|
||||
|
||||
cache_.emplace(key, g);
|
||||
return true;
|
||||
}
|
||||
|
||||
const Glyph* FontAtlas::Lookup(Font& font, std::uint32_t codepoint) const {
|
||||
auto it = cache_.find(Key{&font, codepoint});
|
||||
return it == cache_.end() ? nullptr : &it->second;
|
||||
}
|
||||
|
||||
void FontAtlas::Update(VkCommandBuffer cmd) {
|
||||
if (!dirty) return;
|
||||
image.Update(cmd, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
|
||||
dirty = false;
|
||||
}
|
||||
354
implementations/Crafter.Graphics-UIRenderer.cpp
Normal file
354
implementations/Crafter.Graphics-UIRenderer.cpp
Normal file
|
|
@ -0,0 +1,354 @@
|
|||
/*
|
||||
Crafter®.Graphics
|
||||
Copyright (C) 2026 Catcrafts®
|
||||
catcrafts.net
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License version 3.0 as published by the Free Software Foundation;
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
module;
|
||||
#include "vulkan/vulkan.h"
|
||||
module Crafter.Graphics:UIRenderer_impl;
|
||||
import :UIRenderer;
|
||||
import :Device;
|
||||
import :Window;
|
||||
import :DescriptorHeapVulkan;
|
||||
import :VulkanBuffer;
|
||||
import :ShaderVulkan;
|
||||
import :ImageVulkan;
|
||||
import :UIDrawList;
|
||||
import :UIAtlas;
|
||||
import std;
|
||||
|
||||
using namespace Crafter;
|
||||
using namespace Crafter::UI;
|
||||
|
||||
namespace {
|
||||
// Push-constant block — must match shaders/ui.comp.glsl. The shader's
|
||||
// `vec2 surfaceSize` field has 8-byte alignment under std430, so we
|
||||
// insert explicit padding after `itemCount` to keep the C++ and GLSL
|
||||
// layouts byte-identical (40 bytes total).
|
||||
struct PC {
|
||||
std::uint32_t itemCount; // 0
|
||||
std::uint32_t _pad0; // 4
|
||||
float surfaceSize[2]; // 8
|
||||
float scale; // 16
|
||||
std::uint32_t outImageHeapIdx; // 20
|
||||
std::uint32_t itemBufHeapIdx; // 24
|
||||
std::uint32_t atlasTextureHeapIdx; // 28
|
||||
std::uint32_t bindlessBaseHeapIdx; // 32
|
||||
std::uint32_t linearSamplerHeapIdx; // 36
|
||||
};
|
||||
static_assert(sizeof(PC) == 40, "PC layout must match shader push-constant block");
|
||||
static_assert(sizeof(PC) <= 128, "Push-constant block exceeds the spec-mandated minimum (128 bytes)");
|
||||
}
|
||||
|
||||
void UIRenderer::Initialize(Window& window,
|
||||
VkCommandBuffer initCmd,
|
||||
const std::filesystem::path& spvPath,
|
||||
std::uint16_t bindlessImageCount) {
|
||||
if (!window.descriptorHeap) {
|
||||
throw std::runtime_error("UIRenderer::Initialize: window.descriptorHeap must be set first");
|
||||
}
|
||||
window_ = &window;
|
||||
bindlessCount_ = bindlessImageCount;
|
||||
auto& heap = *window.descriptorHeap;
|
||||
|
||||
// Slot allocation. Layout in the resource heap (image-typed indexing):
|
||||
// [outImageBase_ ..] : Window::numFrames swapchain views (storage)
|
||||
// [atlasImageSlot_] : 1 sampled SDF atlas
|
||||
// [bindlessBase_ ..] : bindlessImageCount user image slots
|
||||
auto imgSlots = heap.AllocateImageSlots(
|
||||
Window::numFrames + 1 + bindlessImageCount
|
||||
);
|
||||
outImageBase_ = imgSlots.firstElement;
|
||||
atlasImageSlot_ = imgSlots.firstElement + Window::numFrames;
|
||||
bindlessBase_ = imgSlots.firstElement + Window::numFrames + 1;
|
||||
|
||||
// One SSBO per swapchain frame.
|
||||
auto bufSlots = heap.AllocateBufferSlots(Window::numFrames);
|
||||
itemBufBase_ = bufSlots.firstElement;
|
||||
|
||||
// One linear sampler.
|
||||
auto sampSlots = heap.AllocateSamplerSlots(1);
|
||||
linearSamplerSlot_ = sampSlots.firstElement;
|
||||
|
||||
// Initial item-buffer capacity (grows on demand).
|
||||
GrowItemBuffersIfNeeded(256);
|
||||
|
||||
// Atlas image — Initialize records a layout transition into initCmd.
|
||||
atlas.Initialize(initCmd);
|
||||
|
||||
CreatePipeline(spvPath);
|
||||
WriteSwapchainDescriptors();
|
||||
WriteAtlasDescriptor();
|
||||
WriteSamplerDescriptors();
|
||||
WriteItemBufferDescriptors();
|
||||
|
||||
for (auto& h : heap.resourceHeap) h.FlushDevice();
|
||||
for (auto& h : heap.samplerHeap) h.FlushDevice();
|
||||
}
|
||||
|
||||
void UIRenderer::GrowItemBuffersIfNeeded(std::uint32_t needed) {
|
||||
if (needed <= itemCapacity_) return;
|
||||
std::uint32_t newCap = itemCapacity_ ? itemCapacity_ * 2 : 256;
|
||||
while (newCap < needed) newCap *= 2;
|
||||
itemCapacity_ = static_cast<std::uint16_t>(std::min<std::uint32_t>(newCap, 65535));
|
||||
|
||||
for (auto& b : itemBufs_) {
|
||||
b.Resize(
|
||||
VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT,
|
||||
VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT,
|
||||
itemCapacity_
|
||||
);
|
||||
}
|
||||
// Item buffer descriptors point at the buffers' device addresses, so
|
||||
// they must be re-written after Resize.
|
||||
if (window_) WriteItemBufferDescriptors();
|
||||
}
|
||||
|
||||
void UIRenderer::SetItems(std::span<const UIItem> items) {
|
||||
if (items.size() > itemCapacity_) {
|
||||
GrowItemBuffersIfNeeded(static_cast<std::uint32_t>(items.size()));
|
||||
}
|
||||
pendingItemCount = static_cast<std::uint32_t>(items.size());
|
||||
auto& buf = itemBufs_[window_->currentBuffer];
|
||||
if (!items.empty()) {
|
||||
std::memcpy(buf.value, items.data(), items.size() * sizeof(UIItem));
|
||||
}
|
||||
buf.FlushDevice();
|
||||
}
|
||||
|
||||
void UIRenderer::Record(VkCommandBuffer cmd, std::uint32_t frameIdx, Window& window) {
|
||||
// Make sure any glyph rasterisation done during Emit lands on the GPU
|
||||
// before we sample the atlas.
|
||||
atlas.Update(cmd);
|
||||
|
||||
vkCmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_COMPUTE, pipeline_);
|
||||
|
||||
PC pc{};
|
||||
pc.itemCount = pendingItemCount;
|
||||
pc.surfaceSize[0] = static_cast<float>(window.width);
|
||||
pc.surfaceSize[1] = static_cast<float>(window.height);
|
||||
#ifdef CRAFTER_GRAPHICS_WINDOW_WAYLAND
|
||||
pc.scale = window.scale;
|
||||
#else
|
||||
pc.scale = 1.0f;
|
||||
#endif
|
||||
pc.outImageHeapIdx = outImageBase_ + frameIdx;
|
||||
// Buffer-typed shader views index the *whole* heap in buffer-descriptor
|
||||
// units, so we offset past the image region: bufferStartElement is the
|
||||
// first element index where buffer descriptors actually live.
|
||||
pc.itemBufHeapIdx = window.descriptorHeap->bufferStartElement
|
||||
+ itemBufBase_ + frameIdx;
|
||||
pc.atlasTextureHeapIdx = atlasImageSlot_;
|
||||
pc.bindlessBaseHeapIdx = bindlessBase_;
|
||||
pc.linearSamplerHeapIdx = linearSamplerSlot_;
|
||||
|
||||
// Pipelines created with VK_PIPELINE_CREATE_2_DESCRIPTOR_HEAP_BIT_EXT
|
||||
// use vkCmdPushDataEXT for push constants (the spec requires layout to
|
||||
// be VK_NULL_HANDLE in that mode, which means vkCmdPushConstants has
|
||||
// nowhere to attach to).
|
||||
VkPushDataInfoEXT pushInfo{
|
||||
.sType = VK_STRUCTURE_TYPE_PUSH_DATA_INFO_EXT,
|
||||
.offset = 0,
|
||||
.data = { .address = &pc, .size = sizeof(PC) },
|
||||
};
|
||||
Device::vkCmdPushDataEXT(cmd, &pushInfo);
|
||||
|
||||
std::uint32_t gx = (window.width + 15) / 16;
|
||||
std::uint32_t gy = (window.height + 15) / 16;
|
||||
vkCmdDispatch(cmd, gx, gy, 1);
|
||||
}
|
||||
|
||||
void UIRenderer::CreatePipeline(const std::filesystem::path& spvPath) {
|
||||
VulkanShader shader(spvPath, "main", VK_SHADER_STAGE_COMPUTE_BIT, nullptr);
|
||||
|
||||
// Spec: "If VkPipelineCreateFlags2CreateInfoKHR::flags includes
|
||||
// VK_PIPELINE_CREATE_2_DESCRIPTOR_HEAP_BIT_EXT, layout must be
|
||||
// VK_NULL_HANDLE." Push constants are then attached via
|
||||
// vkCmdPushDataEXT at draw time, not via the layout.
|
||||
VkPipelineCreateFlags2CreateInfo flags2{
|
||||
.sType = VK_STRUCTURE_TYPE_PIPELINE_CREATE_FLAGS_2_CREATE_INFO,
|
||||
.flags = VK_PIPELINE_CREATE_2_DESCRIPTOR_HEAP_BIT_EXT,
|
||||
};
|
||||
VkComputePipelineCreateInfo info{
|
||||
.sType = VK_STRUCTURE_TYPE_COMPUTE_PIPELINE_CREATE_INFO,
|
||||
.pNext = &flags2,
|
||||
.stage = {
|
||||
.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO,
|
||||
.stage = VK_SHADER_STAGE_COMPUTE_BIT,
|
||||
.module = shader.shader,
|
||||
.pName = "main",
|
||||
},
|
||||
.layout = VK_NULL_HANDLE,
|
||||
};
|
||||
Device::CheckVkResult(vkCreateComputePipelines(
|
||||
Device::device, VK_NULL_HANDLE, 1, &info, nullptr, &pipeline_));
|
||||
}
|
||||
|
||||
// ─── descriptor writes ───────────────────────────────────────────────────
|
||||
|
||||
void UIRenderer::WriteSwapchainDescriptors() {
|
||||
auto& heap = *window_->descriptorHeap;
|
||||
|
||||
// One write per (frame, frame index) pairing — same swapchain view per
|
||||
// frame index for each per-frame heap copy.
|
||||
std::array<VkImageDescriptorInfoEXT, Window::numFrames * Window::numFrames> infos{};
|
||||
std::array<VkResourceDescriptorInfoEXT, Window::numFrames * Window::numFrames> resources{};
|
||||
std::array<VkHostAddressRangeEXT, Window::numFrames * Window::numFrames> destinations{};
|
||||
|
||||
std::size_t k = 0;
|
||||
for (std::uint32_t heapFrame = 0; heapFrame < Window::numFrames; ++heapFrame) {
|
||||
for (std::uint32_t imgFrame = 0; imgFrame < Window::numFrames; ++imgFrame) {
|
||||
infos[k] = {
|
||||
.sType = VK_STRUCTURE_TYPE_IMAGE_DESCRIPTOR_INFO_EXT,
|
||||
.pView = &window_->imageViews[imgFrame],
|
||||
.layout = VK_IMAGE_LAYOUT_GENERAL,
|
||||
};
|
||||
resources[k] = {
|
||||
.sType = VK_STRUCTURE_TYPE_RESOURCE_DESCRIPTOR_INFO_EXT,
|
||||
.type = VK_DESCRIPTOR_TYPE_STORAGE_IMAGE,
|
||||
.data = { .pImage = &infos[k] },
|
||||
};
|
||||
destinations[k] = {
|
||||
.address = heap.resourceHeap[heapFrame].value
|
||||
+ heap.ImageByteOffset(static_cast<std::uint16_t>(outImageBase_ + imgFrame)),
|
||||
.size = Device::descriptorHeapProperties.imageDescriptorSize,
|
||||
};
|
||||
++k;
|
||||
}
|
||||
}
|
||||
Device::vkWriteResourceDescriptorsEXT(
|
||||
Device::device, static_cast<std::uint32_t>(k),
|
||||
resources.data(), destinations.data()
|
||||
);
|
||||
}
|
||||
|
||||
void UIRenderer::WriteAtlasDescriptor() {
|
||||
auto& heap = *window_->descriptorHeap;
|
||||
|
||||
// Build a stable VkImageViewCreateInfo for the atlas. ImageVulkan
|
||||
// pre-creates a VkImageView, but the descriptor-heap path needs a
|
||||
// pointer to a create-info — keep one on the renderer so the
|
||||
// pointers we hand to vkWriteResourceDescriptorsEXT stay valid.
|
||||
atlasViewCreateInfo_ = {
|
||||
.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO,
|
||||
.image = atlas.image.image,
|
||||
.viewType = VK_IMAGE_VIEW_TYPE_2D,
|
||||
.format = VK_FORMAT_R8_UNORM,
|
||||
.components = {
|
||||
VK_COMPONENT_SWIZZLE_IDENTITY, VK_COMPONENT_SWIZZLE_IDENTITY,
|
||||
VK_COMPONENT_SWIZZLE_IDENTITY, VK_COMPONENT_SWIZZLE_IDENTITY,
|
||||
},
|
||||
.subresourceRange = {
|
||||
.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
|
||||
.baseMipLevel = 0,
|
||||
.levelCount = 1,
|
||||
.baseArrayLayer = 0,
|
||||
.layerCount = 1,
|
||||
},
|
||||
};
|
||||
|
||||
std::array<VkImageDescriptorInfoEXT, Window::numFrames> infos{};
|
||||
std::array<VkResourceDescriptorInfoEXT, Window::numFrames> resources{};
|
||||
std::array<VkHostAddressRangeEXT, Window::numFrames> destinations{};
|
||||
|
||||
for (std::uint32_t f = 0; f < Window::numFrames; ++f) {
|
||||
infos[f] = {
|
||||
.sType = VK_STRUCTURE_TYPE_IMAGE_DESCRIPTOR_INFO_EXT,
|
||||
.pView = &atlasViewCreateInfo_,
|
||||
.layout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL,
|
||||
};
|
||||
resources[f] = {
|
||||
.sType = VK_STRUCTURE_TYPE_RESOURCE_DESCRIPTOR_INFO_EXT,
|
||||
.type = VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE,
|
||||
.data = { .pImage = &infos[f] },
|
||||
};
|
||||
destinations[f] = {
|
||||
.address = heap.resourceHeap[f].value
|
||||
+ heap.ImageByteOffset(atlasImageSlot_),
|
||||
.size = Device::descriptorHeapProperties.imageDescriptorSize,
|
||||
};
|
||||
}
|
||||
Device::vkWriteResourceDescriptorsEXT(
|
||||
Device::device, Window::numFrames, resources.data(), destinations.data()
|
||||
);
|
||||
}
|
||||
|
||||
void UIRenderer::WriteSamplerDescriptors() {
|
||||
auto& heap = *window_->descriptorHeap;
|
||||
|
||||
VkSamplerCreateInfo info{
|
||||
.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO,
|
||||
.magFilter = VK_FILTER_LINEAR,
|
||||
.minFilter = VK_FILTER_LINEAR,
|
||||
.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR,
|
||||
.addressModeU = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE,
|
||||
.addressModeV = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE,
|
||||
.addressModeW = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE,
|
||||
.maxAnisotropy = 1.0f,
|
||||
.minLod = 0.0f,
|
||||
.maxLod = VK_LOD_CLAMP_NONE,
|
||||
};
|
||||
std::array<VkSamplerCreateInfo, Window::numFrames> infos{};
|
||||
std::array<VkHostAddressRangeEXT, Window::numFrames> destinations{};
|
||||
for (std::uint32_t f = 0; f < Window::numFrames; ++f) {
|
||||
infos[f] = info;
|
||||
destinations[f] = {
|
||||
.address = heap.samplerHeap[f].value
|
||||
+ heap.SamplerByteOffset(linearSamplerSlot_),
|
||||
.size = Device::descriptorHeapProperties.samplerDescriptorSize,
|
||||
};
|
||||
}
|
||||
Device::vkWriteSamplerDescriptorsEXT(
|
||||
Device::device, Window::numFrames, infos.data(), destinations.data()
|
||||
);
|
||||
}
|
||||
|
||||
void UIRenderer::WriteItemBufferDescriptors() {
|
||||
auto& heap = *window_->descriptorHeap;
|
||||
|
||||
std::array<VkDeviceAddressRangeEXT, Window::numFrames * Window::numFrames> ranges{};
|
||||
std::array<VkResourceDescriptorInfoEXT, Window::numFrames * Window::numFrames> resources{};
|
||||
std::array<VkHostAddressRangeEXT, Window::numFrames * Window::numFrames> destinations{};
|
||||
|
||||
std::size_t k = 0;
|
||||
for (std::uint32_t heapFrame = 0; heapFrame < Window::numFrames; ++heapFrame) {
|
||||
for (std::uint32_t bufFrame = 0; bufFrame < Window::numFrames; ++bufFrame) {
|
||||
ranges[k] = {
|
||||
.address = itemBufs_[bufFrame].address,
|
||||
.size = itemBufs_[bufFrame].size,
|
||||
};
|
||||
resources[k] = {
|
||||
.sType = VK_STRUCTURE_TYPE_RESOURCE_DESCRIPTOR_INFO_EXT,
|
||||
.type = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER,
|
||||
.data = { .pAddressRange = &ranges[k] },
|
||||
};
|
||||
destinations[k] = {
|
||||
.address = heap.resourceHeap[heapFrame].value + heap.BufferByteOffset(static_cast<std::uint16_t>(itemBufBase_ + bufFrame)),
|
||||
.size = Device::descriptorHeapProperties.bufferDescriptorSize,
|
||||
};
|
||||
++k;
|
||||
}
|
||||
}
|
||||
Device::vkWriteResourceDescriptorsEXT(
|
||||
Device::device, static_cast<std::uint32_t>(k),
|
||||
resources.data(), destinations.data()
|
||||
);
|
||||
}
|
||||
|
||||
void UIRenderer::CreateLinearSampler() {
|
||||
// Not used — VK_EXT_descriptor_heap writes the sampler create-info
|
||||
// directly into the heap (see WriteSamplerDescriptors).
|
||||
}
|
||||
166
implementations/Crafter.Graphics-UIScene.cpp
Normal file
166
implementations/Crafter.Graphics-UIScene.cpp
Normal file
|
|
@ -0,0 +1,166 @@
|
|||
/*
|
||||
Crafter®.Graphics
|
||||
Copyright (C) 2026 Catcrafts®
|
||||
catcrafts.net
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License version 3.0 as published by the Free Software Foundation;
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
module;
|
||||
#include "vulkan/vulkan.h"
|
||||
module Crafter.Graphics:UIScene_impl;
|
||||
import :UIScene;
|
||||
import :Window;
|
||||
import :Types;
|
||||
import :DescriptorHeapVulkan;
|
||||
import :UIRenderer;
|
||||
import :UIHit;
|
||||
import :UILayout;
|
||||
import :UIDrawList;
|
||||
import :UIWidget;
|
||||
import Crafter.Event;
|
||||
import std;
|
||||
|
||||
using namespace Crafter;
|
||||
using namespace Crafter::UI;
|
||||
|
||||
UIScene::~UIScene() {
|
||||
// Release listeners before the rest of the scene tears down.
|
||||
mouseListener_.reset();
|
||||
updateListener_.reset();
|
||||
textListener_.reset();
|
||||
keyListener_.reset();
|
||||
focused_ = nullptr;
|
||||
|
||||
if (window_) {
|
||||
// De-register the renderer pass.
|
||||
auto& v = window_->passes;
|
||||
v.erase(std::remove(v.begin(), v.end(), static_cast<RenderPass*>(&renderer)), v.end());
|
||||
|
||||
// Clear the descriptor-heap pointer if we owned it; the heap's
|
||||
// destructor releases its Vulkan buffers on its own.
|
||||
if (ownsHeap_ && window_->descriptorHeap == &ownedHeap_) {
|
||||
window_->descriptorHeap = nullptr;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
float UIScene::WindowScale() const {
|
||||
if (!window_) return 1.0f;
|
||||
#ifdef CRAFTER_GRAPHICS_WINDOW_WAYLAND
|
||||
return window_->scale;
|
||||
#else
|
||||
return 1.0f;
|
||||
#endif
|
||||
}
|
||||
|
||||
void UIScene::Initialize(Window& window, const std::filesystem::path& spvPath) {
|
||||
window_ = &window;
|
||||
|
||||
// Auto-create a heap for UI-only apps. Generous defaults so most
|
||||
// user-augmented heaps will fit too — if the user wants to share with
|
||||
// 3D content, they should pre-create their own heap and attach it
|
||||
// before calling Initialize.
|
||||
if (!window.descriptorHeap) {
|
||||
ownedHeap_.Initialize(/*images*/ 388, /*buffers*/ 35, /*samplers*/ 17);
|
||||
window.descriptorHeap = &ownedHeap_;
|
||||
ownsHeap_ = true;
|
||||
}
|
||||
|
||||
// One-shot init — needed by the atlas image transition. Each
|
||||
// StartInit/FinishInit pair reuses the per-frame command buffer.
|
||||
VkCommandBuffer cmd = window.StartInit();
|
||||
renderer.Initialize(window, cmd, spvPath);
|
||||
window.FinishInit();
|
||||
|
||||
// Register as a RenderPass (after any other pass already in
|
||||
// window.passes — typically RTPass for mixed scenes).
|
||||
window.passes.push_back(&renderer);
|
||||
|
||||
// Mouse: update focus to the topmost focusable under the cursor (or
|
||||
// null if none), then dispatch the click via the bubble chain.
|
||||
mouseListener_ = std::make_unique<EventListener<void>>(
|
||||
&window.onMouseLeftClick,
|
||||
[this]() {
|
||||
if (!root_) return;
|
||||
float x = window_->currentMousePos.x;
|
||||
float y = window_->currentMousePos.y;
|
||||
|
||||
Widget* hit = UI::HitTest(*root_, x, y);
|
||||
Widget* focusTarget = nullptr;
|
||||
for (Widget* w = hit; w != nullptr; w = w->parent) {
|
||||
if (w->IsFocusable()) { focusTarget = w; break; }
|
||||
}
|
||||
SetFocus(focusTarget);
|
||||
|
||||
UI::DispatchClick(*root_, x, y);
|
||||
}
|
||||
);
|
||||
|
||||
// Text input: only the currently-focused widget receives it.
|
||||
textListener_ = std::make_unique<EventListener<const std::string_view>>(
|
||||
&window.onTextInput,
|
||||
[this](std::string_view t) {
|
||||
if (focused_) focused_->OnTextInput(t);
|
||||
}
|
||||
);
|
||||
|
||||
// Non-character keys (Backspace, arrows, Enter, …).
|
||||
keyListener_ = std::make_unique<EventListener<CrafterKeys>>(
|
||||
&window.onAnyKeyDown,
|
||||
[this](CrafterKeys key) {
|
||||
if (focused_) focused_->OnKeyDown(key);
|
||||
}
|
||||
);
|
||||
|
||||
// Per-frame: re-layout, emit, push items.
|
||||
updateListener_ = std::make_unique<EventListener<FrameTime>>(
|
||||
&window.onUpdate,
|
||||
[this](FrameTime) { RebuildFrame(); }
|
||||
);
|
||||
}
|
||||
|
||||
void UIScene::SetFocus(Widget* w) {
|
||||
if (w == focused_) return;
|
||||
if (focused_) focused_->OnBlur();
|
||||
focused_ = w;
|
||||
if (focused_) focused_->OnFocus();
|
||||
}
|
||||
|
||||
void UIScene::RebuildFrame() {
|
||||
if (!root_ || !window_) return;
|
||||
float sc = WindowScale();
|
||||
|
||||
// Layout the tree against the current surface size.
|
||||
UI::RunLayout(
|
||||
*root_,
|
||||
{ static_cast<float>(window_->width), static_cast<float>(window_->height) },
|
||||
sc
|
||||
);
|
||||
|
||||
// Emit draw items.
|
||||
drawList.Reset();
|
||||
drawList.atlas = &renderer.atlas;
|
||||
drawList.bindlessBaseHeapIdx = renderer.BindlessBaseHeapIdx();
|
||||
drawList.scale = sc;
|
||||
if (background_) {
|
||||
drawList.AddRect(
|
||||
{ 0, 0, static_cast<float>(window_->width), static_cast<float>(window_->height) },
|
||||
*background_
|
||||
);
|
||||
}
|
||||
UI::EmitTree(*root_, drawList);
|
||||
|
||||
// Stage to GPU.
|
||||
renderer.SetItems(drawList.items);
|
||||
}
|
||||
|
|
@ -37,16 +37,13 @@ module;
|
|||
#include <print>
|
||||
#include <wayland-client.h>
|
||||
#include <wayland-client-protocol.h>
|
||||
#ifdef CRAFTER_GRAPHICS_RENDERER_SOFTWARE
|
||||
#include <sys/stat.h>
|
||||
#include <time.h>
|
||||
#endif
|
||||
#endif
|
||||
#ifdef CRAFTER_GRAPHICS_WINDOW_WIN32
|
||||
#include <windows.h>
|
||||
#include <cassert>
|
||||
#endif
|
||||
#ifdef CRAFTER_GRAPHICS_RENDERER_VULKAN
|
||||
#include "vulkan/vulkan.h"
|
||||
#ifdef CRAFTER_GRAPHICS_WINDOW_WAYLAND
|
||||
#include "vulkan/vulkan_wayland.h"
|
||||
|
|
@ -54,17 +51,14 @@ module;
|
|||
#ifdef CRAFTER_GRAPHICS_WINDOW_WIN32
|
||||
#include "vulkan/vulkan_win32.h"
|
||||
#endif
|
||||
#endif
|
||||
#define STB_IMAGE_WRITE_IMPLEMENTATION
|
||||
#include "../lib/stb_image_write.h"
|
||||
module Crafter.Graphics:Window_impl;
|
||||
import :Window;
|
||||
import :Transform2D;
|
||||
import :MouseElement;
|
||||
import :Device;
|
||||
#ifdef CRAFTER_GRAPHICS_RENDERER_VULKAN
|
||||
import :VulkanTransition;
|
||||
import :DescriptorHeapVulkan;
|
||||
import :PipelineRTVulkan;
|
||||
#endif
|
||||
import :RenderPass;
|
||||
import std;
|
||||
|
||||
using namespace Crafter;
|
||||
|
|
@ -336,52 +330,24 @@ LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) {
|
|||
case WM_LBUTTONDOWN: {
|
||||
window->mouseLeftHeld = true;
|
||||
window->onMouseLeftClick.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->onMouseLeftClick.Invoke();
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case WM_LBUTTONUP: {
|
||||
window->mouseLeftHeld = false;
|
||||
window->onMouseLeftRelease.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->onMouseLeftRelease.Invoke();
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case WM_RBUTTONDOWN: {
|
||||
window->mouseRightHeld = true;
|
||||
window->onMouseRightClick.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->onMouseRightClick.Invoke();
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case WM_RBUTTONUP: {
|
||||
window->mouseRightHeld = false;
|
||||
window->onMouseRightRelease.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->onMouseRightRelease.Invoke();
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
|
|
@ -405,11 +371,7 @@ Window::Window(std::uint32_t width, std::uint32_t height, const std::string_view
|
|||
SetTitle(title);
|
||||
}
|
||||
|
||||
#ifdef CRAFTER_GRAPHICS_RENDERER_SOFTWARE
|
||||
Window::Window(std::uint32_t width, std::uint32_t height) : width(width), height(height), renderer(width, height) {
|
||||
#else
|
||||
Window::Window(std::uint32_t width, std::uint32_t height) : width(width), height(height) {
|
||||
#endif
|
||||
#ifdef CRAFTER_GRAPHICS_WINDOW_WAYLAND
|
||||
Device::windows.push_back(this);
|
||||
surface = wl_compositor_create_surface(Device::compositor);
|
||||
|
|
@ -434,39 +396,7 @@ Window::Window(std::uint32_t width, std::uint32_t height) : width(width), height
|
|||
wp_viewport_set_destination(wpViewport, std::ceil(width/scale), std::ceil(height/scale));
|
||||
|
||||
wl_surface_commit(surface);
|
||||
#ifdef CRAFTER_GRAPHICS_RENDERER_SOFTWARE
|
||||
// Create a wl_buffer, attach it to the surface and commit the surface
|
||||
int stride = width * 4;
|
||||
int size = stride * height;
|
||||
|
||||
// Allocate a shared memory file with the right size
|
||||
int fd = create_shm_file(size);
|
||||
if (fd < 0) {
|
||||
throw std::runtime_error(std::format("creating a buffer file for {}B failed", size));
|
||||
}
|
||||
|
||||
// Map the shared memory file
|
||||
renderer.buffer[0] = reinterpret_cast<Vector<std::uint8_t, 4, 4>*>(mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0));
|
||||
if (renderer.buffer[0] == MAP_FAILED) {
|
||||
throw std::runtime_error("mmap failed");
|
||||
}
|
||||
|
||||
wl_shm_pool *pool = wl_shm_create_pool(Device::shm, fd, size);
|
||||
buffer = wl_shm_pool_create_buffer(pool, 0, width, height, stride, WL_SHM_FORMAT_ARGB8888);
|
||||
wl_shm_pool_destroy(pool);
|
||||
|
||||
close(fd);
|
||||
|
||||
if (buffer == nullptr) {
|
||||
throw std::runtime_error("wl_buffer creation failed");
|
||||
}
|
||||
|
||||
wl_surface_attach(surface, buffer, 0, 0);
|
||||
wl_surface_commit(surface);
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifdef CRAFTER_GRAPHICS_RENDERER_VULKAN
|
||||
|
||||
#ifdef CRAFTER_GRAPHICS_WINDOW_WIN32
|
||||
// Initialize the window class
|
||||
|
|
@ -574,7 +504,6 @@ Window::Window(std::uint32_t width, std::uint32_t height) : width(width), height
|
|||
submitInfo.signalSemaphoreCount = 1;
|
||||
submitInfo.pSignalSemaphores = &semaphores.renderComplete;
|
||||
submitInfo.pNext = VK_NULL_HANDLE;
|
||||
#endif
|
||||
|
||||
lastMousePos = {0,0};
|
||||
mouseDelta = {0,0};
|
||||
|
|
@ -588,13 +517,11 @@ void Window::SetTitle(const std::string_view title) {
|
|||
}
|
||||
|
||||
void Window::SetCusorImage(std::uint16_t sizeX, std::uint16_t sizeY) {
|
||||
new (&cursorRenderer) Rendertarget<std::uint8_t, 4, 4, 1>(sizeX, sizeY);
|
||||
#ifdef CRAFTER_GRAPHICS_WINDOW_WAYLAND
|
||||
if(cursorSurface == nullptr) {
|
||||
cursorSurface = wl_compositor_create_surface(Device::compositor);
|
||||
} else {
|
||||
wl_buffer_destroy(cursorWlBuffer);
|
||||
munmap(cursorRenderer.buffer[0], cursorBufferOldSize);
|
||||
}
|
||||
|
||||
int stride = sizeX * 4;
|
||||
|
|
@ -607,11 +534,6 @@ void Window::SetCusorImage(std::uint16_t sizeX, std::uint16_t sizeY) {
|
|||
throw std::runtime_error(std::format("creating a buffer file for {}B failed", size));
|
||||
}
|
||||
|
||||
cursorRenderer.buffer[0] = reinterpret_cast<Vector<std::uint8_t, 4, 4>*>(mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0));
|
||||
if (cursorRenderer.buffer[0] == MAP_FAILED) {
|
||||
throw std::runtime_error("mmap failed");
|
||||
}
|
||||
|
||||
wl_shm_pool *pool = wl_shm_create_pool(Device::shm, fd, size);
|
||||
cursorWlBuffer = wl_shm_pool_create_buffer(pool, 0, sizeX, sizeY, stride, WL_SHM_FORMAT_ARGB8888);
|
||||
wl_shm_pool_destroy(pool);
|
||||
|
|
@ -673,21 +595,11 @@ void Window::SetCusorImageDefault() {
|
|||
|
||||
void Window::UpdateCursorImage() {
|
||||
#ifdef CRAFTER_GRAPHICS_WINDOW_WAYLAND
|
||||
cursorRenderer.Render(0);
|
||||
for(std::uint32_t i = 0; i < cursorBufferOldSize / 4; i++) {
|
||||
std::swap(cursorRenderer.buffer[0][i].b, cursorRenderer.buffer[0][i].r);
|
||||
}
|
||||
wl_surface_attach(cursorSurface, cursorWlBuffer, 0, 0);
|
||||
wl_surface_damage(cursorSurface, 0, 0, 9999999, 99999999);
|
||||
wl_surface_commit(cursorSurface);
|
||||
#endif
|
||||
#ifdef CRAFTER_GRAPHICS_WINDOW_WIN32
|
||||
cursorRenderer.Render(0);
|
||||
|
||||
// Swap R and B channels (renderer is RGBA, GDI DIB is BGRA)
|
||||
for (std::uint32_t i = 0; i < (std::uint32_t)(cursorSizeX * cursorSizeY); i++) {
|
||||
std::swap(cursorRenderer.buffer[0][i].r, cursorRenderer.buffer[0][i].b);
|
||||
}
|
||||
|
||||
// Create a mask bitmap (all zeros = fully opaque, alpha comes from color bitmap)
|
||||
HBITMAP hMask = CreateBitmap(cursorSizeX, cursorSizeY, 1, 1, nullptr);
|
||||
|
|
@ -781,15 +693,6 @@ void Window::Update() {
|
|||
}
|
||||
|
||||
void Window::Render() {
|
||||
#ifdef CRAFTER_GRAPHICS_RENDERER_SOFTWARE
|
||||
renderer.Render(0);
|
||||
#ifdef CRAFTER_GRAPHICS_WINDOW_WAYLAND
|
||||
wl_surface_attach(surface, buffer, 0, 0);
|
||||
wl_surface_commit(surface);
|
||||
wl_surface_damage(surface, 0, 0, 10000, 100000);
|
||||
#endif
|
||||
#endif
|
||||
#ifdef CRAFTER_GRAPHICS_RENDERER_VULKAN
|
||||
// Acquire the next image from the swap chain
|
||||
Device::CheckVkResult(vkAcquireNextImageKHR(Device::device, swapChain, UINT64_MAX, semaphores.presentComplete, (VkFence)nullptr, ¤tBuffer));
|
||||
submitInfo.commandBufferCount = 1;
|
||||
|
|
@ -810,7 +713,7 @@ void Window::Render() {
|
|||
VkImageMemoryBarrier image_memory_barrier {
|
||||
.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
|
||||
.srcAccessMask = 0,
|
||||
.dstAccessMask = VK_ACCESS_SHADER_WRITE_BIT,
|
||||
.dstAccessMask = VK_ACCESS_SHADER_WRITE_BIT | VK_ACCESS_TRANSFER_WRITE_BIT,
|
||||
.oldLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR,
|
||||
.newLayout = VK_IMAGE_LAYOUT_GENERAL,
|
||||
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
|
||||
|
|
@ -819,7 +722,7 @@ void Window::Render() {
|
|||
.subresourceRange = range
|
||||
};
|
||||
|
||||
vkCmdPipelineBarrier(drawCmdBuffers[currentBuffer], VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, VK_PIPELINE_STAGE_RAY_TRACING_SHADER_BIT_KHR, 0, 0, nullptr, 0, nullptr, 1, &image_memory_barrier);
|
||||
vkCmdPipelineBarrier(drawCmdBuffers[currentBuffer], VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, 0, 0, nullptr, 0, nullptr, 1, &image_memory_barrier);
|
||||
|
||||
onUpdate.Invoke({startTime, startTime-lastFrameBegin});
|
||||
#ifdef CRAFTER_TIMING
|
||||
|
|
@ -831,31 +734,48 @@ void Window::Render() {
|
|||
}
|
||||
#endif
|
||||
|
||||
vkCmdBindPipeline(drawCmdBuffers[currentBuffer], VK_PIPELINE_BIND_POINT_RAY_TRACING_KHR, pipeline->pipeline);
|
||||
|
||||
VkBindHeapInfoEXT resourceHeapInfo = {
|
||||
.sType = VK_STRUCTURE_TYPE_BIND_HEAP_INFO_EXT,
|
||||
.heapRange = {
|
||||
.address = descriptorHeap->resourceHeap[currentBuffer].address,
|
||||
.size = static_cast<std::uint32_t>(descriptorHeap->resourceHeap[currentBuffer].size)
|
||||
},
|
||||
.reservedRangeOffset = (descriptorHeap->resourceHeap[currentBuffer].size - Device::descriptorHeapProperties.minResourceHeapReservedRange) & ~(Device::descriptorHeapProperties.imageDescriptorAlignment - 1),
|
||||
.reservedRangeSize = Device::descriptorHeapProperties.minResourceHeapReservedRange
|
||||
};
|
||||
Device::vkCmdBindResourceHeapEXT(drawCmdBuffers[currentBuffer], &resourceHeapInfo);
|
||||
if (descriptorHeap) {
|
||||
VkBindHeapInfoEXT resourceHeapInfo = {
|
||||
.sType = VK_STRUCTURE_TYPE_BIND_HEAP_INFO_EXT,
|
||||
.heapRange = {
|
||||
.address = descriptorHeap->resourceHeap[currentBuffer].address,
|
||||
.size = static_cast<std::uint32_t>(descriptorHeap->resourceHeap[currentBuffer].size)
|
||||
},
|
||||
.reservedRangeOffset = (descriptorHeap->resourceHeap[currentBuffer].size - Device::descriptorHeapProperties.minResourceHeapReservedRange) & ~(Device::descriptorHeapProperties.imageDescriptorAlignment - 1),
|
||||
.reservedRangeSize = Device::descriptorHeapProperties.minResourceHeapReservedRange
|
||||
};
|
||||
Device::vkCmdBindResourceHeapEXT(drawCmdBuffers[currentBuffer], &resourceHeapInfo);
|
||||
|
||||
VkBindHeapInfoEXT samplerHeapInfo = {
|
||||
.sType = VK_STRUCTURE_TYPE_BIND_HEAP_INFO_EXT,
|
||||
.heapRange = {
|
||||
.address = descriptorHeap->samplerHeap[currentBuffer].address,
|
||||
.size = static_cast<std::uint32_t>(descriptorHeap->samplerHeap[currentBuffer].size)
|
||||
},
|
||||
.reservedRangeOffset = descriptorHeap->samplerHeap[currentBuffer].size - Device::descriptorHeapProperties.minSamplerHeapReservedRange,
|
||||
.reservedRangeSize = Device::descriptorHeapProperties.minSamplerHeapReservedRange
|
||||
};
|
||||
Device::vkCmdBindSamplerHeapEXT(drawCmdBuffers[currentBuffer], &samplerHeapInfo);
|
||||
VkBindHeapInfoEXT samplerHeapInfo = {
|
||||
.sType = VK_STRUCTURE_TYPE_BIND_HEAP_INFO_EXT,
|
||||
.heapRange = {
|
||||
.address = descriptorHeap->samplerHeap[currentBuffer].address,
|
||||
.size = static_cast<std::uint32_t>(descriptorHeap->samplerHeap[currentBuffer].size)
|
||||
},
|
||||
.reservedRangeOffset = descriptorHeap->samplerHeap[currentBuffer].size - Device::descriptorHeapProperties.minSamplerHeapReservedRange,
|
||||
.reservedRangeSize = Device::descriptorHeapProperties.minSamplerHeapReservedRange
|
||||
};
|
||||
Device::vkCmdBindSamplerHeapEXT(drawCmdBuffers[currentBuffer], &samplerHeapInfo);
|
||||
}
|
||||
|
||||
Device::vkCmdTraceRaysKHR(drawCmdBuffers[currentBuffer], &pipeline->raygenRegion, &pipeline->missRegion, &pipeline->hitRegion, &pipeline->callableRegion, width, height, 1);
|
||||
// Note: vkCmdClearColorImage is unavailable here — the swapchain is
|
||||
// created with VK_IMAGE_USAGE_STORAGE_BIT only (no TRANSFER_DST_BIT).
|
||||
// Passes that need a background should write one explicitly (UIScene
|
||||
// exposes a `background()` setter for this purpose).
|
||||
(void)clearColor;
|
||||
|
||||
for (std::size_t i = 0; i < passes.size(); ++i) {
|
||||
passes[i]->Record(drawCmdBuffers[currentBuffer], currentBuffer, *this);
|
||||
|
||||
if (i + 1 < passes.size()) {
|
||||
VkMemoryBarrier mb {
|
||||
.sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER,
|
||||
.srcAccessMask = VK_ACCESS_SHADER_WRITE_BIT,
|
||||
.dstAccessMask = VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT,
|
||||
};
|
||||
vkCmdPipelineBarrier(drawCmdBuffers[currentBuffer], VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, 0, 1, &mb, 0, nullptr, 0, nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
VkImageMemoryBarrier image_memory_barrier2 {
|
||||
.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
|
||||
|
|
@ -869,7 +789,7 @@ void Window::Render() {
|
|||
.subresourceRange = range
|
||||
};
|
||||
|
||||
vkCmdPipelineBarrier(drawCmdBuffers[currentBuffer], VK_PIPELINE_STAGE_RAY_TRACING_SHADER_BIT_KHR, VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, 0, 0, nullptr, 0, nullptr, 1, &image_memory_barrier2);
|
||||
vkCmdPipelineBarrier(drawCmdBuffers[currentBuffer], VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, 0, 0, nullptr, 0, nullptr, 1, &image_memory_barrier2);
|
||||
|
||||
Device::CheckVkResult(vkEndCommandBuffer(drawCmdBuffers[currentBuffer]));
|
||||
|
||||
|
|
@ -894,7 +814,6 @@ void Window::Render() {
|
|||
Device::CheckVkResult(result);
|
||||
}
|
||||
Device::CheckVkResult(vkQueueWaitIdle(Device::queue));
|
||||
#endif
|
||||
}
|
||||
|
||||
#ifdef CRAFTER_TIMING
|
||||
|
|
@ -935,7 +854,6 @@ void Window::LogTiming() {
|
|||
}
|
||||
#endif
|
||||
|
||||
#ifdef CRAFTER_GRAPHICS_RENDERER_VULKAN
|
||||
void Window::CreateSwapchain()
|
||||
{
|
||||
// Store the current swap chain handle so we can use it later on to ease up recreation
|
||||
|
|
@ -1009,7 +927,7 @@ void Window::CreateSwapchain()
|
|||
swapchainCI.imageFormat = colorFormat;
|
||||
swapchainCI.imageColorSpace = colorSpace;
|
||||
swapchainCI.imageExtent = { swapchainExtent.width, swapchainExtent.height };
|
||||
swapchainCI.imageUsage = VK_IMAGE_USAGE_STORAGE_BIT;
|
||||
swapchainCI.imageUsage = VK_IMAGE_USAGE_STORAGE_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT;
|
||||
swapchainCI.preTransform = (VkSurfaceTransformFlagBitsKHR)preTransform;
|
||||
swapchainCI.imageArrayLayers = 1;
|
||||
swapchainCI.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE;
|
||||
|
|
@ -1120,8 +1038,6 @@ void Window::EndCmd(VkCommandBuffer cmd) {
|
|||
Device::CheckVkResult(vkQueueWaitIdle(Device::queue));
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
#ifdef CRAFTER_GRAPHICS_WINDOW_WAYLAND
|
||||
void Window::wl_surface_frame_done(void* data, struct wl_callback *cb, uint32_t time) {
|
||||
wl_callback_destroy(cb);
|
||||
|
|
@ -1167,4 +1083,135 @@ void Window::xdg_surface_handle_preferred_scale(void* data, wp_fractional_scale_
|
|||
window->scale = scale / 120.0f;
|
||||
}
|
||||
|
||||
#endif
|
||||
#endif
|
||||
|
||||
void Window::SaveFrame(const std::filesystem::path& path) {
|
||||
// Staging buffer big enough for one RGBA frame.
|
||||
VkDeviceSize bufSize = static_cast<VkDeviceSize>(width) * height * 4;
|
||||
|
||||
VkBufferCreateInfo bci{
|
||||
.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO,
|
||||
.size = bufSize,
|
||||
.usage = VK_BUFFER_USAGE_TRANSFER_DST_BIT,
|
||||
.sharingMode = VK_SHARING_MODE_EXCLUSIVE,
|
||||
};
|
||||
VkBuffer stagingBuf = VK_NULL_HANDLE;
|
||||
Device::CheckVkResult(vkCreateBuffer(Device::device, &bci, nullptr, &stagingBuf));
|
||||
|
||||
VkMemoryRequirements memReqs;
|
||||
vkGetBufferMemoryRequirements(Device::device, stagingBuf, &memReqs);
|
||||
VkMemoryAllocateInfo mai{
|
||||
.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO,
|
||||
.allocationSize = memReqs.size,
|
||||
.memoryTypeIndex = Device::GetMemoryType(memReqs.memoryTypeBits,
|
||||
VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT),
|
||||
};
|
||||
VkDeviceMemory stagingMem = VK_NULL_HANDLE;
|
||||
Device::CheckVkResult(vkAllocateMemory(Device::device, &mai, nullptr, &stagingMem));
|
||||
Device::CheckVkResult(vkBindBufferMemory(Device::device, stagingBuf, stagingMem, 0));
|
||||
|
||||
// One-shot command buffer so we don't trash the per-frame ones.
|
||||
VkCommandBufferAllocateInfo cba{
|
||||
.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO,
|
||||
.commandPool = Device::commandPool,
|
||||
.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY,
|
||||
.commandBufferCount = 1,
|
||||
};
|
||||
VkCommandBuffer cmd = VK_NULL_HANDLE;
|
||||
Device::CheckVkResult(vkAllocateCommandBuffers(Device::device, &cba, &cmd));
|
||||
|
||||
VkCommandBufferBeginInfo cbi{
|
||||
.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO,
|
||||
.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT,
|
||||
};
|
||||
Device::CheckVkResult(vkBeginCommandBuffer(cmd, &cbi));
|
||||
|
||||
VkImageSubresourceRange range{
|
||||
.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
|
||||
.baseMipLevel = 0,
|
||||
.levelCount = 1,
|
||||
.baseArrayLayer = 0,
|
||||
.layerCount = 1,
|
||||
};
|
||||
|
||||
// Render() leaves the image in PRESENT_SRC_KHR.
|
||||
VkImageMemoryBarrier toSrc{
|
||||
.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
|
||||
.srcAccessMask = 0,
|
||||
.dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT,
|
||||
.oldLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR,
|
||||
.newLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
|
||||
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
|
||||
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
|
||||
.image = images[currentBuffer],
|
||||
.subresourceRange = range,
|
||||
};
|
||||
vkCmdPipelineBarrier(cmd,
|
||||
VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT,
|
||||
0, 0, nullptr, 0, nullptr, 1, &toSrc);
|
||||
|
||||
VkBufferImageCopy region{
|
||||
.bufferOffset = 0,
|
||||
.bufferRowLength = 0,
|
||||
.bufferImageHeight = 0,
|
||||
.imageSubresource = { VK_IMAGE_ASPECT_COLOR_BIT, 0, 0, 1 },
|
||||
.imageOffset = { 0, 0, 0 },
|
||||
.imageExtent = { width, height, 1 },
|
||||
};
|
||||
vkCmdCopyImageToBuffer(cmd, images[currentBuffer],
|
||||
VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, stagingBuf, 1, ®ion);
|
||||
|
||||
VkImageMemoryBarrier back{
|
||||
.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
|
||||
.srcAccessMask = VK_ACCESS_TRANSFER_READ_BIT,
|
||||
.dstAccessMask = 0,
|
||||
.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
|
||||
.newLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR,
|
||||
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
|
||||
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
|
||||
.image = images[currentBuffer],
|
||||
.subresourceRange = range,
|
||||
};
|
||||
vkCmdPipelineBarrier(cmd,
|
||||
VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT,
|
||||
0, 0, nullptr, 0, nullptr, 1, &back);
|
||||
|
||||
Device::CheckVkResult(vkEndCommandBuffer(cmd));
|
||||
|
||||
VkSubmitInfo si{
|
||||
.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO,
|
||||
.commandBufferCount = 1,
|
||||
.pCommandBuffers = &cmd,
|
||||
};
|
||||
Device::CheckVkResult(vkQueueSubmit(Device::queue, 1, &si, VK_NULL_HANDLE));
|
||||
Device::CheckVkResult(vkQueueWaitIdle(Device::queue));
|
||||
|
||||
// Read back, swizzle BGRA → RGBA if needed, write PNG.
|
||||
void* mapped = nullptr;
|
||||
Device::CheckVkResult(vkMapMemory(Device::device, stagingMem, 0, VK_WHOLE_SIZE, 0, &mapped));
|
||||
const std::uint8_t* src = static_cast<const std::uint8_t*>(mapped);
|
||||
|
||||
std::vector<std::uint8_t> rgba(static_cast<std::size_t>(width) * height * 4);
|
||||
bool bgr = (colorFormat == VK_FORMAT_B8G8R8A8_UNORM);
|
||||
for (std::uint32_t i = 0; i < width * height; ++i) {
|
||||
if (bgr) {
|
||||
rgba[i*4+0] = src[i*4+2];
|
||||
rgba[i*4+1] = src[i*4+1];
|
||||
rgba[i*4+2] = src[i*4+0];
|
||||
rgba[i*4+3] = src[i*4+3];
|
||||
} else {
|
||||
rgba[i*4+0] = src[i*4+0];
|
||||
rgba[i*4+1] = src[i*4+1];
|
||||
rgba[i*4+2] = src[i*4+2];
|
||||
rgba[i*4+3] = src[i*4+3];
|
||||
}
|
||||
}
|
||||
vkUnmapMemory(Device::device, stagingMem);
|
||||
|
||||
stbi_write_png(path.string().c_str(), static_cast<int>(width), static_cast<int>(height),
|
||||
4, rgba.data(), static_cast<int>(width) * 4);
|
||||
|
||||
vkFreeCommandBuffers(Device::device, Device::commandPool, 1, &cmd);
|
||||
vkDestroyBuffer(Device::device, stagingBuf, nullptr);
|
||||
vkFreeMemory(Device::device, stagingMem, nullptr);
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue