summaryrefslogtreecommitdiff
path: root/src/blu_cat/int
diff options
context:
space:
mode:
Diffstat (limited to 'src/blu_cat/int')
-rw-r--r--src/blu_cat/int/controller.hpp50
-rw-r--r--src/blu_cat/int/core.cpp799
-rw-r--r--src/blu_cat/int/core.hpp147
-rw-r--r--src/blu_cat/int/mode.hpp41
4 files changed, 1037 insertions, 0 deletions
diff --git a/src/blu_cat/int/controller.hpp b/src/blu_cat/int/controller.hpp
new file mode 100644
index 0000000..deedde3
--- /dev/null
+++ b/src/blu_cat/int/controller.hpp
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2022-2025 Frederico de Oliveira Linhares
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef BLU_CAT_INT_CONTROLLER_H
+#define BLU_CAT_INT_CONTROLLER_H 1
+
+#include <SDL3/SDL.h>
+
+namespace BluCat::INT
+{
+
+struct Controller
+{
+ virtual void
+ key_down(SDL_Keycode keycode){};
+ virtual void
+ key_up(SDL_Keycode keycode){};
+
+ virtual void
+ mouse_button_down(SDL_MouseButtonEvent& e){};
+ virtual void
+ mouse_button_up(SDL_MouseButtonEvent& e){};
+ virtual void
+ mouse_motion(int x, int y, int xrel, int yrel){};
+
+ virtual void
+ tick(){};
+ virtual void
+ render(){};
+
+ virtual
+ ~Controller(){};
+};
+
+}
+
+#endif /* BLU_CAT_INT_CONTROLLER_H */
diff --git a/src/blu_cat/int/core.cpp b/src/blu_cat/int/core.cpp
new file mode 100644
index 0000000..6f67b98
--- /dev/null
+++ b/src/blu_cat/int/core.cpp
@@ -0,0 +1,799 @@
+/*
+ * Copyright 2022-2025 Frederico de Oliveira Linhares
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "core.hpp"
+
+namespace
+{
+
+#ifdef DEBUG
+VKAPI_ATTR VkBool32 VKAPI_CALL
+vk_debug_callback(
+ VkDebugUtilsMessageSeverityFlagBitsEXT message_severity,
+ VkDebugUtilsMessageTypeFlagsEXT message_type,
+ const VkDebugUtilsMessengerCallbackDataEXT* callback_data,
+ void* _obj)
+{
+ // Set level.
+ Log::Level log_level;
+ switch(message_severity)
+ {
+ case VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT:
+ log_level = Log::Level::Trace;
+ break;
+ case VK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT:
+ log_level = Log::Level::Information;
+ break;
+ case VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT:
+ log_level = Log::Level::Warning;
+ break;
+ case VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT:
+ default:
+ log_level = Log::Level::Error;
+ break;
+ }
+
+ // Log message.
+ BluCat::INT::core.log.message(log_level, callback_data->pMessage);
+
+ return VK_FALSE;
+}
+#endif
+
+void
+load_sdl(void *obj)
+{
+ if(!SDL_Init(SDL_INIT_VIDEO | SDL_INIT_EVENTS))
+ {
+ std::string error{"SDL could not initialize! SDL Error → "};
+ error += SDL_GetError();
+ throw CommandError{error};
+ }
+
+ if(!SDL_Vulkan_LoadLibrary(nullptr))
+ {
+ SDL_Quit();
+ std::string error{"SDL could not initialize Vulkan! SDL_Error → "};
+ error += SDL_GetError();
+ throw CommandError{error};
+ }
+}
+
+void
+unload_sdl(void *obj)
+{
+ SDL_Vulkan_UnloadLibrary();
+ SDL_Quit();
+}
+
+void
+load_window(void *obj)
+{
+ BluCat::INT::core.window = SDL_CreateWindow(
+ BluCat::INT::core.game_name.c_str(),
+ BluCat::INT::core.display_width, BluCat::INT::core.display_height,
+ SDL_WINDOW_VULKAN);
+ if(BluCat::INT::core.window == NULL)
+ {
+ std::string error{"Window could not be created! SDL_Error → "};
+ error += SDL_GetError();
+ throw CommandError{error};
+ }
+}
+
+void
+unload_window(void *obj)
+{
+ SDL_DestroyWindow(BluCat::INT::core.window);
+}
+
+void
+load_vk_instance(void *obj)
+{
+ std::vector<const char*> vk_extensions;
+ std::vector<const char*> vk_required_layers_names;
+
+ // Get extensions.
+ {
+ uint32_t vk_extensions_count;
+ std::vector<const char*> vk_required_extensions;
+
+ // Load debuging layers.
+#ifdef DEBUG
+ vk_required_extensions.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME);
+ vk_required_layers_names.push_back("VK_LAYER_KHRONOS_validation");
+#endif
+
+ // Get extensions for SDL.
+ {
+ uint32_t vk_sdl_extension_count;
+ std::vector<const char*> vk_sdl_extensions;
+
+ const char * const *instance_extensions =
+ SDL_Vulkan_GetInstanceExtensions(&vk_sdl_extension_count);
+
+ if(instance_extensions == NULL)
+ {
+ std::string error{
+ "Vulkan extensions could not be loaded by SDL! SDL_Error: "};
+ error += SDL_GetError();
+ throw CommandError{error};
+ }
+
+ for(auto i{0}; i < vk_sdl_extension_count; i++)
+ vk_sdl_extensions.push_back(instance_extensions[i]);
+
+ // Combine all extensions.
+ vk_extensions_count = vk_sdl_extension_count +
+ vk_required_extensions.size();
+ vk_extensions.resize(vk_extensions_count);
+ for(auto i{0}; i < vk_sdl_extension_count; i++)
+ vk_extensions[i] = vk_sdl_extensions[i];
+ for(auto i{0}; i < vk_required_extensions.size(); i++)
+ vk_extensions[i + vk_sdl_extension_count] = vk_required_extensions[i];
+ }
+
+#ifdef DEBUG
+ BluCat::INT::core.log.message(Log::Level::Trace, "Enabled VK extensions.");
+ for(auto vk_extension: vk_extensions)
+ {
+ std::string message{"Extension name: "};
+ message += vk_extension;
+ BluCat::INT::core.log.message(Log::Level::Trace, message);
+ }
+#endif
+ }
+
+ // Get available instance layers.
+ {
+ uint32_t vk_available_layers_count;
+ std::vector<VkLayerProperties> vk_available_layers;
+ std::vector<const char*> vk_available_layers_names;
+
+ vkEnumerateInstanceLayerProperties(&vk_available_layers_count, nullptr);
+ vk_available_layers.resize(vk_available_layers_count);
+ vkEnumerateInstanceLayerProperties(&vk_available_layers_count,
+ vk_available_layers.data());
+ vk_available_layers_names.resize(vk_available_layers_count);
+#ifdef DEBUG
+ BluCat::INT::core.log.message(
+ Log::Level::Trace, "Available VK instance layers.");
+#endif
+ for(uint32_t i = 0; i < vk_available_layers_count; i++)
+ {
+#ifdef DEBUG
+ std::stringstream message{};
+ message << "\nname: " << vk_available_layers[i].layerName << std::endl;
+ message << "Description: " << vk_available_layers[i].description <<
+ std::endl;
+ message << "Spec version: " << vk_available_layers[i].specVersion <<
+ std::endl;
+ message << "Implementation version: " <<
+ vk_available_layers[i].implementationVersion << std::endl;
+ BluCat::INT::core.log.message(Log::Level::Trace, message.str());
+#endif
+
+ vk_available_layers_names[i] = vk_available_layers[i].layerName;
+ }
+
+ // If required layers are not all available.
+ if(!std::includes(
+ vk_available_layers_names.begin(), vk_available_layers_names.end(),
+ vk_required_layers_names.begin(), vk_required_layers_names.begin()))
+ throw CommandError{"Some required Vulkan layers are not available."};
+ }
+
+ {
+ VkApplicationInfo app_info;
+ app_info.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO;
+ app_info.pNext = nullptr;
+ app_info.pApplicationName = BluCat::INT::core.game_name.c_str();
+ app_info.applicationVersion = VK_MAKE_VERSION(
+ BluCat::INT::core.game_version_major,
+ BluCat::INT::core.game_version_minor,
+ BluCat::INT::core.game_version_patch);
+ app_info.pEngineName = "BluCat::GRA";
+ app_info.engineVersion = VK_MAKE_VERSION(
+ BLU_CAT_VERSION_MAJOR,
+ BLU_CAT_VERSION_MINOR,
+ BLU_CAT_VERSION_PATCH);
+ app_info.apiVersion = VK_API_VERSION_1_0;
+
+ VkInstanceCreateInfo create_info;
+ create_info.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO;
+ create_info.pNext = nullptr;
+ create_info.flags = 0;
+ create_info.pApplicationInfo = &app_info;
+ create_info.enabledExtensionCount = vk_extensions.size();
+ create_info.ppEnabledExtensionNames = vk_extensions.data();
+ create_info.enabledLayerCount = vk_required_layers_names.size();
+ create_info.ppEnabledLayerNames = vk_required_layers_names.data();
+
+ VkResult result =
+ vkCreateInstance(&create_info, nullptr, &BluCat::INT::core.vk_instance);
+ if(result != VK_SUCCESS)
+ {
+ std::string error{""};
+ switch(result)
+ {
+ case VK_ERROR_LAYER_NOT_PRESENT:
+ error = " Layer not present.";
+ break;
+ case VK_ERROR_EXTENSION_NOT_PRESENT:
+ error = " Extension not present.";
+ }
+ error = "Failed to create Vulkan instance." + error;
+ throw CommandError{error};
+ }
+ }
+}
+
+void
+unload_vk_instance(void *obj)
+{
+ vkDestroyInstance(BluCat::INT::core.vk_instance, nullptr);
+}
+
+void
+load_window_surface(void *obj)
+{
+ if(!SDL_Vulkan_CreateSurface(
+ BluCat::INT::core.window, BluCat::INT::core.vk_instance,
+ nullptr, &BluCat::INT::core.window_surface))
+ {
+ std::string error{"Failed to create a window surface → "};
+ error += SDL_GetError();
+ throw CommandError{error};
+ }
+}
+
+void
+unload_window_surface(void *obj)
+{
+ vkDestroySurfaceKHR(
+ BluCat::INT::core.vk_instance, BluCat::INT::core.window_surface, nullptr);
+}
+
+void
+load_threads(void *obj)
+{
+ auto num_threads{std::thread::hardware_concurrency() - 1};
+ for(auto i{0}; i < num_threads; i++)
+ BluCat::INT::core.threads.emplace_back(
+ BluCat::INT::core.workers.emplace_back(&BluCat::INT::core.job_queue));
+}
+
+void
+unload_threads(void *obj)
+{
+ BluCat::INT::core.job_queue.stop();
+ for(auto &t: BluCat::INT::core.threads) t.join();
+}
+
+void
+load_fps(void *obj)
+{
+ using namespace std::chrono;
+
+ BluCat::INT::core.max_frame_duration =
+ duration<long long, std::milli>(1000 / BluCat::INT::core.fps);
+ // FIXME: actually calculates the real delta time.
+ BluCat::INT::core.delta_time = 1.0f / BluCat::INT::core.fps;
+}
+
+void
+load_font_library(void *obj)
+{
+ FT_Error error{FT_Init_FreeType(&BluCat::INT::core.font_library)};
+ if(error) throw CommandError{"Failed to open the FreeType library."};
+}
+
+void
+unload_font_library(void *obj)
+{
+ FT_Done_FreeType(BluCat::INT::core.font_library);
+}
+
+#ifdef DEBUG
+void
+load_vk_debug_callback(void *obj)
+{
+ PFN_vkCreateDebugUtilsMessengerEXT debug_messenger;
+
+ // A Vulkan instance extension named VK_EXT_debug_utils and a Vulkan instance
+ // layer named VK_LAYER_LUNARG_standard_validation are required to enable
+ // this callback. These instance extension and instance layer are loaded at
+ // Instance::load_blucat_instance.
+ VkDebugUtilsMessengerCreateInfoEXT create_info;
+ create_info.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT;
+ create_info.pNext = nullptr;
+ create_info.messageSeverity =
+ VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT |
+ VK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT |
+ VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT |
+ VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT;
+ create_info.messageType = VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT |
+ VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT |
+ VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT;
+ create_info.pfnUserCallback = vk_debug_callback;
+ create_info.pUserData = nullptr;
+ create_info.flags = 0;
+
+ debug_messenger = (PFN_vkCreateDebugUtilsMessengerEXT)
+ vkGetInstanceProcAddr(BluCat::INT::core.vk_instance,
+ "vkCreateDebugUtilsMessengerEXT");
+
+ if(debug_messenger(BluCat::INT::core.vk_instance, &create_info, nullptr,
+ &BluCat::INT::core.vk_callback) != VK_SUCCESS)
+ CommandError{"Failed to setup debug callback for Vulkan."};
+}
+
+void
+unload_vk_debug_callback(void *obj)
+{
+ PFN_vkDestroyDebugUtilsMessengerEXT debug_messenger;
+
+ debug_messenger = (PFN_vkDestroyDebugUtilsMessengerEXT)
+ vkGetInstanceProcAddr(BluCat::INT::core.vk_instance,
+ "vkDestroyDebugUtilsMessengerEXT");
+
+ debug_messenger(BluCat::INT::core.vk_instance, BluCat::INT::core.vk_callback, nullptr);
+}
+#endif
+
+void
+load_devices(void *obj)
+{
+ uint32_t devices_count;
+ std::vector<VkPhysicalDevice> vk_physical_devices;
+
+ // Enumerate physical devices
+ {
+ vkEnumeratePhysicalDevices(
+ BluCat::INT::core.vk_instance, &devices_count, nullptr);
+ if(devices_count < 1)
+ CommandError{"Failed to find GPUs with Vulkan support."};
+ vk_physical_devices.resize(devices_count);
+ vkEnumeratePhysicalDevices(
+ BluCat::INT::core.vk_instance, &devices_count, vk_physical_devices.data());
+ }
+
+#ifdef DEBUG
+ BluCat::INT::core.log.message(Log::Level::Trace, "Physical devices properties");
+#endif
+
+ BluCat::INT::core.vk_devices.reserve(devices_count);
+ for(auto i = 0; i < devices_count; i++)
+ {
+ // Use swapchain on first device.
+ if(i == 0)
+ {
+ BluCat::INT::core.vk_devices.emplace_back(vk_physical_devices[i], true);
+ BluCat::INT::core.vk_device_with_swapchain = &BluCat::INT::core.vk_devices[i];
+ }
+ else
+ BluCat::INT::core.vk_devices.emplace_back(vk_physical_devices[i], false);
+ }
+}
+
+void
+unload_devices(void *obj)
+{
+ BluCat::INT::core.vk_devices.clear();
+}
+
+static void
+load_swapchain(void *obj)
+{
+ try { BluCat::INT::core.vk_swapchain = new BluCat::GRA::Swapchain(); }
+ catch(const CommandError &error)
+ {
+ std::string error_message{"Failed to create swapchain → "};
+ error_message += error.what();
+ throw CommandError{error_message};
+ }
+}
+
+void
+unload_swapchain(void *obj)
+{
+ delete BluCat::INT::core.vk_swapchain;
+}
+
+void
+load_framebuffer(void *obj)
+{
+ try
+ {
+ BluCat::INT::core.vk_framebuffer = new BluCat::GRA::Framebuffer();
+ }
+ catch(const CommandError &e)
+ {
+ throw CommandError{"Failed to create framebuffer."};
+ }
+}
+
+void
+unload_framebuffer(void *obj)
+{
+ delete BluCat::INT::core.vk_framebuffer;
+}
+
+void
+load_render_pass(void *obj)
+{
+ try
+ {
+ BluCat::INT::core.vk_render_pass = new BluCat::GRA::RenderPass();
+ }
+ catch(const CommandError &e)
+ {
+ throw CommandError{"Failed to create render pass."};
+ }
+}
+
+void
+unload_render_pass(void *obj)
+{
+ delete BluCat::INT::core.vk_render_pass;
+}
+
+void
+load_descriptor_set_layout(void *obj)
+{
+ try
+ {
+ BluCat::INT::core.vk_descriptor_set_layout = new BluCat::GRA::DescriptorSetLayout();
+ }
+ catch(const CommandError &e)
+ {
+ throw CommandError{"Failed to create descriptor set layouts."};
+ }
+}
+
+void
+unload_descriptor_set_layout(void *obj)
+{
+ delete BluCat::INT::core.vk_descriptor_set_layout;
+}
+
+void
+load_graphics_pipeline_3d_layout(void *obj)
+{
+ try
+ {
+ BluCat::INT::core.vk_graphics_pipeline_3d_layout =
+ new BluCat::GRA::GraphicsPipeline3DLayout();
+ }
+ catch(const CommandError &e)
+ {
+ throw CommandError{"Failed to create 3d graphics pipeline."};
+ }
+}
+
+void
+unload_graphics_pipeline_3d_layout(void *obj)
+{
+ delete BluCat::INT::core.vk_graphics_pipeline_3d_layout;
+}
+
+void
+load_graphics_pipeline_2d_solid_layout(void *obj)
+{
+ try
+ {
+ BluCat::INT::core.vk_graphics_pipeline_2d_solid_layout =
+ new BluCat::GRA::GraphicsPipeline2DSolidLayout();
+ }
+ catch(const CommandError &e)
+ {
+ throw CommandError{"Failed to create 2d graphics pipeline."};
+ }
+}
+
+void
+unload_graphics_pipeline_2d_solid_layout(void *obj)
+{
+ delete BluCat::INT::core.vk_graphics_pipeline_2d_solid_layout;
+}
+
+void
+load_graphics_pipeline_2d_wired_layout(void *obj)
+{
+ try
+ {
+ BluCat::INT::core.vk_graphics_pipeline_2d_wired_layout =
+ new BluCat::GRA::GraphicsPipeline2DWiredLayout();
+ }
+ catch(const CommandError &e)
+ {
+ throw CommandError{"Failed to create 2d graphics pipeline."};
+ }
+}
+
+void
+unload_graphics_pipeline_2d_wired_layout(void *obj)
+{
+ delete BluCat::INT::core.vk_graphics_pipeline_2d_wired_layout;
+}
+
+void
+load_light(void *obj)
+{
+ try
+ {
+ BluCat::INT::core.vk_light = new BluCat::GRA::Light();
+ }
+ catch(const CommandError &e)
+ {
+ throw CommandError{"Failed to descriptor sets for light."};
+ }
+}
+
+void
+unload_light(void *obj)
+{
+ delete BluCat::INT::core.vk_light;
+}
+
+void
+load_graphics_pipeline_3d(void *obj)
+{
+ try
+ {
+ BluCat::INT::core.vk_graphics_pipeline_3d =
+ std::make_unique<BluCat::GRA::GraphicsPipeline3D>();
+ }
+ catch(const CommandError &e)
+ {
+ throw CommandError{"Failed to create 3d graphics pipeline."};
+ }
+}
+
+void
+unload_graphics_pipeline_3d(void *obj)
+{
+ BluCat::INT::core.vk_graphics_pipeline_3d = nullptr;
+}
+
+void
+load_graphics_pipeline_3d_skeletal(void *obj)
+{
+ try
+ {
+ BluCat::INT::core.vk_graphics_pipeline_3d_skeletal =
+ std::make_unique<BluCat::GRA::GraphicsPipeline3DSkeletal>();
+ }
+ catch(const CommandError &e)
+ {
+ throw CommandError{"Failed to create 3d skeletal graphics pipeline."};
+ }
+}
+
+void
+unload_graphics_pipeline_3d_skeletal(void *obj)
+{
+ BluCat::INT::core.vk_graphics_pipeline_3d_skeletal = nullptr;
+}
+
+void
+load_graphics_pipeline_sprite_3d(void *obj)
+{
+ try
+ {
+ BluCat::INT::core.vk_graphics_pipeline_sprite_3d =
+ std::make_unique<BluCat::GRA::GraphicsPipelineSprite3D>();
+ }
+ catch(const CommandError &e)
+ {
+ throw CommandError{"Failed to create sprite 3d graphics pipeline."};
+ }
+}
+
+void
+unload_graphics_pipeline_sprite_3d(void *obj)
+{
+ BluCat::INT::core.vk_graphics_pipeline_sprite_3d = nullptr;
+}
+
+void
+load_graphics_pipeline_2d_solid(void *obj)
+{
+ try
+ {
+ BluCat::INT::core.vk_graphics_pipeline_2d_solid =
+ std::make_unique<BluCat::GRA::GraphicsPipeline2DSolid>();
+ }
+ catch(const CommandError &e)
+ {
+ throw CommandError{"Failed to create 2d graphics pipeline."};
+ }
+}
+
+void
+unload_graphics_pipeline_2d_solid(void *obj)
+{
+ BluCat::INT::core.vk_graphics_pipeline_2d_solid = nullptr;
+}
+
+void
+load_graphics_pipeline_2d_wired(void *obj)
+{
+ try
+ {
+ BluCat::INT::core.vk_graphics_pipeline_2d_wired =
+ std::make_unique<BluCat::GRA::GraphicsPipeline2DWired>();
+ }
+ catch(const CommandError &e)
+ {
+ throw CommandError{"Failed to create 2d graphics pipeline."};
+ }
+}
+
+void
+unload_graphics_pipeline_2d_wired(void *obj)
+{
+ BluCat::INT::core.vk_graphics_pipeline_2d_wired = nullptr;
+}
+
+void
+load_renderer(void *obj)
+{
+ try
+ {
+ auto width{static_cast<float>(BluCat::INT::core.display_width)};
+ auto height{static_cast<float>(BluCat::INT::core.display_height)};
+ glm::vec4 region(
+ 0.f, 0.f, width, height);
+ BluCat::INT::core.vk_renderer = new BluCat::GRA::Renderer(
+ {std::make_shared<BluCat::GRA::View>(region, region.z, region.w)},
+ width, height);
+ }
+ catch(const CommandError &e)
+ {
+ throw CommandError{"Failed to create renderer."};
+ }
+}
+
+void
+unload_renderer(void *obj)
+{
+ delete BluCat::INT::core.vk_renderer;
+}
+
+}
+
+namespace BluCat::INT
+{
+
+std::random_device random_seed;
+std::mt19937 random_number_generator;
+
+const CommandChain Core::loader{
+ {&load_sdl, &unload_sdl},
+ {&load_window, &unload_window},
+ {&load_vk_instance, &unload_vk_instance},
+ {&load_window_surface, &unload_window_surface},
+ {&load_threads, &unload_threads},
+ {&load_fps, nullptr},
+ {&load_font_library, &unload_font_library},
+#ifdef DEBUG
+ {&load_vk_debug_callback, &unload_vk_debug_callback},
+#endif
+ {&load_devices, &unload_devices},
+ {&load_swapchain, &unload_swapchain},
+
+ {&load_render_pass, &unload_render_pass},
+ {&load_framebuffer, &unload_framebuffer},
+ {&load_descriptor_set_layout, &unload_descriptor_set_layout},
+ {&load_graphics_pipeline_3d_layout,
+ &unload_graphics_pipeline_3d_layout},
+ {&load_graphics_pipeline_2d_solid_layout,
+ &unload_graphics_pipeline_2d_solid_layout},
+ {&load_graphics_pipeline_2d_wired_layout,
+ &unload_graphics_pipeline_2d_wired_layout},
+ {&load_light, &unload_light},
+ {&load_graphics_pipeline_3d_skeletal,
+ &unload_graphics_pipeline_3d_skeletal},
+ {&load_graphics_pipeline_3d, &unload_graphics_pipeline_3d},
+ {&load_graphics_pipeline_sprite_3d,
+ &unload_graphics_pipeline_sprite_3d},
+ {&load_graphics_pipeline_2d_solid, &unload_graphics_pipeline_2d_solid},
+ {&load_graphics_pipeline_2d_wired, &unload_graphics_pipeline_2d_wired},
+ {&load_renderer, &unload_renderer},
+};
+
+Core core;
+
+void
+run_game(std::unique_ptr<BluCat::INT::Mode> _mode)
+{
+ using namespace std::chrono;
+ using namespace std::this_thread;
+
+ std::unique_ptr<BluCat::INT::Mode> mode{std::move(_mode)};
+ std::unique_ptr<BluCat::INT::Controller> controller{
+ mode->default_controller()};
+
+ bool quit{false};
+ int x, y, xrel, yrel;
+ SDL_Event event;
+ auto frame_start = steady_clock::now();
+
+ while(!quit)
+ {
+ if(core.next_game_mode)
+ {
+ mode = std::move(core.next_game_mode);
+ controller = mode->default_controller();
+ }
+ else if(core.next_game_controller)
+ controller = std::move(core.next_game_controller);
+
+ while(SDL_PollEvent(&event))
+ {
+ switch(event.type)
+ {
+ case SDL_EVENT_KEY_DOWN:
+ if(event.key.repeat != 0) continue;
+ controller->key_down(event.key.key);
+ break;
+ case SDL_EVENT_KEY_UP:
+ if(event.key.repeat != 0) continue;
+ controller->key_up(event.key.key);
+ break;
+ case SDL_EVENT_MOUSE_BUTTON_DOWN:
+ controller->mouse_button_down(event.button);
+ break;
+ case SDL_EVENT_MOUSE_BUTTON_UP:
+ controller->mouse_button_up(event.button);
+ break;
+ case SDL_EVENT_MOUSE_MOTION:
+ x = event.motion.x;
+ y = event.motion.y;
+
+ xrel = event.motion.xrel;
+ yrel = event.motion.yrel;
+
+ controller->mouse_motion(x, y, xrel, yrel);
+ break;
+ case SDL_EVENT_QUIT:
+ quit = true;
+ }
+ }
+
+ controller->tick();
+ mode->render();
+ controller->render();
+ BluCat::INT::core.vk_renderer->draw();
+
+ { // Timer
+ auto frame_stop = steady_clock::now();
+ auto frame_duration = frame_stop - frame_start;
+
+ // If frame take less time than maximum allowed.
+ if(core.max_frame_duration > frame_duration)
+ sleep_for(core.max_frame_duration - frame_duration);
+
+ frame_start = frame_stop;
+ }
+ }
+
+ controller = nullptr;
+ mode = nullptr;
+}
+
+}
diff --git a/src/blu_cat/int/core.hpp b/src/blu_cat/int/core.hpp
new file mode 100644
index 0000000..4737123
--- /dev/null
+++ b/src/blu_cat/int/core.hpp
@@ -0,0 +1,147 @@
+/*
+ * Copyright 2022-2025 Frederico de Oliveira Linhares
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef BLU_CAT_INT_CORE_H
+#define BLU_CAT_INT_CORE_H 1
+
+#define BLU_CAT_VERSION_MAJOR 0
+#define BLU_CAT_VERSION_MINOR 1
+#define BLU_CAT_VERSION_PATCH 0
+
+#include <chrono>
+#include <cstdint>
+#include <memory>
+#include <random>
+
+#define SDL_MAIN_HANDLED
+
+#ifdef _WIN64
+#include <Windows.h>
+#endif
+
+#include <SDL3/SDL.h>
+#include <SDL3/SDL_vulkan.h>
+
+#include <ft2build.h>
+#include FT_FREETYPE_H
+
+#include "../com/command.hpp"
+#include "../com/job_queue.hpp"
+#include "../com/worker.hpp"
+#include "../gra/device.hpp"
+#include "../gra/descriptor_set_layout.hpp"
+#include "../gra/framebuffer.hpp"
+#include "../gra/graphics_pipeline_2d_solid_layout.hpp"
+#include "../gra/graphics_pipeline_2d_wired_layout.hpp"
+#include "../gra/graphics_pipeline_2d_solid.hpp"
+#include "../gra/graphics_pipeline_2d_wired.hpp"
+#include "../gra/graphics_pipeline_3d_layout.hpp"
+#include "../gra/graphics_pipeline_3d.hpp"
+#include "../gra/graphics_pipeline_3d_skeletal.hpp"
+#include "../gra/graphics_pipeline_sprite_3d.hpp"
+#include "../gra/light.hpp"
+#include "../gra/log.hpp"
+#include "../gra/render_pass.hpp"
+#include "../gra/renderer.hpp"
+#include "../gra/swapchain.hpp"
+#include "../gra/vulkan.hpp"
+#include "mode.hpp"
+
+namespace BluCat::INT
+{
+
+extern std::random_device random_seed;
+extern std::mt19937 random_number_generator;
+
+struct Core
+{
+ static const CommandChain loader;
+
+ Log::Logger log;
+
+ COM::JobQueue job_queue;
+ std::vector<COM::Worker> workers;
+ std::vector<std::thread> threads;
+
+ /// Text displayed in the game window.
+ std::string game_name{"BluCat Game"};
+
+ /**
+ * @{
+ * This is the ammount of pixel that the games uses when rendering to the
+ * screen.
+ */
+ uint32_t display_width{800};
+ uint32_t display_height{600};
+ /// @}
+
+ int game_version_major{0};
+ int game_version_minor{1};
+ int game_version_patch{0};
+
+ uint32_t fps{30};
+ std::chrono::duration<long long, std::milli> max_frame_duration;
+ float delta_time;
+
+ SDL_Window *window;
+
+ FT_Library font_library;
+
+ VkSurfaceKHR window_surface;
+ VkInstance vk_instance;
+
+#ifdef DEBUG
+ VkDebugUtilsMessengerEXT vk_callback;
+#endif
+
+ // Vulkan devices.
+ std::vector<BluCat::GRA::Device> vk_devices;
+ BluCat::GRA::Device *vk_device_with_swapchain;
+ BluCat::GRA::Swapchain *vk_swapchain;
+
+ BluCat::GRA::Framebuffer *vk_framebuffer;
+ BluCat::GRA::RenderPass *vk_render_pass;
+ BluCat::GRA::DescriptorSetLayout *vk_descriptor_set_layout;
+ BluCat::GRA::GraphicsPipeline3DLayout *vk_graphics_pipeline_3d_layout;
+ BluCat::GRA::GraphicsPipeline2DSolidLayout
+ *vk_graphics_pipeline_2d_solid_layout;
+ BluCat::GRA::GraphicsPipeline2DWiredLayout
+ *vk_graphics_pipeline_2d_wired_layout;
+ BluCat::GRA::Light *vk_light;
+ std::unique_ptr<BluCat::GRA::GraphicsPipeline3D> vk_graphics_pipeline_3d;
+ std::unique_ptr<BluCat::GRA::GraphicsPipeline3DSkeletal>
+ vk_graphics_pipeline_3d_skeletal;
+ std::unique_ptr<BluCat::GRA::GraphicsPipelineSprite3D>
+ vk_graphics_pipeline_sprite_3d;
+ std::unique_ptr<BluCat::GRA::GraphicsPipeline2DSolid>
+ vk_graphics_pipeline_2d_solid;
+ std::unique_ptr<BluCat::GRA::GraphicsPipeline2DWired>
+ vk_graphics_pipeline_2d_wired;
+
+ BluCat::GRA::Renderer *vk_renderer;
+
+ std::unique_ptr<BluCat::INT::Mode> next_game_mode{nullptr};
+ std::unique_ptr<BluCat::INT::Controller> next_game_controller{nullptr};
+};
+
+extern Core core;
+
+void
+run_game(std::unique_ptr<Mode> initial_mode);
+
+}
+
+#endif /* BLU_CAT_INT_CORE_H */
diff --git a/src/blu_cat/int/mode.hpp b/src/blu_cat/int/mode.hpp
new file mode 100644
index 0000000..8197f75
--- /dev/null
+++ b/src/blu_cat/int/mode.hpp
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2022-2025 Frederico de Oliveira Linhares
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef BLU_CAT_INT_MODE_H
+#define BLU_CAT_INT_MODE_H 1
+
+#include <memory>
+
+#include "controller.hpp"
+
+namespace BluCat::INT
+{
+
+struct Mode
+{
+ virtual std::unique_ptr<Controller>
+ default_controller() = 0;
+
+ virtual void
+ render() = 0;
+
+ virtual
+ ~Mode(){};
+};
+
+}
+
+#endif /* BLU_CAT_INT_MODE_H */