Vulkan device initialization and compilation utils

- Vulkan initialization moved to InitVulkan()
- Renderer thread processes commands from main thread
- SetupDevice() for select physical device and queue families, create logical device and queues.
This commit is contained in:
legatt 2025-08-11 23:51:08 +03:00
parent 0062aa85e5
commit 721e5afbac
8 changed files with 474 additions and 73 deletions

BIN
assets/fonts/ARIAL.TTF Normal file

Binary file not shown.

108
assets/layout/base.rcss Normal file
View File

@ -0,0 +1,108 @@
body, div,
h1, h2, h3, h4,
h5, h6, p,
hr, pre,
tabset tabs
{
display: block;
background-color: white;
color: black;
font-family: Arial;
}
rml, html,
body
{
position: absolute;
left: 0;
top: 0;
margin: 0;
background-color: rgb(255, 255, 255);
width: 100%;
height: 100%;
}
h1
{
font-size: 2em;
margin: .67em 0;
}
h2
{
font-size: 1.5em;
margin: .75em 0;
}
h3
{
font-size: 1.17em;
margin: .83em 0;
}
h4, p
{
margin: 1.12em 0;
}
h5
{
font-size: .83em;
margin: 1.5em 0;
}
h6
{
font-size: .75em;
margin: 1.67em 0;
}
h1, h2, h3, h4,
h5, h6, strong
{
font-weight: bold;
}
em
{
font-style: italic;
}
pre
{
white-space: pre;
}
hr
{
border-width: 1px;
}
table
{
box-sizing: border-box;
display: table;
}
tr
{
box-sizing: border-box;
display: table-row;
}
td
{
box-sizing: border-box;
display: table-cell;
}
col
{
box-sizing: border-box;
display: table-column;
}
colgroup
{
display: table-column-group;
}
thead, tbody, tfoot
{
display: table-row-group;
}

View File

@ -0,0 +1,13 @@
<rml>
<head>
<link type="text/css" href="../base.rcss" />
</head>
<body>
<div>
<p>Hello World!</p>
</div>
</body>
</rml>

View File

@ -41,23 +41,39 @@ target_link_libraries(
$<IF:$<TARGET_EXISTS:SDL2::SDL2>,SDL2::SDL2,SDL2::SDL2-static> $<IF:$<TARGET_EXISTS:SDL2::SDL2>,SDL2::SDL2,SDL2::SDL2-static>
) )
target_link_libraries(${PROJECT_NAME} PRIVATE Vulkan::Vulkan) target_link_libraries(${PROJECT_NAME} PRIVATE Vulkan::Vulkan)
set(RMLUI_BACKEND_PATH ${RmlUi_SOURCE_DIR}/Backends) # Link SDL Vulkan backend for RmlUi
add_library(rmlui_backend_SDL_VK INTERFACE)
target_sources(rmlui_backend_SDL_VK INTERFACE
${RMLUI_BACKEND_PATH}/RmlUi_Platform_SDL.cpp
${RMLUI_BACKEND_PATH}/RmlUi_Renderer_VK.cpp
)
target_include_directories(rmlui_backend_SDL_VK INTERFACE ${RMLUI_BACKEND_PATH})
target_compile_definitions(rmlui_backend_SDL_VK INTERFACE
RMLUI_SDL_VERSION_MAJOR=2
RMLUI_NUM_MSAA_SAMPLES=4
RMLUI_VK_CUSTOM_LOADER=<vulkan/vulkan.h>
)
target_link_libraries(${PROJECT_NAME} PRIVATE RmlUi::RmlUi) target_link_libraries(${PROJECT_NAME} PRIVATE RmlUi::RmlUi)
target_include_directories(${PROJECT_NAME} PUBLIC include/) target_include_directories(${PROJECT_NAME} PUBLIC include/)
if (CMAKE_VERSION VERSION_GREATER 3.12) if (CMAKE_VERSION VERSION_GREATER 3.12)
set_property(TARGET ${PROJECT_NAME} PROPERTY CXX_STANDARD 20) set_property(TARGET ${PROJECT_NAME} PROPERTY CXX_STANDARD 20)
endif() endif()
# Compile options
if (APP_VERSION)
string(REPLACE "." ";" APP_VERSION_LIST ${APP_VERSION})
list(GET APP_VERSION_LIST 0 MAJOR_VERSION)
list(GET APP_VERSION_LIST 1 MINOR_VERSION)
list(GET APP_VERSION_LIST 2 PATCH_VERSION)
target_compile_definitions(${PROJECT_NAME}
PRIVATE APP_VERSION
PRIVATE MAJOR_VERSION=${MAJOR_VERSION}
PRIVATE MINOR_VERSION=${MINOR_VERSION}
PRIVATE PATCH_VERSION=${PATCH_VERSION}
)
endif()
if (VALIDATION_LAYER)
target_compile_definitions(${PROJECT_NAME} PRIVATE VALIDATION_LAYER_ON)
endif()
# Set a symbolic link so executable can find the /assets folder
set(link_src "${CMAKE_SOURCE_DIR}/assets")
set(link_dst "${CMAKE_CURRENT_BINARY_DIR}/assets")
add_custom_command(
TARGET ${PROJECT_NAME}
POST_BUILD COMMAND ${CMAKE_COMMAND}
-E create_symlink ${link_src} ${link_dst}
DEPENDS ${link_dst}
COMMENT "symbolic link ${link_src} => ${link_dst}"
)

View File

@ -1,12 +1,41 @@
#pragma once #pragma once
#include <thread> #include <thread>
#include <atomic> #include <atomic>
#include <optional>
#include <Boost/lockfree/spsc_queue.hpp>
#include <SDL2/SDL.h> #include <SDL2/SDL.h>
#include <SDL2/SDL_vulkan.h> #include <SDL2/SDL_vulkan.h>
#include <vulkan/vulkan.h> #include <vulkan/vulkan.hpp>
#include <RmlUi/Core.h> #include <RmlUi/Core.h>
#ifdef VALIDATION_LAYER_ON
#define VALIDATION_LAYER "VK_LAYER_KHRONOS_validation"
#else
#define VALIDATION_LAYER
#endif
// GPU properties priority for selection
#define GPU_DISCRETE_PRIORITY 1000
struct RenderCommand
{
enum Type
{
NONE,
//STORE screenHeight and screenWidth BEFORE request
RESIZE_WINDOW,
SHUTDOWN
} type = NONE;
};
struct QueueFamiliesBundle
{
std::optional<uint32_t> graphics;
std::optional<uint32_t> presence;
};
class Renderer class Renderer
{ {
private: private:
@ -14,18 +43,35 @@ private:
std::jthread renderThread; std::jthread renderThread;
SDL_Window *window; SDL_Window *window;
VkInstance vulkan; VkInstance vulkan;
VkPhysicalDevice gpu;
VkDevice device;
QueueFamiliesBundle queueFamilies;
const float defaultQueuePriority = 1.0f;
Rml::Context *uiContext; Rml::Context *uiContext;
boost::lockfree::spsc_queue<RenderCommand, boost::lockfree::capacity<512>> commands;
void InitVulkan();
void SetupDevice();
void Threading(); void Threading();
void Loop(); void Loop();
void ProcessCommand(const RenderCommand &command);
void Release(); void Release();
public: public:
std::atomic<bool> isRunning; std::atomic<bool> isRunning;
std::atomic<int> screenWidth, screenHeight;
Renderer(SDL_Window *window); Renderer(SDL_Window *window);
~Renderer();
void Start(); void Start();
void Stop(); void Shutdown();
void Request(const RenderCommand &command);
void Request(RenderCommand::Type type);
}; };

View File

@ -0,0 +1,13 @@
#pragma once
#if !_DEBUG | NDEBUG
#define P7_SINK L"/P7.Sink=FileTxt"
#else
#define P7_SINK L"/P7.Sink=Console"
#endif
#if !APP_VERSION
#define MAJOR_VERSION 0
#define MINOR_VERSION 0
#define PATCH_VERSION 0
#endif

View File

@ -1,14 +1,216 @@
#if !APP_VERSION
#include "compile_config.hpp"
#endif
#include "Renderer.hpp" #include "Renderer.hpp"
#include "Logger.h" #include "Logger.h"
#include <allocator.hpp> #include <allocator.hpp>
#include <exception>
Renderer::Renderer(SDL_Window *window) : isRunning(false) Renderer::Renderer(SDL_Window *window) : isRunning(false)
{ {
this->window = window; this->window = window;
} }
Renderer::~Renderer()
{
Release();
}
void Renderer::InitVulkan()
{
std::vector<const char*> vulkanExtensions {
//
};
std::vector<const char*> vulkanLayers {
VALIDATION_LAYER
};
// Get Vulkan extensions list
uint32_t extensionsSDLCount = 0;
if ( !SDL_Vulkan_GetInstanceExtensions(window, &extensionsSDLCount, nullptr) )
LOG(CRITICAL, "Fail to get SDL Vulkan extensions number! %hs", SDL_GetError());
std::vector<const char*> vulkanExtensionsSDL(extensionsSDLCount);
if ( !SDL_Vulkan_GetInstanceExtensions(window, &extensionsSDLCount, vulkanExtensionsSDL.data()) )
{
LOG(CRITICAL, "Fail to register SDL Vulkan extensions! %hs", SDL_GetError());
vulkanExtensionsSDL.clear();
}
else vulkanExtensions.insert(vulkanExtensions.end(), vulkanExtensionsSDL.begin(), vulkanExtensionsSDL.end());
for (const char *extension : vulkanExtensions)
{
LOG(INFO, "%hs Vulkan extension registered", extension);
}
for (const char *layer : vulkanLayers)
{
LOG(INFO, "%hs Vulkan layer registered", layer);
}
// Create VkInstance
VkApplicationInfo vkAppConfig {
.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO,
.pNext = nullptr,
.pApplicationName = "Etredia Empires",
.applicationVersion = VK_MAKE_VERSION(MAJOR_VERSION, MINOR_VERSION, PATCH_VERSION),
.pEngineName = "No Engine",
.engineVersion = VK_MAKE_VERSION(1, 0, 0),
.apiVersion = VK_API_VERSION_1_0
};
VkInstanceCreateInfo vkCreateConfig {
.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO,
.pNext = nullptr,
.pApplicationInfo = &vkAppConfig,
.enabledLayerCount = static_cast<uint32_t>(vulkanLayers.size()),
.ppEnabledLayerNames = vulkanLayers.data(),
.enabledExtensionCount = static_cast<uint32_t>(vulkanExtensions.size()),
.ppEnabledExtensionNames = vulkanExtensions.data(),
};
VkResult vkResult = vkCreateInstance(&vkCreateConfig, nullptr, &vulkan);
if (vkResult != VK_SUCCESS)
LOG(CRITICAL, "Vulkan is not instantiated! Code: %d", vkResult);
else LOG(INFO, "Vulkan is instantiated");
}
void Renderer::SetupDevice()
{
// Select physical device and queue families
VkResult vkResult;
uint32_t devicesCount = 0;
vkResult = vkEnumeratePhysicalDevices(vulkan, &devicesCount, nullptr);
if (vkResult != VK_SUCCESS)
{
LOG(CRITICAL, "Failed to enumerate physical devices. Code: %d", vkResult);
return;
}
if (devicesCount == 0)
{
LOG(CRITICAL, "No devices with Vulkan support found!");
return;
}
std::vector<VkPhysicalDevice> devices(devicesCount);
vkResult = vkEnumeratePhysicalDevices(vulkan, &devicesCount, devices.data());
if (vkResult != VK_SUCCESS)
{
LOG(CRITICAL, "Failed to enumerate %d physical devices. Code: %d", devicesCount, vkResult);
return;
}
else
{
LOG(INFO, "Found %d Vulkan compatible physical devices.", devices.size());
}
gpu = VK_NULL_HANDLE;
VkPhysicalDeviceProperties deviceProperties {};
VkPhysicalDeviceFeatures deviceFeatures {};
VkPhysicalDeviceFeatures enabledFeatures {};
uint32_t best_priority = 0;
for (const VkPhysicalDevice &physicalDevice : devices)
{
uint32_t priority = 1;
vkGetPhysicalDeviceFeatures(physicalDevice, &deviceFeatures);
if (deviceFeatures.wideLines) enabledFeatures.wideLines = VK_TRUE; else continue;
if (deviceFeatures.independentBlend) enabledFeatures.independentBlend = VK_TRUE; else continue;
vkGetPhysicalDeviceProperties(physicalDevice, &deviceProperties);
uint32_t queueFamiliesCount = 0;
vkGetPhysicalDeviceQueueFamilyProperties(
physicalDevice,
&queueFamiliesCount,
nullptr
);
std::vector<VkQueueFamilyProperties> queueFamiliesProperties(queueFamiliesCount);
vkGetPhysicalDeviceQueueFamilyProperties(
physicalDevice,
&queueFamiliesCount,
queueFamiliesProperties.data()
);
uint32_t i = 0;
QueueFamiliesBundle queueFamiliesBuffer {};
for (const VkQueueFamilyProperties &familyProps : queueFamiliesProperties)
{
if (familyProps.queueFlags & VK_QUEUE_GRAPHICS_BIT)
{
queueFamiliesBuffer.graphics = i;
queueFamiliesBuffer.presence = i;
}
//TODO: if (vkGetPhysicalDeviceSurfaceSupportKHR(gpu, i, surface, value))
i++;
}
if (
!queueFamiliesBuffer.graphics.has_value() ||
!queueFamiliesBuffer.presence.has_value()
)
continue;
if (deviceProperties.deviceType == VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU)
priority += GPU_DISCRETE_PRIORITY;
if (priority > best_priority)
{
best_priority = priority;
gpu = physicalDevice;
queueFamilies = queueFamiliesBuffer;
}
}
if (gpu == VK_NULL_HANDLE)
{
LOG(CRITICAL, "Failed to select suitable physical device");
return;
}
// Create queues
VkDeviceQueueCreateInfo graphicsQueueCreateConfig {
.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO,
.pNext = nullptr,
.queueFamilyIndex = queueFamilies.graphics.value(),
.queueCount = 1,
.pQueuePriorities = &defaultQueuePriority
};
// Create logical device
std::vector<const char*> deviceExtensions = {
"VK_KHR_swapchain"
};
VkDeviceCreateInfo deviceInfo = {
.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO,
.pNext = nullptr,
.queueCreateInfoCount = 1,
.pQueueCreateInfos = &graphicsQueueCreateConfig,
.enabledExtensionCount = static_cast<uint32_t>(deviceExtensions.size()),
.ppEnabledExtensionNames = deviceExtensions.data(),
.pEnabledFeatures = &enabledFeatures,
};
vkResult = vkCreateDevice(gpu, &deviceInfo, nullptr, &device);
if (vkResult != VK_SUCCESS)
{
LOG(CRITICAL, "Failed to create logical device on %hs. Code: %d", deviceProperties.deviceName, vkResult);
return;
}
else LOG(INFO, "%hs is selected for render", deviceProperties.deviceName);
}
void Renderer::Start() void Renderer::Start()
{ {
if (isRunning) throw std::exception("Renderer can be started only once");
isRunning = true; isRunning = true;
renderThread = std::jthread([this](std::stop_token stop) { renderThread = std::jthread([this](std::stop_token stop) {
this->stoken = &stop; this->stoken = &stop;
@ -20,43 +222,9 @@ void Renderer::Threading()
{ {
Logger::trace->Register_Thread(L"Renderer", 0); Logger::trace->Register_Thread(L"Renderer", 0);
LOG(INFO, "Renderer thread started"); LOG(INFO, "Renderer thread started");
// Setup Vulkan
uint32_t sdlExtensionsCount = 0;
if ( !SDL_Vulkan_GetInstanceExtensions(window, &sdlExtensionsCount, nullptr) )
LOG(CRITICAL, "Fail to get SDL Vulkan extensions number! %hs", SDL_GetError());
const char **sdlExtentions = AllocateArray<const char*>(sdlExtensionsCount);
if ( !SDL_Vulkan_GetInstanceExtensions(window, &sdlExtensionsCount, sdlExtentions) ) InitVulkan();
LOG(CRITICAL, "Fail to load SDL Vulkan extentions! %hs", SDL_GetError()); SetupDevice();
else LOG(INFO, "Load %d SDL Vulkan extenstions", sdlExtensionsCount);
VkInstanceCreateInfo vkCreateConfig = {
.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO,
.enabledLayerCount = 0,
.enabledExtensionCount = sdlExtensionsCount,
.ppEnabledExtensionNames = sdlExtentions
};
VkResult vkCreateResult = vkCreateInstance(&vkCreateConfig, nullptr, &vulkan);
if (vkCreateResult != VK_SUCCESS)
LOG(CRITICAL, "Vulkan is not instantiated! Code: %d", vkCreateResult);
else LOG(INFO, "Vulkan is instantiated");
DeallocateArray(sdlExtentions);
//Setup RmlUi
if (!Rml::Initialise())
LOG(CRITICAL, "RmlUi initialization failed!");
else LOG(INFO, "RmlUi initialized");
int32_t screenWidth, screenHeight;
SDL_GetWindowSize(window, &screenWidth, &screenHeight);
uiContext = Rml::CreateContext("default", Rml::Vector2i(screenWidth, screenHeight));
if (!uiContext)
LOG(CRITICAL, "RmlUi context creation failed!");
else LOG(INFO, "RmlUi context created");
Loop(); Loop();
@ -65,25 +233,63 @@ void Renderer::Threading()
void Renderer::Loop() void Renderer::Loop()
{ {
RenderCommand commandBuffer;
while (!stoken->stop_requested()) while (!stoken->stop_requested())
{ {
/* */ while (commands.pop(commandBuffer))
LOG(DEBUG, "..."); {
Sleep(1000); ProcessCommand(commandBuffer);
}
} }
} }
void Renderer::Stop() void Renderer::ProcessCommand(const RenderCommand &command)
{
switch(command.type)
{
case RenderCommand::SHUTDOWN:
{
LOG(INFO, "SHUTDOWN command");
Shutdown();
break;
}
case RenderCommand::RESIZE_WINDOW:
{
LOG(INFO, "RESIZE_WINDOW command");
break;
}
default:
{
LOG(WARNING, "Unhandled command. Type id: %d", command.type);
break;
}
}
}
void Renderer::Shutdown()
{ {
renderThread.request_stop(); renderThread.request_stop();
} }
void Renderer::Request(const RenderCommand &command)
{
commands.push(command);
}
void Renderer::Request(RenderCommand::Type type)
{
commands.push(RenderCommand {.type = type});
}
void Renderer::Release() void Renderer::Release()
{ {
Rml::Shutdown(); vkDestroyDevice(device, nullptr);
vkDestroyInstance(vulkan, nullptr); vkDestroyInstance(vulkan, nullptr);
isRunning = false; LOG(INFO, "Renderer is released");
isRunning.store(false);
LOG(INFO, "Renderer released");
} }

View File

@ -1,15 +1,9 @@
#include "Logger.h" #include "Logger.h"
#include <SDL2/SDL.h> #include <SDL2/SDL.h>
#include "compile_config.hpp"
#include "Renderer.hpp" #include "Renderer.hpp"
#ifdef _DEBUG
#define P7_SINK L"/P7.Sink=Console"
#else
#define P7_SINK L"/P7.Sink=FileTxt"
#endif
IP7_Trace* Logger::trace; IP7_Trace* Logger::trace;
int SDL_main(int argc, char **argv) int SDL_main(int argc, char **argv)
@ -20,11 +14,13 @@ int SDL_main(int argc, char **argv)
Logger::trace = P7_Create_Trace(p7client, L"TC 1"); Logger::trace = P7_Create_Trace(p7client, L"TC 1");
Logger::trace->Register_Thread(L"Main", 0); Logger::trace->Register_Thread(L"Main", 0);
LOG(INFO, "Etredia Empires, v%d.%d.%d", MAJOR_VERSION, MINOR_VERSION, PATCH_VERSION);
// Setup SDL // Setup SDL
bool quit = false; bool quit = false;
SDL_Event event; SDL_Event event;
SDL_Init(SDL_INIT_VIDEO); SDL_Init(SDL_INIT_VIDEO | SDL_INIT_EVENTS | SDL_INIT_TIMER);
SDL_Window *window = SDL_CreateWindow( SDL_Window *window = SDL_CreateWindow(
"Etredia Empires", "Etredia Empires",
@ -34,6 +30,7 @@ int SDL_main(int argc, char **argv)
600, 600,
SDL_WINDOW_VULKAN SDL_WINDOW_VULKAN
); );
SDL_StopTextInput();
if (!window) if (!window)
{ {
@ -49,12 +46,13 @@ int SDL_main(int argc, char **argv)
// Process SDL events // Process SDL events
while(!quit) while(!quit)
{ {
SDL_WaitEvent(&event); SDL_PollEvent(&event);
switch(event.type) switch(event.type)
{ {
case SDL_QUIT: case SDL_QUIT:
{ {
SDL_RestoreWindow(window);
quit = true; quit = true;
break; break;
} }
@ -65,8 +63,9 @@ int SDL_main(int argc, char **argv)
{ {
case SDL_WINDOWEVENT_RESIZED: case SDL_WINDOWEVENT_RESIZED:
{ {
//TODO: move to renderer renderer.screenWidth.store(event.window.data1);
//uiContext->SetDimensions(Rml::Vector2i(event.window.data1, event.window.data2)); renderer.screenHeight.store(event.window.data2);
renderer.Request(RenderCommand::RESIZE_WINDOW);
break; break;
} }
} }
@ -74,12 +73,12 @@ int SDL_main(int argc, char **argv)
break; break;
} }
} }
} }
// Free memory // Free memory
LOG(INFO, "Exit..."); LOG(INFO, "Exit...");
renderer.Stop(); while(renderer.isRunning) {/* */} renderer.Shutdown(); while(renderer.isRunning.load());
SDL_DestroyWindow(window);
SDL_Quit(); SDL_Quit();
Logger::trace->Unregister_Thread(0); Logger::trace->Unregister_Thread(0);
Logger::trace->Release(); Logger::trace->Release();