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 @@
+
+
+
+
+
+
+
+
+
+
+
\ 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();