Crafter.Graphics/implementations/Crafter.Graphics-Device.cpp

797 lines
34 KiB
C++
Raw Normal View History

2026-03-09 20:10:19 +01:00
/*
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/vk_enum_string_helper.h"
#define GET_EXTENSION_FUNCTION(_id) ((PFN_##_id)(vkGetInstanceProcAddr(instance, #_id)))
#ifdef CRAFTER_GRAPHICS_WINDOW_WAYLAND
#include <linux/input-event-codes.h>
#include <xkbcommon/xkbcommon.h>
#include <string.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 <wayland-client.h>
#include <wayland-client-protocol.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/mman.h>
#endif
module Crafter.Graphics:Device_impl;
import :Device;
import :Window;
import :Types;
import std;
using namespace Crafter;
const char* const instanceExtensionNames[] = {
"VK_EXT_debug_utils",
"VK_KHR_surface",
2026-03-09 22:53:26 +01:00
#ifdef CRAFTER_GRAPHICS_WINDOW_WIN32
2026-03-09 20:10:19 +01:00
"VK_KHR_win32_surface"
2026-03-09 22:53:26 +01:00
#endif
#ifdef CRAFTER_GRAPHICS_WINDOW_WAYLAND
2026-03-09 20:10:19 +01:00
"VK_KHR_wayland_surface"
#endif
};
const char* const deviceExtensionNames[] = {
"VK_KHR_swapchain",
"VK_KHR_spirv_1_4",
"VK_KHR_shader_float_controls",
"VK_KHR_acceleration_structure",
"VK_KHR_ray_tracing_pipeline",
2026-05-05 23:49:29 +02:00
"VK_KHR_ray_query",
"VK_EXT_shader_atomic_float",
2026-04-05 22:53:59 +02:00
"VK_EXT_descriptor_heap",
"VK_KHR_deferred_host_operations",
"VK_KHR_maintenance5",
2026-04-09 00:15:09 +02:00
"VK_KHR_shader_untyped_pointers",
"VK_EXT_device_fault"
2026-03-09 20:10:19 +01:00
};
const char* const layerNames[] = {
"VK_LAYER_KHRONOS_validation"
};
void Device::CheckVkResult(VkResult result) {
if (result != VK_SUCCESS)
{
2026-04-09 00:15:09 +02:00
if(result == VK_ERROR_DEVICE_LOST) {
VkDeviceFaultCountsEXT faultCounts = {
.sType = VK_STRUCTURE_TYPE_DEVICE_FAULT_COUNTS_EXT,
.pNext = NULL,
};
Device::vkGetDeviceFaultInfoEXT(device, &faultCounts, NULL);
std::vector<VkDeviceFaultAddressInfoEXT> addressInfos(faultCounts.addressInfoCount);
std::vector<VkDeviceFaultVendorInfoEXT> vendorInfos(faultCounts.vendorInfoCount);
std::vector<char> vendorBinaryData(faultCounts.vendorBinarySize);
VkDeviceFaultInfoEXT faultInfo = {
.sType = VK_STRUCTURE_TYPE_DEVICE_FAULT_INFO_EXT,
.pNext = NULL,
.pAddressInfos = addressInfos.data(),
.pVendorInfos = vendorInfos.data(),
.pVendorBinaryData = vendorBinaryData.data(),
};
Device::vkGetDeviceFaultInfoEXT(device, &faultCounts, &faultInfo);
std::println("{}", faultInfo.description);
std::println("{} AddressInfos:", addressInfos.size());
for(const VkDeviceFaultAddressInfoEXT& info : addressInfos) {
std::println("\t{} {}", static_cast<uint32_t>(info.addressType), info.reportedAddress);
}
std::println("{} vendorInfos:", vendorInfos.size());
for(const VkDeviceFaultVendorInfoEXT& info : vendorInfos) {
std::println("\t{} {} {}", info.description, info.vendorFaultCode, info.vendorFaultData);
}
if(!vendorBinaryData.empty()) {
std::string ext = ".bin";
if(vendorBinaryData.size() >= sizeof(VkDeviceFaultVendorBinaryHeaderVersionOneEXT)) {
VkDeviceFaultVendorBinaryHeaderVersionOneEXT header;
std::memcpy(&header, vendorBinaryData.data(), sizeof(header));
if(header.vendorID == 0x10DE) { // NVIDIA
ext = ".nv-gpudmp";
}
}
const auto now = std::chrono::system_clock::now();
const std::string dumpPath = std::format("gpu_crash_dump-{:%Y%m%d-%H%M%S}{}", now, ext);
std::ofstream file(dumpPath, std::ios::binary);
if(file.write(vendorBinaryData.data(), vendorBinaryData.size())) {
std::println("Vendor binary saved to: {}", std::filesystem::canonical(dumpPath).string());
} else {
std::println(stderr, "Failed to write vendor binary to: {}", dumpPath);
}
}
}
2026-03-09 20:10:19 +01:00
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;
}
#ifdef CRAFTER_GRAPHICS_WINDOW_WAYLAND
void Device::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);
}
void Device::handle_global(void *data, wl_registry *registry, std::uint32_t name, const char *interface, std::uint32_t version) {
if (strcmp(interface, wl_shm_interface.name) == 0) {
shm = reinterpret_cast<wl_shm*>(wl_registry_bind(registry, name, &wl_shm_interface, 1));
} else if (strcmp(interface, wl_seat_interface.name) == 0) {
2026-05-12 00:24:48 +02:00
// Assign to Device::seat (not a fresh local) so SetClipboardText
// and any other code that needs the seat post-init can find it.
seat = reinterpret_cast<wl_seat*>(wl_registry_bind(registry, name, &wl_seat_interface, 1));
2026-03-09 20:10:19 +01:00
wl_seat_add_listener(seat, &seat_listener, nullptr);
2026-05-12 00:24:48 +02:00
// If the manager came in first, the data device couldn't be
// created yet — do it now that we have the seat.
if (dataDeviceManager != nullptr && dataDevice == nullptr) {
dataDevice = wl_data_device_manager_get_data_device(dataDeviceManager, seat);
}
2026-03-09 20:10:19 +01:00
} else if (compositor == nullptr && 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) {
xdgWmBase = reinterpret_cast<xdg_wm_base*>(wl_registry_bind(registry, name, &xdg_wm_base_interface, 1));
xdg_wm_base_add_listener(xdgWmBase, &xdgWmBaseListener, nullptr);
} else if (strcmp(interface, zxdg_decoration_manager_v1_interface.name) == 0) {
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) {
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) {
fractionalScaleManager = reinterpret_cast<wp_fractional_scale_manager_v1*>(wl_registry_bind(registry, name, &wp_fractional_scale_manager_v1_interface, 1));
2026-05-12 00:24:48 +02:00
} else if (strcmp(interface, wl_data_device_manager_interface.name) == 0) {
// v3 gives us the full set of data_source events (target / send /
// cancelled / dnd_*). Universally supported by the compositors
// we target — fall back path is the per-source listener simply
// not getting the v3-only callbacks.
dataDeviceManager = reinterpret_cast<wl_data_device_manager*>(
wl_registry_bind(registry, name, &wl_data_device_manager_interface, 3));
if (seat != nullptr && dataDevice == nullptr) {
dataDevice = wl_data_device_manager_get_data_device(dataDeviceManager, seat);
}
2026-03-09 20:10:19 +01:00
}
}
void Device::handle_global_remove(void* data, wl_registry* registry, uint32_t name) {
}
void Device::pointer_handle_button(void* data, wl_pointer* pointer, std::uint32_t serial, std::uint32_t time, std::uint32_t button, std::uint32_t state) {
if (button == BTN_LEFT) {
if(state == WL_POINTER_BUTTON_STATE_PRESSED) {
Device::focusedWindow->mouseLeftHeld = true;
Device::focusedWindow->onMouseLeftClick.Invoke();
} else {
Device::focusedWindow->mouseLeftHeld = false;
Device::focusedWindow->onMouseLeftRelease.Invoke();
}
} else if(button == BTN_RIGHT){
if(state == WL_POINTER_BUTTON_STATE_PRESSED) {
Device::focusedWindow->mouseRightHeld = true;
Device::focusedWindow->onMouseRightClick.Invoke();
} else {
2026-03-09 21:59:27 +01:00
Device::focusedWindow->mouseRightHeld = false;
2026-05-01 23:35:37 +02:00
Device::focusedWindow->onMouseRightRelease.Invoke();
2026-03-09 20:10:19 +01:00
}
}
}
void Device::PointerListenerHandleMotion(void* data, wl_pointer* wl_pointer, std::uint32_t time, wl_fixed_t surface_x, wl_fixed_t surface_y) {
Vector<float, 2> pos(wl_fixed_to_double(surface_x), wl_fixed_to_double(surface_y));
2026-03-09 21:59:27 +01:00
//Device::focusedWindow->lastMousePos = Device::focusedWindow->currentMousePos;
2026-03-09 20:10:19 +01:00
Device::focusedWindow->currentMousePos = pos * Device::focusedWindow->scale;
2026-03-09 21:59:27 +01:00
//Device::focusedWindow->mouseDelta = {Device::focusedWindow->currentMousePos.x-Device::focusedWindow->lastMousePos.x, Device::focusedWindow->currentMousePos.y-Device::focusedWindow->lastMousePos.y};
2026-03-09 20:10:19 +01:00
Device::focusedWindow->onMouseMove.Invoke();
}
void Device::PointerListenerHandleEnter(void* data, wl_pointer* wl_pointer, std::uint32_t serial, wl_surface* surface, wl_fixed_t surface_x, wl_fixed_t surface_y) {
2026-03-12 01:07:46 +01:00
Device::wlPointer = wl_pointer;
2026-03-09 20:10:19 +01:00
for(Window* window : windows) {
if(window->surface == surface) {
2026-05-02 21:08:20 +02:00
window->lastPointerSerial_ = serial;
2026-03-12 01:07:46 +01:00
if(window->cursorSurface != nullptr) {
2026-05-02 21:08:20 +02:00
wl_pointer_set_cursor(wl_pointer, serial, window->cursorSurface,
window->cursorHotspotX_, window->cursorHotspotY_);
2026-03-12 01:07:46 +01:00
}
2026-03-09 20:10:19 +01:00
focusedWindow = window;
window->onMouseEnter.Invoke();
return;
}
}
}
void Device::PointerListenerHandleLeave(void* data, wl_pointer*, std::uint32_t, wl_surface*) {
Device::focusedWindow->onMouseLeave.Invoke();
focusedWindow = nullptr;
}
void Device::PointerListenerHandleAxis(void*, wl_pointer*, std::uint32_t, std::uint32_t, wl_fixed_t value) {
}
void Device::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 Device::keyboard_enter(void *data, wl_keyboard *keyboard, uint32_t serial, wl_surface *surface, wl_array *keys) {
}
void Device::keyboard_leave(void *data, wl_keyboard *keyboard, uint32_t serial, wl_surface *surface) {
}
void Device::keyboard_key(void *data, wl_keyboard *keyboard, uint32_t serial, uint32_t time, uint32_t key, uint32_t state) {
2026-05-12 00:24:48 +02:00
// `key` is the kernel input-event-code (KEY_*). That is exactly what
// :Keys returns for Wayland builds, so we store it verbatim with no
// translation. The +8 X11 offset is only needed for the XKB layer,
// which we still consult to produce UTF-8 text.
KeyCode code = key;
xkb_keycode_t xkbKeycode = key + 8;
2026-03-09 20:10:19 +01:00
2026-04-16 23:03:24 +02:00
if (state == WL_KEYBOARD_KEY_STATE_PRESSED) {
2026-05-12 00:24:48 +02:00
if (focusedWindow->heldKeys.contains(code)) {
focusedWindow->onRawKeyHold.Invoke(code);
2026-04-16 23:03:24 +02:00
} else {
2026-05-12 00:24:48 +02:00
focusedWindow->heldKeys.insert(code);
focusedWindow->onRawKeyDown.Invoke(code);
2026-03-09 20:10:19 +01:00
}
2026-04-16 23:03:24 +02:00
std::string buf;
buf.resize(16);
2026-05-12 00:24:48 +02:00
int n = xkb_state_key_get_utf8(xkb_state, xkbKeycode, buf.data(), 16);
2026-05-02 00:03:24 +02:00
std::string utf8;
if (n > 0 && (unsigned char)buf[0] >= 0x20 && buf[0] != 0x7f) {
buf.resize(n);
utf8 = buf;
focusedWindow->onTextInput.Invoke(utf8);
2026-04-16 23:03:24 +02:00
}
2026-05-02 00:03:24 +02:00
// Replace the active repeat with this key — most recent press wins,
// matching xkbcommon's typical behaviour and most desktop apps.
keyRepeat.active = (keyRepeat.rate > 0);
2026-05-12 00:24:48 +02:00
keyRepeat.key = code;
2026-05-02 00:03:24 +02:00
keyRepeat.utf8 = std::move(utf8);
keyRepeat.pressTime = std::chrono::steady_clock::now();
keyRepeat.lastFireTime = keyRepeat.pressTime;
2026-04-16 23:03:24 +02:00
} else {
2026-05-12 00:24:48 +02:00
focusedWindow->heldKeys.erase(code);
focusedWindow->onRawKeyUp.Invoke(code);
2026-05-02 00:03:24 +02:00
// If the released key was the one repeating, stop. Otherwise leave
// the existing repeat alone (user pressed/released a modifier
// mid-repeat etc.).
2026-05-12 00:24:48 +02:00
if (keyRepeat.active && keyRepeat.key == code) {
2026-05-02 00:03:24 +02:00
keyRepeat.active = false;
keyRepeat.utf8.clear();
}
2026-03-09 20:10:19 +01:00
}
}
void Device::keyboard_modifiers(void *data, wl_keyboard *keyboard, uint32_t serial, uint32_t mods_depressed, uint32_t mods_latched, uint32_t mods_locked, uint32_t group) {
2026-04-16 23:03:24 +02:00
xkb_state_update_mask(xkb_state, mods_depressed, mods_latched, mods_locked, 0, 0, group);
2026-03-09 20:10:19 +01:00
}
void Device::keyboard_repeat_info(void *data, wl_keyboard *keyboard, int32_t rate, int32_t delay) {
2026-05-02 00:03:24 +02:00
keyRepeat.rate = rate;
keyRepeat.delay = delay;
if (rate <= 0) keyRepeat.active = false; // compositor disabled repeat
}
2026-03-09 20:10:19 +01:00
2026-05-02 00:03:24 +02:00
void Device::TickKeyRepeats() {
if (!keyRepeat.active || !focusedWindow) return;
if (keyRepeat.rate <= 0) return;
auto now = std::chrono::steady_clock::now();
using ms = std::chrono::milliseconds;
auto sincePress = std::chrono::duration_cast<ms>(now - keyRepeat.pressTime).count();
if (sincePress < keyRepeat.delay) return;
auto period = std::chrono::milliseconds(1000 / keyRepeat.rate);
auto sinceLastFire = std::chrono::duration_cast<ms>(now - keyRepeat.lastFireTime).count();
if (sinceLastFire < period.count()) return;
// Catch up — emit one event per missed period so a paused frame doesn't
// make the repeat permanently lag behind.
while (now - keyRepeat.lastFireTime >= period) {
2026-05-12 00:24:48 +02:00
focusedWindow->onRawKeyDown.Invoke(keyRepeat.key);
focusedWindow->onRawKeyHold.Invoke(keyRepeat.key);
2026-05-02 00:03:24 +02:00
if (!keyRepeat.utf8.empty()) {
focusedWindow->onTextInput.Invoke(keyRepeat.utf8);
}
keyRepeat.lastFireTime += period;
}
2026-03-09 20:10:19 +01:00
}
void Device::seat_handle_capabilities(void* data, wl_seat* seat, uint32_t capabilities) {
seat = seat;
if (capabilities & WL_SEAT_CAPABILITY_POINTER) {
wl_pointer* pointer = wl_seat_get_pointer(seat);
wl_pointer_add_listener(pointer, &pointer_listener, nullptr);
}
if (capabilities & WL_SEAT_CAPABILITY_KEYBOARD) {
wl_keyboard* keyboard = wl_seat_get_keyboard(seat);
wl_keyboard_add_listener(keyboard, &keyboard_listener, nullptr);
}
}
#endif
void Device::Initialize() {
#ifdef CRAFTER_GRAPHICS_WINDOW_WAYLAND
display = wl_display_connect(NULL);
if (display == nullptr) {
throw std::runtime_error("Could not connect to wayland display");
}
wl_registry* registry = wl_display_get_registry(display);
wl_registry_add_listener(registry, &registry_listener, nullptr);
if (wl_display_roundtrip(display) == -1) {
exit(EXIT_FAILURE);
}
if (shm == NULL || compositor == NULL || xdgWmBase == NULL) {
throw std::runtime_error("No wl_shm, wl_compositor or xdg_wm_base support");
}
#endif
VkApplicationInfo app{VK_STRUCTURE_TYPE_APPLICATION_INFO};
app.pApplicationName = "";
app.pEngineName = "Crafter.Graphics";
2026-04-05 22:53:59 +02:00
app.apiVersion = VK_MAKE_VERSION(1, 4, 0);
2026-05-05 23:49:29 +02:00
// TODO(re-enable GPU-AV): once Vulkan SDK > 1.4.341 is the floor.
//
// GPU-Assisted Validation is opt-in via the enable list — leaving it
// out disables it. SDK 1.4.341's GPU-AV does not handle
// descriptor_heap pipelines (VK_PIPELINE_CREATE_2_DESCRIPTOR_HEAP_BIT_EXT
// with layout = VK_NULL_HANDLE): `PipelineSubState::GetPipelineLayoutUnion`
// null-derefs on the first dispatch/draw against such a pipeline.
//
// Tracked + fixed upstream:
// https://github.com/KhronosGroup/Vulkan-ValidationLayers/issues/12103
// Per spencer-lunarg (LunarG): broken in 1.4.341, fixed and landing
// in the next SDK release. Once we bump our Vulkan-Headers / SDK
// dependency past 1.4.341, restore the original enable list:
//
// VkValidationFeatureEnableEXT enables[] = {
// VK_VALIDATION_FEATURE_ENABLE_GPU_ASSISTED_EXT
// };
// validationFeatures.enabledValidationFeatureCount = 1;
// validationFeatures.pEnabledValidationFeatures = enables;
//
// Standard validation (the layer itself) is still on; only the GPU-AV
// out-of-bounds / shader-instrumentation checks are temporarily off.
2026-04-05 22:53:59 +02:00
VkValidationFeaturesEXT validationFeatures = {
.sType = VK_STRUCTURE_TYPE_VALIDATION_FEATURES_EXT,
};
2026-03-09 20:10:19 +01:00
VkInstanceCreateInfo instanceCreateInfo = {};
instanceCreateInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO;
2026-04-05 22:53:59 +02:00
instanceCreateInfo.pNext = &validationFeatures;
2026-03-09 20:10:19 +01:00
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];
2026-04-03 03:29:51 +02:00
uint32_t score;
2026-03-09 20:10:19 +01:00
VkPhysicalDeviceProperties properties;
vkGetPhysicalDeviceProperties(device, &properties);
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;
}
}
2026-05-12 00:24:48 +02:00
// Enumerate available device extensions so we can opt into
// VK_EXT_memory_decompression when the driver advertises it. Drivers
// without it (AMD, Intel as of early 2026) get the CPU-decode fallback.
{
std::uint32_t extCount = 0;
vkEnumerateDeviceExtensionProperties(physDevice, nullptr, &extCount, nullptr);
std::vector<VkExtensionProperties> exts(extCount);
vkEnumerateDeviceExtensionProperties(physDevice, nullptr, &extCount, exts.data());
for (const VkExtensionProperties& e : exts) {
if (std::strcmp(e.extensionName, VK_EXT_MEMORY_DECOMPRESSION_EXTENSION_NAME) == 0) {
memoryDecompressionSupported = true;
break;
}
}
}
// Properties query: chain memory-decompression props only when supported,
// otherwise sType validation flags it as an unrecognized struct on
// drivers that don't expose the extension.
if (memoryDecompressionSupported) {
memoryDecompressionProperties.pNext = const_cast<void*>(rayTracingProperties.pNext);
rayTracingProperties.pNext = &memoryDecompressionProperties;
}
2026-04-05 22:53:59 +02:00
VkPhysicalDeviceProperties2 properties2 {
2026-04-03 03:29:51 +02:00
.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2,
.pNext = &rayTracingProperties
};
vkGetPhysicalDeviceProperties2(physDevice, &properties2);
2026-05-12 00:24:48 +02:00
// Sanity-gate: GDeflate 1.0 must actually be in the supported method set.
if (memoryDecompressionSupported &&
(memoryDecompressionProperties.decompressionMethods & VK_MEMORY_DECOMPRESSION_METHOD_GDEFLATE_1_0_BIT_EXT) == 0) {
memoryDecompressionSupported = false;
}
2026-03-09 20:10:19 +01:00
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;
2026-04-09 00:15:09 +02:00
VkPhysicalDeviceFaultFeaturesEXT faultFeatures2 = {
.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FAULT_FEATURES_EXT,
};
VkPhysicalDeviceFeatures2 features22 = {
.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2,
.pNext = &faultFeatures2,
};
vkGetPhysicalDeviceFeatures2(physDevice, &features22);
VkPhysicalDeviceFaultFeaturesEXT faultFeatures = {
.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FAULT_FEATURES_EXT,
.deviceFault = VK_TRUE,
.deviceFaultVendorBinary = faultFeatures2.deviceFaultVendorBinary,
};
2026-04-05 22:53:59 +02:00
VkPhysicalDeviceShaderUntypedPointersFeaturesKHR untypedPointersFeatures {
.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SHADER_UNTYPED_POINTERS_FEATURES_KHR,
2026-04-09 00:15:09 +02:00
.pNext = &faultFeatures,
2026-04-05 22:53:59 +02:00
.shaderUntypedPointers = VK_TRUE,
};
2026-05-12 00:24:48 +02:00
// Enables synchronization2 sentinels (VkMemoryBarrier2, VK_PIPELINE_STAGE_2_*,
// VK_ACCESS_2_*) — required for VK_EXT_memory_decompression's sync tokens.
VkPhysicalDeviceVulkan13Features features13 {
.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_3_FEATURES,
.pNext = &untypedPointersFeatures,
.synchronization2 = VK_TRUE,
};
VkPhysicalDeviceMemoryDecompressionFeaturesEXT memoryDecompressionFeatures {
.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MEMORY_DECOMPRESSION_FEATURES_EXT,
.pNext = &features13,
.memoryDecompression = VK_TRUE,
};
void* postDecompressChain = memoryDecompressionSupported
? static_cast<void*>(&memoryDecompressionFeatures)
: static_cast<void*>(&features13);
2026-04-05 22:53:59 +02:00
VkPhysicalDeviceDescriptorHeapFeaturesEXT desciptorHeapFeatures {
.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DESCRIPTOR_HEAP_FEATURES_EXT,
2026-05-12 00:24:48 +02:00
.pNext = postDecompressChain,
2026-04-05 22:53:59 +02:00
.descriptorHeap = VK_TRUE,
};
2026-04-01 18:43:18 +02:00
VkPhysicalDevice16BitStorageFeatures bit16 {
.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_16BIT_STORAGE_FEATURES,
2026-04-05 22:53:59 +02:00
.pNext = &desciptorHeapFeatures,
2026-04-01 18:43:18 +02:00
.storageBuffer16BitAccess = VK_TRUE,
};
2026-03-09 20:10:19 +01:00
VkPhysicalDeviceVulkan12Features features12 {
.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_2_FEATURES,
2026-04-01 18:43:18 +02:00
.pNext = &bit16,
2026-04-09 00:15:09 +02:00
.shaderFloat16 = VK_TRUE,
2026-05-01 23:35:37 +02:00
// Bindless / runtime descriptor array indexing — needed for the
// descriptor_heap shader path.
.shaderUniformBufferArrayNonUniformIndexing = VK_TRUE,
.shaderSampledImageArrayNonUniformIndexing = VK_TRUE,
.shaderStorageBufferArrayNonUniformIndexing = VK_TRUE,
.shaderStorageImageArrayNonUniformIndexing = VK_TRUE,
2026-03-09 20:10:19 +01:00
.runtimeDescriptorArray = VK_TRUE,
2026-05-01 23:35:37 +02:00
.scalarBlockLayout = VK_TRUE,
.bufferDeviceAddress = VK_TRUE
2026-03-09 20:10:19 +01:00
};
2026-05-05 23:49:29 +02:00
VkPhysicalDeviceRayQueryFeaturesKHR physicalDeviceRayQueryFeatures{
.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_RAY_QUERY_FEATURES_KHR,
.pNext = &features12,
.rayQuery = VK_TRUE
};
2026-03-09 20:10:19 +01:00
VkPhysicalDeviceRayTracingPipelineFeaturesKHR physicalDeviceRayTracingPipelineFeatures{
.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_RAY_TRACING_PIPELINE_FEATURES_KHR,
2026-05-05 23:49:29 +02:00
.pNext = &physicalDeviceRayQueryFeatures,
2026-03-09 20:10:19 +01:00
.rayTracingPipeline = VK_TRUE
};
VkPhysicalDeviceAccelerationStructureFeaturesKHR deviceAccelerationStructureFeature = {
.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_ACCELERATION_STRUCTURE_FEATURES_KHR,
.pNext = &physicalDeviceRayTracingPipelineFeatures,
.accelerationStructure = VK_TRUE
};
VkPhysicalDeviceFeatures2 physical_features2 = {
.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2,
2026-04-05 22:53:59 +02:00
.pNext = &deviceAccelerationStructureFeature,
2026-03-09 20:10:19 +01:00
.features = {
2026-05-01 23:35:37 +02:00
// Order matches VkPhysicalDeviceFeatures declaration so the
// designated-initializer-order warning stays quiet.
.samplerAnisotropy = VK_TRUE,
.shaderStorageImageReadWithoutFormat = VK_TRUE,
.shaderStorageImageWriteWithoutFormat = VK_TRUE,
// Bindless dynamic indexing — required to index `images[]`,
// `textures[]`, `samplers[]`, `itemHeap[]` with a runtime value.
.shaderSampledImageArrayDynamicIndexing = VK_TRUE,
.shaderStorageBufferArrayDynamicIndexing = VK_TRUE,
.shaderStorageImageArrayDynamicIndexing = VK_TRUE,
.shaderInt16 = VK_TRUE
2026-03-09 20:10:19 +01:00
}
};
2026-05-12 00:24:48 +02:00
// Build the enabled-extension list dynamically so we can append the
// optional VK_EXT_memory_decompression entry only when the driver
// advertises it.
std::vector<const char*> enabledDeviceExtensions(
std::begin(deviceExtensionNames),
std::end(deviceExtensionNames));
if (memoryDecompressionSupported) {
enabledDeviceExtensions.push_back(VK_EXT_MEMORY_DECOMPRESSION_EXTENSION_NAME);
}
2026-03-09 20:10:19 +01:00
VkDeviceCreateInfo deviceCreateInfo = {};
deviceCreateInfo.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO;
deviceCreateInfo.queueCreateInfoCount = 1;
deviceCreateInfo.pQueueCreateInfos = &queueCreateInfo;
2026-05-12 00:24:48 +02:00
deviceCreateInfo.enabledExtensionCount = static_cast<std::uint32_t>(enabledDeviceExtensions.size());
deviceCreateInfo.ppEnabledExtensionNames = enabledDeviceExtensions.data();
2026-03-09 20:10:19 +01:00
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);
vkGetAccelerationStructureBuildSizesKHR = reinterpret_cast<PFN_vkGetAccelerationStructureBuildSizesKHR>(vkGetInstanceProcAddr(instance, "vkGetAccelerationStructureBuildSizesKHR"));
vkCreateAccelerationStructureKHR = reinterpret_cast<PFN_vkCreateAccelerationStructureKHR>(vkGetInstanceProcAddr(instance, "vkCreateAccelerationStructureKHR"));
2026-05-05 23:49:29 +02:00
vkDestroyAccelerationStructureKHR = reinterpret_cast<PFN_vkDestroyAccelerationStructureKHR>(vkGetInstanceProcAddr(instance, "vkDestroyAccelerationStructureKHR"));
2026-03-09 20:10:19 +01:00
vkCmdBuildAccelerationStructuresKHR = reinterpret_cast<PFN_vkCmdBuildAccelerationStructuresKHR>(vkGetInstanceProcAddr(instance, "vkCmdBuildAccelerationStructuresKHR"));
vkGetAccelerationStructureDeviceAddressKHR = reinterpret_cast<PFN_vkGetAccelerationStructureDeviceAddressKHR>(vkGetInstanceProcAddr(instance, "vkGetAccelerationStructureDeviceAddressKHR"));
vkCreateRayTracingPipelinesKHR = reinterpret_cast<PFN_vkCreateRayTracingPipelinesKHR>(vkGetInstanceProcAddr(instance, "vkCreateRayTracingPipelinesKHR"));
vkGetRayTracingShaderGroupHandlesKHR = reinterpret_cast<PFN_vkGetRayTracingShaderGroupHandlesKHR>(vkGetInstanceProcAddr(instance, "vkGetRayTracingShaderGroupHandlesKHR"));
vkCmdTraceRaysKHR = reinterpret_cast<PFN_vkCmdTraceRaysKHR>(vkGetInstanceProcAddr(instance, "vkCmdTraceRaysKHR"));
2026-04-05 22:53:59 +02:00
vkCmdBindResourceHeapEXT = reinterpret_cast<PFN_vkCmdBindResourceHeapEXT>(vkGetInstanceProcAddr(instance, "vkCmdBindResourceHeapEXT"));
vkCmdBindSamplerHeapEXT = reinterpret_cast<PFN_vkCmdBindSamplerHeapEXT>(vkGetInstanceProcAddr(instance, "vkCmdBindSamplerHeapEXT"));
vkWriteResourceDescriptorsEXT = reinterpret_cast<PFN_vkWriteResourceDescriptorsEXT>(vkGetInstanceProcAddr(instance, "vkWriteResourceDescriptorsEXT"));
2026-05-01 23:35:37 +02:00
vkWriteSamplerDescriptorsEXT = reinterpret_cast<PFN_vkWriteSamplerDescriptorsEXT>(vkGetInstanceProcAddr(instance, "vkWriteSamplerDescriptorsEXT"));
vkCmdPushDataEXT = reinterpret_cast<PFN_vkCmdPushDataEXT>(vkGetInstanceProcAddr(instance, "vkCmdPushDataEXT"));
2026-04-05 22:53:59 +02:00
vkGetPhysicalDeviceDescriptorSizeEXT = reinterpret_cast<PFN_vkGetPhysicalDeviceDescriptorSizeEXT>(vkGetInstanceProcAddr(instance, "vkGetPhysicalDeviceDescriptorSizeEXT"));
2026-04-09 00:15:09 +02:00
vkGetDeviceFaultInfoEXT = reinterpret_cast<PFN_vkGetDeviceFaultInfoEXT>(vkGetInstanceProcAddr(instance, "vkGetDeviceFaultInfoEXT"));
2026-05-12 00:24:48 +02:00
if (memoryDecompressionSupported) {
// vkGetDeviceProcAddr skips the loader trampoline that vkGetInstanceProcAddr
// requires for device-level functions. The other PFNs above predate this
// realization; opportunistic adoption for new entry points only.
vkCmdDecompressMemoryEXT = reinterpret_cast<PFN_vkCmdDecompressMemoryEXT>(
vkGetDeviceProcAddr(device, "vkCmdDecompressMemoryEXT"));
if (vkCmdDecompressMemoryEXT == nullptr) {
// Driver advertised the extension but didn't expose the entry
// point — defensively fall back to CPU decode.
memoryDecompressionSupported = false;
}
}
2026-03-09 20:10:19 +01:00
}
std::uint32_t Device::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");
2026-05-01 23:35:37 +02:00
}