2025-11-25 02:21:06 +01:00
/*
Crafter ® . Graphics
2026-03-09 20:10:19 +01:00
Copyright ( C ) 2026 Catcrafts ®
2025-11-25 02:21:06 +01:00
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
*/
2026-03-09 20:10:19 +01:00
module ;
# ifdef CRAFTER_GRAPHICS_WINDOW_WAYLAND
# 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 <cassert>
# 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>
# ifdef CRAFTER_GRAPHICS_RENDERER_SOFTWARE
# include <sys/stat.h>
# include <time.h>
# endif
# endif
# ifdef CRAFTER_GRAPHICS_WINDOW_WIN32
# include <windows.h>
# include <cassert>
# endif
# ifdef CRAFTER_GRAPHICS_RENDERER_VULKAN
# include "vulkan/vulkan.h"
# ifdef CRAFTER_GRAPHICS_WINDOW_WAYLAND
# include "vulkan/vulkan_wayland.h"
# endif
# ifdef CRAFTER_GRAPHICS_WINDOW_WIN32
# include "vulkan/vulkan_win32.h"
# endif
# endif
2025-11-25 02:21:06 +01:00
module Crafter . Graphics : Window_impl ;
import : Window ;
2026-03-09 20:10:19 +01:00
import : Transform2D ;
2025-12-30 23:28:38 +01:00
import : MouseElement ;
2026-03-09 20:10:19 +01:00
import : Device ;
# ifdef CRAFTER_GRAPHICS_RENDERER_VULKAN
import : VulkanTransition ;
2026-04-05 22:53:59 +02:00
import : DescriptorHeapVulkan ;
import : PipelineRTVulkan ;
2026-03-09 20:10:19 +01:00
# endif
2025-11-25 02:21:06 +01:00
import std ;
using namespace Crafter ;
2026-03-09 20:10:19 +01:00
# ifdef CRAFTER_GRAPHICS_WINDOW_WAYLAND
void randname ( char * buf ) {
struct timespec ts ;
clock_gettime ( CLOCK_REALTIME , & ts ) ;
long r = ts . tv_nsec ;
for ( int i = 0 ; i < 6 ; + + i ) {
buf [ i ] = ' A ' + ( r & 15 ) + ( r & 16 ) * 2 ;
r > > = 5 ;
}
}
int anonymous_shm_open ( void ) {
char name [ ] = " /hello-wayland-XXXXXX " ;
int retries = 100 ;
do {
randname ( name + strlen ( name ) - 6 ) ;
- - retries ;
// shm_open guarantees that O_CLOEXEC is set
int fd = shm_open ( name , O_RDWR | O_CREAT | O_EXCL , 0600 ) ;
if ( fd > = 0 ) {
shm_unlink ( name ) ;
return fd ;
}
} while ( retries > 0 & & errno = = EEXIST ) ;
return - 1 ;
}
int create_shm_file ( off_t size ) {
int fd = anonymous_shm_open ( ) ;
if ( fd < 0 ) {
return fd ;
}
if ( ftruncate ( fd , size ) < 0 ) {
close ( fd ) ;
return - 1 ;
}
return fd ;
}
# endif
# ifdef CRAFTER_GRAPHICS_WINDOW_WIN32
CrafterKeys vk_to_crafter_key ( WPARAM vk )
{
switch ( vk )
{
// Alphabet
case ' A ' : return CrafterKeys : : A ;
case ' B ' : return CrafterKeys : : B ;
case ' C ' : return CrafterKeys : : C ;
case ' D ' : return CrafterKeys : : D ;
case ' E ' : return CrafterKeys : : E ;
case ' F ' : return CrafterKeys : : F ;
case ' G ' : return CrafterKeys : : G ;
case ' H ' : return CrafterKeys : : H ;
case ' I ' : return CrafterKeys : : I ;
case ' J ' : return CrafterKeys : : J ;
case ' K ' : return CrafterKeys : : K ;
case ' L ' : return CrafterKeys : : L ;
case ' M ' : return CrafterKeys : : M ;
case ' N ' : return CrafterKeys : : N ;
case ' O ' : return CrafterKeys : : O ;
case ' P ' : return CrafterKeys : : P ;
case ' Q ' : return CrafterKeys : : Q ;
case ' R ' : return CrafterKeys : : R ;
case ' S ' : return CrafterKeys : : S ;
case ' T ' : return CrafterKeys : : T ;
case ' U ' : return CrafterKeys : : U ;
case ' V ' : return CrafterKeys : : V ;
case ' W ' : return CrafterKeys : : W ;
case ' X ' : return CrafterKeys : : X ;
case ' Y ' : return CrafterKeys : : Y ;
case ' Z ' : return CrafterKeys : : Z ;
// Numbers
case ' 0 ' : return CrafterKeys : : _0 ;
case ' 1 ' : return CrafterKeys : : _1 ;
case ' 2 ' : return CrafterKeys : : _2 ;
case ' 3 ' : return CrafterKeys : : _3 ;
case ' 4 ' : return CrafterKeys : : _4 ;
case ' 5 ' : return CrafterKeys : : _5 ;
case ' 6 ' : return CrafterKeys : : _6 ;
case ' 7 ' : return CrafterKeys : : _7 ;
case ' 8 ' : return CrafterKeys : : _8 ;
case ' 9 ' : return CrafterKeys : : _9 ;
// Function keys
case VK_F1 : return CrafterKeys : : F1 ;
case VK_F2 : return CrafterKeys : : F2 ;
case VK_F3 : return CrafterKeys : : F3 ;
case VK_F4 : return CrafterKeys : : F4 ;
case VK_F5 : return CrafterKeys : : F5 ;
case VK_F6 : return CrafterKeys : : F6 ;
case VK_F7 : return CrafterKeys : : F7 ;
case VK_F8 : return CrafterKeys : : F8 ;
case VK_F9 : return CrafterKeys : : F9 ;
case VK_F10 : return CrafterKeys : : F10 ;
case VK_F11 : return CrafterKeys : : F11 ;
case VK_F12 : return CrafterKeys : : F12 ;
// Control keys
case VK_ESCAPE : return CrafterKeys : : Escape ;
case VK_TAB : return CrafterKeys : : Tab ;
case VK_RETURN : return CrafterKeys : : Enter ;
case VK_SPACE : return CrafterKeys : : Space ;
case VK_BACK : return CrafterKeys : : Backspace ;
case VK_DELETE : return CrafterKeys : : Delete ;
case VK_INSERT : return CrafterKeys : : Insert ;
case VK_HOME : return CrafterKeys : : Home ;
case VK_END : return CrafterKeys : : End ;
case VK_PRIOR : return CrafterKeys : : PageUp ;
case VK_NEXT : return CrafterKeys : : PageDown ;
case VK_CAPITAL : return CrafterKeys : : CapsLock ;
case VK_NUMLOCK : return CrafterKeys : : NumLock ;
case VK_SCROLL : return CrafterKeys : : ScrollLock ;
// Modifiers
2026-03-09 22:34:14 -05:00
case VK_SHIFT : return CrafterKeys : : LeftShift ;
2026-03-09 20:10:19 +01:00
case VK_LSHIFT : return CrafterKeys : : LeftShift ;
2026-03-09 22:34:14 -05:00
case VK_RSHIFT : return CrafterKeys : : RightShift ;
case VK_CONTROL : return CrafterKeys : : LeftCtrl ;
2026-03-09 20:10:19 +01:00
case VK_LCONTROL : return CrafterKeys : : LeftCtrl ;
case VK_RCONTROL : return CrafterKeys : : RightCtrl ;
case VK_LMENU : return CrafterKeys : : LeftAlt ;
case VK_RMENU : return CrafterKeys : : RightAlt ;
case VK_LWIN : return CrafterKeys : : LeftSuper ;
case VK_RWIN : return CrafterKeys : : RightSuper ;
// Arrows
case VK_UP : return CrafterKeys : : Up ;
case VK_DOWN : return CrafterKeys : : Down ;
case VK_LEFT : return CrafterKeys : : Left ;
case VK_RIGHT : return CrafterKeys : : Right ;
// Keypad
case VK_NUMPAD0 : return CrafterKeys : : keypad_0 ;
case VK_NUMPAD1 : return CrafterKeys : : keypad_1 ;
case VK_NUMPAD2 : return CrafterKeys : : keypad_2 ;
case VK_NUMPAD3 : return CrafterKeys : : keypad_3 ;
case VK_NUMPAD4 : return CrafterKeys : : keypad_4 ;
case VK_NUMPAD5 : return CrafterKeys : : keypad_5 ;
case VK_NUMPAD6 : return CrafterKeys : : keypad_6 ;
case VK_NUMPAD7 : return CrafterKeys : : keypad_7 ;
case VK_NUMPAD8 : return CrafterKeys : : keypad_8 ;
case VK_NUMPAD9 : return CrafterKeys : : keypad_9 ;
case VK_SEPARATOR : return CrafterKeys : : keypad_enter ;
case VK_ADD : return CrafterKeys : : keypad_plus ;
case VK_SUBTRACT : return CrafterKeys : : keypad_minus ;
case VK_MULTIPLY : return CrafterKeys : : keypad_multiply ;
case VK_DIVIDE : return CrafterKeys : : keypad_divide ;
case VK_DECIMAL : return CrafterKeys : : keypad_decimal ;
// Punctuation
case VK_OEM_3 : return CrafterKeys : : grave ; // `
case VK_OEM_MINUS : return CrafterKeys : : minus ; // -
case VK_OEM_PLUS : return CrafterKeys : : equal ; // =
case VK_OEM_4 : return CrafterKeys : : bracket_left ; // [
case VK_OEM_6 : return CrafterKeys : : bracket_right ; // ]
case VK_OEM_5 : return CrafterKeys : : backslash ; //
case VK_OEM_1 : return CrafterKeys : : semicolon ; // ;
case VK_OEM_7 : return CrafterKeys : : quote ; // '
case VK_OEM_COMMA : return CrafterKeys : : comma ; // ,
case VK_OEM_PERIOD : return CrafterKeys : : period ; // .
case VK_OEM_2 : return CrafterKeys : : slash ; // /
default : throw std : : runtime_error ( std : : format ( " Unkown VK {} " , vk ) ) ;
}
2025-11-25 18:52:32 +01:00
}
2026-03-09 20:10:19 +01:00
// Define a window class name
const char g_szClassName [ ] = " myWindowClass " ;
// Window procedure function that processes messages
LRESULT CALLBACK WndProc ( HWND hwnd , UINT msg , WPARAM wParam , LPARAM lParam ) {
Window * window = nullptr ;
if ( msg = = WM_NCCREATE )
{
CREATESTRUCT * pCreate = reinterpret_cast < CREATESTRUCT * > ( lParam ) ;
window = static_cast < Window * > ( pCreate - > lpCreateParams ) ;
SetWindowLongPtr ( hwnd , GWLP_USERDATA , reinterpret_cast < LONG_PTR > ( window ) ) ;
return TRUE ;
}
else
{
window = reinterpret_cast < Window * > (
GetWindowLongPtr ( hwnd , GWLP_USERDATA )
) ;
}
switch ( msg ) {
case WM_DESTROY : {
PostQuitMessage ( 0 ) ;
break ;
2025-12-29 18:56:06 +01:00
}
2026-03-09 20:10:19 +01:00
case WM_KEYDOWN : {
if ( ( lParam & ( 1 < < 30 ) ) = = 0 ) { // only first press
CrafterKeys crafterKey = vk_to_crafter_key ( wParam ) ;
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 ) ;
}
}
break ;
}
case WM_KEYUP : {
CrafterKeys crafterKey = vk_to_crafter_key ( wParam ) ;
window - > heldkeys [ static_cast < std : : uint8_t > ( crafterKey ) ] = false ;
window - > onKeyUp [ static_cast < std : : uint8_t > ( crafterKey ) ] . Invoke ( ) ;
window - > onAnyKeyUp . Invoke ( crafterKey ) ;
break ;
}
case WM_MOUSEMOVE : {
int x = LOWORD ( lParam ) ;
int y = HIWORD ( lParam ) ;
Vector < float , 2 > pos ( x , y ) ;
window - > currentMousePos = pos ;
window - > onMouseMove . Invoke ( ) ;
for ( MouseElement * element : window - > mouseElements ) {
if ( element ) {
if ( window - > currentMousePos . x > = element - > scaled . position . x & & window - > currentMousePos . x < = element - > scaled . position . x + element - > scaled . size . x & & window - > currentMousePos . y > element - > scaled . position . y & & window - > currentMousePos . y < element - > scaled . position . y + element - > scaled . size . y ) {
element - > onMouseMove . Invoke ( ) ;
2026-03-12 01:07:46 +01:00
if ( ! element - > mouseHover ) {
element - > mouseHover = true ;
2026-03-09 20:10:19 +01:00
element - > onMouseEnter . Invoke ( ) ;
}
2026-03-12 01:07:46 +01:00
} else if ( element - > mouseHover ) {
2026-04-02 16:52:10 +02:00
element - > mouseHover = false ;
2026-03-09 20:10:19 +01:00
element - > onMouseLeave . Invoke ( ) ;
}
}
}
window - > mouseElements . erase ( std : : remove ( window - > mouseElements . begin ( ) , window - > mouseElements . end ( ) , static_cast < MouseElement * > ( nullptr ) ) , window - > mouseElements . end ( ) ) ;
break ;
}
case WM_LBUTTONDOWN : {
window - > mouseLeftHeld = true ;
window - > onMouseLeftClick . Invoke ( ) ;
for ( MouseElement * element : window - > mouseElements ) {
if ( element ) {
if ( window - > currentMousePos . x > = element - > scaled . position . x & & window - > currentMousePos . x < = element - > scaled . position . x + element - > scaled . size . x & & window - > currentMousePos . y > element - > scaled . position . y & & window - > currentMousePos . y < element - > scaled . position . y + element - > scaled . size . y ) {
element - > onMouseLeftClick . Invoke ( ) ;
}
}
}
break ;
}
case WM_LBUTTONUP : {
window - > mouseLeftHeld = false ;
window - > onMouseLeftRelease . Invoke ( ) ;
for ( MouseElement * element : window - > mouseElements ) {
if ( element ) {
if ( window - > currentMousePos . x > = element - > scaled . position . x & & window - > currentMousePos . x < = element - > scaled . position . x + element - > scaled . size . x & & window - > currentMousePos . y > element - > scaled . position . y & & window - > currentMousePos . y < element - > scaled . position . y + element - > scaled . size . y ) {
element - > onMouseLeftRelease . Invoke ( ) ;
}
}
}
break ;
}
case WM_RBUTTONDOWN : {
window - > mouseRightHeld = true ;
window - > onMouseRightClick . Invoke ( ) ;
for ( MouseElement * element : window - > mouseElements ) {
if ( element ) {
if ( window - > currentMousePos . x > = element - > scaled . position . x & & window - > currentMousePos . x < = element - > scaled . position . x + element - > scaled . size . x & & window - > currentMousePos . y > element - > scaled . position . y & & window - > currentMousePos . y < element - > scaled . position . y + element - > scaled . size . y ) {
element - > onMouseRightClick . Invoke ( ) ;
}
}
}
break ;
}
case WM_RBUTTONUP : {
window - > mouseRightHeld = false ;
window - > onMouseRightRelease . Invoke ( ) ;
for ( MouseElement * element : window - > mouseElements ) {
if ( element ) {
if ( window - > currentMousePos . x > = element - > scaled . position . x & & window - > currentMousePos . x < = element - > scaled . position . x + element - > scaled . size . x & & window - > currentMousePos . y > element - > scaled . position . y & & window - > currentMousePos . y < element - > scaled . position . y + element - > scaled . size . y ) {
element - > onMouseRightRelease . Invoke ( ) ;
}
}
}
break ;
}
2026-04-02 16:52:10 +02:00
case WM_SETCURSOR : {
if ( LOWORD ( lParam ) = = HTCLIENT & & window - > cursorHandle ) {
SetCursor ( window - > cursorHandle ) ;
return TRUE ;
}
break ;
}
2026-03-09 20:10:19 +01:00
default : return DefWindowProc ( hwnd , msg , wParam , lParam ) ;
}
return 0 ;
}
# endif
Window : : Window ( std : : uint32_t width , std : : uint32_t height , const std : : string_view title ) : Window ( width , height ) {
SetTitle ( title ) ;
}
# ifdef CRAFTER_GRAPHICS_RENDERER_SOFTWARE
2026-03-12 01:07:46 +01:00
Window : : Window ( std : : uint32_t width , std : : uint32_t height ) : width ( width ) , height ( height ) , renderer ( width , height ) {
2026-03-09 20:10:19 +01:00
# else
Window : : Window ( std : : uint32_t width , std : : uint32_t height ) : width ( width ) , height ( height ) {
# endif
# ifdef CRAFTER_GRAPHICS_WINDOW_WAYLAND
Device : : windows . push_back ( this ) ;
surface = wl_compositor_create_surface ( Device : : compositor ) ;
xdgSurface = xdg_wm_base_get_xdg_surface ( Device : : 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 ( Device : : fractionalScaleManager , surface ) ;
wp_fractional_scale_v1_add_listener ( wp_scale , & wp_fractional_scale_v1_listener , this ) ;
while ( wl_display_dispatch ( Device : : display ) ! = - 1 & & ! configured ) { }
wl_surface_commit ( surface ) ;
zxdg_toplevel_decoration_v1 * decoration = zxdg_decoration_manager_v1_get_toplevel_decoration ( Device : : manager , xdgToplevel ) ;
zxdg_toplevel_decoration_v1_set_mode ( decoration , ZXDG_TOPLEVEL_DECORATION_V1_MODE_SERVER_SIDE ) ;
wpViewport = wp_viewporter_get_viewport ( Device : : wpViewporter , surface ) ;
wp_viewport_set_destination ( wpViewport , std : : ceil ( width / scale ) , std : : ceil ( height / scale ) ) ;
wl_surface_commit ( surface ) ;
# ifdef CRAFTER_GRAPHICS_RENDERER_SOFTWARE
// Create a wl_buffer, attach it to the surface and commit the surface
int stride = width * 4 ;
int size = stride * height ;
// Allocate a shared memory file with the right size
int fd = create_shm_file ( size ) ;
if ( fd < 0 ) {
throw std : : runtime_error ( std : : format ( " creating a buffer file for {}B failed " , size ) ) ;
}
// Map the shared memory file
2026-03-12 21:13:53 +01:00
renderer . buffer [ 0 ] = reinterpret_cast < Vector < std : : uint8_t , 4 , 4 > * > ( mmap ( NULL , size , PROT_READ | PROT_WRITE , MAP_SHARED , fd , 0 ) ) ;
if ( renderer . buffer [ 0 ] = = MAP_FAILED ) {
2026-03-09 20:10:19 +01:00
throw std : : runtime_error ( " mmap failed " ) ;
}
wl_shm_pool * pool = wl_shm_create_pool ( Device : : shm , fd , size ) ;
buffer = wl_shm_pool_create_buffer ( pool , 0 , width , height , stride , WL_SHM_FORMAT_ARGB8888 ) ;
wl_shm_pool_destroy ( pool ) ;
close ( fd ) ;
if ( buffer = = nullptr ) {
throw std : : runtime_error ( " wl_buffer creation failed " ) ;
}
wl_surface_attach ( surface , buffer , 0 , 0 ) ;
wl_surface_commit ( surface ) ;
# endif
# endif
# ifdef CRAFTER_GRAPHICS_RENDERER_VULKAN
# ifdef CRAFTER_GRAPHICS_WINDOW_WIN32
// Initialize the window class
WNDCLASS wc = { 0 } ;
wc . lpfnWndProc = WndProc ; // Set window procedure
wc . hInstance = GetModuleHandle ( NULL ) ; // Get instance handle
wc . lpszClassName = g_szClassName ;
wc . hCursor = LoadCursor ( NULL , IDC_ARROW ) ;
if ( ! RegisterClass ( & wc ) ) {
MessageBox ( NULL , " Window Class Registration Failed! " , " Error " , MB_ICONERROR ) ;
}
RECT rc = { 0 , 0 , static_cast < LONG > ( width ) , static_cast < LONG > ( height ) } ;
AdjustWindowRect ( & rc , WS_OVERLAPPEDWINDOW , FALSE ) ;
HWND hwnd = CreateWindowEx (
0 ,
g_szClassName ,
" " ,
WS_OVERLAPPEDWINDOW ,
CW_USEDEFAULT , CW_USEDEFAULT ,
rc . right - rc . left ,
rc . bottom - rc . top ,
NULL , NULL , wc . hInstance , this
) ;
if ( hwnd = = NULL ) {
MessageBox ( NULL , " Window Creation Failed! " , " Error " , MB_ICONERROR ) ;
}
// Show the window
ShowWindow ( hwnd , SW_SHOWNORMAL ) ;
UpdateWindow ( hwnd ) ;
MSG msg ;
while ( PeekMessage ( & msg , NULL , 0 , 0 , PM_REMOVE ) ) {
TranslateMessage ( & msg ) ;
DispatchMessage ( & msg ) ;
2025-12-29 18:56:06 +01:00
}
2026-03-09 20:10:19 +01:00
VkWin32SurfaceCreateInfoKHR createInfo = { } ;
createInfo . sType = VK_STRUCTURE_TYPE_WIN32_SURFACE_CREATE_INFO_KHR ;
createInfo . hinstance = wc . hInstance ;
createInfo . hwnd = hwnd ;
Device : : CheckVkResult ( vkCreateWin32SurfaceKHR ( Device : : instance , & createInfo , NULL , & vulkanSurface ) ) ;
# endif
# ifdef CRAFTER_GRAPHICS_WINDOW_WAYLAND
VkWaylandSurfaceCreateInfoKHR createInfo = { } ;
createInfo . sType = VK_STRUCTURE_TYPE_WAYLAND_SURFACE_CREATE_INFO_KHR ;
createInfo . display = Device : : display ;
createInfo . surface = surface ;
Device : : CheckVkResult ( vkCreateWaylandSurfaceKHR ( Device : : instance , & createInfo , NULL , & vulkanSurface ) ) ;
# endif
// Get list of supported surface formats
std : : uint32_t formatCount ;
Device : : CheckVkResult ( vkGetPhysicalDeviceSurfaceFormatsKHR ( Device : : physDevice , vulkanSurface , & formatCount , NULL ) ) ;
assert ( formatCount > 0 ) ;
std : : vector < VkSurfaceFormatKHR > surfaceFormats ( formatCount ) ;
Device : : CheckVkResult ( vkGetPhysicalDeviceSurfaceFormatsKHR ( Device : : physDevice , vulkanSurface , & formatCount , surfaceFormats . data ( ) ) ) ;
// We want to get a format that best suits our needs, so we try to get one from a set of preferred formats
// Initialize the format to the first one returned by the implementation in case we can't find one of the preffered formats
VkSurfaceFormatKHR selectedFormat = surfaceFormats [ 0 ] ;
std : : vector < VkFormat > preferredImageFormats = {
VK_FORMAT_R8G8B8A8_UNORM ,
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 ( ) ;
VkCommandBufferAllocateInfo cmdBufAllocateInfo { } ;
cmdBufAllocateInfo . sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO ;
cmdBufAllocateInfo . commandPool = Device : : commandPool ;
cmdBufAllocateInfo . level = VK_COMMAND_BUFFER_LEVEL_PRIMARY ;
cmdBufAllocateInfo . commandBufferCount = numFrames ;
Device : : CheckVkResult ( vkAllocateCommandBuffers ( Device : : device , & cmdBufAllocateInfo , drawCmdBuffers ) ) ;
VkSemaphoreCreateInfo semaphoreCreateInfo { } ;
semaphoreCreateInfo . sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO ;
Device : : CheckVkResult ( vkCreateSemaphore ( Device : : device , & semaphoreCreateInfo , nullptr , & semaphores . presentComplete ) ) ;
Device : : CheckVkResult ( vkCreateSemaphore ( Device : : 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 ;
# endif
2026-03-10 03:05:10 +01:00
lastMousePos = { 0 , 0 } ;
mouseDelta = { 0 , 0 } ;
currentMousePos = { 0 , 0 } ;
2025-11-25 18:52:32 +01:00
}
2026-03-09 20:10:19 +01:00
void Window : : SetTitle ( const std : : string_view title ) {
# ifdef CRAFTER_GRAPHICS_WINDOW_WAYLAND
xdg_toplevel_set_title ( xdgToplevel , title . data ( ) ) ;
# endif
2025-11-25 18:52:32 +01:00
}
2026-03-12 01:07:46 +01:00
void Window : : SetCusorImage ( std : : uint16_t sizeX , std : : uint16_t sizeY ) {
2026-03-12 21:13:53 +01:00
new ( & cursorRenderer ) Rendertarget < std : : uint8_t , 4 , 4 , 1 > ( sizeX , sizeY ) ;
2026-03-12 01:07:46 +01:00
# ifdef CRAFTER_GRAPHICS_WINDOW_WAYLAND
if ( cursorSurface = = nullptr ) {
cursorSurface = wl_compositor_create_surface ( Device : : compositor ) ;
} else {
wl_buffer_destroy ( cursorWlBuffer ) ;
2026-03-12 21:13:53 +01:00
munmap ( cursorRenderer . buffer [ 0 ] , cursorBufferOldSize ) ;
2026-03-12 01:07:46 +01:00
}
int stride = sizeX * 4 ;
int size = stride * sizeY ;
cursorBufferOldSize = size ;
// Allocate a shared memory file with the right size
int fd = create_shm_file ( size ) ;
if ( fd < 0 ) {
throw std : : runtime_error ( std : : format ( " creating a buffer file for {}B failed " , size ) ) ;
}
2026-03-12 21:13:53 +01:00
cursorRenderer . buffer [ 0 ] = reinterpret_cast < Vector < std : : uint8_t , 4 , 4 > * > ( mmap ( NULL , size , PROT_READ | PROT_WRITE , MAP_SHARED , fd , 0 ) ) ;
if ( cursorRenderer . buffer [ 0 ] = = MAP_FAILED ) {
2026-03-12 01:07:46 +01:00
throw std : : runtime_error ( " mmap failed " ) ;
}
wl_shm_pool * pool = wl_shm_create_pool ( Device : : shm , fd , size ) ;
cursorWlBuffer = wl_shm_pool_create_buffer ( pool , 0 , sizeX , sizeY , stride , WL_SHM_FORMAT_ARGB8888 ) ;
wl_shm_pool_destroy ( pool ) ;
close ( fd ) ;
wl_surface_attach ( cursorSurface , cursorWlBuffer , 0 , 0 ) ;
wl_surface_damage ( cursorSurface , 0 , 0 , sizeX , sizeY ) ;
wl_surface_commit ( cursorSurface ) ;
# endif
2026-04-02 16:52:10 +02:00
# ifdef CRAFTER_GRAPHICS_WINDOW_WIN32
if ( cursorBitmap ) {
DeleteObject ( cursorBitmap ) ;
}
if ( cursorHandle ) {
DestroyCursor ( cursorHandle ) ;
cursorHandle = nullptr ;
}
BITMAPINFO bmi = { } ;
bmi . bmiHeader . biSize = sizeof ( BITMAPINFOHEADER ) ;
bmi . bmiHeader . biWidth = sizeX ;
bmi . bmiHeader . biHeight = - ( int ) sizeY ; // top-down
bmi . bmiHeader . biPlanes = 1 ;
bmi . bmiHeader . biBitCount = 32 ;
bmi . bmiHeader . biCompression = BI_RGB ;
HDC hdc = GetDC ( nullptr ) ;
cursorBitmap = CreateDIBSection ( hdc , & bmi , DIB_RGB_COLORS , reinterpret_cast < void * * > ( & cursorRenderer . buffer [ 0 ] ) , nullptr , 0 ) ;
ReleaseDC ( nullptr , hdc ) ;
if ( ! cursorBitmap ) {
throw std : : runtime_error ( " CreateDIBSection failed for cursor " ) ;
}
cursorSizeX = sizeX ;
cursorSizeY = sizeY ;
# endif
2026-03-12 01:07:46 +01:00
}
void Window : : SetCusorImageDefault ( ) {
# ifdef CRAFTER_GRAPHICS_WINDOW_WAYLAND
wl_buffer_destroy ( cursorWlBuffer ) ;
wl_surface_destroy ( cursorSurface ) ;
cursorSurface = nullptr ;
# endif
2026-04-02 16:52:10 +02:00
# ifdef CRAFTER_GRAPHICS_WINDOW_WIN32
if ( cursorHandle ) {
DestroyCursor ( cursorHandle ) ;
cursorHandle = nullptr ;
}
if ( cursorBitmap ) {
DeleteObject ( cursorBitmap ) ;
cursorBitmap = nullptr ;
}
// Setting nullptr will make WM_SETCURSOR fall through to the default
# endif
2026-03-12 01:07:46 +01:00
}
void Window : : UpdateCursorImage ( ) {
2026-04-02 16:52:10 +02:00
# ifdef CRAFTER_GRAPHICS_WINDOW_WAYLAND
2026-03-12 21:13:53 +01:00
cursorRenderer . Render ( 0 ) ;
2026-03-12 01:07:46 +01:00
for ( std : : uint32_t i = 0 ; i < cursorBufferOldSize / 4 ; i + + ) {
2026-03-12 21:13:53 +01:00
std : : swap ( cursorRenderer . buffer [ 0 ] [ i ] . b , cursorRenderer . buffer [ 0 ] [ i ] . r ) ;
2026-03-12 01:07:46 +01:00
}
wl_surface_attach ( cursorSurface , cursorWlBuffer , 0 , 0 ) ;
wl_surface_damage ( cursorSurface , 0 , 0 , 9999999 , 99999999 ) ;
wl_surface_commit ( cursorSurface ) ;
2026-04-02 16:52:10 +02:00
# endif
# ifdef CRAFTER_GRAPHICS_WINDOW_WIN32
cursorRenderer . Render ( 0 ) ;
// Swap R and B channels (renderer is RGBA, GDI DIB is BGRA)
for ( std : : uint32_t i = 0 ; i < ( std : : uint32_t ) ( cursorSizeX * cursorSizeY ) ; i + + ) {
std : : swap ( cursorRenderer . buffer [ 0 ] [ i ] . r , cursorRenderer . buffer [ 0 ] [ i ] . b ) ;
}
// Create a mask bitmap (all zeros = fully opaque, alpha comes from color bitmap)
HBITMAP hMask = CreateBitmap ( cursorSizeX , cursorSizeY , 1 , 1 , nullptr ) ;
ICONINFO ii = { } ;
ii . fIcon = FALSE ;
ii . xHotspot = 0 ;
ii . yHotspot = 0 ;
ii . hbmMask = hMask ;
ii . hbmColor = cursorBitmap ;
if ( cursorHandle ) {
DestroyCursor ( cursorHandle ) ;
}
cursorHandle = ( HCURSOR ) CreateIconIndirect ( & ii ) ;
DeleteObject ( hMask ) ;
if ( cursorHandle ) {
SetCursor ( cursorHandle ) ;
}
# endif
2026-03-12 01:07:46 +01:00
}
2026-03-09 20:10:19 +01:00
void Window : : StartSync ( ) {
# ifdef CRAFTER_GRAPHICS_WINDOW_WAYLAND
while ( open & & wl_display_dispatch ( Device : : display ) ! = - 1 ) {
2026-03-13 01:06:55 +01:00
onBeforeUpdate . Invoke ( ) ;
2026-03-09 20:10:19 +01:00
}
# endif
2026-03-10 02:47:28 +01:00
# ifdef CRAFTER_GRAPHICS_WINDOW_WIN32
2026-03-09 20:10:19 +01:00
while ( open ) {
MSG msg ;
while ( PeekMessage ( & msg , NULL , 0 , 0 , PM_REMOVE ) ) {
TranslateMessage ( & msg ) ;
DispatchMessage ( & msg ) ;
}
2026-03-13 01:06:55 +01:00
onBeforeUpdate . Invoke ( ) ;
2026-03-09 20:10:19 +01:00
if ( updating ) {
Update ( ) ;
}
}
# endif
}
void Window : : StartUpdate ( ) {
lastFrameBegin = std : : chrono : : high_resolution_clock : : now ( ) ;
updating = true ;
# ifdef CRAFTER_GRAPHICS_WINDOW_WAYLAND
cb = wl_surface_frame ( surface ) ;
wl_callback_add_listener ( cb , & wl_callback_listener , this ) ;
# endif
}
void Window : : StopUpdate ( ) {
updating = false ;
}
2026-03-09 21:00:58 +01:00
std : : chrono : : time_point < std : : chrono : : high_resolution_clock > startTime ;
2026-03-09 20:10:19 +01:00
void Window : : Update ( ) {
2026-03-09 21:00:58 +01:00
startTime = std : : chrono : : high_resolution_clock : : now ( ) ;
2026-03-09 20:10:19 +01:00
# ifdef CRAFTER_TIMING
2026-03-09 21:00:58 +01:00
vblank = duration_cast < std : : chrono : : milliseconds > ( startTime - frameEnd ) ;
2026-03-09 20:10:19 +01:00
# endif
2026-03-09 21:50:24 +01:00
mouseDelta = { currentMousePos . x - lastMousePos . x , currentMousePos . y - lastMousePos . y } ;
2026-03-09 22:15:36 +01:00
currentFrameTime = { startTime , startTime - lastFrameBegin } ;
2026-03-09 20:10:19 +01:00
# ifdef CRAFTER_TIMING
auto renderStart = std : : chrono : : high_resolution_clock : : now ( ) ;
renderTimings . clear ( ) ;
# endif
Render ( ) ;
# ifdef CRAFTER_TIMING
auto renderEnd = std : : chrono : : high_resolution_clock : : now ( ) ;
totalRender = renderEnd - renderStart ;
# endif
2026-03-09 21:50:24 +01:00
lastMousePos = currentMousePos ;
2026-03-09 20:10:19 +01:00
# ifdef CRAFTER_TIMING
frameEnd = std : : chrono : : high_resolution_clock : : now ( ) ;
frameTimes . push_back ( totalUpdate + totalRender ) ;
// Keep only the last 100 frame times
if ( frameTimes . size ( ) > 100 ) {
frameTimes . erase ( frameTimes . begin ( ) ) ;
}
# endif
2026-03-09 21:00:58 +01:00
lastFrameBegin = startTime ;
2025-11-25 18:52:32 +01:00
}
2025-11-25 19:43:40 +01:00
2026-03-09 20:10:19 +01:00
void Window : : Render ( ) {
2026-03-09 21:16:14 +01:00
# ifdef CRAFTER_GRAPHICS_RENDERER_SOFTWARE
2026-03-12 21:13:53 +01:00
renderer . Render ( 0 ) ;
2026-03-09 21:16:14 +01:00
# ifdef CRAFTER_GRAPHICS_WINDOW_WAYLAND
wl_surface_attach ( surface , buffer , 0 , 0 ) ;
wl_surface_commit ( surface ) ;
wl_surface_damage ( surface , 0 , 0 , 10000 , 100000 ) ;
# endif
# endif
# ifdef CRAFTER_GRAPHICS_RENDERER_VULKAN
// Acquire the next image from the swap chain
2026-03-09 21:14:53 +01:00
Device : : CheckVkResult ( vkAcquireNextImageKHR ( Device : : device , swapChain , UINT64_MAX , semaphores . presentComplete , ( VkFence ) nullptr , & currentBuffer ) ) ;
2026-03-09 20:10:19 +01:00
submitInfo . commandBufferCount = 1 ;
submitInfo . pCommandBuffers = & drawCmdBuffers [ currentBuffer ] ;
VkCommandBufferBeginInfo cmdBufInfo { } ;
cmdBufInfo . sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO ;
2026-03-09 21:14:53 +01:00
Device : : CheckVkResult ( vkBeginCommandBuffer ( drawCmdBuffers [ currentBuffer ] , & cmdBufInfo ) ) ;
2026-03-09 20:10:19 +01:00
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 ;
VkImageMemoryBarrier image_memory_barrier {
. sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER ,
. srcAccessMask = 0 ,
. dstAccessMask = VK_ACCESS_SHADER_WRITE_BIT ,
. oldLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR ,
. newLayout = VK_IMAGE_LAYOUT_GENERAL ,
. srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED ,
. dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED ,
. image = images [ currentBuffer ] ,
. subresourceRange = range
} ;
vkCmdPipelineBarrier ( drawCmdBuffers [ currentBuffer ] , VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT , VK_PIPELINE_STAGE_RAY_TRACING_SHADER_BIT_KHR , 0 , 0 , nullptr , 0 , nullptr , 1 , & image_memory_barrier ) ;
2026-03-09 21:16:14 +01:00
onUpdate . Invoke ( { startTime , startTime - lastFrameBegin } ) ;
# ifdef CRAFTER_TIMING
totalUpdate = std : : chrono : : nanoseconds ( 0 ) ;
updateTimings . clear ( ) ;
for ( const std : : pair < const EventListener < FrameTime > * , std : : chrono : : nanoseconds > & entry : onUpdate . listenerTimes ) {
updateTimings . push_back ( entry ) ;
totalUpdate + = entry . second ;
}
# endif
2026-04-05 22:53:59 +02:00
vkCmdBindPipeline ( drawCmdBuffers [ currentBuffer ] , VK_PIPELINE_BIND_POINT_RAY_TRACING_KHR , pipeline - > pipeline ) ;
VkBindHeapInfoEXT resourceHeapInfo = {
. sType = VK_STRUCTURE_TYPE_BIND_HEAP_INFO_EXT ,
. heapRange = {
. address = descriptorHeap - > resourceHeap [ currentBuffer ] . address ,
2026-04-10 22:26:15 +02:00
. size = static_cast < std : : uint32_t > ( descriptorHeap - > resourceHeap [ currentBuffer ] . size )
2026-04-05 22:53:59 +02:00
} ,
2026-04-10 22:26:15 +02:00
. reservedRangeOffset = ( descriptorHeap - > resourceHeap [ currentBuffer ] . size - Device : : descriptorHeapProperties . minResourceHeapReservedRange ) & ~ ( Device : : descriptorHeapProperties . imageDescriptorAlignment - 1 ) ,
2026-04-05 22:53:59 +02:00
. reservedRangeSize = Device : : descriptorHeapProperties . minResourceHeapReservedRange
} ;
Device : : vkCmdBindResourceHeapEXT ( drawCmdBuffers [ currentBuffer ] , & resourceHeapInfo ) ;
VkBindHeapInfoEXT samplerHeapInfo = {
. sType = VK_STRUCTURE_TYPE_BIND_HEAP_INFO_EXT ,
. heapRange = {
. address = descriptorHeap - > samplerHeap [ currentBuffer ] . address ,
2026-04-10 22:26:15 +02:00
. size = static_cast < std : : uint32_t > ( descriptorHeap - > samplerHeap [ currentBuffer ] . size )
2026-04-05 22:53:59 +02:00
} ,
2026-04-10 22:26:15 +02:00
. reservedRangeOffset = descriptorHeap - > samplerHeap [ currentBuffer ] . size - Device : : descriptorHeapProperties . minSamplerHeapReservedRange ,
2026-04-05 22:53:59 +02:00
. reservedRangeSize = Device : : descriptorHeapProperties . minSamplerHeapReservedRange
} ;
Device : : vkCmdBindSamplerHeapEXT ( drawCmdBuffers [ currentBuffer ] , & samplerHeapInfo ) ;
2026-03-09 20:10:19 +01:00
2026-04-05 22:53:59 +02:00
Device : : vkCmdTraceRaysKHR ( drawCmdBuffers [ currentBuffer ] , & pipeline - > raygenRegion , & pipeline - > missRegion , & pipeline - > hitRegion , & pipeline - > callableRegion , width , height , 1 ) ;
2026-03-09 20:10:19 +01:00
VkImageMemoryBarrier image_memory_barrier2 {
. sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER ,
. srcAccessMask = VK_ACCESS_SHADER_WRITE_BIT ,
. dstAccessMask = 0 ,
. oldLayout = VK_IMAGE_LAYOUT_GENERAL ,
. newLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR ,
. srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED ,
. dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED ,
. image = images [ currentBuffer ] ,
. subresourceRange = range
} ;
vkCmdPipelineBarrier ( drawCmdBuffers [ currentBuffer ] , VK_PIPELINE_STAGE_RAY_TRACING_SHADER_BIT_KHR , VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT , 0 , 0 , nullptr , 0 , nullptr , 1 , & image_memory_barrier2 ) ;
2026-03-09 21:14:53 +01:00
Device : : CheckVkResult ( vkEndCommandBuffer ( drawCmdBuffers [ currentBuffer ] ) ) ;
2026-03-09 20:10:19 +01:00
2026-03-09 21:14:53 +01:00
Device : : CheckVkResult ( vkQueueSubmit ( Device : : queue , 1 , & submitInfo , VK_NULL_HANDLE ) ) ;
2026-03-09 20:10:19 +01:00
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 ;
}
2026-03-09 21:14:53 +01:00
VkResult result = vkQueuePresentKHR ( Device : : queue , & presentInfo ) ;
2026-03-09 20:10:19 +01:00
if ( result = = VK_SUBOPTIMAL_KHR ) {
CreateSwapchain ( ) ;
} else {
2026-03-09 21:14:53 +01:00
Device : : CheckVkResult ( result ) ;
2026-03-09 20:10:19 +01:00
}
2026-03-09 21:14:53 +01:00
Device : : CheckVkResult ( vkQueueWaitIdle ( Device : : queue ) ) ;
2026-03-09 21:16:14 +01:00
# endif
2025-11-25 19:43:40 +01:00
}
2025-11-25 23:29:48 +01:00
# ifdef CRAFTER_TIMING
void Window : : LogTiming ( ) {
std : : cout < < std : : format ( " Update: {} " , duration_cast < std : : chrono : : milliseconds > ( totalUpdate ) ) < < std : : endl ;
for ( const std : : pair < const EventListener < FrameTime > * , std : : chrono : : nanoseconds > & entry : updateTimings ) {
std : : cout < < std : : format ( " \t {} {} " , reinterpret_cast < const void * > ( entry . first ) , duration_cast < std : : chrono : : microseconds > ( entry . second ) ) < < std : : endl ;
}
std : : cout < < std : : format ( " Render: {} " , duration_cast < std : : chrono : : milliseconds > ( totalRender ) ) < < std : : endl ;
2026-03-09 20:10:19 +01:00
for ( const std : : tuple < const RenderingElement * , std : : uint32_t , std : : uint32_t , std : : chrono : : nanoseconds > & entry : renderer . renderTimings ) {
2025-11-25 23:29:48 +01:00
std : : cout < < std : : format ( " \t {} {}x{} {} " , reinterpret_cast < const void * > ( std : : get < 0 > ( entry ) ) , std : : get < 1 > ( entry ) , std : : get < 2 > ( entry ) , duration_cast < std : : chrono : : microseconds > ( std : : get < 3 > ( entry ) ) ) < < std : : endl ;
}
2025-11-25 23:36:43 +01:00
std : : cout < < std : : format ( " Total: {} " , duration_cast < std : : chrono : : milliseconds > ( totalUpdate + totalRender ) ) < < std : : endl ;
2025-11-25 23:29:48 +01:00
std : : cout < < std : : format ( " Vblank: {} " , duration_cast < std : : chrono : : milliseconds > ( vblank ) ) < < std : : endl ;
2025-11-25 23:36:43 +01:00
// Add 100-frame average and min-max timing info
if ( ! frameTimes . empty ( ) ) {
// Calculate average
std : : chrono : : nanoseconds sum ( 0 ) ;
for ( const auto & frameTime : frameTimes ) {
sum + = frameTime ;
}
auto average = sum / frameTimes . size ( ) ;
// Find min and max
auto min = frameTimes . front ( ) ;
auto max = frameTimes . front ( ) ;
for ( const auto & frameTime : frameTimes ) {
if ( frameTime < min ) min = frameTime ;
if ( frameTime > max ) max = frameTime ;
}
std : : cout < < std : : format ( " Last 100 Frame Times - Avg: {}, Min: {}, Max: {} " ,
duration_cast < std : : chrono : : milliseconds > ( average ) ,
duration_cast < std : : chrono : : milliseconds > ( min ) ,
duration_cast < std : : chrono : : milliseconds > ( max ) ) < < std : : endl ;
}
2025-11-25 23:29:48 +01:00
}
2025-11-26 18:48:58 +01:00
# endif
2026-03-09 20:10:19 +01:00
# ifdef CRAFTER_GRAPHICS_RENDERER_VULKAN
void Window : : CreateSwapchain ( )
{
// Store the current swap chain handle so we can use it later on to ease up recreation
VkSwapchainKHR oldSwapchain = swapChain ;
2025-11-26 18:48:58 +01:00
2026-03-09 20:10:19 +01:00
// Get physical device surface properties and formats
VkSurfaceCapabilitiesKHR surfCaps ;
Device : : CheckVkResult ( vkGetPhysicalDeviceSurfaceCapabilitiesKHR ( Device : : 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 ;
Device : : CheckVkResult ( vkGetPhysicalDeviceSurfacePresentModesKHR ( Device : : physDevice , vulkanSurface , & presentModeCount , NULL ) ) ;
assert ( presentModeCount > 0 ) ;
std : : vector < VkPresentModeKHR > presentModes ( presentModeCount ) ;
Device : : CheckVkResult ( vkGetPhysicalDeviceSurfacePresentModesKHR ( Device : : 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 ;
// 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 = numFrames ;
swapchainCI . imageFormat = colorFormat ;
swapchainCI . imageColorSpace = colorSpace ;
swapchainCI . imageExtent = { swapchainExtent . width , swapchainExtent . height } ;
swapchainCI . imageUsage = VK_IMAGE_USAGE_STORAGE_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 ;
Device : : CheckVkResult ( vkCreateSwapchainKHR ( Device : : 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 ) {
vkDestroySwapchainKHR ( Device : : device , oldSwapchain , nullptr ) ;
}
uint32_t imageCount { 0 } ;
Device : : CheckVkResult ( vkGetSwapchainImagesKHR ( Device : : device , swapChain , & imageCount , nullptr ) ) ;
// Get the swap chain images
Device : : CheckVkResult ( vkGetSwapchainImagesKHR ( Device : : device , swapChain , & imageCount , images ) ) ;
2026-04-05 22:53:59 +02:00
for ( std : : uint8_t i = 0 ; i < numFrames ; i + + ) {
imageViews [ i ] = {
. sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO ,
. flags = 0 ,
. image = images [ i ] ,
. viewType = VK_IMAGE_VIEW_TYPE_2D ,
. format = colorFormat ,
. components = {
VK_COMPONENT_SWIZZLE_R ,
VK_COMPONENT_SWIZZLE_G ,
VK_COMPONENT_SWIZZLE_B ,
VK_COMPONENT_SWIZZLE_A
} ,
. subresourceRange = {
. aspectMask = VK_IMAGE_ASPECT_COLOR_BIT ,
. baseMipLevel = 0 ,
. levelCount = 1 ,
. baseArrayLayer = 0 ,
. layerCount = 1 ,
} ,
} ;
2026-03-09 20:10:19 +01:00
}
}
VkCommandBuffer Window : : StartInit ( ) {
VkCommandBufferBeginInfo cmdBufInfo { } ;
cmdBufInfo . sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO ;
Device : : 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 ;
for ( std : : uint32_t i = 0 ; i < numFrames ; i + + ) {
image_layout_transition ( drawCmdBuffers [ currentBuffer ] ,
images [ i ] ,
VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT ,
VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT ,
0 ,
0 ,
VK_IMAGE_LAYOUT_UNDEFINED ,
VK_IMAGE_LAYOUT_PRESENT_SRC_KHR ,
range
) ;
}
return drawCmdBuffers [ currentBuffer ] ;
}
void Window : : FinishInit ( ) {
VkSubmitInfo submitInfo { } ;
submitInfo . sType = VK_STRUCTURE_TYPE_SUBMIT_INFO ;
submitInfo . commandBufferCount = 1 ;
submitInfo . pCommandBuffers = & drawCmdBuffers [ currentBuffer ] ;
Device : : CheckVkResult ( vkEndCommandBuffer ( drawCmdBuffers [ currentBuffer ] ) ) ;
Device : : CheckVkResult ( vkQueueSubmit ( Device : : queue , 1 , & submitInfo , VK_NULL_HANDLE ) ) ;
Device : : CheckVkResult ( vkQueueWaitIdle ( Device : : queue ) ) ;
}
2026-03-13 01:06:55 +01:00
VkCommandBuffer Window : : GetCmd ( ) {
VkCommandBufferBeginInfo cmdBufInfo { } ;
cmdBufInfo . sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO ;
Device : : 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 ;
return drawCmdBuffers [ currentBuffer ] ;
}
void Window : : EndCmd ( VkCommandBuffer cmd ) {
VkSubmitInfo submitInfo { } ;
submitInfo . sType = VK_STRUCTURE_TYPE_SUBMIT_INFO ;
submitInfo . commandBufferCount = 1 ;
submitInfo . pCommandBuffers = & drawCmdBuffers [ currentBuffer ] ;
Device : : CheckVkResult ( vkEndCommandBuffer ( drawCmdBuffers [ currentBuffer ] ) ) ;
Device : : CheckVkResult ( vkQueueSubmit ( Device : : queue , 1 , & submitInfo , VK_NULL_HANDLE ) ) ;
Device : : CheckVkResult ( vkQueueWaitIdle ( Device : : queue ) ) ;
}
2026-03-09 20:10:19 +01:00
# endif
# ifdef CRAFTER_GRAPHICS_WINDOW_WAYLAND
void Window : : wl_surface_frame_done ( void * data , struct wl_callback * cb , uint32_t time ) {
wl_callback_destroy ( cb ) ;
Window * window = reinterpret_cast < Window * > ( data ) ;
if ( window - > updating ) {
cb = wl_surface_frame ( window - > surface ) ;
wl_callback_add_listener ( cb , & Window : : wl_callback_listener , window ) ;
2026-03-09 21:43:25 +01:00
window - > Update ( ) ;
} else {
cb = nullptr ;
2026-03-09 20:10:19 +01:00
}
}
void Window : : xdg_toplevel_configure ( void * , xdg_toplevel * , std : : int32_t , std : : int32_t , wl_array * ) {
}
void Window : : xdg_toplevel_handle_close ( void * data , xdg_toplevel * ) {
Window * window = reinterpret_cast < Window * > ( data ) ;
window - > onClose . Invoke ( ) ;
window - > open = false ;
}
void Window : : xdg_surface_handle_configure ( void * data , xdg_surface * xdg_surface , std : : uint32_t serial ) {
Window * window = reinterpret_cast < Window * > ( data ) ;
// The compositor configures our surface, acknowledge the configure event
xdg_surface_ack_configure ( xdg_surface , serial ) ;
if ( window - > configured ) {
// If this isn't the first configure event we've received, we already
// have a buffer attached, so no need to do anything. Commit the
// surface to apply the configure acknowledgement.
wl_surface_commit ( window - > surface ) ;
}
window - > configured = true ;
}
void Window : : xdg_surface_handle_preferred_scale ( void * data , wp_fractional_scale_v1 * , std : : uint32_t scale ) {
Window * window = reinterpret_cast < Window * > ( data ) ;
window - > scale = scale / 120.0f ;
}
2025-11-26 18:48:58 +01:00
2026-03-09 20:10:19 +01:00
# endif