Compare commits

..

No commits in common. "4bb7219ccf7433ad795b38f291ebc6a1693a0f37" and "e7d0bc8f8e82da29b98e52452ab1e23f06d862d6" have entirely different histories.

35 changed files with 3062 additions and 13869 deletions

View file

@ -11,8 +11,8 @@ A window with a red colored square.
## Highlighted Code Snippet ## Highlighted Code Snippet
```cpp ```cpp
for(std::uint_fast32_t x = 0; x < 1280; x++) { for(uint32_t x = 0; x < 1280; x++) {
for(std::uint_fast32_t y = 0; y < 720; y++) { for(uint32_t y = 0; y < 720; y++) {
window.framebuffer[x*720+y].r = 255; window.framebuffer[x*720+y].r = 255;
window.framebuffer[x*720+y].g = 0; window.framebuffer[x*720+y].g = 0;
window.framebuffer[x*720+y].b = 0; window.framebuffer[x*720+y].b = 0;

View file

@ -3,10 +3,10 @@ import std;
using namespace Crafter; using namespace Crafter;
int main() { int main() {
Window window("HelloWindow", 1280, 720); WindowWaylandWayland window("HelloWindow", 1280, 720);
for(std::uint_fast32_t x = 0; x < 1280; x++) { for(uint32_t x = 0; x < 1280; x++) {
for(std::uint_fast32_t y = 0; y < 720; y++) { for(uint32_t y = 0; y < 720; y++) {
window.framebuffer[x*720+y].r = 255; window.framebuffer[x*720+y].r = 255;
window.framebuffer[x*720+y].g = 0; window.framebuffer[x*720+y].g = 0;
window.framebuffer[x*720+y].b = 0; window.framebuffer[x*720+y].b = 0;
@ -15,8 +15,8 @@ int main() {
} }
//Semi transparant version: //Semi transparant version:
// for(std::uint_fast32_t x = 0; x < 1280; x++) { // for(uint32_t x = 0; x < 1280; x++) {
// for(std::uint_fast32_t = 0; y < 720; y++) { // for(uint32_t y = 0; y < 720; y++) {
// window.framebuffer[x*720+y].r = 128;//alpha channel must be premultiplied // window.framebuffer[x*720+y].r = 128;//alpha channel must be premultiplied
// window.framebuffer[x*720+y].g = 0; // window.framebuffer[x*720+y].g = 0;
// window.framebuffer[x*720+y].b = 0; // window.framebuffer[x*720+y].b = 0;

View file

@ -6,7 +6,7 @@ using namespace Crafter;
int main() { int main() {
// Create a Wayland window named "HelloWindow" with resolution 1280x720 // Create a Wayland window named "HelloWindow" with resolution 1280x720
// (window creation explained in HelloWindow example) // (window creation explained in HelloWindow example)
Window window("HelloWindow", 1280, 720); WindowWaylandWayland window("HelloWindow", 1280, 720);
// Listen for left mouse click events on the window // 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 // The callback receives the MousePoint struct containing the click coordinates in float pixels from the top left corner

View file

@ -4,7 +4,7 @@ import std;
using namespace Crafter; using namespace Crafter;
int main() { int main() {
Window window("HelloWindow", 1280, 720); WindowWaylandWayland window("HelloWindow", 1280, 720);
UiElement& element = window.elements.emplace_back( UiElement& element = window.elements.emplace_back(
0.5, //anchorX: relative position where this elements x anchor (top-left) is placed to its parent x anchor 0.5, //anchorX: relative position where this elements x anchor (top-left) is placed to its parent x anchor

View file

@ -8,7 +8,7 @@ int main() {
The WindowWaylandWayland class is a specialized window implementation The WindowWaylandWayland class is a specialized window implementation
that uses the Wayland display server protocol and renderer (hence the name "WaylandWayland"). that uses the Wayland display server protocol and renderer (hence the name "WaylandWayland").
*/ */
Window window("HelloWindow", 1280, 720); WindowWaylandWayland window("HelloWindow", 1280, 720);
/* /*
This starts the windows main event loop, allowing it to respond to user input and system events. This starts the windows main event loop, allowing it to respond to user input and system events.

View file

@ -0,0 +1,66 @@
/*
Crafter®.Graphics
Copyright (C) 2025 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;
#include <vulkan/vulkan.h>
module Crafter.Graphics:Camera_impl;
import :Camera;
import Crafter.Math;
import Crafter.Event;
using namespace Crafter;
Camera::Camera(float fov, float aspectRatio, float near, float far) {
projection = MatrixRowMajor<float, 4, 4, 1>::Perspective(fov, aspectRatio, near, far);
view = MatrixRowMajor<float, 4, 4, 1>::Identity();
}
void Camera::Update() {
projectionView = projection*view;
onUpdate.Invoke();
}
Vector<float, 3> Camera::ToRay(uint32_t x, uint32_t y) {
// // Normalize the screen coordinates
// float mx = (2.0f * x) / sizeX - 1.0f;
// float my = 1.0f - (2.0f * y) / sizeY;
//
// // Construct Ray in Homogeneous Clip Space
// Vector<float, 3> rayOrigin = Vector<float, 3>(mx, my, 0.0f);
// Vector<float, 3> rayEnd = Vector<float, 3>(mx, my, 1.0f);
//
// // Transform Ray to View Space
// MatrixRowMajor<float, 4, 4, 1> invProjection = DirectX::XMMatrixInverse(NULL, projectionMatrix);
// Vector<float, 3> rayOriginView = DirectX::XMVector3TransformCoord(rayOrigin, invProjection);
// Vector<float, 3> rayEndView = DirectX::XMVector3TransformCoord(rayEnd, invProjection);
//
// // Transform Ray to World Space
// DirectX::XMMATRIX viewMatrix = GetViewMatrix(); // Assuming this returns the view matrix
// DirectX::XMMATRIX invView = DirectX::XMMatrixInverse(NULL, viewMatrix);
// Vector<float, 3> rayOriginWorld = DirectX::XMVector3TransformCoord(rayOriginView, invView);
// Vector<float, 3> rayEndWorld = DirectX::XMVector3TransformCoord(rayEndView, invView);
//
// // Compute Ray Direction
// DirectX::XMVECTOR rayDirWorld = DirectX::XMVector3Normalize(DirectX::XMVectorSubtract(rayEndWorld, rayOriginWorld));
//
// return rayDirWorld;
return Vector<float, 3>(0,0,0);
}

View file

@ -1,62 +0,0 @@
/*
Crafter®.Graphics
Copyright (C) 2025 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;
#define STB_TRUETYPE_IMPLEMENTATION
#include "../lib/stb_truetype.h"
module Crafter.Graphics:Font_impl;
import :Font;
import std;
using namespace Crafter;
Font::Font(const std::filesystem::path& fontFilePath) {
std::cout << "Tes3t" << std::endl;
// 1. Load the font file into memory
std::ifstream fontFile("inter.ttf", std::ios::binary | std::ios::ate);
if (!fontFile.is_open()) {
std::cerr << "Failed to open font file\n";
}
std::cout << "Test" << std::endl;
std::streamsize size = fontFile.tellg();
fontFile.seekg(0, std::ios::beg);
fontBuffer.resize(size);
if (!fontFile.read((char*)fontBuffer.data(), size)) {
std::cerr << "Failed to read font file\n";
}
std::cout << size << std::endl;
// 2. Initialize the font
if (!stbtt_InitFont(&font, fontBuffer.data(), stbtt_GetFontOffsetForIndex(fontBuffer.data(), 0))) {
std::cout << "Test6" << std::endl;
std::cerr << "Failed to initialize font.\n";
}
int ascent;
int descent;
int lineGap;
stbtt_GetFontVMetrics(&font, &ascent, &descent, &lineGap);
this->ascent = ascent;
this->descent = descent;
this->lineGap = lineGap;
}

View file

@ -18,21 +18,12 @@ License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/ */
module;
#define STB_IMAGE_IMPLEMENTATION
#include "../lib/stb_image.h"
#include "../lib/stb_truetype.h"
module Crafter.Graphics:UiElement_impl; module Crafter.Graphics:UiElement_impl;
import :UiElement; import :UiElement;
import :Font;
import std; import std;
using namespace Crafter; using namespace Crafter;
UiElement::UiElement(float anchorX, float anchorY, float relativeWidth, float relativeHeight, float anchorOffsetX, float anchorOffsetY, float z, bool ignoreScaling) : anchorX(anchorX), anchorY(anchorY), relativeWidth(relativeWidth), relativeHeight(relativeHeight), anchorOffsetX(anchorOffsetX), anchorOffsetY(anchorOffsetY), z(z), useRelativeSize(true), ignoreScaling(ignoreScaling) {
}
UiElement::UiElement(float anchorX, float anchorY, std::uint32_t bufferWidth, std::uint32_t bufferHeight, std::uint32_t absoluteWidth, std::uint32_t absoluteHeight, float anchorOffsetX, float anchorOffsetY, float z, bool ignoreScaling) : anchorX(anchorX), anchorY(anchorY), bufferWidth(bufferWidth), bufferHeight(bufferHeight), absoluteWidth(absoluteWidth), absoluteHeight(absoluteHeight), anchorOffsetX(anchorOffsetX), anchorOffsetY(anchorOffsetY), z(z), buffer(bufferWidth*bufferHeight), useRelativeSize(false), ignoreScaling(ignoreScaling) { UiElement::UiElement(float anchorX, float anchorY, std::uint32_t bufferWidth, std::uint32_t bufferHeight, std::uint32_t absoluteWidth, std::uint32_t absoluteHeight, float anchorOffsetX, float anchorOffsetY, float z, bool ignoreScaling) : anchorX(anchorX), anchorY(anchorY), bufferWidth(bufferWidth), bufferHeight(bufferHeight), absoluteWidth(absoluteWidth), absoluteHeight(absoluteHeight), anchorOffsetX(anchorOffsetX), anchorOffsetY(anchorOffsetY), z(z), buffer(bufferWidth*bufferHeight), useRelativeSize(false), ignoreScaling(ignoreScaling) {
} }
@ -40,77 +31,3 @@ UiElement::UiElement(float anchorX, float anchorY, std::uint32_t bufferWidth, st
UiElement::UiElement(float anchorX, float anchorY, std::uint32_t bufferWidth, std::uint32_t bufferHeight, float relativeWidth, float relativeHeight, float anchorOffsetX, float anchorOffsetY, float z, bool ignoreScaling) : anchorX(anchorX), anchorY(anchorY), bufferWidth(bufferWidth), bufferHeight(bufferHeight), relativeWidth(relativeWidth), relativeHeight(relativeHeight), anchorOffsetX(anchorOffsetX), anchorOffsetY(anchorOffsetY), z(z), buffer(bufferWidth*bufferHeight), useRelativeSize(true), ignoreScaling(ignoreScaling) { UiElement::UiElement(float anchorX, float anchorY, std::uint32_t bufferWidth, std::uint32_t bufferHeight, float relativeWidth, float relativeHeight, float anchorOffsetX, float anchorOffsetY, float z, bool ignoreScaling) : anchorX(anchorX), anchorY(anchorY), bufferWidth(bufferWidth), bufferHeight(bufferHeight), relativeWidth(relativeWidth), relativeHeight(relativeHeight), anchorOffsetX(anchorOffsetX), anchorOffsetY(anchorOffsetY), z(z), buffer(bufferWidth*bufferHeight), useRelativeSize(true), ignoreScaling(ignoreScaling) {
} }
UiElement::UiElement(const std::filesystem::path& image, float anchorX, float anchorY, float relativeWidth, float relativeHeight, float anchorOffsetX, float anchorOffsetY, float z, bool ignoreScaling) : anchorX(anchorX), anchorY(anchorY), relativeWidth(relativeWidth), relativeHeight(relativeHeight), anchorOffsetX(anchorOffsetX), anchorOffsetY(anchorOffsetY), z(z), useRelativeSize(true), ignoreScaling(ignoreScaling) {
std::filesystem::path abs = std::filesystem::absolute(image);
int xSize;
int ySize;
unsigned char* bgData = stbi_load(abs.string().c_str(), &xSize, &ySize, nullptr, 4);
bufferWidth = xSize;
bufferHeight = ySize;
buffer.resize(bufferWidth*bufferHeight);
for(std::uint_fast32_t x = 0; x < xSize; x++) {
for(std::uint_fast32_t y = 0; y < ySize; y++) {
std::uint_fast32_t idx = (x*ySize+y)*4;
buffer[x*ySize+y].r = bgData[idx];
buffer[x*ySize+y].g = bgData[idx+1];
buffer[x*ySize+y].b = bgData[idx+2];
buffer[x*ySize+y].a = bgData[idx+3];
}
}
}
TextElement::TextElement(const std::string_view text, float size, Pixel_BU8_GU8_RU8_AU8 color, const Font& font, float anchorX, float anchorY, float relativeWidth, float relativeHeight, float anchorOffsetX, float anchorOffsetY, float z, bool ignoreScaling) : UiElement(anchorX, anchorY, relativeWidth, relativeHeight, anchorOffsetX, anchorOffsetY, z, ignoreScaling) {
RenderText(text, size, color, font);
}
void TextElement::RenderText(const std::string_view text, float size, Pixel_BU8_GU8_RU8_AU8 color, const Font& font) {
float scale = stbtt_ScaleForPixelHeight(&font.font, size);
int baseline = (int)(font.ascent * scale);
bufferWidth = 0;
bufferHeight = (int)((font.ascent -font.descent) * scale);
for (const char c : text) {
int advance, lsb;
stbtt_GetCodepointHMetrics(&font.font, c, &advance, &lsb);
bufferWidth += (int)(advance * scale);
}
buffer.resize(bufferWidth * bufferHeight);
int x = 0;
for (std::uint_fast32_t i = 0; i < text.size(); i++) {
int codepoint = text[i];
int ax;
int lsb;
stbtt_GetCodepointHMetrics(&font.font, codepoint, &ax, &lsb);
int c_x1, c_y1, c_x2, c_y2;
stbtt_GetCodepointBitmapBox(&font.font, codepoint, scale, scale, &c_x1, &c_y1, &c_x2, &c_y2);
int w = c_x2 - c_x1;
int h = c_y2 - c_y1;
std::vector<unsigned char> bitmap(w * h);
stbtt_MakeCodepointBitmap(&font.font, bitmap.data(), w, h, w, scale, scale, codepoint);
for (int j = 0; j < h; j++) {
for (int i = 0; i < w; i++) {
int bufIndex = ((baseline + j + c_y1) * bufferWidth + (x + i + c_x1));
unsigned char val = bitmap[j * w + i];
buffer[bufIndex] = {color.r, color.g, color.b, val};
}
}
x += (int)(ax * scale);
if (i + 1 < text.size()) {
x += (int)stbtt_GetCodepointKernAdvance(&font.font, codepoint, text[i+1] * scale);
}
}
}

View file

@ -0,0 +1,305 @@
/*
Crafter®.Graphics
Copyright (C) 2025 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;
#include <vulkan/vulkan.h>
#include <vulkan/vulkan_wayland.h>
#include <iostream>
#include <exception>
// #include <vulkan/vk_enum_string_helper.h>
#include <cstring>
#include <print>
#include <cstdio>
#include "../../lib/VulkanInitializers.hpp"
#define GET_EXTENSION_FUNCTION(_id) ((PFN_##_id)(vkGetInstanceProcAddr(instance, #_id)))
module Crafter.Graphics:VulkanDevice_impl;
import :VulkanDevice;
using namespace Crafter;
const char* const instanceExtensionNames[] = {
"VK_EXT_debug_utils",
"VK_KHR_surface",
"VK_KHR_wayland_surface"
};
const char* const deviceExtensionNames[] = {
"VK_KHR_swapchain",
"VK_KHR_spirv_1_4",
"VK_EXT_mesh_shader",
"VK_KHR_shader_float_controls",
"VK_KHR_dynamic_rendering"
};
const char* const layerNames[] = {
"VK_LAYER_KHRONOS_validation"
};
void VulkanDevice::CHECK_VK_RESULT(VkResult result) {
if (result != VK_SUCCESS)
{
throw std::runtime_error("vk error");
}
}
VkBool32 onError(VkDebugUtilsMessageSeverityFlagBitsEXT severity, VkDebugUtilsMessageTypeFlagsEXT type, const VkDebugUtilsMessengerCallbackDataEXT* callbackData, void* userData)
{
printf("Vulkan ");
switch (type)
{
case VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT :
printf("general ");
break;
case VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT :
printf("validation ");
break;
case VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT :
printf("performance ");
break;
}
switch (severity)
{
case VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT :
printf("(verbose): ");
break;
default :
case VK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT :
printf("(info): ");
break;
case VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT :
printf("(warning): ");
break;
case VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT :
printf("(error): ");
break;
}
printf("%s\n", callbackData->pMessage);
return 0;
}
void VulkanDevice::CreateDevice() {
VkApplicationInfo app{VK_STRUCTURE_TYPE_APPLICATION_INFO};
app.pApplicationName = "";
app.pEngineName = "Crafter.Graphics";
app.apiVersion = VK_MAKE_VERSION(1, 4, 0);
VkInstanceCreateInfo instanceCreateInfo = {};
instanceCreateInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO;
instanceCreateInfo.pApplicationInfo = &app;
instanceCreateInfo.enabledExtensionCount = sizeof(instanceExtensionNames) / sizeof(const char*);
instanceCreateInfo.ppEnabledExtensionNames = instanceExtensionNames;
size_t foundInstanceLayers = 0;
std::uint32_t instanceLayerCount;
CHECK_VK_RESULT(vkEnumerateInstanceLayerProperties(&instanceLayerCount, NULL));
std::vector<VkLayerProperties> instanceLayerProperties(instanceLayerCount);
CHECK_VK_RESULT(vkEnumerateInstanceLayerProperties(&instanceLayerCount, instanceLayerProperties.data()));
for (uint32_t i = 0; i < instanceLayerCount; i++)
{
for (size_t j = 0; j < sizeof(layerNames) / sizeof(const char*); j++)
{
if (std::strcmp(instanceLayerProperties[i].layerName, layerNames[j]) == 0)
{
foundInstanceLayers++;
}
}
}
if (foundInstanceLayers >= sizeof(layerNames) / sizeof(const char*))
{
instanceCreateInfo.enabledLayerCount = sizeof(layerNames) / sizeof(const char*);
instanceCreateInfo.ppEnabledLayerNames = layerNames;
}
CHECK_VK_RESULT(vkCreateInstance(&instanceCreateInfo, NULL, &instance));
VkDebugUtilsMessengerCreateInfoEXT debugUtilsMessengerCreateInfo = {};
debugUtilsMessengerCreateInfo.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT;
debugUtilsMessengerCreateInfo.messageSeverity = VK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT;
debugUtilsMessengerCreateInfo.messageType = VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT;
debugUtilsMessengerCreateInfo.pfnUserCallback = onError;
CHECK_VK_RESULT(GET_EXTENSION_FUNCTION(vkCreateDebugUtilsMessengerEXT)(instance, &debugUtilsMessengerCreateInfo, NULL, &debugMessenger));
uint32_t physDeviceCount;
vkEnumeratePhysicalDevices(instance, &physDeviceCount, NULL);
std::vector<VkPhysicalDevice> physDevices(physDeviceCount);
vkEnumeratePhysicalDevices(instance, &physDeviceCount, physDevices.data());
uint32_t bestScore = 0;
for (uint32_t i = 0; i < physDeviceCount; i++)
{
VkPhysicalDevice device = physDevices[i];
VkPhysicalDeviceProperties properties;
vkGetPhysicalDeviceProperties(device, &properties);
uint32_t score;
switch (properties.deviceType)
{
default :
continue;
case VK_PHYSICAL_DEVICE_TYPE_OTHER :
score = 1;
break;
case VK_PHYSICAL_DEVICE_TYPE_INTEGRATED_GPU :
score = 4;
break;
case VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU :
score = 5;
break;
case VK_PHYSICAL_DEVICE_TYPE_VIRTUAL_GPU :
score = 3;
break;
case VK_PHYSICAL_DEVICE_TYPE_CPU :
score = 2;
break;
}
if (score > bestScore)
{
physDevice = device;
bestScore = score;
}
}
uint32_t queueFamilyCount;
vkGetPhysicalDeviceQueueFamilyProperties(physDevice, &queueFamilyCount, NULL);
std::vector<VkQueueFamilyProperties> queueFamilies(queueFamilyCount);
vkGetPhysicalDeviceQueueFamilyProperties(physDevice, &queueFamilyCount, queueFamilies.data());
for (uint32_t i = 0; i < queueFamilyCount; i++)
{
if (queueFamilies[i].queueFlags & VK_QUEUE_GRAPHICS_BIT)
{
queueFamilyIndex = i;
break;
}
}
float priority = 1;
VkDeviceQueueCreateInfo queueCreateInfo = {};
queueCreateInfo.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO;
queueCreateInfo.queueFamilyIndex = queueFamilyIndex;
queueCreateInfo.queueCount = 1;
queueCreateInfo.pQueuePriorities = &priority;
VkPhysicalDeviceDynamicRenderingFeaturesKHR dynamicRenderingFeature = {VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DYNAMIC_RENDERING_FEATURES_KHR};
dynamicRenderingFeature.dynamicRendering = VK_TRUE;
VkPhysicalDeviceMeshShaderFeaturesEXT ext_feature = {VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MESH_SHADER_FEATURES_EXT};
ext_feature.meshShader = VK_TRUE;
ext_feature.pNext = &dynamicRenderingFeature;
VkPhysicalDeviceFeatures2 physical_features2 = {VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2};
physical_features2.pNext = &ext_feature;
VkDeviceCreateInfo deviceCreateInfo = {};
deviceCreateInfo.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO;
deviceCreateInfo.queueCreateInfoCount = 1;
deviceCreateInfo.pQueueCreateInfos = &queueCreateInfo;
deviceCreateInfo.enabledExtensionCount = sizeof(deviceExtensionNames) / sizeof(const char*);
deviceCreateInfo.ppEnabledExtensionNames = deviceExtensionNames;
deviceCreateInfo.pNext = &physical_features2;
uint32_t deviceLayerCount;
CHECK_VK_RESULT(vkEnumerateDeviceLayerProperties(physDevice, &deviceLayerCount, NULL));
std::vector<VkLayerProperties> deviceLayerProperties(deviceLayerCount);
CHECK_VK_RESULT(vkEnumerateDeviceLayerProperties(physDevice, &deviceLayerCount, deviceLayerProperties.data()));
size_t foundDeviceLayers = 0;
for (uint32_t i = 0; i < deviceLayerCount; i++)
{
for (size_t j = 0; j < sizeof(layerNames) / sizeof(const char*); j++)
{
if (strcmp(deviceLayerProperties[i].layerName, layerNames[j]) == 0)
{
foundDeviceLayers++;
}
}
}
if (foundDeviceLayers >= sizeof(layerNames) / sizeof(const char*))
{
deviceCreateInfo.enabledLayerCount = sizeof(layerNames) / sizeof(const char*);
deviceCreateInfo.ppEnabledLayerNames = layerNames;
}
CHECK_VK_RESULT(vkCreateDevice(physDevice, &deviceCreateInfo, NULL, &device));
vkGetDeviceQueue(device, queueFamilyIndex, 0, &queue);
VkCommandPoolCreateInfo commandPoolcreateInfo = {};
commandPoolcreateInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO;
commandPoolcreateInfo.queueFamilyIndex = queueFamilyIndex;
commandPoolcreateInfo.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT;
CHECK_VK_RESULT(vkCreateCommandPool(device, &commandPoolcreateInfo, NULL, &commandPool));
vkGetPhysicalDeviceMemoryProperties(physDevice, &memoryProperties);
std::vector<VkFormat> formatList = {
VK_FORMAT_D32_SFLOAT,
};
for (auto& format : formatList) {
VkFormatProperties formatProps;
vkGetPhysicalDeviceFormatProperties(physDevice, format, &formatProps);
if (formatProps.optimalTilingFeatures)
{
depthFormat = format;
break;
}
}
vkCmdDrawMeshTasksEXTProc = reinterpret_cast<PFN_vkCmdDrawMeshTasksEXT>(vkGetDeviceProcAddr(device, "vkCmdDrawMeshTasksEXT"));
vkCmdBeginRenderingKHRProc = reinterpret_cast<PFN_vkCmdBeginRenderingKHR>(vkGetInstanceProcAddr(instance, "vkCmdBeginRenderingKHR"));
vkCmdEndRenderingKHRProc = reinterpret_cast<PFN_vkCmdEndRenderingKHR>(vkGetInstanceProcAddr(instance, "vkCmdEndRenderingKHR"));
}
std::uint32_t VulkanDevice::GetMemoryType(uint32_t typeBits, VkMemoryPropertyFlags properties) {
for (uint32_t i = 0; i < memoryProperties.memoryTypeCount; i++)
{
if ((typeBits & 1) == 1)
{
if ((memoryProperties.memoryTypes[i].propertyFlags & properties) == properties)
{
return i;
}
}
typeBits >>= 1;
}
throw std::runtime_error("Could not find a matching memory type");
}

View file

@ -21,182 +21,15 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
module; module;
#include <cstdint> #include <cstdint>
#include <fcntl.h> #include <string>
#include <linux/input.h>
#include <unistd.h>
#include <wayland-cursor.h>
#include <vulkan/vulkan.h>
#include <vulkan/vulkan_wayland.h>
#include <wayland-client.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <unistd.h>
#include <wayland-client.h>
#include <wayland-client-protocol.h>
#include <linux/input-event-codes.h>
#include <xkbcommon/xkbcommon.h>
#include "../lib/xdg-shell-client-protocol.h"
#include "../lib/wayland-xdg-decoration-unstable-v1-client-protocol.h"
#include <string.h>
#include <errno.h>
#include <fcntl.h>
#include <linux/input.h>
#include <sys/mman.h>
#include <unistd.h>
#include <wayland-cursor.h>
#include <xkbcommon/xkbcommon.h>
#include <vulkan/vulkan.h>
#include <vulkan/vulkan_wayland.h>
#include <wayland-client.h>
#include <errno.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <time.h>
#include <unistd.h>
#include <string.h>
#include <print>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <unistd.h>
#include <wayland-client.h>
#include <wayland-client-protocol.h>
#include <linux/input-event-codes.h>
module Crafter.Graphics:Window_impl; module Crafter.Graphics:Window_impl;
import :Window; import :Window;
import std;
import Crafter.Event; import Crafter.Event;
using namespace Crafter; using namespace Crafter;
void 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 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 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;
}
int counter = 0;
void ScaleBitmapR8G8B8(Pixel_BU8_GU8_RU8_AU8* dst, const Pixel_BU8_GU8_RU8_AU8* src, std::uint32_t srcWidth, std::uint32_t srcHeight, std::uint32_t dstWidth, std::uint32_t dstHeight) {
for (std::uint32_t y = 0; y < dstHeight; y++) {
std::uint32_t srcY = y * srcHeight / dstHeight;
for (std::uint32_t x = 0; x < dstWidth; x++) {
std::uint32_t srcX = x * srcWidth / dstWidth;
const Pixel_BU8_GU8_RU8_AU8* srcPixel = src + (srcY * srcWidth + srcX);
Pixel_BU8_GU8_RU8_AU8* dstPixel = dst + (y * dstWidth + x);
dstPixel[0] = srcPixel[0];
}
}
}
Window::Window(std::string name, std::uint32_t width, std::uint32_t height) : name(name), width(width), height(height) { Window::Window(std::string name, std::uint32_t width, std::uint32_t height) : name(name), width(width), height(height) {
display = wl_display_connect(NULL);
if (display == NULL) {
std::cerr << "failed to create display" << std::endl;
}
wl_registry* registry = wl_display_get_registry(display);
wl_registry_add_listener(registry, &registry_listener, this);
if (wl_display_roundtrip(display) == -1) {
exit(EXIT_FAILURE);
}
if (shm == NULL || compositor == NULL || xdgWmBase == NULL) {
std::cerr << "no wl_shm, wl_compositor or xdg_wm_base support" << std::endl;
exit(EXIT_FAILURE);
}
surface = wl_compositor_create_surface(compositor);
xdgSurface = xdg_wm_base_get_xdg_surface(xdgWmBase, surface);
xdgToplevel = xdg_surface_get_toplevel(xdgSurface);
cb = wl_surface_frame(surface);
wl_callback_add_listener(cb, &surface_frame_listener, this);
xdg_surface_add_listener(xdgSurface, &xdg_surface_listener, this);
xdg_toplevel_add_listener(xdgToplevel, &xdg_toplevel_listener, this);
wl_surface_commit(surface);
xdg_toplevel_set_title(xdgToplevel, name.c_str());
while (wl_display_dispatch(display) != -1 && !configured) {}
wl_surface_commit(surface);
zxdg_toplevel_decoration_v1* decoration = zxdg_decoration_manager_v1_get_toplevel_decoration(manager, xdgToplevel);
zxdg_toplevel_decoration_v1_set_mode(decoration, ZXDG_TOPLEVEL_DECORATION_V1_MODE_SERVER_SIDE);
// 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) {
fprintf(stderr, "creating a buffer file for %d B failed: %m\n", size);
}
// Map the shared memory file
framebuffer = reinterpret_cast<Pixel_BU8_GU8_RU8_AU8*>(mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0));
if (framebuffer == MAP_FAILED) {
fprintf(stderr, "mmap failed: %m\n");
close(fd);
}
// Create a wl_buffer from our shared memory file descriptor
wl_shm_pool *pool = wl_shm_create_pool(shm, fd, size);
buffer = wl_shm_pool_create_buffer(pool, 0, width, height, stride, WL_SHM_FORMAT_ARGB8888);
wl_shm_pool_destroy(pool);
// Now that we've mapped the file and created the wl_buffer, we no longer
// need to keep file descriptor opened
close(fd);
if (buffer == NULL) {
exit(EXIT_FAILURE);
}
wl_surface_attach(surface, buffer, 0, 0);
wl_surface_commit(surface);
} }
ScaleData Window::ScaleElement(const UiElement& element) { ScaleData Window::ScaleElement(const UiElement& element) {
@ -222,384 +55,3 @@ ScaleData Window::ScaleElement(const UiElement& element) {
data.y = (element.anchorY*height)-(element.anchorOffsetY*data.height); data.y = (element.anchorY*height)-(element.anchorOffsetY*data.height);
return data; return data;
} }
ScaleData ScaleElement(const UiElement& element, const ScaleData& parent, const Window* window) {
ScaleData data;
if(element.ignoreScaling) {
if(element.useRelativeSize) {
data.width = element.relativeWidth*parent.width;
data.height = element.relativeHeight*parent.height;
} else {
data.width = element.absoluteWidth;
data.height = element.absoluteHeight;
}
} else {
if(element.useRelativeSize) {
data.width = element.relativeWidth*parent.width*window->scale;
data.height = element.relativeHeight*parent.height*window->scale;
} else {
data.width = element.absoluteWidth*window->scale;
data.height = element.absoluteHeight*window->scale;
}
}
data.x = ((element.anchorX*parent.width)-(element.anchorOffsetX*data.width))+parent.x;
data.y = ((element.anchorY*parent.height)-(element.anchorOffsetY*data.height))+parent.y;
return data;
}
static void xdg_wm_base_handle_ping(void* data, xdg_wm_base* xdg_wm_base, std::uint32_t serial) {
xdg_wm_base_pong(xdg_wm_base, serial);
}
xdg_wm_base_listener xdgWmBaseListener = {
.ping = xdg_wm_base_handle_ping,
};
void RenderElements(Window* window, const UiElement& parent, const ScaleData& parentScale) {
std::vector<UiElement*> drawOrder;
drawOrder.reserve(parent.children.size());
for (const std::unique_ptr<UiElement>& e : parent.children) drawOrder.push_back(e.get());
std::sort(drawOrder.begin(), drawOrder.end(), [](UiElement* a, UiElement* b){ return a->z < b->z; });
for(const UiElement* element : drawOrder) {
ScaleData data = ScaleElement(*element, parentScale, window);
std::vector<Pixel_BU8_GU8_RU8_AU8> scaled(data.width*data.height);
ScaleBitmapR8G8B8(scaled.data(), element->buffer.data(), element->bufferWidth, element->bufferHeight, data.width, data.height);
for (std::int32_t x = data.x; x - data.x < data.width; x++) {
for (std::int32_t y = data.y; y - data.y < data.height; y++) {
if (x >= 0 && x < window->width && y >= 0 && y < window->height) {
Pixel_BU8_GU8_RU8_AU8& dst = window->framebuffer[y * window->width + x];
const Pixel_BU8_GU8_RU8_AU8& src = scaled[(y - data.y) * data.width + (x - data.x)];
float srcA = src.a / 255.0f;
float dstA = dst.a / 255.0f;
float outA = srcA + dstA * (1.0f - srcA);
if (outA > 0.0f) {
dst.r = static_cast<uint8_t>((src.r * srcA + dst.r * dstA * (1.0f - srcA)) / outA);
dst.g = static_cast<uint8_t>((src.g * srcA + dst.g * dstA * (1.0f - srcA)) / outA);
dst.b = static_cast<uint8_t>((src.b * srcA + dst.b * dstA * (1.0f - srcA)) / outA);
dst.a = static_cast<uint8_t>(outA * 255);
}
}
}
}
RenderElements(window, *element, data);
}
}
void Window::wl_surface_frame_done(void* data, struct wl_callback *cb, uint32_t time)
{
wl_callback_destroy(cb);
Window* window = reinterpret_cast<Window*>(data);
cb = wl_surface_frame(window->surface);
wl_callback_add_listener(cb, &Window::surface_frame_listener, window);
std::vector<UiElement*> drawOrder;
drawOrder.reserve(window->elements.size());
for (UiElement& e : window->elements) drawOrder.push_back(&e);
std::sort(drawOrder.begin(), drawOrder.end(), [](UiElement* a, UiElement* b){ return a->z < b->z; });
for(const UiElement* element : drawOrder) {
ScaleData data = window->ScaleElement(*element);
std::vector<Pixel_BU8_GU8_RU8_AU8> scaled(data.width*data.height);
ScaleBitmapR8G8B8(scaled.data(), element->buffer.data(), element->bufferWidth, element->bufferHeight, data.width, data.height);
for (std::int32_t x = data.x; x - data.x < data.width; x++) {
for (std::int32_t y = data.y; y - data.y < data.height; y++) {
if (x >= 0 && x < window->width && y >= 0 && y < window->height) {
Pixel_BU8_GU8_RU8_AU8& dst = window->framebuffer[y * window->width + x];
const Pixel_BU8_GU8_RU8_AU8& src = scaled[(y - data.y) * data.width + (x - data.x)];
float srcA = src.a / 255.0f;
float dstA = dst.a / 255.0f;
float outA = srcA + dstA * (1.0f - srcA);
if (outA > 0.0f) {
dst.r = static_cast<uint8_t>((src.r * srcA + dst.r * dstA * (1.0f - srcA)) / outA);
dst.g = static_cast<uint8_t>((src.g * srcA + dst.g * dstA * (1.0f - srcA)) / outA);
dst.b = static_cast<uint8_t>((src.b * srcA + dst.b * dstA * (1.0f - srcA)) / outA);
dst.a = static_cast<uint8_t>(outA * 255);
}
}
}
}
RenderElements(window, *element, data);
}
wl_surface_attach(window->surface, window->buffer, 0, 0);
wl_surface_damage(window->surface, 0, 0, window->width, window->height);
wl_surface_commit(window->surface);
}
wl_callback_listener Window::surface_frame_listener = {
.done = wl_surface_frame_done,
};
void Window::pointer_handle_button(void* data, wl_pointer* pointer, std::uint32_t serial, std::uint32_t time, std::uint32_t button, std::uint32_t state) {
Window* window = reinterpret_cast<Window*>(data);
if (button == BTN_LEFT) {
if(state == WL_POINTER_BUTTON_STATE_PRESSED) {
window->mouseLeftHeld = true;
window->onMouseLeftClick.Invoke(window->currentMousePos);
for(UiElement& element : window->elements) {
ScaleData data = window->ScaleElement(element);
if(window->currentMousePos.x >= data.x && window->currentMousePos.x <= data.x+data.width && window->currentMousePos.y > data.y && window->currentMousePos.y < data.y+data.height) {
element.onMouseLeftClick.Invoke({window->currentMousePos.x-data.x, window->currentMousePos.y-data.y});
}
}
} else {
window->mouseLeftHeld = false;
window->onMouseLeftRelease.Invoke(window->currentMousePos);
for(UiElement& element : window->elements) {
ScaleData data = window->ScaleElement(element);
if(window->currentMousePos.x >= data.x && window->currentMousePos.x <= data.x+data.width && window->currentMousePos.y > data.y && window->currentMousePos.y < data.y+data.height) {
element.onMouseLeftRelease.Invoke({window->currentMousePos.x-data.x, window->currentMousePos.y-data.y});
}
}
}
} else if(button == BTN_RIGHT){
if(state == WL_POINTER_BUTTON_STATE_PRESSED) {
window->mouseRightHeld = true;
window->onMouseRightClick.Invoke(window->currentMousePos);
for(UiElement& element : window->elements) {
ScaleData data = window->ScaleElement(element);
if(window->currentMousePos.x >= data.x && window->currentMousePos.x <= data.x+data.width && window->currentMousePos.y > data.y && window->currentMousePos.y < data.y+data.height) {
element.onMouseRightClick.Invoke({window->currentMousePos.x-data.x, window->currentMousePos.y-data.y});
}
}
} else {
window->mouseRightHeld = true;
window->onMouseRightRelease.Invoke(window->currentMousePos);
for(UiElement& element : window->elements) {
ScaleData data = window->ScaleElement(element);
if(window->currentMousePos.x >= data.x && window->currentMousePos.x <= data.x+data.width && window->currentMousePos.y > data.y && window->currentMousePos.y < data.y+data.height) {
element.onMouseRightRelease.Invoke({window->currentMousePos.x-data.x, window->currentMousePos.y-data.y});
}
}
}
}
}
void Window::PointerListenerHandleMotion(void* data, wl_pointer* wl_pointer, uint time, wl_fixed_t surface_x, wl_fixed_t surface_y) {
Window* window = reinterpret_cast<Window*>(data);
MousePoint pos = {wl_fixed_to_double(surface_x), wl_fixed_to_double(surface_y)};
window->lastMousePos = window->currentMousePos;
window->currentMousePos = pos;
window->mouseDelta = {window->currentMousePos.x-window->lastMousePos.x, window->currentMousePos.y-window->lastMousePos.y};
window->onMouseMove.Invoke({window->lastMousePos, window->currentMousePos, window->mouseDelta});
for(UiElement& element : window->elements) {
ScaleData data = window->ScaleElement(element);
if(window->currentMousePos.x >= data.x && window->currentMousePos.x <= data.x+data.width && window->currentMousePos.y > data.y && window->currentMousePos.y < data.y+data.height) {
element.onMouseMove.Invoke({window->currentMousePos.x-data.x, window->currentMousePos.y-data.y});
if(!(window->lastMousePos.x >= data.x && window->lastMousePos.x <= data.x+data.width && window->lastMousePos.y > data.y && window->lastMousePos.y < data.y+data.height)) {
element.onMouseEnter.Invoke({window->currentMousePos.x-data.x, window->currentMousePos.y-data.y});
}
} else if(window->lastMousePos.x >= data.x && window->lastMousePos.x <= data.x+data.width && window->lastMousePos.y > data.y && window->lastMousePos.y < data.y+data.height) {
element.onMouseLeave.Invoke({window->currentMousePos.x-data.x, window->currentMousePos.y-data.y});
}
}
}
void Window::PointerListenerHandleEnter(void* data, wl_pointer* wl_pointer, uint serial, wl_surface* surface, wl_fixed_t surface_x, wl_fixed_t surface_y) {
Window* window = reinterpret_cast<Window*>(data);
window->onMouseEnter.Invoke({window->lastMousePos, window->currentMousePos, window->mouseDelta});
}
void Window::PointerListenerHandleLeave(void* data, wl_pointer*, std::uint32_t, wl_surface*) {
Window* window = reinterpret_cast<Window*>(data);
window->onMouseEnter.Invoke({window->lastMousePos, window->currentMousePos, window->mouseDelta});
}
void Window::PointerListenerHandleAxis(void*, wl_pointer*, std::uint32_t, std::uint32_t, wl_fixed_t value) {
}
wl_pointer_listener Window::pointer_listener = {
.enter = Window::PointerListenerHandleEnter,
.leave = Window::PointerListenerHandleLeave,
.motion = Window::PointerListenerHandleMotion,
.button = Window::pointer_handle_button,
.axis = Window::PointerListenerHandleAxis,
};
xkb_keymap* xkb_keymap;
xkb_context* xkb_context = xkb_context_new(XKB_CONTEXT_NO_FLAGS);
xkb_state* xkb_state;
void keyboard_keymap(void *data, wl_keyboard *keyboard, uint32_t format, int fd, uint32_t size) {
if (format != WL_KEYBOARD_KEYMAP_FORMAT_XKB_V1) {
close(fd);
fprintf(stderr, "Unsupported keymap format\n");
return;
}
void *map = mmap(NULL, size, PROT_READ, MAP_SHARED, fd, 0);
if (map == MAP_FAILED) {
close(fd);
perror("mmap");
return;
}
xkb_context = xkb_context_new(XKB_CONTEXT_NO_FLAGS);
xkb_keymap = xkb_keymap_new_from_string(xkb_context, (const char *)map, XKB_KEYMAP_FORMAT_TEXT_V1,XKB_KEYMAP_COMPILE_NO_FLAGS);
munmap(map, size);
close(fd);
xkb_state = xkb_state_new(xkb_keymap);
}
void keyboard_enter(void *data, wl_keyboard *keyboard, uint32_t serial, wl_surface *surface, wl_array *keys) {
}
void keyboard_leave(void *data, wl_keyboard *keyboard, uint32_t serial, wl_surface *surface) {
}
void keyboard_key(void *data, wl_keyboard *keyboard, uint32_t serial, uint32_t time, uint32_t key, uint32_t state) {
if (!xkb_state) {
return;
}
Window* window = reinterpret_cast<Window*>(data);
xkb_keycode_t keycode = key + 8;
xkb_keysym_t keysym = xkb_state_key_get_one_sym(xkb_state, keycode);
char utf8[8] = {0};
int len = xkb_keysym_to_utf8(keysym, utf8, sizeof(utf8));
if (len != 0) {
char keypress = utf8[0];
if(state == WL_KEYBOARD_KEY_STATE_PRESSED) {
if(window->heldkeys[keypress]) {
window->onKeyHold[keypress].Invoke();
window->onAnyKeyHold.Invoke(keypress);
} else{
window->heldkeys[keypress] = true;
window->onKeyDown[keypress].Invoke();
window->onAnyKeyDown.Invoke(keypress);
}
} else{
window->heldkeys[keypress] = false;
window->onKeyUp[keypress].Invoke();
window->onAnyKeyUp.Invoke(keypress);
}
} else {
// // fallback for keys like Return, Escape, etc.
// char name[64];
// if (xkb_keysym_get_name(keysym, name, sizeof(name)) > 0) {
// printf("Key %s pressed (non-printable or multi-char)\n", name);
// }
}
}
void keyboard_modifiers(void *data, wl_keyboard *keyboard, uint32_t serial, uint32_t mods_depressed, uint32_t mods_latched, uint32_t mods_locked, uint32_t group) {
}
void keyboard_repeat_info(void *data, wl_keyboard *keyboard, int32_t rate, int32_t delay) {
}
wl_keyboard_listener Window::keyboard_listener = {
.keymap = keyboard_keymap,
.enter = keyboard_enter,
.leave = keyboard_leave,
.key = keyboard_key,
.modifiers = keyboard_modifiers,
.repeat_info = keyboard_repeat_info,
};
void Window::seat_handle_capabilities(void* data, wl_seat* seat, uint32_t capabilities) {
Window* window = reinterpret_cast<Window*>(data);
window->seat = seat;
if (capabilities & WL_SEAT_CAPABILITY_POINTER) {
wl_pointer* pointer = wl_seat_get_pointer(seat);
wl_pointer_add_listener(pointer, &pointer_listener, window);
}
if (capabilities & WL_SEAT_CAPABILITY_KEYBOARD) {
wl_keyboard* keyboard = wl_seat_get_keyboard(seat);
wl_keyboard_add_listener(keyboard, &keyboard_listener, window);
}
}
wl_seat_listener Window::seat_listener = {
.capabilities = seat_handle_capabilities,
};
void Window::handle_global(void *data, wl_registry *registry, std::uint32_t name, const char *interface, std::uint32_t version) {
Window* window = reinterpret_cast<Window*>(data);
if (strcmp(interface, wl_shm_interface.name) == 0) {
window->shm = reinterpret_cast<wl_shm*>(wl_registry_bind(registry, name, &wl_shm_interface, 1));
} else if (strcmp(interface, wl_seat_interface.name) == 0) {
wl_seat* seat = reinterpret_cast<wl_seat*>(wl_registry_bind(registry, name, &wl_seat_interface, 1));
wl_seat_add_listener(seat, &seat_listener, window);
} else if (compositor == NULL && strcmp(interface, wl_compositor_interface.name) == 0) {
compositor = reinterpret_cast<wl_compositor*>(wl_registry_bind(registry, name, &wl_compositor_interface, 1));
} else if (strcmp(interface, xdg_wm_base_interface.name) == 0) {
window->xdgWmBase = reinterpret_cast<xdg_wm_base*>(wl_registry_bind(registry, name, &xdg_wm_base_interface, 1));
xdg_wm_base_add_listener(window->xdgWmBase, &xdgWmBaseListener, NULL);
} else if (strcmp(interface, zxdg_decoration_manager_v1_interface.name) == 0) {
window->manager = reinterpret_cast<zxdg_decoration_manager_v1*>(wl_registry_bind(registry, name, &zxdg_decoration_manager_v1_interface, 1));
}
}
static void handle_global_remove(void* data, wl_registry* registry, uint32_t name) {
}
wl_registry_listener Window::registry_listener = {
.global = Window::handle_global,
.global_remove = handle_global_remove,
};
static void noop5(void*, xdg_toplevel*, std::int32_t, std::int32_t, wl_array*){
}
void Window::xdg_toplevel_handle_close(void* data, xdg_toplevel*) {
Window* window = reinterpret_cast<Window*>(data);
window->onClose.Invoke();
window->open = false;
}
xdg_toplevel_listener Window::xdg_toplevel_listener = {
.configure = noop5,
.close = Window::xdg_toplevel_handle_close,
};
void Window::xdg_surface_handle_configure(void* data, xdg_surface* xdg_surface, std::uint32_t serial) {
Window* window = reinterpret_cast<Window*>(data);
// The compositor configures our surface, acknowledge the configure event
xdg_surface_ack_configure(xdg_surface, serial);
if (window->configured) {
// If this isn't the first configure event we've received, we already
// have a buffer attached, so no need to do anything. Commit the
// surface to apply the configure acknowledgement.
wl_surface_commit(window->surface);
}
window->configured = true;
}
xdg_surface_listener Window::xdg_surface_listener = {
.configure = xdg_surface_handle_configure,
};
Window::~Window() {
xdg_toplevel_destroy(xdgToplevel);
xdg_surface_destroy(xdgSurface);
wl_surface_destroy(surface);
wl_buffer_destroy(buffer);
}
void Window::StartSync() {
while (open && wl_display_dispatch(display) != -1) {
}
}

View file

@ -0,0 +1,357 @@
/*
Crafter®.Graphics
Copyright (C) 2025 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;
#include <errno.h>
#include <fcntl.h>
#include <linux/input.h>
#include <unistd.h>
#include <wayland-cursor.h>
#include <vulkan/vulkan.h>
#include <vulkan/vulkan_wayland.h>
#include <wayland-client.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <unistd.h>
#include <wayland-client.h>
#include <wayland-client-protocol.h>
#include <linux/input-event-codes.h>
#include <xkbcommon/xkbcommon.h>
#include "../lib/xdg-shell-client-protocol.h"
#include "../lib/wayland-xdg-decoration-unstable-v1-client-protocol.h"
#include <string.h>
module Crafter.Graphics:WindowWayland_impl;
import :WindowWayland;
import std;
import Crafter.Event;
using namespace Crafter;
static void xdg_wm_base_handle_ping(void* data, xdg_wm_base* xdg_wm_base, std::uint32_t serial) {
xdg_wm_base_pong(xdg_wm_base, serial);
}
xdg_wm_base_listener xdgWmBaseListener = {
.ping = xdg_wm_base_handle_ping,
};
void WindowWayland::pointer_handle_button(void* data, wl_pointer* pointer, std::uint32_t serial, std::uint32_t time, std::uint32_t button, std::uint32_t state) {
WindowWayland* window = reinterpret_cast<WindowWayland*>(data);
if (button == BTN_LEFT) {
if(state == WL_POINTER_BUTTON_STATE_PRESSED) {
window->mouseLeftHeld = true;
window->onMouseLeftClick.Invoke(window->currentMousePos);
for(UiElement& element : window->elements) {
ScaleData data = window->ScaleElement(element);
if(window->currentMousePos.x >= data.x && window->currentMousePos.x <= data.x+data.width && window->currentMousePos.y > data.y && window->currentMousePos.y < data.y+data.height) {
element.onMouseLeftClick.Invoke({window->currentMousePos.x-data.x, window->currentMousePos.y-data.y});
}
}
} else {
window->mouseLeftHeld = false;
window->onMouseLeftRelease.Invoke(window->currentMousePos);
for(UiElement& element : window->elements) {
ScaleData data = window->ScaleElement(element);
if(window->currentMousePos.x >= data.x && window->currentMousePos.x <= data.x+data.width && window->currentMousePos.y > data.y && window->currentMousePos.y < data.y+data.height) {
element.onMouseLeftRelease.Invoke({window->currentMousePos.x-data.x, window->currentMousePos.y-data.y});
}
}
}
} else if(button == BTN_RIGHT){
if(state == WL_POINTER_BUTTON_STATE_PRESSED) {
window->mouseRightHeld = true;
window->onMouseRightClick.Invoke(window->currentMousePos);
for(UiElement& element : window->elements) {
ScaleData data = window->ScaleElement(element);
if(window->currentMousePos.x >= data.x && window->currentMousePos.x <= data.x+data.width && window->currentMousePos.y > data.y && window->currentMousePos.y < data.y+data.height) {
element.onMouseRightClick.Invoke({window->currentMousePos.x-data.x, window->currentMousePos.y-data.y});
}
}
} else {
window->mouseRightHeld = true;
window->onMouseRightRelease.Invoke(window->currentMousePos);
for(UiElement& element : window->elements) {
ScaleData data = window->ScaleElement(element);
if(window->currentMousePos.x >= data.x && window->currentMousePos.x <= data.x+data.width && window->currentMousePos.y > data.y && window->currentMousePos.y < data.y+data.height) {
element.onMouseRightRelease.Invoke({window->currentMousePos.x-data.x, window->currentMousePos.y-data.y});
}
}
}
}
}
void WindowWayland::PointerListenerHandleMotion(void* data, wl_pointer* wl_pointer, uint time, wl_fixed_t surface_x, wl_fixed_t surface_y) {
WindowWayland* window = reinterpret_cast<WindowWayland*>(data);
MousePoint pos = {wl_fixed_to_double(surface_x), wl_fixed_to_double(surface_y)};
window->lastMousePos = window->currentMousePos;
window->currentMousePos = pos;
window->mouseDelta = {window->currentMousePos.x-window->lastMousePos.x, window->currentMousePos.y-window->lastMousePos.y};
window->onMouseMove.Invoke({window->lastMousePos, window->currentMousePos, window->mouseDelta});
for(UiElement& element : window->elements) {
ScaleData data = window->ScaleElement(element);
if(window->currentMousePos.x >= data.x && window->currentMousePos.x <= data.x+data.width && window->currentMousePos.y > data.y && window->currentMousePos.y < data.y+data.height) {
element.onMouseMove.Invoke({window->currentMousePos.x-data.x, window->currentMousePos.y-data.y});
if(!(window->lastMousePos.x >= data.x && window->lastMousePos.x <= data.x+data.width && window->lastMousePos.y > data.y && window->lastMousePos.y < data.y+data.height)) {
element.onMouseEnter.Invoke({window->currentMousePos.x-data.x, window->currentMousePos.y-data.y});
}
} else if(window->lastMousePos.x >= data.x && window->lastMousePos.x <= data.x+data.width && window->lastMousePos.y > data.y && window->lastMousePos.y < data.y+data.height) {
element.onMouseLeave.Invoke({window->currentMousePos.x-data.x, window->currentMousePos.y-data.y});
}
}
}
void WindowWayland::PointerListenerHandleEnter(void* data, wl_pointer* wl_pointer, uint serial, wl_surface* surface, wl_fixed_t surface_x, wl_fixed_t surface_y) {
WindowWayland* window = reinterpret_cast<WindowWayland*>(data);
window->onMouseEnter.Invoke({window->lastMousePos, window->currentMousePos, window->mouseDelta});
}
void WindowWayland::PointerListenerHandleLeave(void* data, wl_pointer*, std::uint32_t, wl_surface*) {
WindowWayland* window = reinterpret_cast<WindowWayland*>(data);
window->onMouseEnter.Invoke({window->lastMousePos, window->currentMousePos, window->mouseDelta});
}
void WindowWayland::PointerListenerHandleAxis(void*, wl_pointer*, std::uint32_t, std::uint32_t, wl_fixed_t value) {
std::cout << wl_fixed_to_double(value) << std::endl;
}
wl_pointer_listener WindowWayland::pointer_listener = {
.enter = WindowWayland::PointerListenerHandleEnter,
.leave = WindowWayland::PointerListenerHandleLeave,
.motion = WindowWayland::PointerListenerHandleMotion,
.button = WindowWayland::pointer_handle_button,
.axis = WindowWayland::PointerListenerHandleAxis,
};
xkb_keymap* xkb_keymap;
xkb_context* xkb_context = xkb_context_new(XKB_CONTEXT_NO_FLAGS);
xkb_state* xkb_state;
void keyboard_keymap(void *data, wl_keyboard *keyboard, uint32_t format, int fd, uint32_t size) {
if (format != WL_KEYBOARD_KEYMAP_FORMAT_XKB_V1) {
close(fd);
fprintf(stderr, "Unsupported keymap format\n");
return;
}
void *map = mmap(NULL, size, PROT_READ, MAP_SHARED, fd, 0);
if (map == MAP_FAILED) {
close(fd);
perror("mmap");
return;
}
xkb_context = xkb_context_new(XKB_CONTEXT_NO_FLAGS);
xkb_keymap = xkb_keymap_new_from_string(xkb_context, (const char *)map, XKB_KEYMAP_FORMAT_TEXT_V1,XKB_KEYMAP_COMPILE_NO_FLAGS);
munmap(map, size);
close(fd);
xkb_state = xkb_state_new(xkb_keymap);
}
void keyboard_enter(void *data, wl_keyboard *keyboard, uint32_t serial, wl_surface *surface, wl_array *keys) {
}
void keyboard_leave(void *data, wl_keyboard *keyboard, uint32_t serial, wl_surface *surface) {
}
void keyboard_key(void *data, wl_keyboard *keyboard, uint32_t serial, uint32_t time, uint32_t key, uint32_t state) {
if (!xkb_state) {
return;
}
WindowWayland* window = reinterpret_cast<WindowWayland*>(data);
xkb_keycode_t keycode = key + 8;
xkb_keysym_t keysym = xkb_state_key_get_one_sym(xkb_state, keycode);
char utf8[8] = {0};
int len = xkb_keysym_to_utf8(keysym, utf8, sizeof(utf8));
if (len != 0) {
char keypress = utf8[0];
if(state == WL_KEYBOARD_KEY_STATE_PRESSED) {
if(window->heldkeys[keypress]) {
window->onKeyHold[keypress].Invoke();
window->onAnyKeyHold.Invoke(keypress);
} else{
window->heldkeys[keypress] = true;
window->onKeyDown[keypress].Invoke();
window->onAnyKeyDown.Invoke(keypress);
}
} else{
window->heldkeys[keypress] = false;
window->onKeyUp[keypress].Invoke();
window->onAnyKeyUp.Invoke(keypress);
}
} else {
// // fallback for keys like Return, Escape, etc.
// char name[64];
// if (xkb_keysym_get_name(keysym, name, sizeof(name)) > 0) {
// printf("Key %s pressed (non-printable or multi-char)\n", name);
// }
}
}
void keyboard_modifiers(void *data, wl_keyboard *keyboard, uint32_t serial, uint32_t mods_depressed, uint32_t mods_latched, uint32_t mods_locked, uint32_t group) {
}
void keyboard_repeat_info(void *data, wl_keyboard *keyboard, int32_t rate, int32_t delay) {
}
wl_keyboard_listener WindowWayland::keyboard_listener = {
.keymap = keyboard_keymap,
.enter = keyboard_enter,
.leave = keyboard_leave,
.key = keyboard_key,
.modifiers = keyboard_modifiers,
.repeat_info = keyboard_repeat_info,
};
void WindowWayland::seat_handle_capabilities(void* data, wl_seat* seat, uint32_t capabilities) {
WindowWayland* window = reinterpret_cast<WindowWayland*>(data);
window->seat = seat;
if (capabilities & WL_SEAT_CAPABILITY_POINTER) {
wl_pointer* pointer = wl_seat_get_pointer(seat);
wl_pointer_add_listener(pointer, &pointer_listener, window);
}
if (capabilities & WL_SEAT_CAPABILITY_KEYBOARD) {
wl_keyboard* keyboard = wl_seat_get_keyboard(seat);
wl_keyboard_add_listener(keyboard, &keyboard_listener, window);
}
}
wl_seat_listener WindowWayland::seat_listener = {
.capabilities = seat_handle_capabilities,
};
void WindowWayland::handle_global(void *data, wl_registry *registry, std::uint32_t name, const char *interface, std::uint32_t version) {
WindowWayland* window = reinterpret_cast<WindowWayland*>(data);
if (strcmp(interface, wl_shm_interface.name) == 0) {
window->shm = reinterpret_cast<wl_shm*>(wl_registry_bind(registry, name, &wl_shm_interface, 1));
} else if (strcmp(interface, wl_seat_interface.name) == 0) {
wl_seat* seat = reinterpret_cast<wl_seat*>(wl_registry_bind(registry, name, &wl_seat_interface, 1));
wl_seat_add_listener(seat, &seat_listener, window);
} else if (compositor == NULL && strcmp(interface, wl_compositor_interface.name) == 0) {
compositor = reinterpret_cast<wl_compositor*>(wl_registry_bind(registry, name, &wl_compositor_interface, 1));
} else if (strcmp(interface, xdg_wm_base_interface.name) == 0) {
window->xdgWmBase = reinterpret_cast<xdg_wm_base*>(wl_registry_bind(registry, name, &xdg_wm_base_interface, 1));
xdg_wm_base_add_listener(window->xdgWmBase, &xdgWmBaseListener, NULL);
} else if (strcmp(interface, zxdg_decoration_manager_v1_interface.name) == 0) {
window->manager = reinterpret_cast<zxdg_decoration_manager_v1*>(wl_registry_bind(registry, name, &zxdg_decoration_manager_v1_interface, 1));
}
}
static void handle_global_remove(void* data, wl_registry* registry, uint32_t name) {
}
wl_registry_listener WindowWayland::registry_listener = {
.global = WindowWayland::handle_global,
.global_remove = handle_global_remove,
};
static void noop5(void*, xdg_toplevel*, std::int32_t, std::int32_t, wl_array*){
}
void WindowWayland::xdg_toplevel_handle_close(void* data, xdg_toplevel*) {
WindowWayland* window = reinterpret_cast<WindowWayland*>(data);
window->onClose.Invoke();
window->open = false;
}
xdg_toplevel_listener WindowWayland::xdg_toplevel_listener = {
.configure = noop5,
.close = WindowWayland::xdg_toplevel_handle_close,
};
void WindowWayland::xdg_surface_handle_configure(void* data, xdg_surface* xdg_surface, std::uint32_t serial) {
WindowWayland* window = reinterpret_cast<WindowWayland*>(data);
// The compositor configures our surface, acknowledge the configure event
xdg_surface_ack_configure(xdg_surface, serial);
if (window->configured) {
// If this isn't the first configure event we've received, we already
// have a buffer attached, so no need to do anything. Commit the
// surface to apply the configure acknowledgement.
wl_surface_commit(window->surface);
}
window->configured = true;
}
xdg_surface_listener WindowWayland::xdg_surface_listener = {
.configure = xdg_surface_handle_configure,
};
WindowWayland::WindowWayland(std::string name, std::uint32_t width, std::uint32_t height) : Window(name, width, height) {
// Connect to the Wayland compositor
display = wl_display_connect(NULL);
if (display == NULL) {
std::cerr << "failed to create display" << std::endl;
}
// Obtain the wl_registry and fetch the list of globals
wl_registry* registry = wl_display_get_registry(display);
wl_registry_add_listener(registry, &registry_listener, this);
if (wl_display_roundtrip(display) == -1) {
exit(EXIT_FAILURE);
}
// Check that all globals we require are available
if (shm == NULL || compositor == NULL || xdgWmBase == NULL) {
std::cerr << "no wl_shm, wl_compositor or xdg_wm_base support" << std::endl;
exit(EXIT_FAILURE);
}
// Create a wl_surface, a xdg_surface and a xdg_toplevel
surface = wl_compositor_create_surface(compositor);
xdgSurface = xdg_wm_base_get_xdg_surface(xdgWmBase, surface);
xdgToplevel = xdg_surface_get_toplevel(xdgSurface);
xdg_surface_add_listener(xdgSurface, &xdg_surface_listener, this);
xdg_toplevel_add_listener(xdgToplevel, &xdg_toplevel_listener, this);
wl_surface_commit(surface);
while (wl_display_dispatch(display) != -1 && !configured) {
// This space intentionally left blank
}
wl_surface_commit(surface);
zxdg_toplevel_decoration_v1* decoration = zxdg_decoration_manager_v1_get_toplevel_decoration(manager, xdgToplevel); // toplevel is from xdg_surface_get_toplevel
zxdg_toplevel_decoration_v1_set_mode(decoration, ZXDG_TOPLEVEL_DECORATION_V1_MODE_SERVER_SIDE);
}
WindowWayland::~WindowWayland() {
xdg_toplevel_destroy(xdgToplevel);
xdg_surface_destroy(xdgSurface);
wl_surface_destroy(surface);
wl_buffer_destroy(buffer);
}

View file

@ -0,0 +1,488 @@
/*
Crafter®.Graphics
Copyright (C) 2025 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;
#include <vulkan/vulkan.h>
#include <vulkan/vulkan_wayland.h>
#include <wayland-client.h>
module Crafter.Graphics;
import std;
import Crafter.Event;
using namespace Crafter;
void WindowWaylandVulkan::CreateSwapchain()
{
// Store the current swap chain handle so we can use it later on to ease up recreation
VkSwapchainKHR oldSwapchain = swapChain;
// Get physical device surface properties and formats
VkSurfaceCapabilitiesKHR surfCaps;
VulkanDevice::CHECK_VK_RESULT(vkGetPhysicalDeviceSurfaceCapabilitiesKHR(VulkanDevice::physDevice, vulkanSurface, &surfCaps));
VkExtent2D swapchainExtent = {};
// If width (and height) equals the special value 0xFFFFFFFF, the size of the surface will be set by the swapchain
if (surfCaps.currentExtent.width == (uint32_t)-1)
{
// If the surface size is undefined, the size is set to the size of the images requested
swapchainExtent.width = width;
swapchainExtent.height = height;
}
else
{
// If the surface size is defined, the swap chain size must match
swapchainExtent = surfCaps.currentExtent;
width = surfCaps.currentExtent.width;
height = surfCaps.currentExtent.height;
}
// Select a present mode for the swapchain
uint32_t presentModeCount;
VulkanDevice::CHECK_VK_RESULT(vkGetPhysicalDeviceSurfacePresentModesKHR(VulkanDevice::physDevice, vulkanSurface, &presentModeCount, NULL));
assert(presentModeCount > 0);
std::vector<VkPresentModeKHR> presentModes(presentModeCount);
VulkanDevice::CHECK_VK_RESULT(vkGetPhysicalDeviceSurfacePresentModesKHR(VulkanDevice::physDevice, vulkanSurface, &presentModeCount, presentModes.data()));
// The VK_PRESENT_MODE_FIFO_KHR mode must always be present as per spec
// This mode waits for the vertical blank ("v-sync")
VkPresentModeKHR swapchainPresentMode = VK_PRESENT_MODE_FIFO_KHR;
// Determine the number of images
uint32_t desiredNumberOfSwapchainImages = surfCaps.minImageCount + 1;
if ((surfCaps.maxImageCount > 0) && (desiredNumberOfSwapchainImages > surfCaps.maxImageCount))
{
desiredNumberOfSwapchainImages = surfCaps.maxImageCount;
}
// Find the transformation of the surface
VkSurfaceTransformFlagsKHR preTransform;
if (surfCaps.supportedTransforms & VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR)
{
// We prefer a non-rotated transform
preTransform = VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR;
}
else
{
preTransform = surfCaps.currentTransform;
}
// Find a supported composite alpha format (not all devices support alpha opaque)
VkCompositeAlphaFlagBitsKHR compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR;
// Simply select the first composite alpha format available
std::vector<VkCompositeAlphaFlagBitsKHR> compositeAlphaFlags = {
VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR,
VK_COMPOSITE_ALPHA_PRE_MULTIPLIED_BIT_KHR,
VK_COMPOSITE_ALPHA_POST_MULTIPLIED_BIT_KHR,
VK_COMPOSITE_ALPHA_INHERIT_BIT_KHR,
};
for (auto& compositeAlphaFlag : compositeAlphaFlags) {
if (surfCaps.supportedCompositeAlpha & compositeAlphaFlag) {
compositeAlpha = compositeAlphaFlag;
break;
};
}
VkSwapchainCreateInfoKHR swapchainCI = {};
swapchainCI.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR;
swapchainCI.surface = vulkanSurface;
swapchainCI.minImageCount = desiredNumberOfSwapchainImages;
swapchainCI.imageFormat = colorFormat;
swapchainCI.imageColorSpace = colorSpace;
swapchainCI.imageExtent = { swapchainExtent.width, swapchainExtent.height };
swapchainCI.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT;
swapchainCI.preTransform = (VkSurfaceTransformFlagBitsKHR)preTransform;
swapchainCI.imageArrayLayers = 1;
swapchainCI.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE;
swapchainCI.queueFamilyIndexCount = 0;
swapchainCI.presentMode = swapchainPresentMode;
// Setting oldSwapChain to the saved handle of the previous swapchain aids in resource reuse and makes sure that we can still present already acquired images
swapchainCI.oldSwapchain = oldSwapchain;
// Setting clipped to VK_TRUE allows the implementation to discard rendering outside of the surface area
swapchainCI.clipped = VK_TRUE;
swapchainCI.compositeAlpha = compositeAlpha;
// Enable transfer source on swap chain images if supported
if (surfCaps.supportedUsageFlags & VK_IMAGE_USAGE_TRANSFER_SRC_BIT) {
swapchainCI.imageUsage |= VK_IMAGE_USAGE_TRANSFER_SRC_BIT;
}
// Enable transfer destination on swap chain images if supported
if (surfCaps.supportedUsageFlags & VK_IMAGE_USAGE_TRANSFER_DST_BIT) {
swapchainCI.imageUsage |= VK_IMAGE_USAGE_TRANSFER_DST_BIT;
}
VulkanDevice::CHECK_VK_RESULT(vkCreateSwapchainKHR(VulkanDevice::device, &swapchainCI, nullptr, &swapChain));
// If an existing swap chain is re-created, destroy the old swap chain and the ressources owned by the application (image views, images are owned by the swap chain)
if (oldSwapchain != VK_NULL_HANDLE) {
for (auto i = 0; i < images.size(); i++) {
vkDestroyImageView(VulkanDevice::device, imageViews[i], nullptr);
}
vkDestroySwapchainKHR(VulkanDevice::device, oldSwapchain, nullptr);
}
uint32_t imageCount{ 0 };
VulkanDevice::CHECK_VK_RESULT(vkGetSwapchainImagesKHR(VulkanDevice::device, swapChain, &imageCount, nullptr));
// Get the swap chain images
images.resize(imageCount);
VulkanDevice::CHECK_VK_RESULT(vkGetSwapchainImagesKHR(VulkanDevice::device, swapChain, &imageCount, images.data()));
// Get the swap chain buffers containing the image and imageview
imageViews.resize(imageCount);
for (auto i = 0; i < images.size(); i++)
{
VkImageViewCreateInfo colorAttachmentView = {};
colorAttachmentView.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
colorAttachmentView.pNext = NULL;
colorAttachmentView.format = colorFormat;
colorAttachmentView.components = {
VK_COMPONENT_SWIZZLE_R,
VK_COMPONENT_SWIZZLE_G,
VK_COMPONENT_SWIZZLE_B,
VK_COMPONENT_SWIZZLE_A
};
colorAttachmentView.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
colorAttachmentView.subresourceRange.baseMipLevel = 0;
colorAttachmentView.subresourceRange.levelCount = 1;
colorAttachmentView.subresourceRange.baseArrayLayer = 0;
colorAttachmentView.subresourceRange.layerCount = 1;
colorAttachmentView.viewType = VK_IMAGE_VIEW_TYPE_2D;
colorAttachmentView.flags = 0;
colorAttachmentView.image = images[i];
VulkanDevice::CHECK_VK_RESULT(vkCreateImageView(VulkanDevice::device, &colorAttachmentView, nullptr, &imageViews[i]));
}
}
WindowWaylandVulkan::WindowWaylandVulkan(std::string name, std::uint32_t width, std::uint32_t height) : WindowWayland(name, width, height) {
VkWaylandSurfaceCreateInfoKHR createInfo = {};
createInfo.sType = VK_STRUCTURE_TYPE_WAYLAND_SURFACE_CREATE_INFO_KHR;
createInfo.display = display;
createInfo.surface = surface;
VulkanDevice::CHECK_VK_RESULT(vkCreateWaylandSurfaceKHR(VulkanDevice::instance, &createInfo, NULL, &vulkanSurface));
// Get list of supported surface formats
std::uint32_t formatCount;
VulkanDevice::CHECK_VK_RESULT(vkGetPhysicalDeviceSurfaceFormatsKHR(VulkanDevice::physDevice, vulkanSurface, &formatCount, NULL));
assert(formatCount > 0);
std::vector<VkSurfaceFormatKHR> surfaceFormats(formatCount);
VulkanDevice::CHECK_VK_RESULT(vkGetPhysicalDeviceSurfaceFormatsKHR(VulkanDevice::physDevice, vulkanSurface, &formatCount, surfaceFormats.data()));
// We want to get a format that best suits our needs, so we try to get one from a set of preferred formats
// Initialize the format to the first one returned by the implementation in case we can't find one of the preffered formats
VkSurfaceFormatKHR selectedFormat = surfaceFormats[0];
std::vector<VkFormat> preferredImageFormats = {
VK_FORMAT_R8G8B8A8_UNORM,
};
for (auto& availableFormat : surfaceFormats) {
if (std::find(preferredImageFormats.begin(), preferredImageFormats.end(), availableFormat.format) != preferredImageFormats.end()) {
selectedFormat = availableFormat;
break;
}
}
colorFormat = selectedFormat.format;
colorSpace = selectedFormat.colorSpace;
CreateSwapchain();
std::array<VkAttachmentDescription, 2> attachments = {};
// Color attachment
attachments[0].format = colorFormat;
attachments[0].samples = VK_SAMPLE_COUNT_1_BIT;
attachments[0].loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
attachments[0].storeOp = VK_ATTACHMENT_STORE_OP_STORE;
attachments[0].stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
attachments[0].stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
attachments[0].initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
attachments[0].finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;
// Depth attachment
attachments[1].format = VulkanDevice::depthFormat;
attachments[1].samples = VK_SAMPLE_COUNT_1_BIT;
attachments[1].loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
attachments[1].storeOp = VK_ATTACHMENT_STORE_OP_STORE;
attachments[1].stencilLoadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
attachments[1].stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
attachments[1].initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
attachments[1].finalLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
VkAttachmentReference colorReference = {};
colorReference.attachment = 0;
colorReference.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
VkAttachmentReference depthReference = {};
depthReference.attachment = 1;
depthReference.layout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
VkSubpassDescription subpassDescription = {};
subpassDescription.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS;
subpassDescription.colorAttachmentCount = 1;
subpassDescription.pColorAttachments = &colorReference;
subpassDescription.pDepthStencilAttachment = &depthReference;
subpassDescription.inputAttachmentCount = 0;
subpassDescription.pInputAttachments = nullptr;
subpassDescription.preserveAttachmentCount = 0;
subpassDescription.pPreserveAttachments = nullptr;
subpassDescription.pResolveAttachments = nullptr;
// Subpass dependencies for layout transitions
std::array<VkSubpassDependency, 2> dependencies{};
dependencies[0].srcSubpass = VK_SUBPASS_EXTERNAL;
dependencies[0].dstSubpass = 0;
dependencies[0].srcStageMask = VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT;
dependencies[0].dstStageMask = VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT;
dependencies[0].srcAccessMask = VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT;
dependencies[0].dstAccessMask = VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT;
dependencies[0].dependencyFlags = 0;
dependencies[1].srcSubpass = VK_SUBPASS_EXTERNAL;
dependencies[1].dstSubpass = 0;
dependencies[1].srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
dependencies[1].dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
dependencies[1].srcAccessMask = 0;
dependencies[1].dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT | VK_ACCESS_COLOR_ATTACHMENT_READ_BIT;
dependencies[1].dependencyFlags = 0;
VkRenderPassCreateInfo renderPassInfo = {};
renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO;
renderPassInfo.attachmentCount = static_cast<uint32_t>(attachments.size());
renderPassInfo.pAttachments = attachments.data();
renderPassInfo.subpassCount = 1;
renderPassInfo.pSubpasses = &subpassDescription;
renderPassInfo.dependencyCount = static_cast<uint32_t>(dependencies.size());
renderPassInfo.pDependencies = dependencies.data();
VulkanDevice::CHECK_VK_RESULT(vkCreateRenderPass(VulkanDevice::device, &renderPassInfo, nullptr, &renderPass));
VkImageCreateInfo imageCI{};
imageCI.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
imageCI.imageType = VK_IMAGE_TYPE_2D;
imageCI.format = VulkanDevice::depthFormat;
imageCI.extent = { width, height, 1 };
imageCI.mipLevels = 1;
imageCI.arrayLayers = 1;
imageCI.samples = VK_SAMPLE_COUNT_1_BIT;
imageCI.tiling = VK_IMAGE_TILING_OPTIMAL;
imageCI.usage = VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT;
VulkanDevice::CHECK_VK_RESULT(vkCreateImage(VulkanDevice::device, &imageCI, nullptr, &depthStencil.image));
VkMemoryRequirements memReqs{};
vkGetImageMemoryRequirements(VulkanDevice::device, depthStencil.image, &memReqs);
VkMemoryAllocateInfo memAllloc{};
memAllloc.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
memAllloc.allocationSize = memReqs.size;
memAllloc.memoryTypeIndex = VulkanDevice::GetMemoryType(memReqs.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
VulkanDevice::CHECK_VK_RESULT(vkAllocateMemory(VulkanDevice::device, &memAllloc, nullptr, &depthStencil.memory));
VulkanDevice::CHECK_VK_RESULT(vkBindImageMemory(VulkanDevice::device, depthStencil.image, depthStencil.memory, 0));
VkImageViewCreateInfo imageViewCI{};
imageViewCI.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
imageViewCI.viewType = VK_IMAGE_VIEW_TYPE_2D;
imageViewCI.image = depthStencil.image;
imageViewCI.format = VulkanDevice::depthFormat;
imageViewCI.subresourceRange.baseMipLevel = 0;
imageViewCI.subresourceRange.levelCount = 1;
imageViewCI.subresourceRange.baseArrayLayer = 0;
imageViewCI.subresourceRange.layerCount = 1;
imageViewCI.subresourceRange.aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT;
VulkanDevice::CHECK_VK_RESULT(vkCreateImageView(VulkanDevice::device, &imageViewCI, nullptr, &depthStencil.view));
// Create frame buffers for every swap chain image
frameBuffers.resize(images.size());
for (uint32_t i = 0; i < frameBuffers.size(); i++)
{
const VkImageView attachments[2] = {
imageViews[i],
depthStencil.view
};
VkFramebufferCreateInfo frameBufferCreateInfo{};
frameBufferCreateInfo.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO;
frameBufferCreateInfo.renderPass = renderPass;
frameBufferCreateInfo.attachmentCount = 2;
frameBufferCreateInfo.pAttachments = attachments;
frameBufferCreateInfo.width = width;
frameBufferCreateInfo.height = height;
frameBufferCreateInfo.layers = 1;
VulkanDevice::CHECK_VK_RESULT(vkCreateFramebuffer(VulkanDevice::device, &frameBufferCreateInfo, nullptr, &frameBuffers[i]));
}
drawCmdBuffers.resize(images.size());
VkCommandBufferAllocateInfo cmdBufAllocateInfo = vks::initializers::commandBufferAllocateInfo(VulkanDevice::commandPool, VK_COMMAND_BUFFER_LEVEL_PRIMARY, static_cast<uint32_t>(drawCmdBuffers.size()));
VulkanDevice::CHECK_VK_RESULT(vkAllocateCommandBuffers(VulkanDevice::device, &cmdBufAllocateInfo, drawCmdBuffers.data()));
VkSemaphoreCreateInfo semaphoreCreateInfo = vks::initializers::semaphoreCreateInfo();
VulkanDevice::CHECK_VK_RESULT(vkCreateSemaphore(VulkanDevice::device, &semaphoreCreateInfo, nullptr, &semaphores.presentComplete));
VulkanDevice::CHECK_VK_RESULT(vkCreateSemaphore(VulkanDevice::device, &semaphoreCreateInfo, nullptr, &semaphores.renderComplete));
// Set up submit info structure
// Semaphores will stay the same during application lifetime
// Command buffer submission info is set by each example
submitInfo = vks::initializers::submitInfo();
submitInfo.pWaitDstStageMask = &submitPipelineStages;
submitInfo.waitSemaphoreCount = 1;
submitInfo.pWaitSemaphores = &semaphores.presentComplete;
submitInfo.signalSemaphoreCount = 1;
submitInfo.pSignalSemaphores = &semaphores.renderComplete;
}
WindowWaylandVulkan::~WindowWaylandVulkan() {
if (swapChain != VK_NULL_HANDLE) {
for (auto i = 0; i < images.size(); i++) {
vkDestroyImageView(VulkanDevice::device, imageViews[i], nullptr);
}
vkDestroySwapchainKHR(VulkanDevice::device, swapChain, nullptr);
}
if (vulkanSurface != VK_NULL_HANDLE) {
vkDestroySurfaceKHR(VulkanDevice::instance, vulkanSurface, nullptr);
}
}
VkCommandBuffer WindowWaylandVulkan::StartInit() {
VkCommandBufferBeginInfo cmdBufInfo = vks::initializers::commandBufferBeginInfo();
VulkanDevice::CHECK_VK_RESULT(vkBeginCommandBuffer(drawCmdBuffers[currentBuffer], &cmdBufInfo));
return drawCmdBuffers[currentBuffer];
}
void WindowWaylandVulkan::FinishInit() {
VkSubmitInfo submitInfo{};
submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
submitInfo.commandBufferCount = 1;
submitInfo.pCommandBuffers = &drawCmdBuffers[currentBuffer];
VulkanDevice::CHECK_VK_RESULT(vkEndCommandBuffer(drawCmdBuffers[currentBuffer]));
VulkanDevice::CHECK_VK_RESULT(vkQueueSubmit(VulkanDevice::queue, 1, &submitInfo, VK_NULL_HANDLE));
VulkanDevice::CHECK_VK_RESULT(vkQueueWaitIdle(VulkanDevice::queue));
}
void WindowWaylandVulkan::StartAsync() {
thread = std::thread(&WindowWaylandVulkan::StartSync, this);
}
void WindowWaylandVulkan::StartSync() {
while (open && wl_display_dispatch(display) != -1) {
// Acquire the next image from the swap chain
VulkanDevice::CHECK_VK_RESULT(vkAcquireNextImageKHR(VulkanDevice::device, swapChain, UINT64_MAX, semaphores.presentComplete, (VkFence)nullptr, &currentBuffer));
submitInfo.commandBufferCount = 1;
submitInfo.pCommandBuffers = &drawCmdBuffers[currentBuffer];
VkCommandBufferBeginInfo cmdBufInfo = vks::initializers::commandBufferBeginInfo();
VkClearValue clearValues[2];
clearValues[0].color = { };;
clearValues[1].depthStencil = { 1.0f, 0 };
VulkanDevice::CHECK_VK_RESULT(vkBeginCommandBuffer(drawCmdBuffers[currentBuffer], &cmdBufInfo));
VkImageSubresourceRange range{};
range.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
range.baseMipLevel = 0;
range.levelCount = VK_REMAINING_MIP_LEVELS;
range.baseArrayLayer = 0;
range.layerCount = VK_REMAINING_ARRAY_LAYERS;
VkImageSubresourceRange depth_range{range};
depth_range.aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT;
image_layout_transition(drawCmdBuffers[currentBuffer],
images[currentBuffer],
VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT,
VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT,
0,
VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT,
VK_IMAGE_LAYOUT_UNDEFINED,
VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL,
range);
image_layout_transition(drawCmdBuffers[currentBuffer],
depthStencil.image,
VK_IMAGE_LAYOUT_UNDEFINED,
VK_IMAGE_LAYOUT_DEPTH_ATTACHMENT_OPTIMAL,
depth_range);
VkRenderingAttachmentInfoKHR color_attachment_info = {VK_STRUCTURE_TYPE_RENDERING_ATTACHMENT_INFO_KHR, VK_NULL_HANDLE};
color_attachment_info.imageView = imageViews[currentBuffer];
color_attachment_info.imageLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
color_attachment_info.resolveMode = VK_RESOLVE_MODE_NONE;
color_attachment_info.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
color_attachment_info.storeOp = VK_ATTACHMENT_STORE_OP_STORE;
color_attachment_info.clearValue = { 0.0f, 0.0f, 0.2f, 1.0f };
VkRenderingAttachmentInfoKHR depth_attachment_info = {VK_STRUCTURE_TYPE_RENDERING_ATTACHMENT_INFO_KHR, VK_NULL_HANDLE};
depth_attachment_info.imageView = depthStencil.view;
depth_attachment_info.imageLayout = VK_IMAGE_LAYOUT_DEPTH_ATTACHMENT_OPTIMAL;
depth_attachment_info.resolveMode = VK_RESOLVE_MODE_NONE;
depth_attachment_info.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
depth_attachment_info.storeOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
depth_attachment_info.clearValue = { 1.0f, 0 };
VkRenderingInfo render_info = {VK_STRUCTURE_TYPE_RENDERING_INFO_KHR,VK_NULL_HANDLE,0};
render_info.renderArea = VkRect2D{VkOffset2D{}, VkExtent2D{width, height}};
render_info.viewMask = 0;
render_info.layerCount = 1;
render_info.colorAttachmentCount = 1;
render_info.pColorAttachments = &color_attachment_info;
render_info.pDepthAttachment = &depth_attachment_info;
render_info.pStencilAttachment = VK_NULL_HANDLE;
VulkanDevice::vkCmdBeginRenderingKHRProc(drawCmdBuffers[currentBuffer], &render_info);
VkViewport viewport = vks::initializers::viewport((float)width, (float)height, 0.0f, 1.0f);
vkCmdSetViewport(drawCmdBuffers[currentBuffer], 0, 1, &viewport);
VkRect2D scissor = vks::initializers::rect2D(width, height, 0, 0);
vkCmdSetScissor(drawCmdBuffers[currentBuffer], 0, 1, &scissor);
onDraw.Invoke(drawCmdBuffers[currentBuffer]);
mouseDelta = {0, 0};
VulkanDevice::vkCmdEndRenderingKHRProc(drawCmdBuffers[currentBuffer]);
image_layout_transition(drawCmdBuffers[currentBuffer],
images[currentBuffer],
VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL,
VK_IMAGE_LAYOUT_PRESENT_SRC_KHR,
range
);
VulkanDevice::CHECK_VK_RESULT(vkEndCommandBuffer(drawCmdBuffers[currentBuffer]));
VulkanDevice::CHECK_VK_RESULT(vkQueueSubmit(VulkanDevice::queue, 1, &submitInfo, VK_NULL_HANDLE));
VkPresentInfoKHR presentInfo = {};
presentInfo.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR;
presentInfo.pNext = NULL;
presentInfo.swapchainCount = 1;
presentInfo.pSwapchains = &swapChain;
presentInfo.pImageIndices = &currentBuffer;
// Check if a wait semaphore has been specified to wait for before presenting the image
if (semaphores.renderComplete != VK_NULL_HANDLE)
{
presentInfo.pWaitSemaphores = &semaphores.renderComplete;
presentInfo.waitSemaphoreCount = 1;
}
VulkanDevice::CHECK_VK_RESULT(vkQueuePresentKHR(VulkanDevice::queue, &presentInfo));
VulkanDevice::CHECK_VK_RESULT(vkQueueWaitIdle(VulkanDevice::queue));
}
}

View file

@ -0,0 +1,174 @@
/*
Crafter®.Graphics
Copyright (C) 2025 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;
#include <errno.h>
#include <fcntl.h>
#include <linux/input.h>
#include <sys/mman.h>
#include <unistd.h>
#include <wayland-cursor.h>
#include <xkbcommon/xkbcommon.h>
#include <vulkan/vulkan.h>
#include <vulkan/vulkan_wayland.h>
#include <wayland-client.h>
#include <errno.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <time.h>
#include <unistd.h>
#include <string.h>
#include <print>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <unistd.h>
#include <wayland-client.h>
#include <wayland-client-protocol.h>
#include <linux/input-event-codes.h>
export module Crafter.Graphics:WindowWaylandWayland_impl;
import :WindowWaylandWayland;
import std;
import Crafter.Event;
using namespace Crafter;
static void 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;
}
}
static int 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 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;
}
void ScaleBitmapR8G8B8(Pixel_BU8_GU8_RU8_AU8* dst, const Pixel_BU8_GU8_RU8_AU8* src, std::uint32_t srcWidth, std::uint32_t srcHeight, std::uint32_t dstWidth, std::uint32_t dstHeight) {
for (std::uint32_t y = 0; y < dstHeight; y++) {
std::uint32_t srcY = y * srcHeight / dstHeight;
for (std::uint32_t x = 0; x < dstWidth; x++) {
std::uint32_t srcX = x * srcWidth / dstWidth;
const Pixel_BU8_GU8_RU8_AU8* srcPixel = src + (srcY * srcWidth + srcX);
Pixel_BU8_GU8_RU8_AU8* dstPixel = dst + (y * dstWidth + x);
dstPixel[0] = srcPixel[0];
}
}
}
WindowWaylandWayland::WindowWaylandWayland(std::string name, std::uint32_t width, std::uint32_t height) : WindowWayland(name, width, height) {
// 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) {
fprintf(stderr, "creating a buffer file for %d B failed: %m\n", size);
}
// Map the shared memory file
framebuffer = reinterpret_cast<Pixel_BU8_GU8_RU8_AU8*>(mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0));
if (framebuffer == MAP_FAILED) {
fprintf(stderr, "mmap failed: %m\n");
close(fd);
}
// Create a wl_buffer from our shared memory file descriptor
wl_shm_pool *pool = wl_shm_create_pool(shm, fd, size);
buffer = wl_shm_pool_create_buffer(pool, 0, width, height, stride, WL_SHM_FORMAT_ARGB8888);
wl_shm_pool_destroy(pool);
// Now that we've mapped the file and created the wl_buffer, we no longer
// need to keep file descriptor opened
close(fd);
if (buffer == NULL) {
exit(EXIT_FAILURE);
}
wl_surface_attach(surface, buffer, 0, 0);
wl_surface_commit(surface);
}
WindowWaylandWayland::~WindowWaylandWayland() {
open = false;
thread.join();
}
void WindowWaylandWayland::StartAsync() {
thread = std::thread(&WindowWaylandWayland::StartSync, this);
}
void WindowWaylandWayland::StartSync() {
while (open && wl_display_dispatch(display) != -1) {
wl_surface_attach(surface, buffer, 0, 0);
for(const UiElement& element : elements) {
ScaleData data = ScaleElement(element);
std::vector<Pixel_BU8_GU8_RU8_AU8> scaled(data.width*data.height);
ScaleBitmapR8G8B8(scaled.data(), element.buffer.data(), element.bufferWidth, element.bufferHeight, data.width, data.height);
for(std::int32_t x = data.x; x-data.x < data.width; x++) {
for(std::int32_t y = data.y; y-data.y < data.height; y++) {
if(x > 0 && x < width && y > 0 && y < height) {
framebuffer[y*width+x] = scaled[(y-data.y)*data.width+(x-data.x)];
}
}
}
wl_surface_damage(surface, data.x, data.y, data.width, data.height);
}
wl_surface_commit(surface);
}
}

View file

@ -0,0 +1,59 @@
/*
Crafter®.Graphics
Copyright (C) 2025 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
*/
export module Crafter.Graphics:Camera;
import std;
import Crafter.Math;
import Crafter.Event;
namespace Crafter {
/**
* @brief Camera with projection and view matrices.
*/
export class Camera {
public:
MatrixRowMajor<float, 4, 4, 1> projection;
MatrixRowMajor<float, 4, 4, 1> view;
MatrixRowMajor<float, 4, 4, 1> projectionView;
Event<void> onUpdate;
/**
* @brief Constructs a camera.
* @param fov Field of view in radians.
* @param aspectRatio Aspect ratio of the camera (width / height).
* @param near Near clipping plane.
* @param far Far clipping plane.
*/
Camera(float fov, float aspectRatio, float near, float far);
/**
* @brief Calculates the projectionView matrix by multiplying the projection and view matricies.
*/
void Update();
/**
* @brief Converts screen-space coordinates to a 3D world-space ray.
* @param x The x-coordinate on the screen (in pixels).
* @param y The y-coordinate on the screen (in pixels).
* @return A normalized 3D direction vector representing the ray from the camera.
*/
Vector<float, 3> ToRay(std::uint32_t x, std::uint32_t y);
};
}

View file

@ -0,0 +1,172 @@
/*
Crafter®.Graphics
Copyright (C) 2025 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;
#include <vulkan/vulkan.h>
export module Crafter.Graphics:DescriptorSet;
import std;
import Crafter.Event;
import :VulkanDevice;
import :VulkanShader;
import :WindowWaylandVulkan;
import :VulkanPipeline;
namespace Crafter {
struct DescriptorEntry {
VkDescriptorType type;
bool occured = true;
};
/**
* @brief Holder for VkDescriptorSet.
*
* This class stores 2 VkDescriptorSet one for the first stage and one for the second stage,
* This class has static members to effeciently use a VkDescriptorPool.
* @tparam MeshShader the VulkanShader to use for the first stage.
* @tparam FragmentShader the VulkanShader to use for the second stage.
*/
export template <typename MeshShader, typename FragmentShader>
class DescriptorSet {
public:
/**
* @brief [0] refers to the first stage, [1] refers to the second stage.
*/
VkDescriptorSet set[2];
/**
* @brief This event is triggered when a descriptor is aded to this static set which invalidates all previous descriptors, subscribe to this event to renew them.
*/
inline static Event<void> onDescriptorRefresh;
private:
inline static std::vector<DescriptorSet*> sets;
inline static VkDescriptorPool descriptorPool = VK_NULL_HANDLE;
consteval static std::uint32_t GetUniqueDiscriptorCount() {
DescriptorEntry types[] = {{VK_DESCRIPTOR_TYPE_SAMPLER, 0},{VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 0},{VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, 0},{VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, 0},{VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER, 0},{VK_DESCRIPTOR_TYPE_STORAGE_TEXEL_BUFFER, 0},{VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 0},{VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 0},{VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC, 0},{VK_DESCRIPTOR_TYPE_STORAGE_BUFFER_DYNAMIC, 0},{VK_DESCRIPTOR_TYPE_INPUT_ATTACHMENT, 0},{VK_DESCRIPTOR_TYPE_INLINE_UNIFORM_BLOCK, 0},{VK_DESCRIPTOR_TYPE_ACCELERATION_STRUCTURE_KHR, 0},{VK_DESCRIPTOR_TYPE_ACCELERATION_STRUCTURE_NV, 0},{VK_DESCRIPTOR_TYPE_SAMPLE_WEIGHT_IMAGE_QCOM, 0},{VK_DESCRIPTOR_TYPE_BLOCK_MATCH_IMAGE_QCOM, 0},{VK_DESCRIPTOR_TYPE_MUTABLE_EXT, 0},{VK_DESCRIPTOR_TYPE_PARTITIONED_ACCELERATION_STRUCTURE_NV, 0},{VK_DESCRIPTOR_TYPE_INLINE_UNIFORM_BLOCK_EXT, 0},{VK_DESCRIPTOR_TYPE_MUTABLE_VALVE, 0}};
for(const DescriptorBinding& binding : MeshShader::descriptors) {
for(DescriptorEntry& type : types) {
if(type.type == binding.type) {
type.occured = true;
}
}
}
for(const DescriptorBinding& binding : FragmentShader::descriptors) {
for(DescriptorEntry& type : types) {
if(type.type == binding.type) {
type.occured = true;
}
}
}
std::uint32_t size = 0;
for(DescriptorEntry& type : types) {
if(type.occured) {
size++;
}
}
return size;
}
constexpr static std::uint32_t uniqueDescriptorCount = GetUniqueDiscriptorCount();
consteval static std::array<VkDescriptorPoolSize, uniqueDescriptorCount> GetPoolSizes() {
std::array<VkDescriptorPoolSize, uniqueDescriptorCount> types = {};
for(std::uint32_t i = 0; i < uniqueDescriptorCount; i++){
types[i].descriptorCount = 12345;
}
for(const DescriptorBinding& binding : MeshShader::descriptors) {
bool found = false;
for(VkDescriptorPoolSize& type : types) {
if(type.type == binding.type && type.descriptorCount != 12345) {
type.descriptorCount += 1;
found = true;
}
}
if(!found) {
for(std::uint32_t i = 0; i < uniqueDescriptorCount; i++){
if(types[i].descriptorCount == 12345) {
types[i].type = binding.type;
types[i].descriptorCount = 1;
break;
}
}
}
}
for(const DescriptorBinding& binding : FragmentShader::descriptors) {
bool found = false;
for(VkDescriptorPoolSize& type : types) {
if(type.type == binding.type) {
type.descriptorCount += 1;
found = true;
}
}
if(!found) {
for(std::uint32_t i = 0; i < uniqueDescriptorCount; i++){
if(types[i].descriptorCount == 12345) {
types[i].type = binding.type;
types[i].descriptorCount = 1;
break;
}
}
}
}
return types;
}
public:
/**
* @brief Allocates 2 VkDescriptorSet from the pool, all descriptors previously allocated become invalid.
*/
DescriptorSet() {
sets.push_back(this);
if(descriptorPool != VK_NULL_HANDLE) {
vkDestroyDescriptorPool(VulkanDevice::device, descriptorPool, nullptr);
}
std::array<VkDescriptorPoolSize, uniqueDescriptorCount> poolSizes = GetPoolSizes();
for(VkDescriptorPoolSize& size : poolSizes) {
size.descriptorCount *= sets.size();
}
VkDescriptorPoolCreateInfo descriptorPoolInfo = vks::initializers::descriptorPoolCreateInfo(uniqueDescriptorCount, poolSizes.data(), sets.size()*2);
VulkanDevice::CHECK_VK_RESULT(vkCreateDescriptorPool(VulkanDevice::device, &descriptorPoolInfo, nullptr, &descriptorPool));
for(DescriptorSet* set : sets) {
VkDescriptorSetAllocateInfo allocInfo = vks::initializers::descriptorSetAllocateInfo(descriptorPool, &VulkanPipeline<MeshShader, FragmentShader>::descriptorSetLayout[0], 2);
VulkanDevice::CHECK_VK_RESULT(vkAllocateDescriptorSets(VulkanDevice::device, &allocInfo, set->set));
}
onDescriptorRefresh.Invoke();
}
/**
* @brief Deallocates 2 VkDescriptorSet from the pool.
*/
~DescriptorSet() {
sets.erase(find(sets.begin(), sets.end(), this));
}
};
}

View file

@ -1,38 +0,0 @@
/*
Crafter®.Graphics
Copyright (C) 2025 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;
#include "../lib/stb_truetype.h"
export module Crafter.Graphics:Font;
import std;
namespace Crafter {
export class Font {
public:
std::vector<unsigned char> fontBuffer;
std::int_fast32_t ascent;
std::int_fast32_t descent;
std::int_fast32_t lineGap;
stbtt_fontinfo font;
Font(const std::filesystem::path& font);
};
}

View file

@ -0,0 +1,69 @@
/*
Crafter®.Graphics
Copyright (C) 2025 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;
#include <vulkan/vulkan.h>
export module Crafter.Graphics:HeightmapShader;
import std;
import :Camera;
import :VulkanPipeline;
import :DescriptorSet;
import Crafter.Math;
namespace Crafter {
struct HeightMapData {
MatrixRowMajor<float, 4, 4, 1> mvp;
uint32_t stride;
float spacing;
uint32_t padding[14];
};
export template <typename VertexType>
class HeightmapShader {
public:
MatrixRowMajor<float, 4, 4, 1> transform;
Camera* camera;
Buffer<HeightMapData> data;
Buffer<VertexType> heights;
std::uint32_t threadCount;
EventListener<void> cameraUpdate;
HeightmapShader(uint32_t amount, uint32_t stride, float spacing, Camera* camera) : threadCount(amount), heights(VK_BUFFER_USAGE_STORAGE_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, amount * 4 * 64), data(VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT), camera(camera), cameraUpdate(
&camera->onUpdate, [this](){
Update();
}
) {
data.value->stride = stride;
data.value->spacing = spacing;
transform = MatrixRowMajor<float, 4, 4, 1>::Identity();
}
void WriteDescriptors(VkDescriptorSet set) {
VkWriteDescriptorSet write[2] = {
vks::initializers::writeDescriptorSet(set, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 0, &data.descriptor),
vks::initializers::writeDescriptorSet(set, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1, &heights.descriptor)
};
vkUpdateDescriptorSets(VulkanDevice::device, 2, &write[0], 0, nullptr);
}
void Update() {
data.value->mvp = camera->projectionView*transform;
}
};
}

View file

@ -0,0 +1,153 @@
/*
Crafter®.Graphics
Copyright (C) 2025 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;
#include <vulkan/vulkan.h>
export module Crafter.Graphics:Mesh;
import std;
import Crafter.Math;
import :VulkanBuffer;
import :Types;
namespace Crafter {
/**
* @brief Holder for a indexed mesh.
* @tparam VertexType The vertex type to use that is internally stored, this must match the type the glsl shader expects.
*/
export template <typename VertexType>
class Mesh {
public:
std::uint32_t vertexCount;
std::uint32_t indexCount;
Buffer<VertexType> verticies;
Buffer<std::uint32_t> indicies;
/**
* @brief Constructs Mesh with empty vertex and index buffer.
* @param vertexCount count of the vertex buffer
* @param vertexCount count of the index buffer
*/
Mesh(std::uint32_t vertexCount, std::uint32_t indexCount) : vertexCount(vertexCount), indexCount(indexCount), verticies(VK_BUFFER_USAGE_STORAGE_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, vertexCount), indicies(VK_BUFFER_USAGE_STORAGE_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, indexCount) {
}
/**
* @brief Constructs Mesh from an in memory char buffer.
* @param asset pointer to the char buffer.
*/
Mesh(const char* asset) requires(std::same_as<VertexType, Vertex>) : vertexCount(reinterpret_cast<const std::uint32_t*>(asset)[0]), indexCount(((reinterpret_cast<const std::uint32_t*>(asset)[1]) + 63) & ~63), verticies(VK_BUFFER_USAGE_STORAGE_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, vertexCount), indicies(VK_BUFFER_USAGE_STORAGE_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, indexCount) {
uint32_t indexCountNoPadding = reinterpret_cast<const std::uint32_t*>(asset)[1];
const float* verticies = reinterpret_cast<const float*>(asset+sizeof(std::uint32_t)*2);
std::uint32_t counter = 0;
for(std::uint32_t i = 0; i < vertexCount*3; i+=3) {
this->verticies.value[counter].x = verticies[i];
this->verticies.value[counter].y = verticies[i+1];
this->verticies.value[counter].z = verticies[i+2];
this->verticies.value[counter].w = 1.0f;
counter++;
}
memcpy(indicies.value, asset+(sizeof(std::uint32_t)*2)+(vertexCount*sizeof(float)*3), indexCountNoPadding*sizeof(std::uint32_t));
for(std::uint32_t i = indexCountNoPadding; i < indexCountNoPadding+(indexCountNoPadding%64); i++) {
indicies.value[i] = 0;//pad indicies to nearest 64
}
}
/**
* @brief Constructs UV Mesh from an in memory char buffer.
* @param asset pointer to the char buffer.
*/
Mesh(const char* asset) requires(std::same_as<VertexType, VertexUV>) : vertexCount(reinterpret_cast<const std::uint32_t*>(asset)[0]), indexCount(((reinterpret_cast<const std::uint32_t*>(asset)[1]) + 63) & ~63), verticies(VK_BUFFER_USAGE_STORAGE_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, vertexCount), indicies(VK_BUFFER_USAGE_STORAGE_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, indexCount) {
uint32_t indexCountNoPadding = reinterpret_cast<const std::uint32_t*>(asset)[1];
const float* verticies = reinterpret_cast<const float*>(asset+sizeof(std::uint32_t)*2);
std::uint32_t counter = 0;
for(std::uint32_t i = 0; i < vertexCount*5; i+=5) {
this->verticies.value[counter].x = verticies[i];
this->verticies.value[counter].y = verticies[i+1];
this->verticies.value[counter].z = verticies[i+2];
this->verticies.value[counter].u = verticies[i+3];
this->verticies.value[counter].v = verticies[i+4];
this->verticies.value[counter].w = 1.0f;
counter++;
}
memcpy(indicies.value, asset+(sizeof(std::uint32_t)*2)+(vertexCount*sizeof(float)*5), indexCountNoPadding*sizeof(std::uint32_t));
for(std::uint32_t i = indexCountNoPadding; i < indexCountNoPadding+(indexCountNoPadding%64); i++) {
indicies.value[i] = 0;//pad indicies to nearest 64
}
}
/**
* @brief Constructs heightmap Mesh from an in memory char buffer.
* @param heights pointer to heights.
* @param sizeX size in the X dimension.
* @param sizeZ size in the Y dimension.
* @param spacing spacing between the points .
*/
Mesh(float* heights, uint32_t sizeX, uint32_t sizeZ, float spacing) : vertexCount(sizeX*sizeZ), indexCount(((((sizeX-1)*(sizeZ-1))*6)+ 63) & ~63), verticies(VK_BUFFER_USAGE_STORAGE_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, vertexCount), indicies(VK_BUFFER_USAGE_STORAGE_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, indexCount) {
uint32_t indexCountNoPadding = ((sizeX-1)*(sizeZ-1))*6;
for (float x = 0; x < sizeX; x++)
{
for (float z = 0; z < sizeZ; z++)
{
uint32_t xInt = static_cast<uint32_t>(x);
uint32_t zInt = static_cast<uint32_t>(z);
Vector<float, 3> pos((x * spacing) - ((sizeX*spacing) / 2.0f), heights[xInt * sizeX + zInt], (z * spacing) - ((sizeZ*spacing) / 2.0f));
Vector<float, 2> uv(x / sizeX, z / sizeZ);
VertexType vertex;
vertex.x = pos.x;
vertex.y = pos.y;
vertex.z = pos.z;
vertex.w = 1.0f;
vertex.u = uv.x;
vertex.v = uv.y;
verticies.value[xInt * sizeX + zInt] = vertex;
}
}
for (uint32_t x = 0; x < sizeX - 1; x++)
{
for (uint32_t z = 0; z < sizeZ - 1; z++)
{
uint32_t topLeftIndex = x * sizeZ + z;
uint32_t topRightIndex = topLeftIndex + 1;
uint32_t bottomLeftIndex = topLeftIndex + sizeZ;
uint32_t bottomRightIndex = bottomLeftIndex + 1;
uint32_t index = (x * sizeX + z)*6;
indicies.value[index] = topRightIndex;
indicies.value[index + 1] = bottomLeftIndex;
indicies.value[index + 2] = topLeftIndex;
indicies.value[index + 3] = bottomRightIndex;
indicies.value[index + 4] = bottomLeftIndex;
indicies.value[index + 5] = topRightIndex;
}
}
for(std::uint32_t i = indexCountNoPadding; i < indexCountNoPadding+(indexCountNoPadding%64); i++) {
indicies.value[i] = 0;//pad indicies to nearest 64
}
}
};
}

View file

@ -0,0 +1,95 @@
/*
Crafter®.Graphics
Copyright (C) 2025 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;
#include <vulkan/vulkan.h>
export module Crafter.Graphics:MeshShader;
import std;
import :Mesh;
import :Camera;
import :VulkanPipeline;
import :DescriptorSet;
import Crafter.Math;
namespace Crafter {
/**
* @brief Shader for rendering indexed meshes
* @tparam VertexType The vertex type to use that is internally stored, this must match the type the glsl shader expects.
*/
export template <typename VertexType>
class MeshShader {
public:
MatrixRowMajor<float, 4, 4, 1> transform;
Mesh<VertexType>* mesh;
Camera* camera;
Buffer<MatrixRowMajor<float, 4, 4, 1>> mvp;
std::uint32_t threadCount;
private:
EventListener<void> cameraUpdate;
public:
/**
* @brief Constructs the MeshShader with a mesh.
* The Model-View-Projection (MVP) matrix and the transform matrix are initialized to the identity matrix.
*
* @param mesh Pointer to the mesh to use. The mesh must remain valid for the lifetime of this object.
*/
MeshShader(Mesh<VertexType>* mesh) : threadCount(std::ceil(static_cast<double>(mesh->indexCount)/64/3)), mvp(VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT), mesh(mesh), camera(nullptr) {
transform = MatrixRowMajor<float, 4, 4, 1>::Identity();
*mvp.value = MatrixRowMajor<float, 4, 4, 1>::Identity();
}
/**
* @brief Constructs the MeshShader with a mesh.
* The transform is initialized to identity, the transform is initialized to identity and the mvp to the camera's projectionView.
*
* @param mesh Pointer to the mesh to use. The mesh must remain valid for the lifetime of this object.
* @param mesh Pointer to the camera to use. The camera must remain valid for the lifetime of this object.
*/
MeshShader(Mesh<VertexType>* mesh, Camera* camera) : threadCount(std::ceil(static_cast<double>(mesh->indexCount)/64/3)), mvp(VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT), mesh(mesh), camera(camera), cameraUpdate(
&camera->onUpdate, [this](){
Update();
}
) {
transform = MatrixRowMajor<float, 4, 4, 1>::Identity();
*mvp.value = camera->projectionView;
}
/**
* @brief Writes this class's 3 descriptors to the set, this method must be called before rendering with this shader.
* Slot 0 mvp: VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER.
* Slot 1 vertex: VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER.
* Slot 2 index: VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER.
*/
void WriteDescriptors(VkDescriptorSet set) {
VkWriteDescriptorSet write[3] = {
vks::initializers::writeDescriptorSet(set, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 0, &mvp.descriptor),
vks::initializers::writeDescriptorSet(set, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1, &mesh->verticies.descriptor),
vks::initializers::writeDescriptorSet(set, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 2, &mesh->indicies.descriptor)
};
vkUpdateDescriptorSets(VulkanDevice::device, 3, &write[0], 0, nullptr);
}
/**
* @brief Must be called after every update to the camera or this transform
*/
void Update() {
*mvp.value = camera->projectionView*transform;
}
};
}

View file

@ -0,0 +1,80 @@
/*
Crafter®.Graphics
Copyright (C) 2025 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;
#include <vulkan/vulkan.h>
export module Crafter.Graphics:TextureShader;
import std;
import :VulkanTexture;
import :VulkanPipeline;
import :DescriptorSet;
namespace Crafter {
/**
* @brief Creates a sampler for a provided VulkanTexture<PixelType>.
* @tparam PixelType The pixeltype to use.
*/
export template <typename PixelType>
class TextureShader {
private:
VkSampler textureSampler;
VkDescriptorImageInfo imageInfo;
VulkanTexture<PixelType>* texture;
public:
/**
* @brief Creates a sampler for a texture.
* @param texture A pointer to the texture to create the sampler for, the texture must remain valid for the lifetime of this object.
*/
TextureShader(VulkanTexture<PixelType>* texture) : texture(texture) {
VkSamplerCreateInfo samplerInfo{};
samplerInfo.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO;
samplerInfo.magFilter = VK_FILTER_LINEAR;
samplerInfo.minFilter = VK_FILTER_LINEAR;
samplerInfo.addressModeU = VK_SAMPLER_ADDRESS_MODE_REPEAT;
samplerInfo.addressModeV = VK_SAMPLER_ADDRESS_MODE_REPEAT;
samplerInfo.addressModeW = VK_SAMPLER_ADDRESS_MODE_REPEAT;
samplerInfo.anisotropyEnable = VK_FALSE;
samplerInfo.maxAnisotropy = 1;
samplerInfo.borderColor = VK_BORDER_COLOR_INT_OPAQUE_BLACK;
samplerInfo.unnormalizedCoordinates = VK_FALSE;
samplerInfo.compareEnable = VK_FALSE;
samplerInfo.compareOp = VK_COMPARE_OP_ALWAYS;
samplerInfo.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR;
samplerInfo.mipLodBias = 0.0f;
samplerInfo.minLod = 0.0f;
samplerInfo.maxLod = 0.0f;
VulkanDevice::CHECK_VK_RESULT(vkCreateSampler(VulkanDevice::device, &samplerInfo, nullptr, &textureSampler));
imageInfo.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
imageInfo.imageView = texture->imageView;
imageInfo.sampler = textureSampler;
}
/**
* @brief Writes this class's 1 descriptor to the set, this method must be called before rendering with this shader.
* Slot 0: VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER.
*/
void WriteDescriptors(VkDescriptorSet set) {
VkWriteDescriptorSet write = vks::initializers::writeDescriptorSet(set, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 0, &imageInfo);
vkUpdateDescriptorSets(VulkanDevice::device, 1, &write, 0, nullptr);
}
};
}

View file

@ -24,7 +24,10 @@ import Crafter.Event;
import :Types; import :Types;
namespace Crafter { namespace Crafter {
export class Font; /**
* @brief General use UiElement for handeling input events.
* Add to a window's elements member to start recieving events.
*/
export class UiElement { export class UiElement {
public: public:
Event<MouseMoveEvent> onMouseMove; Event<MouseMoveEvent> onMouseMove;
@ -50,19 +53,36 @@ namespace Crafter {
float anchorOffsetX; float anchorOffsetX;
float anchorOffsetY; float anchorOffsetY;
std::vector<Pixel_BU8_GU8_RU8_AU8> buffer; std::vector<Pixel_BU8_GU8_RU8_AU8> buffer;
std::vector<std::unique_ptr<UiElement>> children; std::vector<UiElement> children;
UiElement(float anchorX, float anchorY, float relativeWidth, float relativeHeight, float anchorOffsetX = 0.5, float anchorOffsetY = 0.5, float z = 0, bool ignoreScaling = false); /**
* @brief Constructs a UiElement with absolute dimensions
* @param anchorX Relative position where this elements x anchor (top-left) is placed to its parent x anchor
* @param anchorY Relative position where this elements y anchor (top-left) is placed to its parent y anchor
* @param bufferWidth The width of this elements pixel buffer
* @param bufferHeight The height of this elements pixel buffer
* @param absoluteWidth The absolute x size in pixels this element should be scaled to
* @param absoluteHeight The absolute y size in pixels this element should be scaled to
* @param anchorOffsetX The amount this element's anchor should be offset from the top left corner (0.5 to in the middle)
* @param anchorOffsetY The amount this element's anchor should be offset from the top left corner (0.5 to place it in the middle)
* @param z This elements Z position
* @param ignoreScaling Wether this element ignores the scaling of the window, if true its size will be scaled according to the window scale
*/
UiElement(float anchorX, float anchorY, std::uint32_t bufferWidth, std::uint32_t bufferHeight, std::uint32_t absoluteWidth, std::uint32_t absoluteHeight, float anchorOffsetX = 0.5, float anchorOffsetY = 0.5, float z = 0, bool ignoreScaling = false); UiElement(float anchorX, float anchorY, std::uint32_t bufferWidth, std::uint32_t bufferHeight, std::uint32_t absoluteWidth, std::uint32_t absoluteHeight, float anchorOffsetX = 0.5, float anchorOffsetY = 0.5, float z = 0, bool ignoreScaling = false);
UiElement(float anchorX, float anchorY, std::uint32_t bufferWidth, std::uint32_t bufferHeight, float relativeWidth, float relativeHeight, float anchorOffsetX = 0.5, float anchorOffsetY = 0.5, float z = 0, bool ignoreScaling = false);
UiElement(const std::filesystem::path& image, float anchorX, float anchorY, float relativeWidth, float relativeHeight, float anchorOffsetX = 0.5, float anchorOffsetY = 0.5, float z = 0, bool ignoreScaling = false);
UiElement(UiElement&&) noexcept = default;
UiElement& operator=(UiElement&&) noexcept = default;
};
export class TextElement : public UiElement { /**
public: * @brief Constructs a UiElement with relative dimensions
TextElement(const std::string_view text, float size, Pixel_BU8_GU8_RU8_AU8 color, const Font& font, float anchorX, float anchorY, float relativeWidth, float relativeHeight, float anchorOffsetX = 0.5, float anchorOffsetY = 0.5, float z = 0, bool ignoreScaling = false); * @param anchorX Relative position where this elements x anchor (top-left) is placed to its parent x anchor
void RenderText(const std::string_view text, float size, Pixel_BU8_GU8_RU8_AU8 color, const Font& font); * @param anchorY Relative position where this elements y anchor (top-left) is placed to its parent y anchor
* @param bufferWidth The width of this elements pixel buffer
* @param bufferHeight The height of this elements pixel buffer
* @param relativeWidth The relative x size this element should be scaled to compared to its parent
* @param relativeHeight The relative y size this element should be scaled to compared to its parent
* @param anchorOffsetX The amount this element's anchor should be offset from the top left corner (0.5 to in the middle)
* @param anchorOffsetY The amount this element's anchor should be offset from the top left corner (0.5 to place it in the middle)
* @param z This elements Z position
* @param ignoreScaling Wether this element ignores the scaling of the window, if true its size will be scaled according to the window scale
*/
UiElement(float anchorX, float anchorY, std::uint32_t bufferWidth, std::uint32_t bufferHeight, float relativeWidth, float relativeHeight, float anchorOffsetX = 0.5, float anchorOffsetY = 0.5, float z = 0, bool ignoreScaling = false);
}; };
} }

View file

@ -0,0 +1,69 @@
/*
Crafter®.Graphics
Copyright (C) 2025 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
*/
#include <vulkan/vulkan.h>
export module Crafter.Graphics:VoxelShader;
import std;
import :Mesh;
import :Camera;
import :VulkanPipeline;
import :DescriptorSet;
import Crafter.Math;
namespace Crafter {
export struct VoxelShaderData {
MatrixRowMajor<float, 4, 4, 1> mvp;
uint32_t sizeX;
uint32_t sizeY;
uint32_t sizeZ;
};
export template <typename VoxelType>
class VoxelShader {
public:
MatrixRowMajor<float, 4, 4, 1> transform;
Camera* camera;
Buffer<VoxelType> grid;
Buffer<VoxelShaderData> data;
std::uint32_t threadCount;
EventListener<void> cameraUpdate;
VoxelShader(uint32_t sizeX, uint32_t sizeY, uint sizeZ, Camera* camera) : threadCount((sizeX*sizeY*sizeZ)/16), data(VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT), grid(VK_BUFFER_USAGE_STORAGE_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, sizeX*sizeY*sizeZ) ,camera(camera), cameraUpdate(
&camera->onUpdate, [this](){
Update();
}
) {
transform = MatrixRowMajor<float, 4, 4, 1>::Identity();
data.value->sizeX = sizeX;
data.value->sizeY = sizeY;
data.value->sizeZ = sizeZ;
}
void WriteDescriptors(VkDescriptorSet set) {
VkWriteDescriptorSet write[2] = {
vks::initializers::writeDescriptorSet(set, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 0, &data.descriptor),
vks::initializers::writeDescriptorSet(set, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1, &grid.descriptor),
};
vkUpdateDescriptorSets(VulkanDevice::device, 2, &write[0], 0, nullptr);
}
void Update() {
data.value->mvp = camera->projectionView*transform;
}
};
}

View file

@ -0,0 +1,99 @@
/*
Crafter®.Graphics
Copyright (C) 2025 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
*/
#include <vulkan/vulkan.h>
export module Crafter.Graphics:VulkanBuffer;
import std;
import :VulkanDevice;
namespace Crafter {
/**
* @brief VkBuffer holder.
* Stores a value and handles buffer mapping and lifetime management.
* @tparam T The value to store.
*/
export template <typename T>
class Buffer {
public:
T* value;
VkDescriptorBufferInfo descriptor;
public:
/**
* @brief Creates and initializes a Vulkan buffer with the specified usage and memory properties.
*
* This constructor allocates a buffer capable of holding `count` elements of type T.
* The buffer usage and memory property flags control how the buffer will be used
* and how its memory is managed.
*
* @param usageFlags Vulkan buffer usage flags that define allowed operations on the buffer
* (e.g., vertex buffer, index buffer, uniform buffer).
* @param memoryPropertyFlags Vulkan memory property flags specifying the desired memory
* properties (e.g., device local, host visible).
* @param count Number of elements to allocate space for in the buffer. The total buffer size
* will be `count * sizeof(T)`. Must be above 0.
*/
Buffer(VkBufferUsageFlags usageFlags, VkMemoryPropertyFlags memoryPropertyFlags, std::uint32_t count = 1) {
VkBufferCreateInfo bufferCreateInfo = vks::initializers::bufferCreateInfo(usageFlags, sizeof(T)*count);
VulkanDevice::CHECK_VK_RESULT(vkCreateBuffer(VulkanDevice::device, &bufferCreateInfo, nullptr, &buffer));
// Create the memory backing up the buffer handle
VkMemoryRequirements memReqs;
VkMemoryAllocateInfo memAlloc = vks::initializers::memoryAllocateInfo();
vkGetBufferMemoryRequirements(VulkanDevice::device, buffer, &memReqs);
memAlloc.allocationSize = memReqs.size;
// Find a memory type index that fits the properties of the buffer
memAlloc.memoryTypeIndex = VulkanDevice::GetMemoryType(memReqs.memoryTypeBits, memoryPropertyFlags);
// If the buffer has VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT set we also need to enable the appropriate flag during allocation
VkMemoryAllocateFlagsInfoKHR allocFlagsInfo{};
if (usageFlags & VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT) {
allocFlagsInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_FLAGS_INFO_KHR;
allocFlagsInfo.flags = VK_MEMORY_ALLOCATE_DEVICE_ADDRESS_BIT_KHR;
memAlloc.pNext = &allocFlagsInfo;
}
VulkanDevice::CHECK_VK_RESULT(vkAllocateMemory(VulkanDevice::device, &memAlloc, nullptr, &memory));
alignment = memReqs.alignment;
usageFlags = usageFlags;
memoryPropertyFlags = memoryPropertyFlags;
descriptor.offset = 0;
descriptor.buffer = buffer;
descriptor.range = sizeof(T)*count;
VulkanDevice::CHECK_VK_RESULT(vkBindBufferMemory(VulkanDevice::device, buffer, memory, 0));
VulkanDevice::CHECK_VK_RESULT(vkMapMemory(VulkanDevice::device, memory, 0, sizeof(T)*count, 0, reinterpret_cast<void**>(&value)));
}
/**
* @brief Frees all resources assosiacted with the buffer.
*/
~Buffer() {
vkUnmapMemory(VulkanDevice::device, memory);
vkDestroyBuffer(VulkanDevice::device, buffer, nullptr);
vkFreeMemory(VulkanDevice::device, memory, nullptr);
}
private:
VkDeviceSize alignment = 0;
VkMemoryPropertyFlags memoryPropertyFlags;
VkBufferUsageFlags usageFlags;
VkBuffer buffer = VK_NULL_HANDLE;
VkDeviceMemory memory = VK_NULL_HANDLE;
};
}

View file

@ -0,0 +1,73 @@
/*
Crafter®.Graphics
Copyright (C) 2025 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
*/
#include <vulkan/vulkan.h>
#include <vulkan/vulkan_wayland.h>
export module Crafter.Graphics:VulkanDevice;
import std;
export namespace Crafter {
/**
* @brief Static class for initizializing and holding various vulkan handles.
*/
class VulkanDevice {
public:
inline static VkInstance instance = VK_NULL_HANDLE;
inline static VkDebugUtilsMessengerEXT debugMessenger = VK_NULL_HANDLE;
inline static VkPhysicalDevice physDevice = VK_NULL_HANDLE;
inline static VkDevice device = VK_NULL_HANDLE;
inline static std::uint32_t queueFamilyIndex = 0;
inline static VkQueue queue = VK_NULL_HANDLE;
inline static VkCommandPool commandPool = VK_NULL_HANDLE;
inline static VkSwapchainKHR swapchain = VK_NULL_HANDLE;
inline static PFN_vkCmdDrawMeshTasksEXT vkCmdDrawMeshTasksEXTProc;
inline static PFN_vkCmdBeginRenderingKHR vkCmdBeginRenderingKHRProc;
inline static PFN_vkCmdEndRenderingKHR vkCmdEndRenderingKHRProc;
inline static VkPhysicalDeviceMemoryProperties memoryProperties;
inline static VkFormat depthFormat = VK_FORMAT_UNDEFINED;
/**
* @brief Creates the vulkan device, this must be called before any use of vulkan.
*/
static void CreateDevice();
/**
* @brief Checks if result is VK_SUCCESS.
* @param result The VkResult to check.
* @throws std::runtime_error If result != VK_SUCCESS.
*/
static void CHECK_VK_RESULT(VkResult result);
/**
* @brief Finds a suitable memory type index for the device based on required properties.
*
* This function searches through the memory types supported by the physical device to find
* a memory type index that matches the specified bitmask and has the requested property flags.
*
* @param typeBits A bitmask representing the memory types that are suitable. Each bit corresponds
* to a memory type index; if the bit is set, that memory type is considered.
* @param properties A bitmask of VkMemoryPropertyFlags specifying the desired memory properties,
* such as device local, host visible, etc.
*
* @return The index of a suitable memory type that fulfills the requirements.
*
* @throws std::runtime_error If no suitable memory type is found matching the criteria.
*/
static std::uint32_t GetMemoryType(std::uint32_t typeBits, VkMemoryPropertyFlags properties);
};
}

View file

@ -0,0 +1,142 @@
/*
Crafter®.Graphics
Copyright (C) 2025 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;
#include <vulkan/vulkan.h>
export module Crafter.Graphics:VulkanPipeline;
import std;
import :VulkanDevice;
import :VulkanShader;
import :WindowWaylandVulkan;
namespace Crafter {
export template <typename MeshShader, typename FragmentShader> class DescriptorSet;
/**
* @brief A generic Vulkan graphics pipeline wrapper using template-based shaders.
*
* This class encapsulates the creation and management of a Vulkan graphics pipeline
* with a specified mesh shader and fragment shader. It provides static members for
* the pipeline, pipeline layout, and descriptor set layouts, which are shared across
* all instances of this template specialization.
*
* @tparam MeshShader The mesh shader type used in the pipeline.
* @tparam FragmentShader The fragment shader type used in the pipeline.
*
* @note Before using this to render pipeline, the CreatePipeline() function must be called.
*
* @pre VulkanDevice::CreateDevice() must be called before creating or using this class,
VulkanShader::CreateShader() must be called before CreatePipeline().
*/
export template <typename MeshShader, typename FragmentShader>
class VulkanPipeline {
private:
friend class DescriptorSet<MeshShader, FragmentShader>;
template <typename Shader, VkShaderStageFlagBits Flag>
consteval static std::array<VkDescriptorSetLayoutBinding, Shader::descriptorCount> GetDescriptorSet() {
std::array<VkDescriptorSetLayoutBinding, Shader::descriptorCount> set;
for(std::uint32_t i = 0; i < Shader::descriptors.size(); i++) {
set[i] = {Shader::descriptors[i].slot, Shader::descriptors[i].type, 1, Flag, nullptr};
}
return set;
}
inline static VkDescriptorSetLayout descriptorSetLayout[2];
public:
inline static VkPipeline pipeline;
inline static VkPipelineLayout pipelineLayout;
/**
* @brief Creates the vulkan pipeline, this must be called before any use of this pipeline and all shaders must be created before this pipeline is created.
*/
static void CreatePipeline() {
// Layout
constexpr std::array<VkDescriptorSetLayoutBinding, MeshShader::descriptorCount> setLayoutBindingsMesh = GetDescriptorSet<MeshShader, VK_SHADER_STAGE_MESH_BIT_EXT>();
VkDescriptorSetLayoutCreateInfo descriptorLayoutInfoMesh = vks::initializers::descriptorSetLayoutCreateInfo(setLayoutBindingsMesh.data(), MeshShader::descriptorCount);
VulkanDevice::CHECK_VK_RESULT(vkCreateDescriptorSetLayout(VulkanDevice::device, &descriptorLayoutInfoMesh, nullptr, &descriptorSetLayout[0]));
constexpr std::array<VkDescriptorSetLayoutBinding, FragmentShader::descriptorCount> setLayoutBindingsFragment = GetDescriptorSet<FragmentShader, VK_SHADER_STAGE_FRAGMENT_BIT>();
VkDescriptorSetLayoutCreateInfo descriptorLayoutInfoFragment = vks::initializers::descriptorSetLayoutCreateInfo(setLayoutBindingsFragment.data(), FragmentShader::descriptorCount);
VulkanDevice::CHECK_VK_RESULT(vkCreateDescriptorSetLayout(VulkanDevice::device, &descriptorLayoutInfoFragment, nullptr, &descriptorSetLayout[1]));
// Layout
VkPipelineLayoutCreateInfo pipelineLayoutInfo = vks::initializers::pipelineLayoutCreateInfo(&descriptorSetLayout[0], 2);
VulkanDevice::CHECK_VK_RESULT(vkCreatePipelineLayout(VulkanDevice::device, &pipelineLayoutInfo, nullptr, &pipelineLayout));
// Pipeline
VkPipelineInputAssemblyStateCreateInfo inputAssemblyState = vks::initializers::pipelineInputAssemblyStateCreateInfo(VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST, 0, VK_FALSE);
VkPipelineRasterizationStateCreateInfo rasterizationState = vks::initializers::pipelineRasterizationStateCreateInfo(VK_POLYGON_MODE_FILL, VK_CULL_MODE_BACK_BIT, VK_FRONT_FACE_CLOCKWISE, 0);
VkPipelineColorBlendAttachmentState blendAttachmentState = vks::initializers::pipelineColorBlendAttachmentState(0xf, VK_FALSE);
VkPipelineColorBlendStateCreateInfo colorBlendState = vks::initializers::pipelineColorBlendStateCreateInfo(1, &blendAttachmentState);
VkPipelineDepthStencilStateCreateInfo depthStencilState = vks::initializers::pipelineDepthStencilStateCreateInfo(VK_TRUE, VK_TRUE, VK_COMPARE_OP_LESS_OR_EQUAL);
VkPipelineViewportStateCreateInfo viewportState = vks::initializers::pipelineViewportStateCreateInfo(1, 1, 0);
VkPipelineMultisampleStateCreateInfo multisampleState = vks::initializers::pipelineMultisampleStateCreateInfo(VK_SAMPLE_COUNT_1_BIT, 0);
std::vector<VkDynamicState> dynamicStateEnables = { VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR };
VkPipelineDynamicStateCreateInfo dynamicState = vks::initializers::pipelineDynamicStateCreateInfo(dynamicStateEnables);
std::array<VkPipelineShaderStageCreateInfo, 2> shaderStages;
VkFormat format = VK_FORMAT_R8G8B8A8_UNORM;
VkPipelineRenderingCreateInfoKHR pipeline_create{VK_STRUCTURE_TYPE_PIPELINE_RENDERING_CREATE_INFO_KHR};
pipeline_create.pNext = VK_NULL_HANDLE;
pipeline_create.colorAttachmentCount = 1;
pipeline_create.pColorAttachmentFormats = &format;
pipeline_create.depthAttachmentFormat = VulkanDevice::depthFormat;
VkGraphicsPipelineCreateInfo pipelineCI = vks::initializers::pipelineCreateInfo(pipelineLayout, VK_NULL_HANDLE, 0);
pipelineCI.pNext = &pipeline_create;
pipelineCI.pRasterizationState = &rasterizationState;
pipelineCI.pColorBlendState = &colorBlendState;
pipelineCI.pMultisampleState = &multisampleState;
pipelineCI.pViewportState = &viewportState;
pipelineCI.pDepthStencilState = &depthStencilState;
pipelineCI.pDynamicState = &dynamicState;
pipelineCI.stageCount = static_cast<uint32_t>(shaderStages.size());
pipelineCI.pStages = shaderStages.data();
// Not using a vertex shader, mesh shading doesn't require vertex input state
pipelineCI.pInputAssemblyState = nullptr;
pipelineCI.pVertexInputState = nullptr;
//Mesh stage of the pipeline
shaderStages[0].sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
shaderStages[0].stage = MeshShader::_stage;
shaderStages[0].module = MeshShader::shader;
shaderStages[0].pName = MeshShader::_entrypoint.value;
shaderStages[0].flags = 0;
shaderStages[0].pSpecializationInfo = nullptr;
shaderStages[0].pNext = nullptr;
// Fragment stage of the pipeline
shaderStages[1].sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
shaderStages[1].stage = FragmentShader::_stage;
shaderStages[1].module = FragmentShader::shader;
shaderStages[1].pName = FragmentShader::_entrypoint.value;
shaderStages[1].flags = 0;
shaderStages[1].pSpecializationInfo = nullptr;
shaderStages[1].pNext = nullptr;
VulkanDevice::CHECK_VK_RESULT(vkCreateGraphicsPipelines(VulkanDevice::device, VK_NULL_HANDLE, 1, &pipelineCI, nullptr, &pipeline));
}
};
}

View file

@ -0,0 +1,105 @@
/*
Crafter®.Graphics
Copyright (C) 2025 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;
#include <vulkan/vulkan.h>
export module Crafter.Graphics:VulkanShader;
import std;
import :VulkanDevice;
namespace Crafter {
export template<size_t N>
struct StringLiteral {
constexpr StringLiteral(const char (&str)[N]) {
std::copy_n(str, N, value);
}
char value[N];
};
export struct DescriptorBinding {
VkDescriptorType type;
std::uint32_t slot;
};
/**
* @brief Represents a Vulkan shader module with specified configuration.
*
* This class template encapsulates a Vulkan shader, parametrized by the shader's
* source path, entry point, shader stage, and its descriptor bindings.
*
* @tparam path A compile-time string literal specifying the file path to the shader source or binary.
* @tparam entrypoint A compile-time string literal specifying the entry point function name within the shader.
* @tparam stage The Vulkan shader stage flag indicating the shader type (e.g., vertex, fragment).
* @tparam DescriptorCount The number of descriptor bindings used by the shader.
* @tparam Descriptors An array of descriptor binding configurations used by the shader.
*
* This class facilitates compile-time specification of shader resources and metadata,
* allowing for type-safe shader management and potentially more efficient shader pipeline creation.
* CreateShader must be called before using this shader in a pipeline.
*/
export template <
StringLiteral path,
StringLiteral entrypoint,
VkShaderStageFlagBits stage,
std::uint32_t DescriptorCount,
const std::array<DescriptorBinding, DescriptorCount> Descriptors
>
class VulkanShader {
public:
constexpr static VkShaderStageFlagBits _stage = stage;
constexpr static std::array<DescriptorBinding, DescriptorCount> descriptors = Descriptors;
constexpr static std::uint32_t descriptorCount = DescriptorCount;
constexpr static StringLiteral _entrypoint = entrypoint;
inline static VkShaderModule shader;
/**
* @brief Creates the vulkan shader, this must be called before any use of this shader.
*/
static void CreateShader() {
std::ifstream file(path.value, std::ios::binary);
if (!file) {
std::cerr << "Error: Could not open file " << path.value << std::endl;
}
// Move to the end of the file to determine its size
file.seekg(0, std::ios::end);
std::streamsize size = file.tellg();
file.seekg(0, std::ios::beg);
std::vector<std::uint32_t> spirv(size / sizeof(std::uint32_t));
// Read the data into the vector
if (!file.read(reinterpret_cast<char*>(spirv.data()), size)) {
std::cerr << "Error: Could not read data from file" << std::endl;
}
file.close();
VkShaderModuleCreateInfo module_info{VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO};
module_info.codeSize = spirv.size() * sizeof(uint32_t);
module_info.pCode = spirv.data();
VkShaderModule shader_module;
VulkanDevice::CHECK_VK_RESULT(vkCreateShaderModule(VulkanDevice::device, &module_info, nullptr, &shader));
}
};
}

View file

@ -0,0 +1,212 @@
/*
Crafter®.Graphics
Copyright (C) 2025 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;
#include <vulkan/vulkan.h>
export module Crafter.Graphics:VulkanTexture;
import std;
import :VulkanDevice;
import :VulkanBuffer;
namespace Crafter {
/**
* @brief Represents a Vulkan texture with pixel data of a specified type.
*
* This class manages a Vulkan image resource along with its associated memory,
* buffer, and image view. It provides functionality to create textures either
* by specifying dimensions or loading from an asset, and to update the texture
* data on the GPU using command buffers.
*
* @tparam PixelType The type of the pixel data stored in the texture.
*/
export template <typename PixelType>
class VulkanTexture {
public:
uint32_t width;
uint32_t height;
private:
VkImage image;
VkDeviceMemory imageMemory;
Buffer<PixelType> buffer;
VkImageView imageView;
public:
/**
* @brief Constructs a VulkanTexture with the given dimensions.
*
* Creates a Vulkan texture image with the specified width and height,
* initializing necessary resources.
*
* @param width The width of the texture.
* @param height The height of the texture.
* @param cmd Vulkan command buffer used for resource initialization.
*/
VulkanTexture(std::uint32_t width, std::uint32_t height, VkCommandBuffer cmd) : width(width), height(height), buffer(VK_BUFFER_USAGE_TRANSFER_SRC_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, width*height) {
VkImageCreateInfo imageInfo{};
imageInfo.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
imageInfo.imageType = VK_IMAGE_TYPE_2D;
imageInfo.extent.width = width;
imageInfo.extent.height = height;
imageInfo.extent.depth = 1;
imageInfo.mipLevels = 1;
imageInfo.arrayLayers = 1;
imageInfo.format = VK_FORMAT_R8G8B8A8_UNORM;
imageInfo.tiling = VK_IMAGE_TILING_OPTIMAL;
imageInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
imageInfo.usage = VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT;
imageInfo.samples = VK_SAMPLE_COUNT_1_BIT;
imageInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
VulkanDevice::CHECK_VK_RESULT(vkCreateImage(VulkanDevice::device, &imageInfo, nullptr, &image));
VkMemoryRequirements memRequirements;
vkGetImageMemoryRequirements(VulkanDevice::device, image, &memRequirements);
VkMemoryAllocateInfo allocInfo{};
allocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
allocInfo.allocationSize = memRequirements.size;
allocInfo.memoryTypeIndex = VulkanDevice::GetMemoryType(memRequirements.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
VulkanDevice::CHECK_VK_RESULT(vkAllocateMemory(VulkanDevice::device, &allocInfo, nullptr, &imageMemory));
vkBindImageMemory(VulkanDevice::device, image, imageMemory, 0);
VkImageViewCreateInfo viewInfo{};
viewInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
viewInfo.image = image;
viewInfo.viewType = VK_IMAGE_VIEW_TYPE_2D;
viewInfo.format = VK_FORMAT_R8G8B8A8_UNORM;
viewInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
viewInfo.subresourceRange.baseMipLevel = 0;
viewInfo.subresourceRange.levelCount = 1;
viewInfo.subresourceRange.baseArrayLayer = 0;
viewInfo.subresourceRange.layerCount = 1;
VulkanDevice::CHECK_VK_RESULT(vkCreateImageView(VulkanDevice::device, &viewInfo, nullptr, &imageView));
TransitionImageLayout(cmd, buffer, image, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
}
/**
* @brief Constructs a VulkanTexture by loading from an in memory asset.
*
* Loads texture pixel data from the specified asset and initializes
* the Vulkan image resource accordingly.
*
* @param asset Pointer to the in memory asset.
* @param cmd Vulkan command buffer used for resource initialization.
*/
VulkanTexture(const char* asset, VkCommandBuffer cmd) : VulkanTexture(reinterpret_cast<const std::uint32_t*>(asset)[0], reinterpret_cast<const std::uint32_t*>(asset)[1], cmd) {
Update(reinterpret_cast<const PixelType*>(reinterpret_cast<const std::uint32_t*>(asset)+2), cmd);
}
/**
* @brief Updates the texture with new pixel data.
*
* Copies the given pixel data into the texture's buffer and issues Vulkan
* commands to upload the data to the GPU image.
*
* @param bufferdata Pointer to the new pixel data.
* @param cmd Vulkan command buffer used for the update operation.
*/
void Update(const PixelType* bufferdata, VkCommandBuffer cmd) {
memcpy(buffer.value, bufferdata, height*width*sizeof(PixelType));
TransitionImageLayout(cmd, buffer, image, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL);
VkBufferImageCopy region{};
region.bufferOffset = 0;
region.bufferRowLength = 0;
region.bufferImageHeight = 0;
region.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
region.imageSubresource.mipLevel = 0;
region.imageSubresource.baseArrayLayer = 0;
region.imageSubresource.layerCount = 1;
region.imageOffset = {0, 0, 0};
region.imageExtent = { width, height, 1};
vkCmdCopyBufferToImage(
cmd,
buffer.buffer,
image,
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
1,
&region
);
TransitionImageLayout(cmd, buffer, image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
}
private:
void TransitionImageLayout(VkCommandBuffer cmd, Buffer<PixelType>& buffer, VkImage image, VkImageLayout oldLayout, VkImageLayout newLayout) {
VkImageMemoryBarrier barrier{};
barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
barrier.oldLayout = oldLayout;
barrier.newLayout = newLayout;
barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
barrier.image = image;
barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
barrier.subresourceRange.baseMipLevel = 0;
barrier.subresourceRange.levelCount = 1;
barrier.subresourceRange.baseArrayLayer = 0;
barrier.subresourceRange.layerCount = 1;
barrier.srcAccessMask = 0;
barrier.dstAccessMask = 0;
VkPipelineStageFlags sourceStage;
VkPipelineStageFlags destinationStage;
if (oldLayout == VK_IMAGE_LAYOUT_UNDEFINED && newLayout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL) {
barrier.srcAccessMask = 0;
barrier.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
sourceStage = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT;
destinationStage = VK_PIPELINE_STAGE_TRANSFER_BIT;
} else if (oldLayout == VK_IMAGE_LAYOUT_UNDEFINED && newLayout == VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL) {
barrier.srcAccessMask = 0;
barrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT;
sourceStage = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT;
destinationStage = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT;
} else if (oldLayout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL && newLayout == VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL) {
barrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
barrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT;
sourceStage = VK_PIPELINE_STAGE_TRANSFER_BIT;
destinationStage = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT;
} else if (oldLayout == VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL && newLayout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL) {
barrier.srcAccessMask = VK_ACCESS_SHADER_READ_BIT;
barrier.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
sourceStage = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT;
destinationStage = VK_PIPELINE_STAGE_TRANSFER_BIT;
} else {
throw std::invalid_argument("unsupported layout transition!");
}
vkCmdPipelineBarrier(
cmd,
sourceStage, destinationStage,
0,
0, nullptr,
0, nullptr,
1, &barrier
);
}
};
}

View file

@ -18,13 +18,6 @@ License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/ */
module;
#include <wayland-client.h>
#include <xkbcommon/xkbcommon.h>
#include "../lib/xdg-shell-client-protocol.h"
#include "../lib/wayland-xdg-decoration-unstable-v1-client-protocol.h"
export module Crafter.Graphics:Window; export module Crafter.Graphics:Window;
import std; import std;
import Crafter.Event; import Crafter.Event;
@ -39,9 +32,15 @@ export namespace Crafter {
std::int32_t height; std::int32_t height;
}; };
/**
* @brief Represents a GUI window handling input events, mouse states, keyboard states, and UI elements.
*
* The Window class encapsulates event handling for mouse and keyboard interactions,
* manages the state of the mouse and keyboard, and stores UI elements contained within the window.
* It also holds window-specific properties such as name, dimensions, and scaling factor.
*/
class Window { class Window {
public: public:
Pixel_BU8_GU8_RU8_AU8* framebuffer = nullptr;
Event<MousePoint> onMouseRightClick; Event<MousePoint> onMouseRightClick;
Event<MousePoint> onMouseLeftClick; Event<MousePoint> onMouseLeftClick;
Event<MousePoint> onMouseRightHold; Event<MousePoint> onMouseRightHold;
@ -71,42 +70,19 @@ export namespace Crafter {
std::uint32_t height; std::uint32_t height;
float scale = 1; float scale = 1;
bool open = true; bool open = true;
/**
* @brief Constructs a Window with a given name and dimensions.
* @param name The title of the window.
* @param width The width of the window in pixels.
* @param height The height of the window in pixels.
*/
Window(std::string name, std::uint32_t width, std::uint32_t height); Window(std::string name, std::uint32_t width, std::uint32_t height);
~Window(); /**
void StartSync(); * @brief Calculates the real position and size of an UiElement
*
* @param element The UI element to get the position from.
* @return The actual position and size of the element after scaling.
*/
ScaleData ScaleElement(const UiElement& element); ScaleData ScaleElement(const UiElement& element);
protected:
bool configured = false;
wl_shm* shm = NULL;
wl_seat* seat = NULL;
xdg_toplevel* xdgToplevel = NULL;
xdg_wm_base* xdgWmBase = NULL;
zxdg_decoration_manager_v1* manager = NULL;
wl_surface* surface = NULL;
wl_buffer* buffer = NULL;
wl_buffer* backBuffer = NULL;
xdg_surface* xdgSurface = NULL;
wl_display* display = NULL;
wl_callback* cb = nullptr;
inline static wl_compositor* compositor = NULL;
static wl_pointer_listener pointer_listener;
static wl_keyboard_listener keyboard_listener;
static wl_seat_listener seat_listener;
static wl_registry_listener registry_listener;
static xdg_surface_listener xdg_surface_listener;
static xdg_toplevel_listener xdg_toplevel_listener;
static wl_callback_listener surface_frame_listener;
static void wl_surface_frame_done(void *data, wl_callback *cb, uint32_t time);
static void PointerListenerHandleMotion(void* data, wl_pointer* wl_pointer, uint time, wl_fixed_t surface_x, wl_fixed_t surface_y);
static void PointerListenerHandleAxis(void*, wl_pointer*, std::uint32_t, std::uint32_t, wl_fixed_t value);
static void PointerListenerHandleEnter(void* data, wl_pointer* wl_pointer, uint serial, wl_surface* surface, wl_fixed_t surface_x, wl_fixed_t surface_y);
static void PointerListenerHandleLeave(void*, wl_pointer*, std::uint32_t, wl_surface*);
static void xdg_toplevel_handle_close(void* data, xdg_toplevel*);
static void handle_global(void* data, wl_registry* registry, std::uint32_t name, const char* interface, std::uint32_t version);
static void pointer_handle_button(void* data, wl_pointer* pointer, std::uint32_t serial, std::uint32_t time, std::uint32_t button, std::uint32_t state);
static void seat_handle_capabilities(void* data, wl_seat* seat, uint32_t capabilities);
static void xdg_surface_handle_configure(void* data, xdg_surface* xdg_surface, std::uint32_t serial);
}; };
} }

View file

@ -0,0 +1,85 @@
/*
Crafter®.Graphics
Copyright (C) 2025 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;
#include <wayland-client.h>
#include <xkbcommon/xkbcommon.h>
#include "../lib/xdg-shell-client-protocol.h"
#include "../lib/wayland-xdg-decoration-unstable-v1-client-protocol.h"
export module Crafter.Graphics:WindowWayland;
import std;
import Crafter.Event;
import :UiElement;
import :Types;
import :Window;
export namespace Crafter {
/**
* @class WindowWayland
* @brief A window using the Wayland display server protocol.
*
* This class inherits from the base Window class and provides
* functionality specific to Wayland for creating and managing
* windows.
*/
class WindowWayland : public Window {
public:
/**
* @brief Constructs a Wayland window with a given name and size.
* @param name The title for the window.
* @param width The width of the window in pixels.
* @param height The height of the window in pixels.
*/
WindowWayland(std::string name, std::uint32_t width, std::uint32_t height);
/**
* @brief Destructor cleans up Wayland-specific window resources.
*/
~WindowWayland();
protected:
bool configured = false;
wl_shm* shm = NULL;
wl_seat* seat = NULL;
xdg_toplevel* xdgToplevel = NULL;
xdg_wm_base* xdgWmBase = NULL;
zxdg_decoration_manager_v1* manager = NULL;
wl_surface* surface = NULL;
wl_buffer* buffer = NULL;
xdg_surface* xdgSurface = NULL;
wl_display* display = NULL;
inline static wl_compositor* compositor = NULL;
static wl_pointer_listener pointer_listener;
static wl_keyboard_listener keyboard_listener;
static wl_seat_listener seat_listener;
static wl_registry_listener registry_listener;
static xdg_surface_listener xdg_surface_listener;
static xdg_toplevel_listener xdg_toplevel_listener;
static void PointerListenerHandleMotion(void* data, wl_pointer* wl_pointer, uint time, wl_fixed_t surface_x, wl_fixed_t surface_y);
static void PointerListenerHandleAxis(void*, wl_pointer*, std::uint32_t, std::uint32_t, wl_fixed_t value);
static void PointerListenerHandleEnter(void* data, wl_pointer* wl_pointer, uint serial, wl_surface* surface, wl_fixed_t surface_x, wl_fixed_t surface_y);
static void PointerListenerHandleLeave(void*, wl_pointer*, std::uint32_t, wl_surface*);
static void xdg_toplevel_handle_close(void* data, xdg_toplevel*);
static void handle_global(void* data, wl_registry* registry, std::uint32_t name, const char* interface, std::uint32_t version);
static void pointer_handle_button(void* data, wl_pointer* pointer, std::uint32_t serial, std::uint32_t time, std::uint32_t button, std::uint32_t state);
static void seat_handle_capabilities(void* data, wl_seat* seat, uint32_t capabilities);
static void xdg_surface_handle_configure(void* data, xdg_surface* xdg_surface, std::uint32_t serial);
};
}

View file

@ -0,0 +1,117 @@
/*
Crafter®.Graphics
Copyright (C) 2025 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;
#include <vulkan/vulkan.h>
#include <vulkan/vulkan_wayland.h>
export module Crafter.Graphics:WindowWaylandVulkan;
import std;
import Crafter.Event;
import :WindowWayland;
namespace Crafter {
struct DepthStencil {
VkImage image;
VkDeviceMemory memory;
VkImageView view;
};
struct Semaphores {
// Swap chain image presentation
VkSemaphore presentComplete;
// Command buffer submission and execution
VkSemaphore renderComplete;
};
/**
* @class WindowWaylandVulkan
* @brief A Wayland window specialized for Vulkan rendering.
*
* This class extends the WindowWayland base class to support Vulkan graphics
* integration within a Wayland environment. It provides methods for initializing
* Vulkan command buffers and managing drawing operations either synchronously or asynchronously.
*
* The class exposes an event `onDraw` which is called for every frame.
* @pre VulkanDevice::CreateDevice() must be called before creating or using this class.
*/
export class WindowWaylandVulkan : public WindowWayland {
public:
Event<VkCommandBuffer> onDraw;
/**
* @brief Constructs a WindowWaylandVulkan instance.
*
* @param name The title of the window.
* @param width The width of the window in pixels.
* @param height The height of the window in pixels.
*/
WindowWaylandVulkan(std::string name, std::uint32_t width, std::uint32_t height);
/**
* @brief Destructor for the WindowWaylandVulkan.
*
* Cleans up Vulkan and Wayland resources associated with this window.
*/
~WindowWaylandVulkan();
/**
* @brief Starts Vulkan initialization and returns a command buffer.
*
* This command buffer can be used to record Vulkan setup commands.
*
* @return VkCommandBuffer A Vulkan command buffer for recording initialization commands.
*/
VkCommandBuffer StartInit();
/**
* @brief Completes Vulkan initialization.
*
* Finalizes any remaining setup required after recording commands returned by StartInit().
*/
void FinishInit();
/**
* @brief Starts the event loop asynchronously.
*
* This method triggers rendering without blocking the caller.
*/
void StartAsync();
/**
* @brief Starts the event loop synchronously.
*
* This method blocks the caller until the event loop stops.
*/
void StartSync();
private:
void CreateSwapchain();
VkSurfaceKHR vulkanSurface = VK_NULL_HANDLE;
VkSwapchainKHR swapChain = VK_NULL_HANDLE;
VkFormat colorFormat;
VkColorSpaceKHR colorSpace;
std::vector<VkImage> images;
std::vector<VkImageView> imageViews;
std::thread thread;
std::vector<VkCommandBuffer> drawCmdBuffers;
std::vector<VkFramebuffer> frameBuffers;
VkSubmitInfo submitInfo;
DepthStencil depthStencil;
Semaphores semaphores;
uint32_t currentBuffer = 0;
VkPipelineStageFlags submitPipelineStages = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
VkRenderPass renderPass = VK_NULL_HANDLE;
};
}

View file

@ -0,0 +1,65 @@
/*
Crafter®.Graphics
Copyright (C) 2025 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
*/
export module Crafter.Graphics:WindowWaylandWayland;
import std;
import Crafter.Event;
import :WindowWayland;
export namespace Crafter {
/**
* @brief A specialized Wayland window implementation for direct drawing.
*
* This class inherits from `WindowWayland` and provides a framebuffer using the pixel format `Pixel_BU8_GU8_RU8_AU8`.
*/
class WindowWaylandWayland : public WindowWayland {
public:
/**
* @brief Framebuffer for the window using the BGRA 8-bit unsigned pixel format, use this for direct drawing to the window.
*/
Pixel_BU8_GU8_RU8_AU8* framebuffer = nullptr;
/**
* @brief Constructs a new WindowWaylandWayland object.
*
* @param name The title of the window.
* @param width The width of the window in pixels.
* @param height The height of the window in pixels.
*/
WindowWaylandWayland(std::string name, std::uint32_t width, std::uint32_t height);
/**
* @brief Destructor cleans up Wayland-specific window resources and framebuffer.
*/
~WindowWaylandWayland();
/**
* @brief Starts the event loop asynchronously.
*
* This method triggers rendering without blocking the caller.
*/
void StartAsync();
/**
* @brief Starts the event loop synchronously.
*
* This method blocks the caller until the event loop stops.
*/
void StartSync();
private:
std::thread thread;
};
}

View file

@ -21,9 +21,11 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
export module Crafter.Graphics; export module Crafter.Graphics;
export import :Window; export import :Window;
export import :WindowWayland;
export import :WindowWaylandWayland;
export import :UiElement; export import :UiElement;
export import :Types; export import :Types;
export import :Font; export import :Camera;
// export import :WindowWaylandVulkan; // export import :WindowWaylandVulkan;
// export import :VulkanBuffer; // export import :VulkanBuffer;

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -3,14 +3,14 @@
"configurations": [ "configurations": [
{ {
"name": "base", "name": "base",
"implementations": ["implementations/Crafter.Graphics-Window", "implementations/Crafter.Graphics-UiElement", "implementations/Crafter.Graphics-Font"], "implementations": ["implementations/Crafter.Graphics-Window", "implementations/Crafter.Graphics-UiElement", "implementations/Crafter.Graphics-Camera"],
"interfaces": ["interfaces/Crafter.Graphics-Window", "interfaces/Crafter.Graphics", "interfaces/Crafter.Graphics-UiElement", "interfaces/Crafter.Graphics-Types", "interfaces/Crafter.Graphics-Font"], "interfaces": ["interfaces/Crafter.Graphics-Window", "interfaces/Crafter.Graphics", "interfaces/Crafter.Graphics-UiElement", "interfaces/Crafter.Graphics-Types", "interfaces/Crafter.Graphics-Camera"],
"type": "library" "type": "library"
}, },
{ {
"name": "wayland", "name": "wayland",
"implementations": [], "implementations": ["implementations/Crafter.Graphics-WindowWayland", "implementations/Crafter.Graphics-WindowWaylandWayland"],
"interfaces": [], "interfaces": ["interfaces/Crafter.Graphics-WindowWayland", "interfaces/Crafter.Graphics-WindowWaylandWayland"],
"libs": ["wayland-client", "xkbcommon"], "libs": ["wayland-client", "xkbcommon"],
"c_files": ["lib/xdg-shell-protocol", "lib/wayland-xdg-decoration-unstable-v1-client-protocol"], "c_files": ["lib/xdg-shell-protocol", "lib/wayland-xdg-decoration-unstable-v1-client-protocol"],
"extends": ["base"] "extends": ["base"]
@ -70,6 +70,10 @@
{ {
"path":"https://forgejo.catcrafts.net/Catcrafts/Crafter.Event.git", "path":"https://forgejo.catcrafts.net/Catcrafts/Crafter.Event.git",
"configuration":"lib" "configuration":"lib"
},
{
"path":"https://forgejo.catcrafts.net/Catcrafts/Crafter.Math.git",
"configuration":"lib"
} }
] ]
}, },
@ -80,6 +84,10 @@
{ {
"path":"https://forgejo.catcrafts.net/Catcrafts/Crafter.Event.git", "path":"https://forgejo.catcrafts.net/Catcrafts/Crafter.Event.git",
"configuration":"lib-debug" "configuration":"lib-debug"
},
{
"path":"https://forgejo.catcrafts.net/Catcrafts/Crafter.Math.git",
"configuration":"lib-debug"
} }
] ]
}, },