/* * Copyright 2022-2023 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" #include "candy_gear.hpp" #include "font.hpp" #include "graphic.hpp" #include "key.hpp" #include "rotation_3d.hpp" #include "skeletal_model.hpp" #include "skeletal_mesh.hpp" #include "static_model.hpp" #include "static_mesh.hpp" #include "sound.hpp" #include "sprite.hpp" #include "texture.hpp" #include "vector_3d.hpp" #include "vector_4d.hpp" #include "view_2d.hpp" #include "view_3d.hpp" #ifdef DEBUG #include #endif std::random_device random_seed; std::mt19937 random_number_generator; 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. cg_core.log.message(log_level, callback_data->pMessage); return VK_FALSE; } #endif void load_threads(void *obj) { auto num_threads{std::thread::hardware_concurrency() - 1}; for(auto i{0}; i < num_threads; i++) cg_core.threads.emplace_back( cg_core.workers.emplace_back(&cg_core.job_queue)); } void unload_threads(void *obj) { cg_core.job_queue.stop(); for(auto &t: cg_core.threads) t.join(); } void load_mruby_symbols(void *obj) { cg_core.sym_config = mrb_intern_cstr(cg_core.mrb, "config"); cg_core.sym_debug = mrb_intern_cstr(cg_core.mrb, "debug"); cg_core.sym_error = mrb_intern_cstr(cg_core.mrb, "error"); cg_core.sym_fatal = mrb_intern_cstr(cg_core.mrb, "fatal"); cg_core.sym_information = mrb_intern_cstr(cg_core.mrb, "information"); cg_core.sym_init = mrb_intern_cstr(cg_core.mrb, "init"); cg_core.sym_key_down = mrb_intern_cstr(cg_core.mrb, "key_down"); cg_core.sym_key_up = mrb_intern_cstr(cg_core.mrb, "key_up"); cg_core.sym_quit = mrb_intern_cstr(cg_core.mrb, "quit"); cg_core.sym_tick = mrb_intern_cstr(cg_core.mrb, "tick"); cg_core.sym_trace = mrb_intern_cstr(cg_core.mrb, "trace"); cg_core.sym_warning = mrb_intern_cstr(cg_core.mrb, "warning"); } void load_game(void *obj) { using namespace std::chrono; FILE *fp; mrb_value main_obj = mrb_obj_iv_inspect(cg_core.mrb, cg_core.mrb->top_self); cg_core.game_name = "CandyGear Game"; cg_core.display_width = 800; cg_core.display_height = 600; cg_core.game_version_major = 0; cg_core.game_version_minor = 1; cg_core.game_version_patch = 0; cg_core.fps = 30; mrb_define_module(cg_core.mrb, "CandyGear"); cg_candy_gear_init_config(cg_core.mrb); cg_graphic_init_config(cg_core.mrb); fp = fopen(cg_core.game_file.c_str(), "r"); mrb_load_irep_file(cg_core.mrb, fp); fclose(fp); if (cg_core.mrb->exc) { mrb_print_error(cg_core.mrb); throw CommandError{"Error loading game."}; } mrb_funcall_id(cg_core.mrb, main_obj, cg_core.sym_config, 0); if (cg_core.mrb->exc) { mrb_print_error(cg_core.mrb); throw CommandError{"Error configuring game."}; } cg_candy_gear_finish_config(cg_core.mrb); cg_graphic_finish_config(cg_core.mrb); cg_core.max_frame_duration = duration(1000 / cg_core.fps); // FIXME: actually calculates the real delta time. cg_core.delta_time = 1.0f / cg_core.fps; } 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}; } } void unload_sdl(void *obj) { SDL_Vulkan_UnloadLibrary(); SDL_Quit(); } 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}; } } void unload_sdl_mixer(void *obj) { while(Mix_Init(0)) Mix_Quit(); } 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}; } } void unload_sdl_open_audio(void *obj) { Mix_CloseAudio(); } void load_window(void *obj) { cg_core.window = NULL; cg_core.window = SDL_CreateWindow( cg_core.game_name.c_str(), SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, cg_core.display_width, cg_core.display_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}; } } void unload_window(void *obj) { SDL_DestroyWindow(cg_core.window); } void load_font_library(void *obj) { FT_Error error{FT_Init_FreeType(&cg_core.font_library)}; if(error) throw CommandError{"Failed to open the FreeType library."}; } void unload_font_library(void *obj) { FT_Done_FreeType(cg_core.font_library); } 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.c_str(); 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}; } } } void unload_vk_instance(void *obj) { vkDestroyInstance(cg_core.vk_instance, nullptr); } 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}; } } void unload_window_surface(void *obj) { vkDestroySurfaceKHR(cg_core.vk_instance, cg_core.window_surface, nullptr); } #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_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."}; } 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 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); } } 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}; } } void unload_vk_swapchain(void *obj) { delete cg_core.vk_swapchain; } void load_vk_graphics_pipeline_3d_layout(void *obj) { try { cg_core.vk_graphics_pipeline_3d_layout = new VK::GraphicsPipeline3DLayout(); } catch(const CommandError &e) { throw CommandError{"Failed to create 3d graphics pipeline."}; } } void unload_vk_graphics_pipeline_3d_layout(void *obj) { delete cg_core.vk_graphics_pipeline_3d_layout; } void load_vk_graphics_pipeline_2d_solid_layout(void *obj) { try { cg_core.vk_graphics_pipeline_2d_solid_layout = new VK::GraphicsPipeline2DSolidLayout(); } catch(const CommandError &e) { throw CommandError{"Failed to create 2d graphics pipeline."}; } } void unload_vk_graphics_pipeline_2d_solid_layout(void *obj) { delete cg_core.vk_graphics_pipeline_2d_solid_layout; } void load_vk_graphics_pipeline_2d_wired_layout(void *obj) { try { cg_core.vk_graphics_pipeline_2d_wired_layout = new VK::GraphicsPipeline2DWiredLayout(); } catch(const CommandError &e) { throw CommandError{"Failed to create 2d graphics pipeline."}; } } void unload_vk_graphics_pipeline_2d_wired_layout(void *obj) { delete cg_core.vk_graphics_pipeline_2d_wired_layout; } void load_vk_graphics_pipeline_3d(void *obj) { try { cg_core.vk_graphics_pipeline_3d = std::make_unique(); } catch(const CommandError &e) { throw CommandError{"Failed to create 3d graphics pipeline."}; } } void unload_vk_graphics_pipeline_3d(void *obj) { cg_core.vk_graphics_pipeline_3d = nullptr; } void load_vk_graphics_pipeline_3d_skeletal(void *obj) { try { cg_core.vk_graphics_pipeline_3d_skeletal = std::make_unique(); } catch(const CommandError &e) { throw CommandError{"Failed to create 3d skeletal graphics pipeline."}; } } void unload_vk_graphics_pipeline_3d_skeletal(void *obj) { cg_core.vk_graphics_pipeline_3d_skeletal = nullptr; } void load_vk_graphics_pipeline_2d_solid(void *obj) { try { cg_core.vk_graphics_pipeline_2d_solid = std::make_unique(); } catch(const CommandError &e) { throw CommandError{"Failed to create 2d graphics pipeline."}; } } void unload_vk_graphics_pipeline_2d_solid(void *obj) { cg_core.vk_graphics_pipeline_2d_solid = nullptr; } void load_vk_graphics_pipeline_2d_wired(void *obj) { try { cg_core.vk_graphics_pipeline_2d_wired = std::make_unique(); } catch(const CommandError &e) { throw CommandError{"Failed to create 2d graphics pipeline."}; } } void unload_vk_graphics_pipeline_2d_wired(void *obj) { cg_core.vk_graphics_pipeline_2d_wired = nullptr; } void load_vk_renderer(void *obj) { try { glm::vec4 region( 0.f, 0.f, static_cast(cg_core.display_width), static_cast(cg_core.display_height)); cg_core.vk_renderer = new VK::Renderer( {}, {std::make_shared(region, region.z, region.w)}); } catch(const CommandError &e) { throw CommandError{"Failed to create renderer."}; } } void unload_vk_renderer(void *obj) { delete cg_core.vk_renderer; } void load_mruby_interface(void *obj) { cg_candy_gear_init(cg_core.mrb); cg_font_init(cg_core.mrb); cg_key_init(cg_core.mrb); cg_rotation_3d_init(cg_core.mrb); cg_skeletal_model_init(cg_core.mrb); cg_skeletal_mesh_init(cg_core.mrb); cg_static_model_init(cg_core.mrb); cg_static_mesh_init(cg_core.mrb); cg_sound_init(cg_core.mrb); cg_sprite_init(cg_core.mrb); cg_texture_init(cg_core.mrb); cg_vector_3d_init(cg_core.mrb); cg_vector_4d_init(cg_core.mrb); cg_view_2d_init(cg_core.mrb); cg_view_3d_init(cg_core.mrb); } } const CommandChain cg_sCore::loader{ {&load_threads, &unload_threads}, {&load_mruby_symbols, nullptr}, {&load_game, nullptr}, {&load_sdl, &unload_sdl}, {&load_sdl_mixer, &unload_sdl_mixer}, {&load_sdl_open_audio, &unload_sdl_open_audio}, {&load_window, &unload_window}, {&load_font_library, &unload_font_library}, {&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_layout, &unload_vk_graphics_pipeline_3d_layout}, {&load_vk_graphics_pipeline_2d_solid_layout, &unload_vk_graphics_pipeline_2d_solid_layout}, {&load_vk_graphics_pipeline_2d_wired_layout, &unload_vk_graphics_pipeline_2d_wired_layout}, // TODO: finish skeletal mesh animation // {&load_vk_graphics_pipeline_3d_skeletal, // &unload_vk_graphics_pipeline_3d_skeletal}, {&load_vk_graphics_pipeline_3d, &unload_vk_graphics_pipeline_3d}, {&load_vk_graphics_pipeline_2d_solid, &unload_vk_graphics_pipeline_2d_solid}, {&load_vk_graphics_pipeline_2d_wired, &unload_vk_graphics_pipeline_2d_wired}, {&load_vk_renderer, &unload_vk_renderer}, {&load_mruby_interface, nullptr} };