diff --git a/assets/fonts/ARIAL.TTF b/assets/fonts/ARIAL.TTF new file mode 100644 index 0000000..8682d94 Binary files /dev/null and b/assets/fonts/ARIAL.TTF differ diff --git a/assets/layout/base.rcss b/assets/layout/base.rcss new file mode 100644 index 0000000..75cbde1 --- /dev/null +++ b/assets/layout/base.rcss @@ -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; +} \ No newline at end of file diff --git a/assets/layout/test/test.rml b/assets/layout/test/test.rml new file mode 100644 index 0000000..e46e0c0 --- /dev/null +++ b/assets/layout/test/test.rml @@ -0,0 +1,13 @@ + + + + + + + +
+

Hello World!

+
+ + +
\ No newline at end of file diff --git a/frontend/CMakeLists.txt b/frontend/CMakeLists.txt index b4e38a1..9820e04 100644 --- a/frontend/CMakeLists.txt +++ b/frontend/CMakeLists.txt @@ -41,23 +41,39 @@ target_link_libraries( $,SDL2::SDL2,SDL2::SDL2-static> ) 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= -) target_link_libraries(${PROJECT_NAME} PRIVATE RmlUi::RmlUi) target_include_directories(${PROJECT_NAME} PUBLIC include/) if (CMAKE_VERSION VERSION_GREATER 3.12) set_property(TARGET ${PROJECT_NAME} PROPERTY CXX_STANDARD 20) -endif() \ No newline at end of file +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}" +) \ No newline at end of file diff --git a/frontend/include/Renderer.hpp b/frontend/include/Renderer.hpp index 6c91a69..0bce7af 100644 --- a/frontend/include/Renderer.hpp +++ b/frontend/include/Renderer.hpp @@ -1,12 +1,41 @@ #pragma once #include #include +#include +#include #include #include -#include +#include #include +#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 graphics; + std::optional presence; +}; + class Renderer { private: @@ -14,18 +43,35 @@ private: std::jthread renderThread; SDL_Window *window; + VkInstance vulkan; + VkPhysicalDevice gpu; + VkDevice device; + QueueFamiliesBundle queueFamilies; + const float defaultQueuePriority = 1.0f; + Rml::Context *uiContext; + boost::lockfree::spsc_queue> commands; + + void InitVulkan(); + void SetupDevice(); + void Threading(); void Loop(); + void ProcessCommand(const RenderCommand &command); void Release(); public: std::atomic isRunning; + std::atomic screenWidth, screenHeight; Renderer(SDL_Window *window); + ~Renderer(); void Start(); - void Stop(); + void Shutdown(); + + void Request(const RenderCommand &command); + void Request(RenderCommand::Type type); }; \ No newline at end of file diff --git a/frontend/include/compile_config.hpp b/frontend/include/compile_config.hpp new file mode 100644 index 0000000..aef447b --- /dev/null +++ b/frontend/include/compile_config.hpp @@ -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 \ No newline at end of file diff --git a/frontend/src/Renderer.cpp b/frontend/src/Renderer.cpp index aad2dba..fae6b56 100644 --- a/frontend/src/Renderer.cpp +++ b/frontend/src/Renderer.cpp @@ -1,14 +1,216 @@ +#if !APP_VERSION +#include "compile_config.hpp" +#endif + #include "Renderer.hpp" #include "Logger.h" #include +#include Renderer::Renderer(SDL_Window *window) : isRunning(false) { this->window = window; } +Renderer::~Renderer() +{ + Release(); +} + +void Renderer::InitVulkan() +{ + std::vector vulkanExtensions { + // + }; + std::vector 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 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(vulkanLayers.size()), + .ppEnabledLayerNames = vulkanLayers.data(), + .enabledExtensionCount = static_cast(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 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 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 deviceExtensions = { + "VK_KHR_swapchain" + }; + + VkDeviceCreateInfo deviceInfo = { + .sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO, + .pNext = nullptr, + .queueCreateInfoCount = 1, + .pQueueCreateInfos = &graphicsQueueCreateConfig, + .enabledExtensionCount = static_cast(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() { + if (isRunning) throw std::exception("Renderer can be started only once"); + isRunning = true; renderThread = std::jthread([this](std::stop_token stop) { this->stoken = &stop; @@ -20,43 +222,9 @@ void Renderer::Threading() { Logger::trace->Register_Thread(L"Renderer", 0); 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(sdlExtensionsCount); - if ( !SDL_Vulkan_GetInstanceExtensions(window, &sdlExtensionsCount, sdlExtentions) ) - LOG(CRITICAL, "Fail to load SDL Vulkan extentions! %hs", SDL_GetError()); - 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"); + InitVulkan(); + SetupDevice(); Loop(); @@ -65,25 +233,63 @@ void Renderer::Threading() void Renderer::Loop() { + RenderCommand commandBuffer; while (!stoken->stop_requested()) { - /* */ - LOG(DEBUG, "..."); - Sleep(1000); + while (commands.pop(commandBuffer)) + { + 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(); } +void Renderer::Request(const RenderCommand &command) +{ + commands.push(command); +} + +void Renderer::Request(RenderCommand::Type type) +{ + commands.push(RenderCommand {.type = type}); +} + void Renderer::Release() { - Rml::Shutdown(); + vkDestroyDevice(device, nullptr); vkDestroyInstance(vulkan, nullptr); - isRunning = false; - - LOG(INFO, "Renderer released"); + LOG(INFO, "Renderer is released"); + isRunning.store(false); } \ No newline at end of file diff --git a/frontend/src/main.cpp b/frontend/src/main.cpp index 3c8ffb5..85c8771 100644 --- a/frontend/src/main.cpp +++ b/frontend/src/main.cpp @@ -1,15 +1,9 @@ #include "Logger.h" #include +#include "compile_config.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; 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->Register_Thread(L"Main", 0); + LOG(INFO, "Etredia Empires, v%d.%d.%d", MAJOR_VERSION, MINOR_VERSION, PATCH_VERSION); + // Setup SDL bool quit = false; SDL_Event event; - SDL_Init(SDL_INIT_VIDEO); + SDL_Init(SDL_INIT_VIDEO | SDL_INIT_EVENTS | SDL_INIT_TIMER); SDL_Window *window = SDL_CreateWindow( "Etredia Empires", @@ -34,6 +30,7 @@ int SDL_main(int argc, char **argv) 600, SDL_WINDOW_VULKAN ); + SDL_StopTextInput(); if (!window) { @@ -49,12 +46,13 @@ int SDL_main(int argc, char **argv) // Process SDL events while(!quit) { - SDL_WaitEvent(&event); + SDL_PollEvent(&event); switch(event.type) { case SDL_QUIT: { + SDL_RestoreWindow(window); quit = true; break; } @@ -65,8 +63,9 @@ int SDL_main(int argc, char **argv) { case SDL_WINDOWEVENT_RESIZED: { - //TODO: move to renderer - //uiContext->SetDimensions(Rml::Vector2i(event.window.data1, event.window.data2)); + renderer.screenWidth.store(event.window.data1); + renderer.screenHeight.store(event.window.data2); + renderer.Request(RenderCommand::RESIZE_WINDOW); break; } } @@ -74,12 +73,12 @@ int SDL_main(int argc, char **argv) break; } } - } // Free memory LOG(INFO, "Exit..."); - renderer.Stop(); while(renderer.isRunning) {/* */} + renderer.Shutdown(); while(renderer.isRunning.load()); + SDL_DestroyWindow(window); SDL_Quit(); Logger::trace->Unregister_Thread(0); Logger::trace->Release();