/* * Copyright 2022 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" #ifdef DEBUG #include #endif #include std::random_device random_seed; std::mt19937 random_number_generator; #ifdef DEBUG static 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; if(message_severity & VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT) log_level = Log::Level::Trace; else if(message_severity & VK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT) log_level = Log::Level::Information; else if(message_severity & VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT) log_level = Log::Level::Warning; else if(message_severity & VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT) log_level = Log::Level::Error; // Log message. cg_core.log.message(log_level, callback_data->pMessage); return VK_FALSE; } #endif static void load_variables(void *obj) { using namespace std::chrono; YAML::Node root = YAML::LoadFile(cg_core.config_file); std::string game_name = root["name"].as().c_str(); cg_core.game_name = new char[game_name.size() + 1]; strcpy(cg_core.game_name, game_name.c_str()); cg_core.game_width = root["game"]["width"].as(); cg_core.game_height = root["game"]["height"].as(); cg_core.screen_width = root["screen"]["width"].as(); cg_core.screen_height = root["screen"]["height"].as(); 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 = duration(1000 / cg_core.fps); } static void unload_variables(void *obj) { delete[] cg_core.game_name; } static void load_sdl(void *obj) { if(SDL_Init(SDL_INIT_EVERYTHING) < 0) { std::string error{"SDL could not initialize! SDL Error → "}; error += SDL_GetError(); throw error; } 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) { SDL_Vulkan_UnloadLibrary(); SDL_Quit(); } static void load_sdl_image(void *obj) { int flags = IMG_INIT_JPG|IMG_INIT_PNG|IMG_INIT_TIF; if(!(IMG_Init(flags) & flags)) { std::string error{"Could not initialize SDL image → "}; error += IMG_GetError(); throw CommandError{error}; } } static void unload_sdl_image(void *obj) { IMG_Quit(); } static void load_sdl_mixer(void *obj) { int flags = MIX_INIT_OGG|MIX_INIT_MOD; int initted = Mix_Init(flags); if(initted&flags != flags) { std::string error{"Could not initialize SDL mixer → "}; error += Mix_GetError(); throw CommandError{error}; } } static void unload_sdl_mixer(void *obj) { while(Mix_Init(0)) Mix_Quit(); } static void load_sdl_open_audio(void *obj) { if(Mix_OpenAudio(22050, MIX_DEFAULT_FORMAT, 2, 1024) == -1) { std::string error{"Could not open SDL mixer audio → "}; error += Mix_GetError(); throw CommandError{error}; } } static void unload_sdl_open_audio(void *obj) { Mix_CloseAudio(); } static void load_window(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_VULKAN); if(cg_core.window == NULL) { std::string error{"Window could not be created! SDL_Error → "}; error += SDL_GetError(); throw CommandError{error}; } } static void unload_window(void *obj) { SDL_DestroyWindow(cg_core.window); } static void load_vk_instance(void *obj) { std::vector vk_extensions; std::vector vk_required_layers_names; // Get extensions. { uint32_t vk_extensions_count; std::vector 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 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 cg_core.log.message(Log::Level::Trace, "Enabled VK extensions."); for(auto vk_extension: vk_extensions) { std::string message{"Extension name: "}; message += vk_extension; cg_core.log.message(Log::Level::Trace, message); } #endif } // Get available instance layers. { uint32_t vk_available_layers_count; std::vector vk_available_layers; std::vector 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 cg_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; cg_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 = 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_vk_instance(void *obj) { vkDestroyInstance(cg_core.vk_instance, nullptr); } static void load_window_surface(void *obj) { if(!SDL_Vulkan_CreateSurface( cg_core.window, cg_core.vk_instance, &cg_core.window_surface)) { std::string error{"Failed to create a window surface → "}; error += SDL_GetError(); throw CommandError{error}; } } static void unload_window_surface(void *obj) { vkDestroySurfaceKHR(cg_core.vk_instance, cg_core.window_surface, nullptr); } #ifdef DEBUG static 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_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 void load_vk_devices(void *obj) { uint32_t devices_count; std::vector vk_physical_devices; // Enumerate physical devices { 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()); } #ifdef DEBUG cg_core.log.message(Log::Level::Trace, "Physical devices properties"); #endif 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_vk_devices(void *obj) { cg_core.vk_devices.clear(); } static void load_vk_swapchain(void *obj) { 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}; } } static void unload_vk_swapchain(void *obj) { delete cg_core.vk_swapchain; } static void load_vk_graphics_pipeline_3d(void *obj) { try { cg_core.vk_graphics_pipeline_3d = new VK::GraphicsPipeline3D(); } catch(const CommandError &e) { throw CommandError{"Failed to create 3d graphics pipeline."}; } } static void unload_vk_graphics_pipeline_3d(void *obj) { delete cg_core.vk_graphics_pipeline_3d; } static void load_vk_graphics_pipeline_2d(void *obj) { try { cg_core.vk_graphics_pipeline_2d = new VK::GraphicsPipeline2D(); } catch(const CommandError &e) { throw CommandError{"Failed to create 2d graphics pipeline."}; } } static void unload_vk_graphics_pipeline_2d(void *obj) { delete cg_core.vk_graphics_pipeline_2d; } static void load_vk_renderer(void *obj) { try { cg_core.vk_renderer = new VK::Renderer(); } catch(const CommandError &e) { throw CommandError{"Failed to create renderer."}; } } static void unload_vk_renderer(void *obj) { delete cg_core.vk_renderer; } static void load_mruby(void *obj) { cg_core.mrb = mrb_open(); if (!cg_core.mrb) throw CommandError{"Failed to initialize mruby."}; } static void unload_mruby(void *obj) { mrb_close(cg_core.mrb); } static void load_mruby_symbols(void *obj) { cg_core.sym_fatal = mrb_intern_cstr(cg_core.mrb, "fatal"); cg_core.sym_error = mrb_intern_cstr(cg_core.mrb, "error"); cg_core.sym_warning = mrb_intern_cstr(cg_core.mrb, "warning"); cg_core.sym_information = mrb_intern_cstr(cg_core.mrb, "information"); cg_core.sym_debug = mrb_intern_cstr(cg_core.mrb, "debug"); cg_core.sym_trace = mrb_intern_cstr(cg_core.mrb, "trace"); } 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_3d, &unload_vk_graphics_pipeline_3d}, {&load_vk_graphics_pipeline_2d, &unload_vk_graphics_pipeline_2d}, {&load_vk_renderer, &unload_vk_renderer}, {&load_mruby, &unload_mruby}, {&load_mruby_symbols, nullptr} };