new UI system

This commit is contained in:
Jorijn van der Graaf 2026-05-01 23:35:37 +02:00
commit 216972e73a
82 changed files with 4837 additions and 3243 deletions

View file

@ -1,32 +0,0 @@
# HelloWindow Example
## Description
This example demonstrates how to draw pixels to a window.
## Expected Result
A window with a green and blue colored square, when clicking on the square it logs the coordinates relative to the square.
## Highlighted Code Snippet
```cpp
UiElement& element = window.elements.emplace_back(
0.5,
0.5,
2,
1,
0.5f,
0.5f,
0.5,
0.5,
0,
false
);
```
## How to Run
```bash
crafter-build build executable -r
```

View file

@ -1,44 +0,0 @@
import Crafter.Event;
import Crafter.Graphics;
import std;
using namespace Crafter;
int main() {
WindowWayland window(1280, 720, "Hello Input!");
RenderingElementScaling element(
OpaqueType::FullyOpaque, //opaque, wether the element is opague or semi-transparant
2, //bufferWidth: the width of this elements pixel buffer
1, //bufferHeight: the height of this elements pixel buffer
FractionalToMapped(0.5), //anchorX: relative position where this elements x anchor (top-left) is placed to its parent x anchor
FractionalToMapped(0.5), //anchorY: relative position where this elements y anchor (top-left) is placed to its parent y anchor
FractionalToMapped(0.5), //relativeSizeX: the relative x size this element should be scaled to compared to its parent
FractionalToMapped(0.5), //relativeSizeY: the relative y size this element should be scaled to compared to its parent
FractionalToMapped(0.5), //anchorOffsetX: the amount this element's anchor should be offset from the top left corner (0.5 to in the middle)
FractionalToMapped(0.5), //anchorOffsetY: the amount this element's anchor should be offset from the top left corner (0.5 to place it in the middle)
0 //z: this elements Z position
);
window.elements.push_back(&element);
element.buffer = {{255, 0, 0 ,255}, {0, 255, 0 ,255}};
element.UpdatePosition(window);
Animation<std::tuple<std::int_fast32_t>> anim({
{std::chrono::seconds(5), FractionalToMapped(-0.5), FractionalToMapped(1.5)},
});
anim.Start(std::chrono::high_resolution_clock::now());
EventListener<FrameTime> updateListener(&window.onUpdate, [&](FrameTime time){
std::tuple<std::int_fast32_t> value = anim.Play(time.now);
element.anchorX = std::get<0>(value);
element.UpdatePosition(window);
if(anim.currentFrame == anim.keyframes.size()) {
anim.Start(time.now);
}
window.LogTiming();
});
window.StartUpdate();
window.StartSync();
}

View file

@ -1,15 +0,0 @@
{
"name": "crafter-graphics",
"configurations": [
{
"name": "executable",
"implementations": ["main"],
"dependencies": [
{
"path":"../../project.json",
"configuration":"lib-wayland-timing"
}
]
}
]
}

View file

@ -1,28 +0,0 @@
# HelloWindow Example
## Description
This example demonstrates how to draw pixels to a window.
## Expected Result
A window with a red colored square.
## Highlighted Code Snippet
```cpp
for(std::uint_fast32_t x = 0; x < 1280; x++) {
for(std::uint_fast32_t y = 0; y < 720; y++) {
window.framebuffer[x*720+y].r = 255;
window.framebuffer[x*720+y].g = 0;
window.framebuffer[x*720+y].b = 0;
window.framebuffer[x*720+y].a = 255;
}
}
```
## How to Run
```bash
crafter-build build executable -r
```

View file

@ -1,23 +0,0 @@
import Crafter.Graphics;
import std;
using namespace Crafter;
constexpr std::uint32_t width = 1280;
constexpr std::uint32_t height = 720;
int main() {
Device::Initialize();
Window window(width, height, "Hello Drawing!");
for(std::uint32_t x = 0; x < width; x++) {
for(std::uint32_t y = 0; y < height; y++) {
window.renderer.buffer[x*height+y].r = 255;
window.renderer.buffer[x*height+y].g = 0;
window.renderer.buffer[x*height+y].b = 0;
window.renderer.buffer[x*height+y].a = 255;
}
}
window.Render();
window.StartSync();
}

View file

@ -1,16 +0,0 @@
{
"name": "crafter-graphics",
"configurations": [
{
"name": "executable",
"implementations": ["main"],
"dependencies": [
{
"path":"../../project.json",
"configuration":"lib-wayland-debug"
}
],
"debug": true
}
]
}

View file

@ -1,19 +0,0 @@
# HelloGrid Example
This example demonstrates the usage of the GridElement class which arranges its children in configurable grid patterns.
## Features Shown
- Creating a GridElement with specified columns and rows
- Setting spacing between grid cells
- Adding multiple child elements to the grid
- Automatic positioning of children in a grid layout
- Different colored elements to visualize the grid structure
## How It Works
The GridElement automatically distributes its children across a grid defined by:
- Number of columns and rows
- Horizontal and vertical spacing between elements
Each child element is positioned in row-major order, filling the grid from left to right and top to bottom.

View file

@ -1,59 +0,0 @@
import Crafter.Event;
import Crafter.Graphics;
import std;
using namespace Crafter;
int main() {
WindowWayland window(1280, 720, "Hello Grid!");
// Create a GridElement with 3 columns and 2 rows
GridElement grid(
3, // columns
2, // rows
FractionalToMapped(0.1), // spacingX
FractionalToMapped(0.1), // spacingY
FractionalToMapped(0), // anchorX
FractionalToMapped(0), // anchorY
FractionalToMapped(1), // relativeWidth
FractionalToMapped(1), // relativeHeight
FractionalToMapped(0), // anchorOffsetX
FractionalToMapped(0), // anchorOffsetY
0 // z
);
for (int i = 0; i < 6; i++) {
RenderingElementScaling* rendering = new RenderingElementScaling(
OpaqueType::FullyOpaque,
1,
1,
FractionalToMapped(0), // anchorX
FractionalToMapped(0), // anchorY
FractionalToMapped(1.0), // relativeSizeX (will be overridden by grid)
FractionalToMapped(1.0), // relativeSizeY (will be overridden by grid)
FractionalToMapped(0.0), // anchorOffsetX
FractionalToMapped(0.0), // anchorOffsetY
0 // z
);
// // Set different colors for each element
switch (i % 6) {
case 0: rendering->buffer = {{255, 0, 0, 255}}; break; // Red
case 1: rendering->buffer = {{0, 255, 0, 255}}; break; // Green
case 2: rendering->buffer = {{0, 0, 255, 255}}; break; // Blue
case 3: rendering->buffer = {{255, 255, 0, 255}}; break; // Yellow
case 4: rendering->buffer = {{255, 0, 255, 255}}; break; // Magenta
case 5: rendering->buffer = {{0, 255, 255, 255}}; break; // Cyan
}
grid.children.push_back(rendering);
}
// Add the grid to the window
window.elements.push_back(&grid);
// Update positions to arrange children in grid
grid.UpdatePosition(window);
window.Render();
window.StartSync();
}

View file

@ -1,15 +0,0 @@
{
"name": "crafter-graphics",
"configurations": [
{
"name": "executable",
"implementations": ["main"],
"dependencies": [
{
"path":"../../project.json",
"configuration":"lib-wayland"
}
]
}
]
}

View file

@ -1,44 +0,0 @@
# HelloInput Example
## Description
This example demonstrates how to handle basic input events by:
- Create a window using `WindowWaylandWayland`
- Register event listeners using `EventListener<T>` for:
- Mouse click events
- Specific key events
- General keypress events
- Print formatted feedback to the console when events are triggered
## Expected Result
When you interact with the window, you might see console output like:
Clicked on X:450 Y:320!
Pressed specifically the a key!
Pressed the b key!
Make sure that the window has focus.
## Highlighted Code Snippet
```cpp
EventListener<MousePoint> clickListener(&window.onMouseLeftClick, [&window](MousePoint point){
std::cout << std::format("Clicked on X:{} Y:{}!", MappedToPixel(point.x, window.width), MappedToPixel(point.y, window.height));
});
EventListener<void> keyAListener(&window.onKeyDown['a'], [](){
std::cout << std::format("Pressed specifically the a key!");
});
EventListener<char> anyKeyListener(&window.onAnyKeyDown, [](char key){
std::cout << std::format("Pressed the {} key!", key);
});
```
## How to Run
```bash
crafter-build build executable -r
```

View file

@ -1,32 +0,0 @@
import Crafter.Graphics;
import Crafter.Event;
import std;
using namespace Crafter;
int main() {
WindowWayland window(1280, 720, "Hello Input!");
// Listen for left mouse click events on the window
// The callback receives the MousePoint struct containing the click coordinates in float pixels from the top left corner
EventListener<MousePoint> clickListener(&window.onMouseLeftClick, [&window](MousePoint point){
// Print the coordinates where the user clicked, we recieve the point in mapped space so we must convert it to pixels first
std::cout << std::format("Clicked on X:{} Y:{}!", MappedToPixelBoundless(point.x, window.width), MappedToPixelBoundless(point.y, window.height)) << std::endl;
});
// Listen specifically for the 'a' key being pressed down
// The callback takes no parameters since the key is fixed
EventListener<void> keyAListener(&window.onKeyDown['a'], [](){
// Print confirmation of 'a' key press
std::cout << std::format("Pressed specifically the a key!") << std::endl;
});
// Listen for any key press on the window
// The callback receives the character of the key pressed
EventListener<char> anyKeyListener(&window.onAnyKeyDown, [](char key){
// Print which key was pressed
std::cout << std::format("Pressed the {} key!", key) << std::endl;
});
//Start the window event loop, unless the window is started events will not trigger.
window.StartSync();
}

View file

@ -1,15 +0,0 @@
{
"name": "crafter-graphics",
"configurations": [
{
"name": "executable",
"implementations": ["main"],
"dependencies": [
{
"path":"../../project.json",
"configuration":"lib-wayland"
}
]
}
]
}

View file

@ -1,36 +0,0 @@
import Crafter.Event;
import Crafter.Graphics;
import std;
using namespace Crafter;
int main() {
WindowWayland window(200, 200, "Hello Input!");
RenderingElementScalingRotating2D element(
OpaqueType::SemiOpaque, //opaque, wether the element is opague or semi-transparant
1, //bufferWidth: the width of this elements pixel buffer
1, //bufferHeight: the height of this elements pixel buffer
FractionalToMappedBoundlessU(0.125),
FractionalToMapped(0.5), //anchorX: relative position where this elements x anchor (top-left) is placed to its parent x anchor
FractionalToMapped(0.5), //anchorY: relative position where this elements y anchor (top-left) is placed to its parent y anchor
FractionalToMapped(0.5), //relativeSizeX: the relative x size this element should be scaled to compared to its parent
FractionalToMapped(0.5), //relativeSizeY: the relative y size this element should be scaled to compared to its parent
FractionalToMapped(0.5), //anchorOffsetX: the amount this element's anchor should be offset from the top left corner (0.5 to in the middle)
FractionalToMapped(0.5), //anchorOffsetY: the amount this element's anchor should be offset from the top left corner (0.5 to place it in the middle)
0, //z: this elements Z position
);
window.elements.push_back(&element);
element.buffer = {{255, 0, 0 ,255}};
element.UpdatePosition(window);
EventListener<FrameTime> updateListener(&window.onUpdate, [&](FrameTime time){
element.rotation += 50000000000000000;
std::cout << element.rotation << std::endl;
element.UpdatePosition(window);
window.LogTiming();
});
window.StartUpdate();
window.StartSync();
}

View file

@ -1,16 +0,0 @@
{
"name": "crafter-graphics",
"configurations": [
{
"name": "executable",
"implementations": ["main"],
"dependencies": [
{
"path":"../../project.json",
"configuration":"lib-wayland-timing"
}
],
"debug": true
}
]
}

View file

@ -1,32 +0,0 @@
# HelloWindow Example
## Description
This example demonstrates how to draw pixels to a window.
## Expected Result
A window with a green and blue colored square, when clicking on the square it logs the coordinates relative to the square.
## Highlighted Code Snippet
```cpp
UiElement& element = window.elements.emplace_back(
0.5,
0.5,
2,
1,
0.5f,
0.5f,
0.5,
0.5,
0,
false
);
```
## How to Run
```bash
crafter-build build executable -r
```

View file

@ -1,33 +0,0 @@
import Crafter.Event;
import Crafter.Graphics;
import std;
using namespace Crafter;
int main() {
WindowWayland window(1280, 720, "Hello Input!");
RenderingElement<false, false, false> element(
{
FractionalToMapped<std::int32_t>(0), //anchorX: relative position where this elements x anchor (top-left) is placed to its parent x anchor
FractionalToMapped<std::int32_t>(0.5), //anchorY: relative position where this elements y anchor (top-left) is placed to its parent y anchor
FractionalToMapped<std::int32_t>(0.1), //relativeSizeX: the relative x size this element should be scaled to compared to its parent
FractionalToMapped<std::int32_t>(1), //relativeSizeY: the relative y size this element should be scaled to compared to its parent
FractionalToMapped<std::int32_t>(0), //anchorOffsetX: the amount this element's anchor should be offset from the top left corner (0.5 to in the middle)
FractionalToMapped<std::int32_t>(0), //anchorOffsetY: the amount this element's anchor should be offset from the top left corner (0.5 to place it in the middle)
0 //z: this elements Z position
},
OpaqueType::FullyOpaque
);
Font font("inter.ttf");
std::string text = "testtttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttt";
element.UpdatePosition(window);
std::vector<std::string_view> lines = element.ResizeText(window, text, 200, font, TextOverflowMode::Clip, TextScaleMode::Element); // anchor.width automatically scales with our text
element.RenderText(window, lines, 200, {0,0,0,255}, font);
window.elements.push_back(&element);
window.Render();
window.StartSync();
}

View file

@ -1,17 +0,0 @@
{
"name": "crafter-graphics",
"configurations": [
{
"name": "executable",
"implementations": ["main"],
"dependencies": [
{
"path":"../../project.json",
"configuration":"lib-wayland-debug"
}
],
"debug": true,
"additional_files": ["inter.ttf"]
}
]
}

View file

@ -1,32 +0,0 @@
# HelloWindow Example
## Description
This example demonstrates how to draw pixels to a window.
## Expected Result
A window with a green and blue colored square, when clicking on the square it logs the coordinates relative to the square.
## Highlighted Code Snippet
```cpp
UiElement& element = window.elements.emplace_back(
0.5,
0.5,
2,
1,
0.5f,
0.5f,
0.5,
0.5,
0,
false
);
```
## How to Run
```bash
crafter-build build executable -r
```

View file

@ -1,40 +0,0 @@
import Crafter.Event;
import Crafter.Graphics;
import std;
using namespace Crafter;
int main() {
Device::Initialize();
Window window(1280, 720, "Hello Drawing!");
RenderingElement2D<true, true, false> element(
{
0.5, //anchorX: relative position where this elements x anchor (top-left) is placed to its parent x anchor
0.5, //anchorY: relative position where this elements y anchor (top-left) is placed to its parent y anchor
0.5, //relativeSizeX: the relative x size this element should be scaled to compared to its parent
0.5, //relativeSizeY: the relative y size this element should be scaled to compared to its parent
0.5, //anchorOffsetX: the amount this element's anchor should be offset from the top left corner (0.5 to in the middle)
0.5, //anchorOffsetY: the amount this element's anchor should be offset from the top left corner (0.5 to place it in the middle)
0 //z: this elements Z position
},
OpaqueType::FullyOpaque,
2,
1
);
MouseElement mouse(window);
element.children.push_back(&mouse);
window.renderer.elements.push_back(&element);
element.scalingBuffer = {{255, 0, 0 ,255}, {0, 255, 0 ,255}};
element.UpdatePosition(window.renderer);
EventListener<void> clickListener(&mouse.onMouseLeftClick, [&window]() {
std::println("Clicked on X:{} Y:{}!",
window.currentMousePos.x, window.currentMousePos.y
);
});
window.Render();
window.StartSync();
}

View file

@ -1,16 +0,0 @@
{
"name": "crafter-graphics",
"configurations": [
{
"name": "executable",
"implementations": ["main"],
"dependencies": [
{
"path":"../../project.json",
"configuration":"lib-wayland-debug"
}
],
"debug": true
}
]
}

View file

@ -1,50 +0,0 @@
# Crafter.Graphics Examples
Welcome to the **Crafter.Graphics** examples folder!
Here you'll find a variety of demos to help you learn and experiment with the features of the `Crafter.Graphics` library.
## Getting Started
To run any example, navigate into its folder and use the following command:
```bash
crafter-build build executable -r
```
## Hello Series
This series explains the absolute basics.
1. **HelloWindow**
Basic window creation using Crafter.Graphics.
2. **HelloInput**
Handling keyboard and mouse input events.
3. **HelloDrawing**
Introduction to drawing on a window.
4. **HelloUI**
Creating and rendering user interface components.
## Vulkan Series
This series explains the vulkan integration.
1. **VulkanWindow**
HelloWindow vulkan edition.
2. **VulkanTraingle**
Introduction to drawing on a window.
3. **VulkanCube**
Creating a custom shader.
4. **VulkanShader**
Creating a custom shader.
## Notes
- Each example is self-contained and meant to be run individually.
- Make sure your environment is correctly set up with all dependencies required by `Crafter.Graphics`.
- A comaptible WSL envoirement can be set up by running these commands:
```cmd
wsl --update
wsl --install archlinux --name crafter --no-launch
wsl -d crafter pacman -Syu vulkan-devel vulkan-swrast clang git base-devel libpqxx onetbb boost lld glslang --noconfirm
wsl --terminate crafter
```

View file

@ -1,12 +0,0 @@
#version 460
#extension GL_EXT_ray_tracing : enable
#extension GL_EXT_nonuniform_qualifier : enable
layout(location = 0) rayPayloadInEXT vec3 hitValue;
hitAttributeEXT vec2 attribs;
void main()
{
const vec3 barycentricCoords = vec3(1.0f - attribs.x - attribs.y, attribs.x, attribs.y);
hitValue = barycentricCoords;
}

View file

@ -1,175 +0,0 @@
#include "vulkan/vulkan.h"
import Crafter.Graphics;
using namespace Crafter;
import std;
import Crafter.Event;
import Crafter.Math;
typedef VulkanShaderConst<"raygen.spv", "main", VK_SHADER_STAGE_RAYGEN_BIT_KHR> Raygenspv;
typedef VulkanShaderConst<"closesthit.spv", "main", VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR> Closesthitspv;
typedef VulkanShaderConst<"miss.spv", "main", VK_SHADER_STAGE_MISS_BIT_KHR> Misspv;
typedef std::tuple<Raygenspv, Misspv, Closesthitspv> AllShaders;
typedef std::tuple<
ShaderGroup<0, VK_SHADER_UNUSED_KHR, VK_SHADER_UNUSED_KHR, VK_SHADER_UNUSED_KHR>,
ShaderGroup<1, VK_SHADER_UNUSED_KHR, VK_SHADER_UNUSED_KHR, VK_SHADER_UNUSED_KHR>,
ShaderGroup<VK_SHADER_UNUSED_KHR, 2, VK_SHADER_UNUSED_KHR, VK_SHADER_UNUSED_KHR>
> ShaderGroups;
typedef PipelineRTVulkanConst<AllShaders, ShaderGroups> Pipeline;
typedef DescriptorSetLayoutVulkanConst<1, {{
{
.binding = 0,
.descriptorType = VK_DESCRIPTOR_TYPE_ACCELERATION_STRUCTURE_KHR,
.descriptorCount = 1,
.stageFlags = VK_SHADER_STAGE_RAYGEN_BIT_KHR,
},
}}> descriptorSetLayoutTlas;
typedef DescriptorSetLayoutVulkanConst<1, {{
{
.binding = 0,
.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_IMAGE,
.descriptorCount = 1,
.stageFlags = VK_SHADER_STAGE_RAYGEN_BIT_KHR,
}
}}> descriptorSetLayoutImage;
int main() {
Device::CreateDevice();
WindowVulkan window(1280, 720, "HelloVulkan");
VkCommandBuffer cmd = window.StartInit();
Raygenspv::CreateShader();
Closesthitspv::CreateShader();
Misspv::CreateShader();
ShaderBindingTableVulkanConst<AllShaders>::Init();
descriptorSetLayoutTlas::Init();
descriptorSetLayoutImage::Init();
std::array<VkDescriptorSetLayout, 4> layouts {{descriptorSetLayoutTlas::layout, descriptorSetLayoutImage::layout, descriptorSetLayoutImage::layout, descriptorSetLayoutImage::layout}};
DescriptorPool pool;
pool.sets.resize(4);
pool.BuildPool(DescriptorPool::GetPoolSizes<descriptorSetLayoutTlas, descriptorSetLayoutImage, descriptorSetLayoutImage, descriptorSetLayoutImage>(), layouts);
Pipeline::Init(cmd, layouts);
window.SetPipelineRT<Pipeline>();
Mesh triangleMesh;
std::array<Vector<float, 3, 3>, 3> verts {{{-150, -150, 100}, {0, 150, 100}, {150, -150, 100}}};
std::array<std::uint32_t, 3> index {{2,1,0}};
triangleMesh.Build(verts, index, cmd);
RenderingElement3D renderer = {
.instance = {
.instanceCustomIndex = 0,
.mask = 0xFF,
.instanceShaderBindingTableRecordOffset = 0,
.flags = VK_GEOMETRY_INSTANCE_FORCE_OPAQUE_BIT_KHR,
.accelerationStructureReference = triangleMesh.blasAddr
}
};
RenderingElement3D::elements.emplace_back(&renderer);
MatrixRowMajor<float, 4, 3, 1> transform = MatrixRowMajor<float, 4, 3, 1>::Identity();
std::memcpy(renderer.instance.transform.matrix, transform.m, sizeof(transform.m));
RenderingElement3D::tlases.resize(Window::numFrames);
RenderingElement3D::BuildTLAS(cmd, 0);
RenderingElement3D::BuildTLAS(cmd, 1);
RenderingElement3D::BuildTLAS(cmd, 2);
VkDescriptorImageInfo imageInfo[Window::numFrames] = {
{
.imageView = window.imageViews[0],
.imageLayout = VK_IMAGE_LAYOUT_GENERAL
},
{
.imageView = window.imageViews[1],
.imageLayout = VK_IMAGE_LAYOUT_GENERAL
},
{
.imageView = window.imageViews[2],
.imageLayout = VK_IMAGE_LAYOUT_GENERAL
},
};
VkWriteDescriptorSetAccelerationStructureKHR writeDescriptorSetAccelerationStructure {
.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET_ACCELERATION_STRUCTURE_KHR,
.accelerationStructureCount = 1,
.pAccelerationStructures = &RenderingElement3D::tlases[0].accelerationStructure
};
VkWriteDescriptorSet write[4];
write[0] = {
.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET,
.pNext = &writeDescriptorSetAccelerationStructure,
.dstSet = pool.sets[0],
.dstBinding = 0,
.dstArrayElement = 0,
.descriptorCount = 1,
.descriptorType = VK_DESCRIPTOR_TYPE_ACCELERATION_STRUCTURE_KHR,
};
for(std::uint32_t i = 0; i < Window::numFrames; i++) {
write[i+1] = {
.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET,
.dstSet = pool.sets[i+1],
.dstBinding = 0,
.dstArrayElement = 0,
.descriptorCount = 1,
.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_IMAGE,
.pImageInfo = &imageInfo[i]
};
}
vkUpdateDescriptorSets(Device::device, 4, write, 0, nullptr);
window.descriptorsRt = {pool.sets[0], pool.sets[1]};
/*
FinishInit executes all commands recorded to StartInit.
This must be called before the the event loops starts if you called StartInit before.
*/
window.FinishInit();
Animation<std::tuple<float>> anim({
{std::chrono::seconds(3), -600, 600},
});
anim.Start(std::chrono::high_resolution_clock::now());
EventListener<void> updateListener(&window.onRender, [&](){
float value = std::get<0>(anim.Play(window.currentFrameTime.now));
if(anim.currentFrame == anim.keyframes.size()) {
anim.Start(window.currentFrameTime.now);
}
MatrixRowMajor<float, 4, 3, 1> transform = MatrixRowMajor<float, 4, 3, 1>::Translation(value, 0, 0);
std::memcpy(renderer.instance.transform.matrix, transform.m, sizeof(transform.m));
RenderingElement3D::BuildTLAS(window.drawCmdBuffers[window.currentBuffer], window.currentBuffer);
VkWriteDescriptorSetAccelerationStructureKHR writeDescriptorSetAccelerationStructure {
.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET_ACCELERATION_STRUCTURE_KHR,
.accelerationStructureCount = 1,
.pAccelerationStructures = &RenderingElement3D::tlases[window.currentBuffer].accelerationStructure
};
VkWriteDescriptorSet write = {
.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET,
.pNext = &writeDescriptorSetAccelerationStructure,
.dstSet = pool.sets[0],
.dstBinding = 0,
.dstArrayElement = 0,
.descriptorCount = 1,
.descriptorType = VK_DESCRIPTOR_TYPE_ACCELERATION_STRUCTURE_KHR,
};
vkUpdateDescriptorSets(Device::device, 1, &write, 0, nullptr);
window.descriptorsRt[1] = pool.sets[window.currentBuffer+1];
});
window.Render();
window.StartUpdate();
window.StartSync();
}

View file

@ -1,9 +0,0 @@
#version 460
#extension GL_EXT_ray_tracing : enable
layout(location = 0) rayPayloadInEXT vec3 hitValue;
void main()
{
hitValue = vec3(1, 1, 1);
}

View file

@ -1,32 +0,0 @@
{
"name": "crafter-graphics",
"configurations": [
{
"name": "executable",
"implementations": ["main"],
"dependencies": [
{
"path":"../../project.json",
"configuration":"lib-vulkan-debug"
}
],
"shaders": [
{
"path":"raygen.glsl",
"type": 6,
"entrypoint":"main"
},
{
"path":"closesthit.glsl",
"type": 9,
"entrypoint":"main"
},
{
"path":"miss.glsl",
"type": 10,
"entrypoint":"main"
}
]
}
]
}

View file

@ -1,47 +0,0 @@
#version 460
#extension GL_EXT_ray_tracing : enable
#extension GL_EXT_shader_image_load_formatted : enable
layout(binding = 0, set = 0) uniform accelerationStructureEXT topLevelAS;
layout(binding = 0, set = 1, rgba8) uniform writeonly image2D image;
layout(location = 0) rayPayloadEXT vec3 hitValue;
void main()
{
// Pixel coordinates
uvec2 pixel = gl_LaunchIDEXT.xy;
uvec2 resolution = gl_LaunchSizeEXT.xy;
// Normalized coordinates in range [-1, 1]
vec2 uv = (vec2(pixel) + 0.5) / vec2(resolution);
vec2 ndc = uv * 2.0 - 1.0;
// Camera parameters
vec3 origin = vec3(0.0, 0.0, -300.0);
float aspect = float(resolution.x) / float(resolution.y);
float fov = radians(60.0);
float tanHalfFov = tan(fov * 0.5);
// Simple pinhole camera facing +Z
vec3 direction = normalize(vec3(
ndc.x * aspect * tanHalfFov,
-ndc.y * tanHalfFov,
1.0
));
traceRayEXT(
topLevelAS,
gl_RayFlagsNoneEXT,
0xff,
0, 0, 0,
origin,
0.001,
direction,
10000.0,
0
);
imageStore(image, ivec2(pixel), vec4(hitValue, 1.0));
}

View file

@ -96,6 +96,9 @@ int main() {
window.FinishInit();
auto imgSlots = descriptorHeap.AllocateImageSlots(1);
auto bufSlots = descriptorHeap.AllocateBufferSlots(1);
VkDeviceAddressRangeKHR tlasRange0 = {
.address = RenderingElement3D::tlases[0].address,
};
@ -161,27 +164,27 @@ int main() {
VkHostAddressRangeEXT destinations[6] = {
{
.address = descriptorHeap.resourceHeap[0].value + descriptorHeap.bufferStartOffset,
.address = descriptorHeap.resourceHeap[0].value + descriptorHeap.BufferByteOffset(bufSlots.firstElement),
.size = Device::descriptorHeapProperties.bufferDescriptorSize
},
{
.address = descriptorHeap.resourceHeap[0].value,
.address = descriptorHeap.resourceHeap[0].value + descriptorHeap.ImageByteOffset(imgSlots.firstElement),
.size = Device::descriptorHeapProperties.imageDescriptorSize
},
{
.address = descriptorHeap.resourceHeap[1].value + descriptorHeap.bufferStartOffset,
.address = descriptorHeap.resourceHeap[1].value + descriptorHeap.BufferByteOffset(bufSlots.firstElement),
.size = Device::descriptorHeapProperties.bufferDescriptorSize
},
{
.address = descriptorHeap.resourceHeap[1].value,
.address = descriptorHeap.resourceHeap[1].value + descriptorHeap.ImageByteOffset(imgSlots.firstElement),
.size = Device::descriptorHeapProperties.imageDescriptorSize
},
{
.address = descriptorHeap.resourceHeap[2].value + descriptorHeap.bufferStartOffset,
.address = descriptorHeap.resourceHeap[2].value + descriptorHeap.BufferByteOffset(bufSlots.firstElement),
.size = Device::descriptorHeapProperties.bufferDescriptorSize
},
{
.address = descriptorHeap.resourceHeap[2].value,
.address = descriptorHeap.resourceHeap[2].value + descriptorHeap.ImageByteOffset(imgSlots.firstElement),
.size = Device::descriptorHeapProperties.imageDescriptorSize
},
};
@ -192,8 +195,9 @@ int main() {
descriptorHeap.resourceHeap[1].FlushDevice();
descriptorHeap.resourceHeap[2].FlushDevice();
window.pipeline = &pipeline;
window.descriptorHeap = &descriptorHeap;
RTPass rtPass(&pipeline);
window.passes.push_back(&rtPass);
window.Render();
window.StartSync();

View file

@ -5,7 +5,6 @@ using namespace Crafter;
extern "C" Configuration CrafterBuildProject(std::span<const std::string_view> args) {
std::vector<std::string> graphicsArgs(args.begin(), args.end());
graphicsArgs.push_back("--vulkan");
Configuration* graphics = LocalProject({
.projectFile = "../../project.cpp",
.args = graphicsArgs,

View file

@ -1,133 +1,85 @@
#include "vulkan/vulkan.h"
#include <cassert>
import Crafter.Graphics;
using namespace Crafter;
import std;
import Crafter.Event;
import Crafter.Math;
import std;
using namespace Crafter;
int main() {
Device::Initialize();
Window window(1280, 720, "HelloVulkan");
VkCommandBuffer cmd = window.StartInit();
DescriptorHeapVulkan descriptorHeap;
descriptorHeap.Initialize(1,2,0);
VkSpecializationMapEntry entry = {
.constantID = 0,
.offset = 0,
.size = sizeof(uint16_t)
};
VkSpecializationInfo specilizationInfo = {
.mapEntryCount = 1,
.pMapEntries = &entry,
.dataSize = sizeof(uint16_t),
.pData = &descriptorHeap.bufferStartElement
};
std::array<VulkanShader, 1> shaders{{
{"raygen.spv", "main", VK_SHADER_STAGE_RAYGEN_BIT_KHR, &specilizationInfo}
}};
ShaderBindingTableVulkan shaderTable;
shaderTable.Init(shaders);
std::array<VkRayTracingShaderGroupCreateInfoKHR, 1> raygenGroups {{
{
.sType = VK_STRUCTURE_TYPE_RAY_TRACING_SHADER_GROUP_CREATE_INFO_KHR,
.type = VK_RAY_TRACING_SHADER_GROUP_TYPE_GENERAL_KHR,
.generalShader = 0,
.closestHitShader = VK_SHADER_UNUSED_KHR,
.anyHitShader = VK_SHADER_UNUSED_KHR,
.intersectionShader = VK_SHADER_UNUSED_KHR,
},
}};
std::array<VkRayTracingShaderGroupCreateInfoKHR, 0> missGroups;
std::array<VkRayTracingShaderGroupCreateInfoKHR, 0> hitGroups;
PipelineRTVulkan pipeline;
pipeline.Init(cmd, raygenGroups, missGroups, hitGroups, shaderTable);
Window window(1280, 720, "VulkanUI");
window.StartInit();
window.FinishInit();
RenderingElement2DVulkan<true, true> element(
{
0.5, //anchorX: relative position where this elements x anchor (top-left) is placed to its parent x anchor
0.5, //anchorY: relative position where this elements y anchor (top-left) is placed to its parent y anchor
0.5, //relativeSizeX: the relative x size this element should be scaled to compared to its parent
0.5, //relativeSizeY: the relative y size this element should be scaled to compared to its parent
0.5, //anchorOffsetX: the amount this element's anchor should be offset from the top left corner (0.5 to in the middle)
0.5, //anchorOffsetY: the amount this element's anchor should be offset from the top left corner (0.5 to place it in the middle)
0 //z: this elements Z position
},
2,
1
Font font("Inter.ttf");
UI::Theme theme = UI::themes::default_dark();
theme.defaultFont = &font;
// ─────────────────────────────────────────────────────────────────────
// Wire the scene: it auto-creates a descriptor heap, plugs into the
// window's pass list, hooks mouse + update events, and drives a
// compute-shader UI pass per frame.
// ─────────────────────────────────────────────────────────────────────
UI::UIScene scene;
scene.Initialize(window, "ui.comp.spv");
scene.background(UI::Color{0.06f, 0.07f, 0.10f, 1.0f});
scene.Root(
UI::VStack{}
.padding(20)
.spacing(12)
.children(
UI::Text{"Crafter UI — V1"}.font(font).size(28),
UI::Text{}.font(font).size(14).runs(
UI::TextRun{"Click "},
UI::TextRun{"Quit"}.color(UI::Color{1.0f, 0.55f, 0.55f}).bold(),
UI::TextRun{" to close the window. Tabs switch on click. "},
UI::TextRun{"Have fun!"}.color(UI::Color{0.55f, 0.85f, 1.0f})
),
UI::HStack{}
.width(UI::Length::Frac(1))
.spacing(8)
.children(
UI::Button{"Play"} .font(font).style(theme.primary) .onClick([]{ std::println(std::cerr, "[click] Play"); }),
UI::Button{"Options"}.font(font).style(theme.secondary).onClick([]{ std::println(std::cerr, "[click] Options"); }),
UI::Spacer{},
UI::Button{"Quit"} .font(font).style(theme.danger) .onClick([]{ std::println(std::cerr, "[click] Quit"); std::_Exit(0); })
),
UI::ProgressBar{}
.value(0.42f)
.size(UI::Length::Frac(1), UI::Length::Px(20))
.foreground(theme.primary.background),
UI::TabView{}
.font(font)
.width(UI::Length::Frac(1))
.height(UI::Length::Px(220))
.tab("Graphics", UI::VStack{}.padding(8).spacing(8).children(
UI::Text{"Resolution"}.font(font).size(14),
UI::InputField{"1920x1080"}.font(font).style(theme.input),
UI::Text{"Max lights"}.font(font).size(14),
UI::InputField{"32"}.font(font).style(theme.input)
))
.tab("Input", UI::VStack{}.padding(8).spacing(8).children(
UI::Text{"Mouse sensitivity"}.font(font).size(14),
UI::InputField{"1.0"}.font(font).style(theme.input)
))
.tab("Audio", UI::VStack{}.padding(8).spacing(8).children(
UI::Text{"Master volume"}.font(font).size(14),
UI::InputField{"80"}.font(font).style(theme.input)
))
)
);
for(std::uint8_t i = 0; i < Window::numFrames; i++) {
reinterpret_cast<VulkanBuffer<Vector<_Float16, 4, 4>, true>*>(element.buffers[i])->value[0] = {1, 0, 0, 1};
reinterpret_cast<VulkanBuffer<Vector<_Float16, 4, 4>, true>*>(element.buffers[i])->value[1] = {0, 1, 0, 1};
reinterpret_cast<VulkanBuffer<Vector<_Float16, 4, 4>, true>*>(element.buffers[i])->FlushDevice();
}
RendertargetVulkan rendertarget(1280, 720, {&element});
VkImageDescriptorInfoEXT imageInfo0 = {
.sType = VK_STRUCTURE_TYPE_IMAGE_DESCRIPTOR_INFO_EXT,
.pView = &window.imageViews[0],
.layout = VK_IMAGE_LAYOUT_GENERAL
};
VkImageDescriptorInfoEXT imageInfo1 = {
.sType = VK_STRUCTURE_TYPE_IMAGE_DESCRIPTOR_INFO_EXT,
.pView = &window.imageViews[1],
.layout = VK_IMAGE_LAYOUT_GENERAL
};
VkImageDescriptorInfoEXT imageInfo2 = {
.sType = VK_STRUCTURE_TYPE_IMAGE_DESCRIPTOR_INFO_EXT,
.pView = &window.imageViews[2],
.layout = VK_IMAGE_LAYOUT_GENERAL
};
std::array<VkResourceDescriptorInfoEXT, 9> infos;
infos[0] = {
.sType = VK_STRUCTURE_TYPE_RESOURCE_DESCRIPTOR_INFO_EXT,
.type = VK_DESCRIPTOR_TYPE_STORAGE_IMAGE,
.data = { .pImage = &imageInfo0 }
};
infos[1] = {
.sType = VK_STRUCTURE_TYPE_RESOURCE_DESCRIPTOR_INFO_EXT,
.type = VK_DESCRIPTOR_TYPE_STORAGE_IMAGE,
.data = { .pImage = &imageInfo1 }
};
infos[2] = {
.sType = VK_STRUCTURE_TYPE_RESOURCE_DESCRIPTOR_INFO_EXT,
.type = VK_DESCRIPTOR_TYPE_STORAGE_IMAGE,
.data = { .pImage = &imageInfo2 }
};
std::array<VkHostAddressRangeEXT, 9> ranges;
ranges[0] = {
.address = descriptorHeap.resourceHeap[0].value,
.size = Device::descriptorHeapProperties.imageDescriptorSize
};
ranges[1] = {
.address = descriptorHeap.resourceHeap[1].value,
.size = Device::descriptorHeapProperties.imageDescriptorSize
},
ranges[2] = {
.address = descriptorHeap.resourceHeap[2].value,
.size = Device::descriptorHeapProperties.imageDescriptorSize
},
rendertarget.WriteDescriptors(infos, ranges, 3, descriptorHeap.bufferStartOffset, descriptorHeap);
window.pipeline = &pipeline;
window.descriptorHeap = &descriptorHeap;
window.Render();
window.SaveFrame("frame.png");
window.StartUpdate(); // continuous rendering — UIScene re-emits per frame
window.StartSync();
}

View file

@ -0,0 +1,27 @@
import std;
import Crafter.Build;
namespace fs = std::filesystem;
using namespace Crafter;
extern "C" Configuration CrafterBuildProject(std::span<const std::string_view> args) {
std::vector<std::string> graphicsArgs(args.begin(), args.end());
Configuration* graphics = LocalProject({
.projectFile = "../../project.cpp",
.args = graphicsArgs,
});
Configuration cfg;
cfg.path = "./";
cfg.name = "VulkanUI";
cfg.outputName = "VulkanUI";
ApplyStandardArgs(cfg, args);
cfg.dependencies = { graphics };
std::array<fs::path, 0> ifaces = {};
std::array<fs::path, 1> impls = { "main" };
cfg.GetInterfacesAndImplementations(ifaces, impls);
cfg.files.push_back("Inter.ttf");
return cfg;
}

View file

@ -1,23 +0,0 @@
{
"name": "crafter-graphics",
"configurations": [
{
"name": "executable",
"implementations": ["main"],
"dependencies": [
{
"path":"../../project.json",
"configuration":"lib-wayland-vulkan-debug"
}
],
"debug": true,
"shaders": [
{
"path":"raygen.glsl",
"type": 6,
"entrypoint":"main"
}
]
}
]
}

View file

@ -1,48 +0,0 @@
#version 460
#extension GL_EXT_ray_tracing : enable
#extension GL_EXT_shader_image_load_formatted : enable
#extension GL_EXT_shader_explicit_arithmetic_types_int16 : enable
#extension GL_EXT_descriptor_heap : enable
#extension GL_EXT_nonuniform_qualifier : enable
#extension GL_EXT_shader_explicit_arithmetic_types_float16 : enable
struct UIScaledData{
int16_t x;
int16_t y;
int16_t sizeX;
int16_t sizeY;
uint16_t bufferX;
uint16_t bufferY;
};
layout(std430, descriptor_heap) buffer UIScaledDataBuffer {
uint16_t count;
uint16_t pad[5];
UIScaledData data[];
} UITransformBuffer[];
layout(std430, descriptor_heap) buffer UIPixelBufferr {
f16vec4 pixels[];
} UIPixelBuffer[];
layout(constant_id = 0) const uint16_t bufferStart = 0us;
layout(descriptor_heap) uniform writeonly image2D image[];
void main()
{
uvec2 pixel = gl_LaunchIDEXT.xy;
uvec2 resolution = gl_LaunchSizeEXT.xy;
vec4 hitValue = vec4(0);
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(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]);
}
}
imageStore(image[0], ivec2(pixel), hitValue);
}