diff options
Diffstat (limited to 'src/core.cpp')
-rw-r--r-- | src/core.cpp | 490 |
1 files changed, 339 insertions, 151 deletions
diff --git a/src/core.cpp b/src/core.cpp index 12e5b01..7e13f75 100644 --- a/src/core.cpp +++ b/src/core.cpp @@ -14,57 +14,45 @@ * limitations under the License. */ -#include "core.h" +#include "core.hpp" -#include <yaml-cpp/yaml.h> - -/* - Stretches the image to fill the screen vertically, horizontally, or both; - without changing the game aspect ratio. - */ -void -cg_Engine_calculate_full_scale() -{ - double screen_ratio, game_ratio, scale; +#include <iostream> - screen_ratio = - (double)cg_core.screen_width/(double)cg_core.screen_height; - game_ratio = (double)cg_core.game_width/(double)cg_core.game_height; - - // If screen is proportionally taller than game. - if(screen_ratio < game_ratio) - { - scale = (double)cg_core.screen_width/(double)cg_core.game_width; +#include <yaml-cpp/yaml.h> - cg_core.screen_rect.w = cg_core.game_width * scale; - cg_core.screen_rect.h = cg_core.game_height * scale; - cg_core.screen_rect.x = 0; - cg_core.screen_rect.y = cg_core.screen_height/2 - - cg_core.screen_rect.h/2; - } - // If screen is proportionally wider than game. - else if(screen_ratio > game_ratio) - { - scale = (double)cg_core.screen_height/(double)cg_core.game_height; +std::random_device random_seed; +std::mt19937 random_number_generator; - cg_core.screen_rect.w = cg_core.game_width * scale; - cg_core.screen_rect.h = cg_core.game_height * scale; - cg_core.screen_rect.x = cg_core.screen_width/2 - - cg_core.screen_rect.w/2; - cg_core.screen_rect.y = 0; - } - // If they have the same aspect ratio. - else - { - cg_core.screen_rect.x = 0; - cg_core.screen_rect.y = 0; - cg_core.screen_rect.w = cg_core.screen_width; - cg_core.screen_rect.h = cg_core.screen_height; - } +#ifdef DEBUG +static VKAPI_ATTR VkBool32 VKAPI_CALL +vk_debug_callback( + VkDebugUtilsMessageSeverityFlagBitsEXT message_severity, + VkDebugUtilsMessageTypeFlagsEXT message_type, + const VkDebugUtilsMessengerCallbackDataEXT* callback_data, + void* _obj) +{ + // Print message severy code. + std::cout << "[\e[1;0mVK\e[0;0m "; + if(message_severity & VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT) + std::cout << "\e[1;32mV\e[0;0m"; + else if(message_severity & VK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT) + std::cout << "\e[1;34mI\e[0;0m"; + else if(message_severity & VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT) + std::cout << "\e[1;33mW\e[0;0m"; + else if(message_severity & VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT) + std::cout << "\e[1;31mE\e[0;0m"; + + std::cout << "]"; + + // Log message. + std::cout << callback_data->pMessage << std::endl; + + return VK_FALSE; } +#endif -static SDL_bool -load_variables(void *obj, LoaderStack *ls) +static void +load_variables(void *obj) { YAML::Node root = YAML::LoadFile(cg_core.config_file); @@ -72,221 +60,421 @@ load_variables(void *obj, LoaderStack *ls) cg_core.game_name = new char[game_name.size() + 1]; strcpy(cg_core.game_name, game_name.c_str()); - // Based on NES. cg_core.game_width = root["game"]["width"].as<int>(); cg_core.game_height = root["game"]["height"].as<int>(); cg_core.screen_width = root["screen"]["width"].as<int>(); cg_core.screen_height = root["screen"]["height"].as<int>(); + cg_core.game_version_major = 0; + cg_core.game_version_minor = 1; + cg_core.game_version_patch = 0; + cg_core.fps = 30; cg_core.max_frame_duration_ms = 1000 / cg_core.fps; - - return SDL_TRUE; } static void -unload_variables(void *obj, LoaderStack *ls) +unload_variables(void *obj) { delete[] cg_core.game_name; } -static SDL_bool -load_sdl(void *obj, LoaderStack *ls) +static void +load_sdl(void *obj) { if(SDL_Init(SDL_INIT_EVERYTHING) < 0) { - const char* base_error = "SDL could not initialize! SDL Error → "; - LoaderStack_set_error(ls, base_error, SDL_GetError()); - return SDL_FALSE; + std::string error{"SDL could not initialize! SDL Error → "}; + error += SDL_GetError(); + throw error; } - return SDL_TRUE; + if(SDL_Vulkan_LoadLibrary(nullptr) != 0) + { + SDL_Quit(); + std::string error{"SDL could not initialize Vulkan! SDL_Error → "}; + error += SDL_GetError(); + throw CommandError{error}; + } } static void -unload_sdl(void *obj, LoaderStack *ls) +unload_sdl(void *obj) { + SDL_Vulkan_UnloadLibrary(); SDL_Quit(); } -static SDL_bool -load_sdl_image(void *obj, LoaderStack *ls) +static void +load_sdl_image(void *obj) { int flags = IMG_INIT_JPG|IMG_INIT_PNG|IMG_INIT_TIF; if(!(IMG_Init(flags) & flags)) { - const char* base_error = "Could not initialize SDL image → "; - LoaderStack_set_error(ls, base_error, IMG_GetError()); - return SDL_FALSE; + std::string error{"Could not initialize SDL image → "}; + error += IMG_GetError(); + throw CommandError{error}; } - - return SDL_TRUE; } static void -unload_sdl_image(void *obj, LoaderStack *ls) +unload_sdl_image(void *obj) { IMG_Quit(); } -static SDL_bool -load_sdl_mixer(void *obj, LoaderStack *ls) +static void +load_sdl_mixer(void *obj) { int flags = MIX_INIT_OGG|MIX_INIT_MOD; int initted = Mix_Init(flags); if(initted&flags != flags) { - const char* base_error = "Could not initialize SDL mixer → "; - LoaderStack_set_error(ls, base_error, Mix_GetError()); - return SDL_FALSE; + std::string error{"Could not initialize SDL mixer → "}; + error += Mix_GetError(); + throw CommandError{error}; } - - return SDL_TRUE; } static void -unload_sdl_mixer(void *obj, LoaderStack *ls) +unload_sdl_mixer(void *obj) { while(Mix_Init(0)) Mix_Quit(); } -static SDL_bool -load_sdl_open_audio(void *obj, LoaderStack *ls) +static void +load_sdl_open_audio(void *obj) { if(Mix_OpenAudio(22050, MIX_DEFAULT_FORMAT, 2, 1024) == -1) { - const char* base_error = "Could not open SDL mixer audio → "; - LoaderStack_set_error(ls, base_error, Mix_GetError()); - return SDL_FALSE; + std::string error{"Could not open SDL mixer audio → "}; + error += Mix_GetError(); + throw CommandError{error}; } - - return SDL_TRUE; } static void -unload_sdl_open_audio(void *obj, LoaderStack *ls) +unload_sdl_open_audio(void *obj) { Mix_CloseAudio(); } -static SDL_bool -load_sdl_ttf(void *obj, LoaderStack *ls) +static void +load_window(void *obj) { - if(TTF_Init()==-1) + cg_core.window = NULL; + cg_core.window = SDL_CreateWindow( + cg_core.game_name, SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, + cg_core.screen_width, cg_core.screen_height, SDL_WINDOW_VULKAN); + if(cg_core.window == NULL) { - const char* base_error = "Could not initialize SDL ttf → "; - LoaderStack_set_error(ls, base_error, TTF_GetError()); - return SDL_FALSE; + std::string error{"Window could not be created! SDL_Error → "}; + error += SDL_GetError(); + throw CommandError{error}; } - - return SDL_TRUE; } static void -unload_sdl_ttf(void *obj, LoaderStack *ls) +unload_window(void *obj) { - TTF_Quit(); + SDL_DestroyWindow(cg_core.window); } -static SDL_bool -load_window(void *obj, LoaderStack *ls) +static void +load_vk_instance(void *obj) { - cg_core.window = NULL; - cg_core.window = SDL_CreateWindow( - cg_core.game_name, SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, - cg_core.screen_width, cg_core.screen_height, SDL_WINDOW_SHOWN); - if(cg_core.window == NULL) + std::vector<const char*> vk_extensions; + std::vector<const char*> vk_required_layers_names; + + // Get extensions. { - const char* base_error = "Window could not be created! SDL_Error → "; - LoaderStack_set_error(ls, base_error, SDL_GetError()); - return SDL_FALSE; + 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; + + if(!SDL_Vulkan_GetInstanceExtensions( + cg_core.window, &vk_sdl_extension_count, nullptr)) + { + std::string error{ + "Vulkan extensions could not be loaded by SDL! SDL_Error: "}; + error += SDL_GetError(); + throw CommandError{error}; + } + + vk_sdl_extensions.resize(vk_sdl_extension_count); + SDL_Vulkan_GetInstanceExtensions( + cg_core.window, &vk_sdl_extension_count, vk_sdl_extensions.data()); + + // 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 + std::cout << "Enabled VK extensions." << std::endl; + for(auto vk_extension: vk_extensions) + std::cout << "Extension name: " << vk_extension << std::endl; +#endif } - return SDL_TRUE; + // 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 + std::cout << "Available VK instance layers." << std::endl; +#endif + for(uint32_t i = 0; i < vk_available_layers_count; i++) + { +#ifdef DEBUG + std::cout << "\nname: " << vk_available_layers[i].layerName << + std::endl; + std::cout << "Description: " << vk_available_layers[i].description << + std::endl; + std::cout << "Spec version: " << vk_available_layers[i].specVersion << + std::endl; + std::cout << "Implementation version: " << + vk_available_layers[i].implementationVersion << std::endl; +#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 = cg_core.game_name; + app_info.applicationVersion = VK_MAKE_VERSION( + cg_core.game_version_major, + cg_core.game_version_minor, + cg_core.game_version_patch); + app_info.pEngineName = "CandyGear"; + app_info.engineVersion = VK_MAKE_VERSION( + CANDY_GEAR_VERSION_MAJOR, + CANDY_GEAR_VERSION_MINOR, + CANDY_GEAR_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, &cg_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}; + } + } } static void -unload_window(void *obj, LoaderStack *ls) +unload_vk_instance(void *obj) { - SDL_DestroyWindow(cg_core.window); + vkDestroyInstance(cg_core.vk_instance, nullptr); } -static SDL_bool -load_sdl_renderer(void *obj, LoaderStack *ls) +static void +load_window_surface(void *obj) { - cg_core.renderer = NULL; - cg_core.renderer = SDL_CreateRenderer( - cg_core.window, -1, SDL_RENDERER_ACCELERATED | - SDL_RENDERER_TARGETTEXTURE); - if(cg_core.renderer == NULL) + if(!SDL_Vulkan_CreateSurface( + cg_core.window, cg_core.vk_instance, &cg_core.window_surface)) { - const char* base_error = "Could not create SDL renderer → "; - LoaderStack_set_error(ls, base_error, SDL_GetError()); - return SDL_FALSE; + std::string error{"Failed to create a window surface → "}; + error += SDL_GetError(); + throw CommandError{error}; } +} - return SDL_TRUE; +static void +unload_window_surface(void *obj) +{ + vkDestroySurfaceKHR(cg_core.vk_instance, cg_core.window_surface, nullptr); } +#ifdef DEBUG static void -unload_sdl_renderer(void *obj, LoaderStack *ls) +load_vk_debug_callback(void *obj) { - SDL_DestroyRenderer(cg_core.renderer); + 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_vk_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(cg_core.vk_instance, + "vkCreateDebugUtilsMessengerEXT"); + + if(debug_messenger(cg_core.vk_instance, &create_info, nullptr, + &cg_core.vk_callback) != VK_SUCCESS) + CommandError{"Failed to setup debug callback for Vulkan."}; +} + +static void +unload_vk_debug_callback(void *obj) +{ + PFN_vkDestroyDebugUtilsMessengerEXT debug_messenger; + + debug_messenger = (PFN_vkDestroyDebugUtilsMessengerEXT) + vkGetInstanceProcAddr(cg_core.vk_instance, + "vkDestroyDebugUtilsMessengerEXT"); + + debug_messenger(cg_core.vk_instance, cg_core.vk_callback, nullptr); } +#endif -static SDL_bool -load_pre_screen(void *obj, LoaderStack *ls) +static void +load_vk_devices(void *obj) { - cg_core.pre_screen_buffer = SDL_CreateTexture( - cg_core.renderer, SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_TARGET, - cg_core.game_width, cg_core.game_height); + uint32_t devices_count; + std::vector<VkPhysicalDevice> vk_physical_devices; - if(cg_core.pre_screen_buffer == NULL) + // Enumerate physical devices { - const char* base_error = "Could not create renderering buffer → "; - LoaderStack_set_error(ls, base_error, SDL_GetError()); - return SDL_FALSE; + vkEnumeratePhysicalDevices(cg_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( + cg_core.vk_instance, &devices_count, vk_physical_devices.data()); } - cg_Engine_calculate_full_scale(); - - SDL_SetRenderTarget(cg_core.renderer, cg_core.pre_screen_buffer); +#ifdef DEBUG + std::cout << "Physical devices properties" << std::endl; +#endif - return SDL_TRUE; + cg_core.vk_devices.reserve(devices_count); + for(auto i = 0; i < devices_count; i++) + { + // Use swapchain on first device. + if(i == 0) + { + cg_core.vk_devices.emplace_back(vk_physical_devices[i], true); + cg_core.vk_device_with_swapchain = &cg_core.vk_devices[i]; + } + else + cg_core.vk_devices.emplace_back(vk_physical_devices[i], false); + } } static void -unload_pre_screen(void *obj, LoaderStack *ls) +unload_vk_devices(void *obj) { - SDL_DestroyTexture(cg_core.pre_screen_buffer); + cg_core.vk_devices.clear(); } -void -cg_Core_init(LoaderStack *loader) +static void +load_vk_swapchain(void *obj) { - LoaderStack_constructor(loader, NULL); - - LoaderStack_add(loader, &load_variables, &unload_variables); - LoaderStack_add(loader, &load_sdl, &unload_sdl); - LoaderStack_add(loader, &load_sdl_image, &unload_sdl_image); - LoaderStack_add(loader, &load_sdl_mixer, &unload_sdl_mixer); - LoaderStack_add(loader, &load_sdl_open_audio, &unload_sdl_open_audio); - LoaderStack_add(loader, &load_sdl_ttf, &unload_sdl_ttf); - LoaderStack_add(loader, &load_window, &unload_window); - LoaderStack_add(loader, &load_sdl_renderer, &unload_sdl_renderer); - LoaderStack_add(loader, &load_pre_screen, &unload_pre_screen); + try { cg_core.vk_swapchain = new VK::Swapchain(); } + catch(const CommandError &error) + { + std::string error_message{"Failed to create swapchain → "}; + error_message += error.what(); + throw CommandError{error_message}; + } } -SDL_bool -cg_Core_load(LoaderStack *loader) +static void +unload_vk_swapchain(void *obj) { - if(!LoaderStack_load(loader)) return SDL_FALSE; + delete cg_core.vk_swapchain; +} - return SDL_TRUE; +static void +load_vk_graphics_pipeline(void *obj) +{ + try + { + cg_core.vk_graphics_pipeline = new VK::GraphicsPipeline(); + } + catch(const CommandError &e) + { + throw CommandError{"Failed to create graphics pipeline."}; + } } -void -cg_Core_unload(LoaderStack *loader) +static void +unload_vk_graphics_pipeline(void *obj) { - LoaderStack_destructor(loader); + delete cg_core.vk_graphics_pipeline; } + +const CommandChain cg_sCore::loader{ + {&load_variables, &unload_variables}, + {&load_sdl, &unload_sdl}, + {&load_sdl_image, &unload_sdl_image}, + {&load_sdl_mixer, &unload_sdl_mixer}, + {&load_sdl_open_audio, &unload_sdl_open_audio}, + {&load_window, &unload_window}, + {&load_vk_instance, &unload_vk_instance}, + {&load_window_surface, &unload_window_surface}, +#ifdef DEBUG + {&load_vk_debug_callback, &unload_vk_debug_callback}, +#endif + {&load_vk_devices, &unload_vk_devices}, + {&load_vk_swapchain, &unload_vk_swapchain}, + {&load_vk_graphics_pipeline, &unload_vk_graphics_pipeline} +}; |