/* 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_WINDOW_WAYLAND #include #include #include #include #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 #include #include #include #include #include #include #include #include #include #include #ifdef CRAFTER_GRAPHICS_RENDERER_SOFTWARE #include #include #endif #endif #ifdef CRAFTER_GRAPHICS_WINDOW_WIN32 #include #include #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 module Crafter.Graphics:Window_impl; import :Window; import :Transform2D; import :MouseElement; import :Device; #ifdef CRAFTER_GRAPHICS_RENDERER_VULKAN import :VulkanTransition; #endif import std; using namespace Crafter; #ifdef CRAFTER_GRAPHICS_WINDOW_WAYLAND #ifdef CRAFTER_GRAPHICS_RENDERER_SOFTWARE 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 #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 case VK_LSHIFT: return CrafterKeys::LeftShift; case VK_RSHIFT: return CrafterKeys::RightShift; 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)); } } // 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(lParam); window = static_cast(pCreate->lpCreateParams); SetWindowLongPtr(hwnd, GWLP_USERDATA, reinterpret_cast(window)); return TRUE; } else { window = reinterpret_cast( GetWindowLongPtr(hwnd, GWLP_USERDATA) ); } switch (msg) { case WM_DESTROY:{ PostQuitMessage(0); break; } case WM_KEYDOWN:{ if ((lParam & (1 << 30)) == 0) { // only first press CrafterKeys crafterKey = vk_to_crafter_key(wParam); if(window->heldkeys[static_cast(crafterKey)]) { window->onKeyHold[static_cast(crafterKey)].Invoke(); window->onAnyKeyHold.Invoke(crafterKey); } else{ window->heldkeys[static_cast(crafterKey)] = true; window->onKeyDown[static_cast(crafterKey)].Invoke(); window->onAnyKeyDown.Invoke(crafterKey); } } break; } case WM_KEYUP: { CrafterKeys crafterKey = vk_to_crafter_key(wParam); window->heldkeys[static_cast(crafterKey)] = false; window->onKeyUp[static_cast(crafterKey)].Invoke(); window->onAnyKeyUp.Invoke(crafterKey); break; } case WM_MOUSEMOVE: { int x = LOWORD(lParam); int y = HIWORD(lParam); Vector 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(); if(!(window->lastMousePos.x >= element->scaled.position.x && window->lastMousePos.x <= element->scaled.position.x+element->scaled.size.x && window->lastMousePos.y > element->scaled.position.y && window->lastMousePos.y < element->scaled.position.y+element->scaled.size.y)) { element->onMouseEnter.Invoke(); } } else if(window->lastMousePos.x >= element->scaled.position.x && window->lastMousePos.x <= element->scaled.position.x+element->scaled.size.x && window->lastMousePos.y > element->scaled.position.y && window->lastMousePos.y < element->scaled.position.y+element->scaled.size.y) { element->onMouseLeave.Invoke(); } } } window->mouseElements.erase(std::remove(window->mouseElements.begin(), window->mouseElements.end(), static_cast(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; } 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 Window::Window(std::uint32_t width, std::uint32_t height) : width(width), height(height) : Rendertarget(width, height) { #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 renderer.buffer = reinterpret_cast*>(mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0)); renderer.sizeX = width; renderer.sizeY = height; if (renderer.buffer == MAP_FAILED) { 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(width), static_cast(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); } 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 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 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 } void Window::SetTitle(const std::string_view title) { #ifdef CRAFTER_GRAPHICS_WINDOW_WAYLAND xdg_toplevel_set_title(xdgToplevel, title.data()); #endif } void Window::StartSync() { #ifdef CRAFTER_GRAPHICS_WINDOW_WAYLAND while (open && wl_display_dispatch(Device::display) != -1) { } #endif #ifdef CRAFTER_GRAHPICS_WINDOW_WIN32 while(open) { MSG msg; while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) { TranslateMessage(&msg); DispatchMessage(&msg); } 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; } std::chrono::time_point startTime; void Window::Update() { startTime = std::chrono::high_resolution_clock::now(); #ifdef CRAFTER_TIMING vblank = duration_cast(startTime - frameEnd); #endif mouseDelta = {currentMousePos.x-lastMousePos.x, currentMousePos.y-lastMousePos.y}; currentFrameTime = {startTime, startTime-lastFrameBegin}; #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 lastMousePos = currentMousePos; #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 lastFrameBegin = startTime; } void Window::Render() { #ifdef CRAFTER_GRAPHICS_RENDERER_SOFTWARE // elements.erase(std::remove(elements.begin(), elements.end(), static_cast(nullptr)), elements.end()); // std::sort(elements.begin(), elements.end(), [](Transform* a, Transform* b){ return a->anchor.z < b->anchor.z; }); renderer.Render(); #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 Device::CheckVkResult(vkAcquireNextImageKHR(Device::device, swapChain, UINT64_MAX, semaphores.presentComplete, (VkFence)nullptr, ¤tBuffer)); submitInfo.commandBufferCount = 1; submitInfo.pCommandBuffers = &drawCmdBuffers[currentBuffer]; 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; 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); onUpdate.Invoke({startTime, startTime-lastFrameBegin}); #ifdef CRAFTER_TIMING totalUpdate = std::chrono::nanoseconds(0); updateTimings.clear(); for (const std::pair*, std::chrono::nanoseconds>& entry : onUpdate.listenerTimes) { updateTimings.push_back(entry); totalUpdate += entry.second; } #endif vkCmdBindPipeline(drawCmdBuffers[currentBuffer], VK_PIPELINE_BIND_POINT_RAY_TRACING_KHR, rtPipeline); VkBindDescriptorSetsInfo bindDescriptorSetsInfo{ .sType = VK_STRUCTURE_TYPE_BIND_DESCRIPTOR_SETS_INFO, .stageFlags = VK_SHADER_STAGE_ALL, .layout = rtPipelineLayout, .firstSet = 0, .descriptorSetCount = static_cast(descriptorsRt.size()), .pDescriptorSets = descriptorsRt.data() }; vkCmdBindDescriptorSets2(drawCmdBuffers[currentBuffer], &bindDescriptorSetsInfo); Device::vkCmdTraceRaysKHR(drawCmdBuffers[currentBuffer], &raygenRegion, &missRegion, &hitRegion, &callableRegion, width, height, 1); 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); Device::CheckVkResult(vkEndCommandBuffer(drawCmdBuffers[currentBuffer])); Device::CheckVkResult(vkQueueSubmit(Device::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 = ¤tBuffer; // 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; } VkResult result = vkQueuePresentKHR(Device::queue, &presentInfo); if(result == VK_SUBOPTIMAL_KHR) { CreateSwapchain(); } else { Device::CheckVkResult(result); } Device::CheckVkResult(vkQueueWaitIdle(Device::queue)); #endif } #ifdef CRAFTER_TIMING void Window::LogTiming() { std::cout << std::format("Update: {}", duration_cast(totalUpdate)) << std::endl; for (const std::pair*, std::chrono::nanoseconds>& entry : updateTimings) { std::cout << std::format("\t{} {}", reinterpret_cast(entry.first), duration_cast(entry.second)) << std::endl; } std::cout << std::format("Render: {}", duration_cast(totalRender)) << std::endl; for (const std::tuple& entry : renderer.renderTimings) { std::cout << std::format("\t{} {}x{} {}", reinterpret_cast(std::get<0>(entry)), std::get<1>(entry), std::get<2>(entry), duration_cast(std::get<3>(entry))) << std::endl; } std::cout << std::format("Total: {}", duration_cast(totalUpdate+totalRender)) << std::endl; std::cout << std::format("Vblank: {}", duration_cast(vblank)) << std::endl; // 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(average), duration_cast(min), duration_cast(max)) << std::endl; } } #endif #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; // 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 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 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) { for (auto i = 0; i < numFrames; i++) { vkDestroyImageView(Device::device, imageViews[i], nullptr); } 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)); for (auto i = 0; i < numFrames; 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]; Device::CheckVkResult(vkCreateImageView(Device::device, &colorAttachmentView, nullptr, &imageViews[i])); 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; } } 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)); } #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(data); if(window->updating) { cb = wl_surface_frame(window->surface); wl_callback_add_listener(cb, &Window::wl_callback_listener, window); window->Update(); } else { cb = nullptr; } } 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(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(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(data); window->scale = scale / 120.0f; } #endif