vulkan window

This commit is contained in:
Jorijn van der Graaf 2026-01-27 22:34:24 +01:00
commit cfb43257a0
9 changed files with 1674 additions and 34 deletions

View file

@ -10,31 +10,20 @@ int main() {
Things like VkDevice are static members of the VulkanDevice class. Things like VkDevice are static members of the VulkanDevice class.
*/ */
VulkanDevice::CreateDevice(); VulkanDevice::CreateDevice();
WindowVulkan window(1280, 720, "HelloVulkan");
/* // /*
This creates a window titled "HelloWindow" with a size of 1280x720 pixels. // StartInit gives you a VkCommandBuffer to use before the event loop starts
The WindowWaylandVulkan class is a specialized window implementation. // Use this for inititializing things like textures.
that uses the Wayland display server protocol and vulkan renderer (hence the name "WaylandVulkan"). // */
*/ // VkCommandBuffer cmd = window.StartInit();
WindowWaylandVulkan window("HelloWindow", 1280, 720);
/* // /*
StartInit gives you a VkCommandBuffer to use before the event loop starts // FinishInit executes all commands recorded to StartInit.
Use this for inititializing things like textures. // This must be called before the the event loops starts if you called StartInit before.
*/ // */
VkCommandBuffer cmd = window.StartInit(); // window.FinishInit();
/* window.Render();
FinishInit executes all commands recorded to StartInit.
This must be called before the the event loops starts if you called StartInit before.
*/
window.FinishInit();
/*
This starts the windows main event loop, allowing it to respond to user input and system events.
The window will remain open and responsive until it is closed.
You can hook into various events through the event system.
This call blocks the current thread; to run the event loop asynchronously, use StartAsync instead.
*/
window.StartSync(); window.StartSync();
} }

View file

@ -2,19 +2,12 @@
"name": "crafter-graphics", "name": "crafter-graphics",
"configurations": [ "configurations": [
{ {
"name": "example", "name": "executable",
"standard": "c++26", "implementations": ["main"],
"source_files": ["main"],
"module_files": [],
"build_dir": "build",
"output_dir": "bin",
"type":"executable",
"libs": [],
"flags": ["-Wno-uninitialized"],
"dependencies": [ "dependencies": [
{ {
"path":"../../project.json", "path":"../../project.json",
"configuration":"lib-debug" "configuration":"lib-vulkan"
} }
] ]
} }

View file

@ -0,0 +1,301 @@
/*
Crafter®.Graphics
Copyright (C) 2026 Catcrafts®
catcrafts.net
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License version 3.0 as published by the Free Software Foundation;
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
module;
#include <vulkan/vulkan.h>
#include <vulkan/vulkan_wayland.h>
#include <vulkan/vk_enum_string_helper.h>
#define GET_EXTENSION_FUNCTION(_id) ((PFN_##_id)(vkGetInstanceProcAddr(instance, #_id)))
module Crafter.Graphics:VulkanDevice_impl;
import :VulkanDevice;
import std;
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::CheckVkResult(VkResult result) {
if (result != VK_SUCCESS)
{
throw std::runtime_error(string_VkResult(result));
}
}
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;
CheckVkResult(vkEnumerateInstanceLayerProperties(&instanceLayerCount, NULL));
std::vector<VkLayerProperties> instanceLayerProperties(instanceLayerCount);
CheckVkResult(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;
}
CheckVkResult(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;
CheckVkResult(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;
CheckVkResult(vkEnumerateDeviceLayerProperties(physDevice, &deviceLayerCount, NULL));
std::vector<VkLayerProperties> deviceLayerProperties(deviceLayerCount);
CheckVkResult(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 (std::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;
}
CheckVkResult(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;
CheckVkResult(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

@ -0,0 +1,937 @@
/*
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 version 3.0 as published by the Free Software Foundation;
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
module;
#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <linux/input-event-codes.h>
#include "../lib/xdg-shell-client-protocol.h"
#include "../lib/wayland-xdg-decoration-unstable-v1-client-protocol.h"
#include "../lib/fractional-scale-v1.h"
#include "../lib/viewporter.h"
#include <string.h>
#include <linux/input.h>
#include <sys/mman.h>
#include <wayland-cursor.h>
#include <xkbcommon/xkbcommon.h>
#include <errno.h>
#include <fcntl.h>
#include <print>
#include <wayland-client.h>
#include <wayland-client-protocol.h>
#include <vulkan/vulkan.h>
#include <vulkan/vulkan_wayland.h>
module Crafter.Graphics:Window_vulkan_impl;
import :Window;
import :MouseElement;
import std;
import :Types;
import :Shm;
import :VulkanDevice;
import :VulkanTransition;
import Crafter.Event;
using namespace Crafter;
void WindowVulkan::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::CheckVkResult(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::CheckVkResult(vkGetPhysicalDeviceSurfacePresentModesKHR(VulkanDevice::physDevice, vulkanSurface, &presentModeCount, NULL));
assert(presentModeCount > 0);
std::vector<VkPresentModeKHR> presentModes(presentModeCount);
VulkanDevice::CheckVkResult(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::CheckVkResult(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::CheckVkResult(vkGetSwapchainImagesKHR(VulkanDevice::device, swapChain, &imageCount, nullptr));
// Get the swap chain images
images.resize(imageCount);
VulkanDevice::CheckVkResult(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::CheckVkResult(vkCreateImageView(VulkanDevice::device, &colorAttachmentView, nullptr, &imageViews[i]));
}
}
WindowVulkan::WindowVulkan(std::uint32_t width, std::uint32_t height) : Window(width, 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);
xdg_surface_add_listener(xdgSurface, &xdg_surface_listener, this);
xdg_toplevel_add_listener(xdgToplevel, &xdg_toplevel_listener, this);
wl_surface_commit(surface);
wp_scale = wp_fractional_scale_manager_v1_get_fractional_scale(fractionalScaleManager, surface);
wp_fractional_scale_v1_add_listener(wp_scale, &wp_fractional_scale_v1_listener, this);
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);
wpViewport = wp_viewporter_get_viewport(wpViewporter, surface);
wp_viewport_set_destination(wpViewport, std::ceil(width/scale), std::ceil(height/scale));
wl_surface_commit(surface);
VkWaylandSurfaceCreateInfoKHR createInfo = {};
createInfo.sType = VK_STRUCTURE_TYPE_WAYLAND_SURFACE_CREATE_INFO_KHR;
createInfo.display = display;
createInfo.surface = surface;
VulkanDevice::CheckVkResult(vkCreateWaylandSurfaceKHR(VulkanDevice::instance, &createInfo, NULL, &vulkanSurface));
// Get list of supported surface formats
std::uint32_t formatCount;
VulkanDevice::CheckVkResult(vkGetPhysicalDeviceSurfaceFormatsKHR(VulkanDevice::physDevice, vulkanSurface, &formatCount, NULL));
assert(formatCount > 0);
std::vector<VkSurfaceFormatKHR> surfaceFormats(formatCount);
VulkanDevice::CheckVkResult(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_B8G8R8A8_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, 1> 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;
VkAttachmentReference colorReference = {};
colorReference.attachment = 0;
colorReference.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
VkSubpassDescription subpassDescription = {};
subpassDescription.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS;
subpassDescription.colorAttachmentCount = 1;
subpassDescription.pColorAttachments = &colorReference;
subpassDescription.pDepthStencilAttachment = nullptr;
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 = 0;
dependencies[0].dstAccessMask = 0;
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::CheckVkResult(vkCreateRenderPass(VulkanDevice::device, &renderPassInfo, nullptr, &renderPass));
// Create frame buffers for every swap chain image
frameBuffers.resize(images.size());
for (uint32_t i = 0; i < frameBuffers.size(); i++)
{
const VkImageView attachments[1] = {
imageViews[i]
};
VkFramebufferCreateInfo frameBufferCreateInfo{};
frameBufferCreateInfo.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO;
frameBufferCreateInfo.renderPass = renderPass;
frameBufferCreateInfo.attachmentCount = 1;
frameBufferCreateInfo.pAttachments = attachments;
frameBufferCreateInfo.width = width;
frameBufferCreateInfo.height = height;
frameBufferCreateInfo.layers = 1;
VulkanDevice::CheckVkResult(vkCreateFramebuffer(VulkanDevice::device, &frameBufferCreateInfo, nullptr, &frameBuffers[i]));
}
drawCmdBuffers.resize(images.size());
VkCommandBufferAllocateInfo cmdBufAllocateInfo {};
cmdBufAllocateInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
cmdBufAllocateInfo.commandPool = VulkanDevice::commandPool;
cmdBufAllocateInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
cmdBufAllocateInfo.commandBufferCount = static_cast<uint32_t>(drawCmdBuffers.size());
VulkanDevice::CheckVkResult(vkAllocateCommandBuffers(VulkanDevice::device, &cmdBufAllocateInfo, drawCmdBuffers.data()));
VkSemaphoreCreateInfo semaphoreCreateInfo {};
semaphoreCreateInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO;
VulkanDevice::CheckVkResult(vkCreateSemaphore(VulkanDevice::device, &semaphoreCreateInfo, nullptr, &semaphores.presentComplete));
VulkanDevice::CheckVkResult(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.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
submitInfo.pWaitDstStageMask = &submitPipelineStages;
submitInfo.waitSemaphoreCount = 1;
submitInfo.pWaitSemaphores = &semaphores.presentComplete;
submitInfo.signalSemaphoreCount = 1;
submitInfo.pSignalSemaphores = &semaphores.renderComplete;
submitInfo.pNext = VK_NULL_HANDLE;
}
WindowVulkan::WindowVulkan(std::uint32_t width, std::uint32_t height, const std::string_view title) : WindowVulkan(width, height) {
xdg_toplevel_set_title(xdgToplevel, title.data());
}
WindowVulkan::~WindowVulkan() {
xdg_toplevel_destroy(xdgToplevel);
xdg_surface_destroy(xdgSurface);
wl_surface_destroy(surface);
}
void WindowVulkan::StartSync() {
while (open && wl_display_dispatch(display) != -1) {
}
}
void WindowVulkan::Render() {
// Acquire the next image from the swap chain
VulkanDevice::CheckVkResult(vkAcquireNextImageKHR(VulkanDevice::device, swapChain, UINT64_MAX, semaphores.presentComplete, (VkFence)nullptr, &currentBuffer));
submitInfo.commandBufferCount = 1;
submitInfo.pCommandBuffers = &drawCmdBuffers[currentBuffer];
VkCommandBufferBeginInfo cmdBufInfo {};
cmdBufInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
VkClearValue clearValues[1];
clearValues[0].color = { };
VulkanDevice::CheckVkResult(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);
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 };
VkRenderingInfo render_info = {VK_STRUCTURE_TYPE_RENDERING_INFO_KHR,VK_NULL_HANDLE,0};
render_info.renderArea = VkRect2D{VkOffset2D{}, VkExtent2D{static_cast<std::uint32_t>(width), static_cast<std::uint32_t>(height)}};
render_info.viewMask = 0;
render_info.layerCount = 1;
render_info.colorAttachmentCount = 1;
render_info.pColorAttachments = &color_attachment_info;
render_info.pDepthAttachment = VK_NULL_HANDLE;
render_info.pStencilAttachment = VK_NULL_HANDLE;
VulkanDevice::vkCmdBeginRenderingKHRProc(drawCmdBuffers[currentBuffer], &render_info);
VkViewport viewport {};
viewport.width = static_cast<float>(width);
viewport.height = static_cast<float>(height);
viewport.minDepth = 0;
viewport.maxDepth = 1;
vkCmdSetViewport(drawCmdBuffers[currentBuffer], 0, 1, &viewport);
VkRect2D scissor {};
scissor.extent.width = width;
scissor.extent.height = height;
scissor.offset.x = 0;
scissor.offset.y = 0;
vkCmdSetScissor(drawCmdBuffers[currentBuffer], 0, 1, &scissor);
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::CheckVkResult(vkEndCommandBuffer(drawCmdBuffers[currentBuffer]));
VulkanDevice::CheckVkResult(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::CheckVkResult(vkQueuePresentKHR(VulkanDevice::queue, &presentInfo));
VulkanDevice::CheckVkResult(vkQueueWaitIdle(VulkanDevice::queue));
}
VkCommandBuffer WindowVulkan::StartInit() {
VkCommandBufferBeginInfo cmdBufInfo {};
cmdBufInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
VulkanDevice::CheckVkResult(vkBeginCommandBuffer(drawCmdBuffers[currentBuffer], &cmdBufInfo));
return drawCmdBuffers[currentBuffer];
}
void WindowVulkan::FinishInit() {
VkSubmitInfo submitInfo{};
submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
submitInfo.commandBufferCount = 1;
submitInfo.pCommandBuffers = &drawCmdBuffers[currentBuffer];
VulkanDevice::CheckVkResult(vkEndCommandBuffer(drawCmdBuffers[currentBuffer]));
VulkanDevice::CheckVkResult(vkQueueSubmit(VulkanDevice::queue, 1, &submitInfo, VK_NULL_HANDLE));
VulkanDevice::CheckVkResult(vkQueueWaitIdle(VulkanDevice::queue));
}
void WindowVulkan::QueueRender() {
if(cb == nullptr) {
cb = wl_surface_frame(surface);
wl_callback_add_listener(cb, &wl_callback_listener, this);
}
}
void WindowVulkan::StartUpdate() {
lastFrameBegin = std::chrono::high_resolution_clock::now();
cb = wl_surface_frame(surface);
wl_callback_add_listener(cb, &wl_callback_listener, this);
updating = true;
}
void WindowVulkan::StopUpdate() {
updating = false;
}
void WindowVulkan::SetTitle(const std::string_view title) {
xdg_toplevel_set_title(xdgToplevel, title.data());
}
void WindowVulkan::Resize(std::uint32_t width, std::uint32_t height) {
}
void WindowVulkan::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);
}
#ifdef CRAFTER_TIMING
std::chrono::time_point<std::chrono::high_resolution_clock> framEnd;
#endif
void WindowVulkan::wl_surface_frame_done(void* data, struct wl_callback *cb, uint32_t time)
{
auto start = std::chrono::high_resolution_clock::now();
wl_callback_destroy(cb);
cb = nullptr;
WindowVulkan* window = reinterpret_cast<WindowVulkan*>(data);
#ifdef CRAFTER_TIMING
window->vblank = duration_cast<std::chrono::milliseconds>(start - window->frameEnd);
#endif
if(window->updating) {
cb = wl_surface_frame(window->surface);
wl_callback_add_listener(cb, &WindowVulkan::wl_callback_listener, window);
window->onUpdate.Invoke({start, start-window->lastFrameBegin});
#ifdef CRAFTER_TIMING
window->totalUpdate = std::chrono::nanoseconds(0);
window->updateTimings.clear();
for (const std::pair<const EventListener<FrameTime>*, std::chrono::nanoseconds>& entry : window->onUpdate.listenerTimes) {
window->updateTimings.push_back(entry);
window->totalUpdate += entry.second;
}
#endif
}
#ifdef CRAFTER_TIMING
auto renderStart = std::chrono::high_resolution_clock::now();
window->renderTimings.clear();
#endif
window->Render();
#ifdef CRAFTER_TIMING
auto renderEnd = std::chrono::high_resolution_clock::now();
window->totalRender = renderEnd - renderStart;
#endif
#ifdef CRAFTER_TIMING
window->frameEnd = std::chrono::high_resolution_clock::now();
window->frameTimes.push_back(window->totalUpdate+window->totalRender);
// Keep only the last 100 frame times
if (window->frameTimes.size() > 100) {
window->frameTimes.erase(window->frameTimes.begin());
}
#endif
window->lastFrameBegin = start;
}
void WindowVulkan::pointer_handle_button(void* data, wl_pointer* pointer, std::uint32_t serial, std::uint32_t time, std::uint32_t button, std::uint32_t state) {
WindowVulkan* window = reinterpret_cast<WindowVulkan*>(data);
if (button == BTN_LEFT) {
if(state == WL_POINTER_BUTTON_STATE_PRESSED) {
window->mouseLeftHeld = true;
window->onMouseLeftClick.Invoke(window->currentMousePos);
for(MouseElement* element : window->mouseElements) {
if(element) {
if(window->currentMousePos.x >= element->mouseScaled.x && window->currentMousePos.x <= element->mouseScaled.x+element->mouseScaled.width && window->currentMousePos.y > element->mouseScaled.y && window->currentMousePos.y < element->mouseScaled.y+element->mouseScaled.height) {
element->onMouseLeftClick.Invoke({AbsoluteToMappedBoundless(window->currentMousePos.x - element->mouseScaled.x, element->mouseScaled.width), AbsoluteToMappedBoundless(window->currentMousePos.y - element->mouseScaled.y, element->mouseScaled.height)});
}
}
}
} else {
window->mouseLeftHeld = false;
window->onMouseLeftRelease.Invoke(window->currentMousePos);
for(MouseElement* element : window->mouseElements) {
if(element) {
if(window->currentMousePos.x >= element->mouseScaled.x && window->currentMousePos.x <= element->mouseScaled.x+element->mouseScaled.width && window->currentMousePos.y > element->mouseScaled.y && window->currentMousePos.y < element->mouseScaled.y+element->mouseScaled.height) {
element->onMouseLeftRelease.Invoke({AbsoluteToMappedBoundless(window->currentMousePos.x - element->mouseScaled.x, element->mouseScaled.width), AbsoluteToMappedBoundless(window->currentMousePos.y - element->mouseScaled.y, element->mouseScaled.height)});
}
}
}
}
} else if(button == BTN_RIGHT){
if(state == WL_POINTER_BUTTON_STATE_PRESSED) {
window->mouseRightHeld = true;
window->onMouseRightClick.Invoke(window->currentMousePos);
for(MouseElement* element : window->mouseElements) {
if(element) {
if(window->currentMousePos.x >= element->mouseScaled.x && window->currentMousePos.x <= element->mouseScaled.x+element->mouseScaled.width && window->currentMousePos.y > element->mouseScaled.y && window->currentMousePos.y < element->mouseScaled.y+element->mouseScaled.height) {
element->onMouseRightClick.Invoke({AbsoluteToMappedBoundless(window->currentMousePos.x - element->mouseScaled.x, element->mouseScaled.width), AbsoluteToMappedBoundless(window->currentMousePos.y - element->mouseScaled.y, element->mouseScaled.height)});
}
}
}
} else {
window->mouseRightHeld = true;
window->onMouseRightRelease.Invoke(window->currentMousePos);
for(MouseElement* element : window->mouseElements) {
if(element) {
if(window->currentMousePos.x >= element->mouseScaled.x && window->currentMousePos.x <= element->mouseScaled.x+element->mouseScaled.width && window->currentMousePos.y > element->mouseScaled.y && window->currentMousePos.y < element->mouseScaled.y+element->mouseScaled.height) {
element->onMouseRightRelease.Invoke({AbsoluteToMappedBoundless(window->currentMousePos.x - element->mouseScaled.x, element->mouseScaled.width), AbsoluteToMappedBoundless(window->currentMousePos.y - element->mouseScaled.y, element->mouseScaled.height)});
}
}
}
}
}
window->mouseElements.erase(std::remove(window->mouseElements.begin(), window->mouseElements.end(), static_cast<MouseElement*>(nullptr)), window->mouseElements.end());
window->mouseElements.insert(window->mouseElements.end(), window->pendingMouseElements.begin(), window->pendingMouseElements.end());
window->pendingMouseElements.clear();
}
void WindowVulkan::PointerListenerHandleMotion(void* data, wl_pointer* wl_pointer, uint time, wl_fixed_t surface_x, wl_fixed_t surface_y) {
WindowVulkan* window = reinterpret_cast<WindowVulkan*>(data);
MousePoint pos = {FractionalToMappedBoundless<std::uint32_t>((wl_fixed_to_double(surface_x) * window->scale) / window->width), FractionalToMappedBoundless<std::uint32_t>((wl_fixed_to_double(surface_y) * window->scale) / window->height)};
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(MouseElement* element : window->mouseElements) {
if(element) {
if(window->currentMousePos.x >= element->mouseScaled.x && window->currentMousePos.x <= element->mouseScaled.x+element->mouseScaled.width && window->currentMousePos.y > element->mouseScaled.y && window->currentMousePos.y < element->mouseScaled.y+element->mouseScaled.height) {
element->onMouseMove.Invoke({AbsoluteToMappedBoundless(window->currentMousePos.x - element->mouseScaled.x, element->mouseScaled.width), AbsoluteToMappedBoundless(window->currentMousePos.y - element->mouseScaled.y, element->mouseScaled.height)});
if(!(window->lastMousePos.x >= element->mouseScaled.x && window->lastMousePos.x <= element->mouseScaled.x+element->mouseScaled.width && window->lastMousePos.y > element->mouseScaled.y && window->lastMousePos.y < element->mouseScaled.y+element->mouseScaled.height)) {
element->onMouseEnter.Invoke({AbsoluteToMappedBoundless(window->currentMousePos.x - element->mouseScaled.x, element->mouseScaled.width), AbsoluteToMappedBoundless(window->currentMousePos.y - element->mouseScaled.y, element->mouseScaled.height)});
}
} else if(window->lastMousePos.x >= element->mouseScaled.x && window->lastMousePos.x <= element->mouseScaled.x+element->mouseScaled.width && window->lastMousePos.y > element->mouseScaled.y && window->lastMousePos.y < element->mouseScaled.y+element->mouseScaled.height) {
element->onMouseLeave.Invoke({AbsoluteToMappedBoundless(window->currentMousePos.x - element->mouseScaled.x, element->mouseScaled.width), AbsoluteToMappedBoundless(window->currentMousePos.y - element->mouseScaled.y, element->mouseScaled.height)});
}
}
}
window->mouseElements.erase(std::remove(window->mouseElements.begin(), window->mouseElements.end(), static_cast<MouseElement*>(nullptr)), window->mouseElements.end());
}
void WindowVulkan::PointerListenerHandleEnter(void* data, wl_pointer* wl_pointer, uint serial, wl_surface* surface, wl_fixed_t surface_x, wl_fixed_t surface_y) {
WindowVulkan* window = reinterpret_cast<WindowVulkan*>(data);
window->onMouseEnter.Invoke({window->lastMousePos, window->currentMousePos, window->mouseDelta});
}
void WindowVulkan::PointerListenerHandleLeave(void* data, wl_pointer*, std::uint32_t, wl_surface*) {
WindowVulkan* window = reinterpret_cast<WindowVulkan*>(data);
window->onMouseLeave.Invoke({window->lastMousePos, window->currentMousePos, window->mouseDelta});
}
void WindowVulkan::PointerListenerHandleAxis(void*, wl_pointer*, std::uint32_t, std::uint32_t, wl_fixed_t value) {
}
void WindowVulkan::keyboard_keymap(void *data, wl_keyboard *keyboard, uint32_t format, int fd, uint32_t size) {
WindowVulkan* window = reinterpret_cast<WindowVulkan*>(data);
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;
}
window->xkb_context = xkb_context_new(XKB_CONTEXT_NO_FLAGS);
window->xkb_keymap = xkb_keymap_new_from_string(window->xkb_context, (const char *)map, XKB_KEYMAP_FORMAT_TEXT_V1,XKB_KEYMAP_COMPILE_NO_FLAGS);
munmap(map, size);
close(fd);
window->xkb_state = xkb_state_new(window->xkb_keymap);
}
void WindowVulkan::keyboard_enter(void *data, wl_keyboard *keyboard, uint32_t serial, wl_surface *surface, wl_array *keys) {
}
void WindowVulkan::keyboard_leave(void *data, wl_keyboard *keyboard, uint32_t serial, wl_surface *surface) {
}
CrafterKeys keysym_to_crafter_key(xkb_keysym_t sym)
{
switch (sym)
{
// Alphabet
case XKB_KEY_a: return CrafterKeys::A;
case XKB_KEY_b: return CrafterKeys::B;
case XKB_KEY_c: return CrafterKeys::C;
case XKB_KEY_d: return CrafterKeys::D;
case XKB_KEY_e: return CrafterKeys::E;
case XKB_KEY_f: return CrafterKeys::F;
case XKB_KEY_g: return CrafterKeys::G;
case XKB_KEY_h: return CrafterKeys::H;
case XKB_KEY_i: return CrafterKeys::I;
case XKB_KEY_j: return CrafterKeys::J;
case XKB_KEY_k: return CrafterKeys::K;
case XKB_KEY_l: return CrafterKeys::L;
case XKB_KEY_m: return CrafterKeys::M;
case XKB_KEY_n: return CrafterKeys::N;
case XKB_KEY_o: return CrafterKeys::O;
case XKB_KEY_p: return CrafterKeys::P;
case XKB_KEY_q: return CrafterKeys::Q;
case XKB_KEY_r: return CrafterKeys::R;
case XKB_KEY_s: return CrafterKeys::S;
case XKB_KEY_t: return CrafterKeys::T;
case XKB_KEY_u: return CrafterKeys::U;
case XKB_KEY_v: return CrafterKeys::V;
case XKB_KEY_w: return CrafterKeys::W;
case XKB_KEY_x: return CrafterKeys::X;
case XKB_KEY_y: return CrafterKeys::Y;
case XKB_KEY_z: return CrafterKeys::Z;
// Numbers
case XKB_KEY_0: return CrafterKeys::_0;
case XKB_KEY_1: return CrafterKeys::_1;
case XKB_KEY_2: return CrafterKeys::_2;
case XKB_KEY_3: return CrafterKeys::_3;
case XKB_KEY_4: return CrafterKeys::_4;
case XKB_KEY_5: return CrafterKeys::_5;
case XKB_KEY_6: return CrafterKeys::_6;
case XKB_KEY_7: return CrafterKeys::_7;
case XKB_KEY_8: return CrafterKeys::_8;
case XKB_KEY_9: return CrafterKeys::_9;
// Function keys
case XKB_KEY_F1: return CrafterKeys::F1;
case XKB_KEY_F2: return CrafterKeys::F2;
case XKB_KEY_F3: return CrafterKeys::F3;
case XKB_KEY_F4: return CrafterKeys::F4;
case XKB_KEY_F5: return CrafterKeys::F5;
case XKB_KEY_F6: return CrafterKeys::F6;
case XKB_KEY_F7: return CrafterKeys::F7;
case XKB_KEY_F8: return CrafterKeys::F8;
case XKB_KEY_F9: return CrafterKeys::F9;
case XKB_KEY_F10: return CrafterKeys::F10;
case XKB_KEY_F11: return CrafterKeys::F11;
case XKB_KEY_F12: return CrafterKeys::F12;
// Control keys
case XKB_KEY_Escape: return CrafterKeys::Escape;
case XKB_KEY_Tab: return CrafterKeys::Tab;
case XKB_KEY_Return: return CrafterKeys::Enter;
case XKB_KEY_space: return CrafterKeys::Space;
case XKB_KEY_BackSpace: return CrafterKeys::Backspace;
case XKB_KEY_Delete: return CrafterKeys::Delete;
case XKB_KEY_Insert: return CrafterKeys::Insert;
case XKB_KEY_Home: return CrafterKeys::Home;
case XKB_KEY_End: return CrafterKeys::End;
case XKB_KEY_Page_Up: return CrafterKeys::PageUp;
case XKB_KEY_Page_Down: return CrafterKeys::PageDown;
case XKB_KEY_Caps_Lock: return CrafterKeys::CapsLock;
case XKB_KEY_Num_Lock: return CrafterKeys::NumLock;
case XKB_KEY_Scroll_Lock:return CrafterKeys::ScrollLock;
// Modifiers
case XKB_KEY_Shift_L: return CrafterKeys::LeftShift;
case XKB_KEY_Shift_R: return CrafterKeys::RightShift;
case XKB_KEY_Control_L: return CrafterKeys::LeftCtrl;
case XKB_KEY_Control_R: return CrafterKeys::RightCtrl;
case XKB_KEY_Alt_L: return CrafterKeys::LeftAlt;
case XKB_KEY_Alt_R: return CrafterKeys::RightAlt;
case XKB_KEY_Super_L: return CrafterKeys::LeftSuper;
case XKB_KEY_Super_R: return CrafterKeys::RightSuper;
// Arrows
case XKB_KEY_Up: return CrafterKeys::Up;
case XKB_KEY_Down: return CrafterKeys::Down;
case XKB_KEY_Left: return CrafterKeys::Left;
case XKB_KEY_Right: return CrafterKeys::Right;
// Keypad
case XKB_KEY_KP_0: return CrafterKeys::keypad_0;
case XKB_KEY_KP_1: return CrafterKeys::keypad_1;
case XKB_KEY_KP_2: return CrafterKeys::keypad_2;
case XKB_KEY_KP_3: return CrafterKeys::keypad_3;
case XKB_KEY_KP_4: return CrafterKeys::keypad_4;
case XKB_KEY_KP_5: return CrafterKeys::keypad_5;
case XKB_KEY_KP_6: return CrafterKeys::keypad_6;
case XKB_KEY_KP_7: return CrafterKeys::keypad_7;
case XKB_KEY_KP_8: return CrafterKeys::keypad_8;
case XKB_KEY_KP_9: return CrafterKeys::keypad_9;
case XKB_KEY_KP_Enter: return CrafterKeys::keypad_enter;
case XKB_KEY_KP_Add: return CrafterKeys::keypad_plus;
case XKB_KEY_KP_Subtract: return CrafterKeys::keypad_minus;
case XKB_KEY_KP_Multiply: return CrafterKeys::keypad_multiply;
case XKB_KEY_KP_Divide: return CrafterKeys::keypad_divide;
case XKB_KEY_KP_Decimal: return CrafterKeys::keypad_decimal;
// Punctuation
case XKB_KEY_grave: return CrafterKeys::grave;
case XKB_KEY_minus: return CrafterKeys::minus;
case XKB_KEY_equal: return CrafterKeys::equal;
case XKB_KEY_bracketleft: return CrafterKeys::bracket_left;
case XKB_KEY_bracketright:return CrafterKeys::bracket_right;
case XKB_KEY_backslash: return CrafterKeys::backslash;
case XKB_KEY_semicolon: return CrafterKeys::semicolon;
case XKB_KEY_apostrophe: return CrafterKeys::quote;
case XKB_KEY_comma: return CrafterKeys::comma;
case XKB_KEY_period: return CrafterKeys::period;
case XKB_KEY_slash: return CrafterKeys::slash;
default:
return CrafterKeys::CrafterKeysMax;
}
}
void WindowVulkan::keyboard_key(void *data, wl_keyboard *keyboard, uint32_t serial, uint32_t time, uint32_t key, uint32_t state) {
WindowVulkan* window = reinterpret_cast<WindowVulkan*>(data);
if (!window->xkb_state) {
return;
}
xkb_keycode_t keycode = key + 8;
xkb_keysym_t keysym = xkb_state_key_get_one_sym(window->xkb_state, keycode);
CrafterKeys crafterKey = keysym_to_crafter_key(keysym);
if(state == WL_KEYBOARD_KEY_STATE_PRESSED) {
if(window->heldkeys[static_cast<std::uint8_t>(crafterKey)]) {
window->onKeyHold[static_cast<std::uint8_t>(crafterKey)].Invoke();
window->onAnyKeyHold.Invoke(crafterKey);
} else{
window->heldkeys[static_cast<std::uint8_t>(crafterKey)] = true;
window->onKeyDown[static_cast<std::uint8_t>(crafterKey)].Invoke();
window->onAnyKeyDown.Invoke(crafterKey);
}
} else{
window->heldkeys[static_cast<std::uint8_t>(crafterKey)] = false;
window->onKeyUp[static_cast<std::uint8_t>(crafterKey)].Invoke();
window->onAnyKeyUp.Invoke(crafterKey);
}
}
void WindowVulkan::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 WindowVulkan::keyboard_repeat_info(void *data, wl_keyboard *keyboard, int32_t rate, int32_t delay) {
}
void WindowVulkan::seat_handle_capabilities(void* data, wl_seat* seat, uint32_t capabilities) {
WindowVulkan* window = reinterpret_cast<WindowVulkan*>(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);
}
}
void WindowVulkan::handle_global(void *data, wl_registry *registry, std::uint32_t name, const char *interface, std::uint32_t version) {
WindowVulkan* window = reinterpret_cast<WindowVulkan*>(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, 3));
} 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));
} else if (strcmp(interface, wp_viewporter_interface.name) == 0) {
window->wpViewporter = reinterpret_cast<wp_viewporter*>(wl_registry_bind(registry, name, &wp_viewporter_interface, 1));
} else if (strcmp(interface, wp_fractional_scale_manager_v1_interface.name) == 0) {
window->fractionalScaleManager = reinterpret_cast<wp_fractional_scale_manager_v1*>(wl_registry_bind(registry, name, &wp_fractional_scale_manager_v1_interface, 1));
}
}
void WindowVulkan::handle_global_remove(void* data, wl_registry* registry, uint32_t name) {
}
void WindowVulkan::xdg_toplevel_configure(void*, xdg_toplevel*, std::int32_t, std::int32_t, wl_array*){
}
void WindowVulkan::xdg_toplevel_handle_close(void* data, xdg_toplevel*) {
WindowVulkan* window = reinterpret_cast<WindowVulkan*>(data);
window->onClose.Invoke();
window->open = false;
}
void WindowVulkan::xdg_surface_handle_configure(void* data, xdg_surface* xdg_surface, std::uint32_t serial) {
WindowVulkan* window = reinterpret_cast<WindowVulkan*>(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;
}
void WindowVulkan::xdg_surface_handle_preferred_scale(void* data, wp_fractional_scale_v1*, std::uint32_t scale) {
WindowVulkan* window = reinterpret_cast<WindowVulkan*>(data);
window->scale = scale / 120.0f;
}

View file

@ -0,0 +1,51 @@
/*
Crafter®.Graphics
Copyright (C) 2026 Catcrafts®
catcrafts.net
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License version 3.0 as published by the Free Software Foundation;
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
module;
#ifdef CRAFTER_GRAPHICS_VULKAN
#include <vulkan/vulkan.h>
#include <vulkan/vulkan_wayland.h>
#endif
export module Crafter.Graphics:VulkanDevice;
#ifdef CRAFTER_GRAPHICS_VULKAN
import std;
export namespace Crafter {
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;
static void CreateDevice();
static void CheckVkResult(VkResult result);
static std::uint32_t GetMemoryType(std::uint32_t typeBits, VkMemoryPropertyFlags properties);
};
}
#endif

View file

@ -0,0 +1,194 @@
/*
Crafter®.Graphics
Copyright (C) 2026 Catcrafts®
catcrafts.net
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License version 3.0 as published by the Free Software Foundation;
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
module;
#ifdef CRAFTER_GRAPHICS_VULKAN
#include <vulkan/vulkan.h>
#include <assert.h>
#endif
export module Crafter.Graphics:VulkanTransition;
#ifdef CRAFTER_GRAPHICS_VULKAN
import std;
export namespace Crafter {
#include <vulkan/vulkan.h>
VkAccessFlags getAccessFlags(VkImageLayout layout)
{
switch (layout)
{
case VK_IMAGE_LAYOUT_UNDEFINED:
case VK_IMAGE_LAYOUT_PRESENT_SRC_KHR:
return 0;
case VK_IMAGE_LAYOUT_PREINITIALIZED:
return VK_ACCESS_HOST_WRITE_BIT;
case VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL:
return VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
case VK_IMAGE_LAYOUT_DEPTH_ATTACHMENT_OPTIMAL:
return VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT;
case VK_IMAGE_LAYOUT_FRAGMENT_SHADING_RATE_ATTACHMENT_OPTIMAL_KHR:
return VK_ACCESS_FRAGMENT_SHADING_RATE_ATTACHMENT_READ_BIT_KHR;
case VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL:
return VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_INPUT_ATTACHMENT_READ_BIT;
case VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL:
return VK_ACCESS_TRANSFER_READ_BIT;
case VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL:
return VK_ACCESS_TRANSFER_WRITE_BIT;
case VK_IMAGE_LAYOUT_GENERAL:
assert(false && "Don't know how to get a meaningful VkAccessFlags for VK_IMAGE_LAYOUT_GENERAL! Don't use it!");
return 0;
default:
assert(false);
return 0;
}
}
VkPipelineStageFlags getPipelineStageFlags(VkImageLayout layout)
{
switch (layout)
{
case VK_IMAGE_LAYOUT_UNDEFINED:
return VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT;
case VK_IMAGE_LAYOUT_PREINITIALIZED:
return VK_PIPELINE_STAGE_HOST_BIT;
case VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL:
case VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL:
return VK_PIPELINE_STAGE_TRANSFER_BIT;
case VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL:
return VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
case VK_IMAGE_LAYOUT_DEPTH_ATTACHMENT_OPTIMAL:
return VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT;
case VK_IMAGE_LAYOUT_FRAGMENT_SHADING_RATE_ATTACHMENT_OPTIMAL_KHR:
return VK_PIPELINE_STAGE_FRAGMENT_SHADING_RATE_ATTACHMENT_BIT_KHR;
case VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL:
return VK_PIPELINE_STAGE_VERTEX_SHADER_BIT | VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT;
case VK_IMAGE_LAYOUT_PRESENT_SRC_KHR:
return VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT;
case VK_IMAGE_LAYOUT_GENERAL:
assert(false && "Don't know how to get a meaningful VkPipelineStageFlags for VK_IMAGE_LAYOUT_GENERAL! Don't use it!");
return 0;
default:
assert(false);
return 0;
}
}
// Create an image memory barrier for changing the layout of
// an image and put it into an active command buffer
// See chapter 12.4 "Image Layout" for details
void image_layout_transition(VkCommandBuffer command_buffer,
VkImage image,
VkPipelineStageFlags src_stage_mask,
VkPipelineStageFlags dst_stage_mask,
VkAccessFlags src_access_mask,
VkAccessFlags dst_access_mask,
VkImageLayout old_layout,
VkImageLayout new_layout,
VkImageSubresourceRange const &subresource_range)
{
// Create an image barrier object
VkImageMemoryBarrier image_memory_barrier{};
image_memory_barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
image_memory_barrier.srcAccessMask = src_access_mask;
image_memory_barrier.dstAccessMask = dst_access_mask;
image_memory_barrier.oldLayout = old_layout;
image_memory_barrier.newLayout = new_layout;
image_memory_barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
image_memory_barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
image_memory_barrier.image = image;
image_memory_barrier.subresourceRange = subresource_range;
// Put barrier inside setup command buffer
vkCmdPipelineBarrier(command_buffer, src_stage_mask, dst_stage_mask, 0, 0, nullptr, 0, nullptr, 1, &image_memory_barrier);
}
void image_layout_transition(VkCommandBuffer command_buffer,
VkImage image,
VkImageLayout old_layout,
VkImageLayout new_layout,
VkImageSubresourceRange const &subresource_range)
{
VkPipelineStageFlags src_stage_mask = getPipelineStageFlags(old_layout);
VkPipelineStageFlags dst_stage_mask = getPipelineStageFlags(new_layout);
VkAccessFlags src_access_mask = getAccessFlags(old_layout);
VkAccessFlags dst_access_mask = getAccessFlags(new_layout);
image_layout_transition(command_buffer, image, src_stage_mask, dst_stage_mask, src_access_mask, dst_access_mask, old_layout, new_layout, subresource_range);
}
// Fixed sub resource on first mip level and layer
void image_layout_transition(VkCommandBuffer command_buffer,
VkImage image,
VkImageLayout old_layout,
VkImageLayout new_layout)
{
VkImageSubresourceRange subresource_range = {};
subresource_range.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
subresource_range.baseMipLevel = 0;
subresource_range.levelCount = 1;
subresource_range.baseArrayLayer = 0;
subresource_range.layerCount = 1;
image_layout_transition(command_buffer, image, old_layout, new_layout, subresource_range);
}
void image_layout_transition(VkCommandBuffer command_buffer,
std::vector<std::pair<VkImage, VkImageSubresourceRange>> const &imagesAndRanges,
VkImageLayout old_layout,
VkImageLayout new_layout)
{
VkPipelineStageFlags src_stage_mask = getPipelineStageFlags(old_layout);
VkPipelineStageFlags dst_stage_mask = getPipelineStageFlags(new_layout);
VkAccessFlags src_access_mask = getAccessFlags(old_layout);
VkAccessFlags dst_access_mask = getAccessFlags(new_layout);
// Create image barrier objects
std::vector<VkImageMemoryBarrier> image_memory_barriers;
image_memory_barriers.reserve(imagesAndRanges.size());
for (size_t i = 0; i < imagesAndRanges.size(); i++)
{
image_memory_barriers.emplace_back(VkImageMemoryBarrier{VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
nullptr,
src_access_mask,
dst_access_mask,
old_layout,
new_layout,
VK_QUEUE_FAMILY_IGNORED,
VK_QUEUE_FAMILY_IGNORED,
imagesAndRanges[i].first,
imagesAndRanges[i].second});
}
// Put barriers inside setup command buffer
vkCmdPipelineBarrier(command_buffer,
src_stage_mask,
dst_stage_mask,
0,
0,
nullptr,
0,
nullptr,
static_cast<uint32_t>(image_memory_barriers.size()),
image_memory_barriers.data());
}
}
#endif

View file

@ -39,6 +39,29 @@ module;
#include <wayland-client.h> #include <wayland-client.h>
#include <wayland-client-protocol.h> #include <wayland-client-protocol.h>
#endif #endif
#ifdef CRAFTER_GRAPHICS_VULKAN
#include <stdio.h>
#include <stdlib.h>
#include <unistd.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 "../lib/fractional-scale-v1.h"
#include "../lib/viewporter.h"
#include <string.h>
#include <linux/input.h>
#include <sys/mman.h>
#include <wayland-cursor.h>
#include <xkbcommon/xkbcommon.h>
#include <errno.h>
#include <fcntl.h>
#include <print>
#include <wayland-client.h>
#include <wayland-client-protocol.h>
#include <vulkan/vulkan.h>
#include <vulkan/vulkan_wayland.h>
#endif
export module Crafter.Graphics:Window; export module Crafter.Graphics:Window;
import std; import std;
@ -243,4 +266,122 @@ export namespace Crafter {
}; };
}; };
#endif #endif
#ifdef CRAFTER_GRAPHICS_VULKAN
struct Semaphores {
// Swap chain image presentation
VkSemaphore presentComplete;
// Command buffer submission and execution
VkSemaphore renderComplete;
};
class WindowVulkan final : public Window, public WindowKeyboard, public WindowMouse, public WindowTitle {
public:
WindowVulkan(std::uint32_t width, std::uint32_t height);
WindowVulkan(std::uint32_t width, std::uint32_t height, const std::string_view title);
~WindowVulkan();
bool configured = false;
wl_shm* shm = nullptr;
wl_seat* seat = nullptr;
wp_fractional_scale_v1* wp_scale = nullptr;
xdg_toplevel* xdgToplevel = nullptr;
wp_viewport* wpViewport = nullptr;
wp_viewporter* wpViewporter = nullptr;
xdg_wm_base* xdgWmBase = nullptr;
zxdg_decoration_manager_v1* manager = nullptr;
wp_fractional_scale_manager_v1* fractionalScaleManager = nullptr;
wl_surface* surface = nullptr;
xdg_surface* xdgSurface = nullptr;
wl_display* display = nullptr;
wl_callback* cb = nullptr;
xkb_keymap* xkb_keymap;
xkb_context* xkb_context = xkb_context_new(XKB_CONTEXT_NO_FLAGS);
xkb_state* xkb_state;
void Render();
void QueueRender();
void Resize(std::uint32_t width, std::uint32_t height);
void StartSync() override;
void StartUpdate() override;
void StopUpdate() override;
void SetTitle(const std::string_view title) override;
VkCommandBuffer StartInit();
void FinishInit();
inline static wl_compositor* compositor = nullptr;
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);
static void xdg_surface_handle_preferred_scale(void* data, wp_fractional_scale_v1*, std::uint32_t scale);
static void xdg_wm_base_handle_ping(void* data, xdg_wm_base* xdg_wm_base, std::uint32_t serial);
static void keyboard_keymap(void* data, wl_keyboard* keyboard, uint32_t format, int fd, uint32_t size);
static void keyboard_enter(void *data, wl_keyboard *keyboard, uint32_t serial, wl_surface *surface, wl_array *keys);
static void keyboard_leave(void *data, wl_keyboard *keyboard, uint32_t serial, wl_surface *surface);
static void keyboard_key(void *data, wl_keyboard *keyboard, uint32_t serial, uint32_t time, uint32_t key, uint32_t state);
static 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);
static void keyboard_repeat_info(void *data, wl_keyboard *keyboard, int32_t rate, int32_t delay);
static void handle_global_remove(void* data, wl_registry* registry, uint32_t name);
static void xdg_toplevel_configure(void*, xdg_toplevel*, std::int32_t, std::int32_t, wl_array*);
constexpr static wl_pointer_listener pointer_listener = {
.enter = PointerListenerHandleEnter,
.leave = PointerListenerHandleLeave,
.motion = PointerListenerHandleMotion,
.button = pointer_handle_button,
.axis = PointerListenerHandleAxis,
};
constexpr static wl_keyboard_listener keyboard_listener = {
.keymap = keyboard_keymap,
.enter = keyboard_enter,
.leave = keyboard_leave,
.key = keyboard_key,
.modifiers = keyboard_modifiers,
.repeat_info = keyboard_repeat_info,
};
constexpr static wl_seat_listener seat_listener = {
.capabilities = seat_handle_capabilities,
};
constexpr static wl_registry_listener registry_listener = {
.global = handle_global,
.global_remove = handle_global_remove,
};
constexpr static xdg_toplevel_listener xdg_toplevel_listener = {
.configure = xdg_toplevel_configure,
.close = xdg_toplevel_handle_close,
};
constexpr static wl_callback_listener wl_callback_listener = {
.done = wl_surface_frame_done,
};
constexpr static xdg_wm_base_listener xdgWmBaseListener = {
.ping = xdg_wm_base_handle_ping,
};
constexpr static xdg_surface_listener xdg_surface_listener = {
.configure = xdg_surface_handle_configure,
};
constexpr static wp_fractional_scale_v1_listener wp_fractional_scale_v1_listener = {
.preferred_scale = xdg_surface_handle_preferred_scale,
};
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;
Semaphores semaphores;
uint32_t currentBuffer = 0;
VkPipelineStageFlags submitPipelineStages = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
VkRenderPass renderPass = VK_NULL_HANDLE;
};
#endif
} }

View file

@ -31,6 +31,11 @@ export import :Image;
export import :Shm; export import :Shm;
export import :Animation; export import :Animation;
#ifdef CRAFTER_GRAPHICS_VULKAN
export import :VulkanDevice;
export import :VulkanTransition;
#endif
// export import :WindowWaylandVulkan; // export import :WindowWaylandVulkan;
// export import :VulkanBuffer; // export import :VulkanBuffer;
// export import :VoxelShader; // export import :VoxelShader;

View file

@ -4,7 +4,7 @@
{ {
"name": "base", "name": "base",
"implementations": ["implementations/Crafter.Graphics-Font", "implementations/Crafter.Graphics-Shm", "implementations/Crafter.Graphics-Window", "implementations/Crafter.Graphics-MouseElement", "implementations/Crafter.Graphics-Transform", "implementations/Crafter.Graphics-GridElement", "implementations/Crafter.Graphics-Image"], "implementations": ["implementations/Crafter.Graphics-Font", "implementations/Crafter.Graphics-Shm", "implementations/Crafter.Graphics-Window", "implementations/Crafter.Graphics-MouseElement", "implementations/Crafter.Graphics-Transform", "implementations/Crafter.Graphics-GridElement", "implementations/Crafter.Graphics-Image"],
"interfaces": ["interfaces/Crafter.Graphics-Window", "interfaces/Crafter.Graphics", "interfaces/Crafter.Graphics-Types", "interfaces/Crafter.Graphics-Font", "interfaces/Crafter.Graphics-Image", "interfaces/Crafter.Graphics-Shm", "interfaces/Crafter.Graphics-Animation", "interfaces/Crafter.Graphics-RenderingElement", "interfaces/Crafter.Graphics-MouseElement", "interfaces/Crafter.Graphics-Transform", "interfaces/Crafter.Graphics-GridElement"], "interfaces": ["interfaces/Crafter.Graphics-Window", "interfaces/Crafter.Graphics", "interfaces/Crafter.Graphics-Types", "interfaces/Crafter.Graphics-Font", "interfaces/Crafter.Graphics-Image", "interfaces/Crafter.Graphics-Shm", "interfaces/Crafter.Graphics-Animation", "interfaces/Crafter.Graphics-RenderingElement", "interfaces/Crafter.Graphics-MouseElement", "interfaces/Crafter.Graphics-Transform", "interfaces/Crafter.Graphics-GridElement", "interfaces/Crafter.Graphics-VulkanDevice", "interfaces/Crafter.Graphics-VulkanTransition"],
"type": "library" "type": "library"
}, },
{ {
@ -20,6 +20,19 @@
} }
] ]
}, },
{
"name": "vulkan",
"implementations": ["implementations/Crafter.Graphics-VulkanDevice", "implementations/Crafter.Graphics-Window_vulkan"],
"interfaces": [],
"libs": ["wayland-client", "xkbcommon", "vulkan"],
"c_files": ["lib/xdg-shell-protocol", "lib/wayland-xdg-decoration-unstable-v1-client-protocol", "lib/fractional-scale-v1", "lib/viewporter"],
"extends": ["base"],
"defines": [
{
"name": "CRAFTER_GRAPHICS_VULKAN"
}
]
},
{ {
"name": "deps", "name": "deps",
"dependencies": [ "dependencies": [
@ -63,6 +76,22 @@
"name": "lib-wayland-debug", "name": "lib-wayland-debug",
"type": "library", "type": "library",
"extends": ["wayland", "deps-debug"] "extends": ["wayland", "deps-debug"]
},
{
"name": "lib-vulkan",
"extends": ["vulkan", "deps"],
"type": "library"
},
{
"name": "lib-vulkan-timing",
"extends": ["vulkan", "deps-timing"],
"type": "library",
"defines": [{ "name": "CRAFTER_TIMING" }]
},
{
"name": "lib-vulkan-debug",
"type": "library",
"extends": ["vulkan", "deps-debug"]
} }
] ]
} }