本文主要分析Piccolo引擎启动的主要流程,并作为学习的一个记录。
Piccolo引擎主函数的代码段如下,而本文主要关注引擎与编辑器的初始化部分,也就是主函数的前7行。

int main(int argc, char** argv)
{
    std::filesystem::path executable_path(argv[0]);
    std::filesystem::path config_file_path = executable_path.parent_path() / "PiccoloEditor.ini";

    Piccolo::PiccoloEngine* engine = new Piccolo::PiccoloEngine();

    engine->startEngine(config_file_path.generic_string());
    engine->initialize();

    Piccolo::PiccoloEditor* editor = new Piccolo::PiccoloEditor();
    editor->initialize(engine);
    
    // run
    
    // cleanup

    return 0;
}

1.引擎初始化

引擎的初始化主要由startEngine完成,而initialize方法在当前版本还是空的。startEngine有两个步骤,一个是进行meta data的注册,来初始化反射相关数据,随后就是g_runtime_global_context各个子系统的初始化。

void PiccoloEngine::startEngine(const std::string& config_file_path)
{
    Reflection::TypeMetaRegister::metaRegister();

    g_runtime_global_context.startSystems(config_file_path);

    LOG_INFO("engine start");
}

关于反射相关的部分我会在后续的文章中进行分析。这篇文章主要关注g_runtime_global_context.startSystems(config_file_path);开始的初始化内容。这个config_file_path指的就是Piccolo编译生成的可执行文件同级目录中的PiccoloEditor.ini文件,文件内容如下,主要包括一些关键的资源与资源目录配置:

BinaryRootFolder=.
AssetFolder=asset
SchemaFolder=schema
BigIconFile=resource/PiccoloEditorBigIcon.png
SmallIconFile=resource/PiccoloEditorSmallIcon.png
FontFile=resource/PiccoloEditorFont.TTF
DefaultWorld=asset/world/hello.world.json
GlobalRenderingRes=asset/global/rendering.global.json
GlobalParticleRes=asset/global/particle.global.json
JoltAssetFolder=jolt-asset

g_runtime_global_context.startSystems(config_file_path);内容如下,按照给定顺序依次进行子系统的初始化:

void RuntimeGlobalContext::startSystems(const std::string& config_file_path)
{
    m_config_manager = std::make_shared<ConfigManager>();
    m_config_manager->initialize(config_file_path);

    m_file_system = std::make_shared<FileSystem>();

    m_logger_system = std::make_shared<LogSystem>();

    m_asset_manager = std::make_shared<AssetManager>();

    m_physics_manager = std::make_shared<PhysicsManager>();
    m_physics_manager->initialize();

    m_world_manager = std::make_shared<WorldManager>();
    m_world_manager->initialize();

    m_window_system = std::make_shared<WindowSystem>();
    WindowCreateInfo window_create_info;
    m_window_system->initialize(window_create_info);

    m_input_system = std::make_shared<InputSystem>();
    m_input_system->initialize();

    m_particle_manager = std::make_shared<ParticleManager>();
    m_particle_manager->initialize();

    m_render_system = std::make_shared<RenderSystem>();
    RenderSystemInitInfo render_init_info;
    render_init_info.window_system = m_window_system;
    m_render_system->initialize(render_init_info);

    m_debugdraw_manager = std::make_shared<DebugDrawManager>();
    m_debugdraw_manager->initialize();

    m_render_debug_config = std::make_shared<RenderDebugConfig>();
}

1.1. Config System初始化

Config System初始化是对Piccolo的配置文件进行解析,用的是一个手写的解析器。由于需要解析的内容十分固定,所以采用这种if else的方法可以用简单的代码完成需求。

void ConfigManager::initialize(const std::filesystem::path& config_file_path)
{
    // read configs
    std::ifstream config_file(config_file_path);
    std::string   config_line;
    while (std::getline(config_file, config_line))
    {
        size_t seperate_pos = config_line.find_first_of('=');
        if (seperate_pos > 0 && seperate_pos < (config_line.length() - 1))
        {
            std::string name  = config_line.substr(0, seperate_pos);
            std::string value = config_line.substr(seperate_pos + 1, config_line.length() - seperate_pos - 1);
            if (name == "BinaryRootFolder")
            {
                m_root_folder = config_file_path.parent_path() / value;
            }
            else if (name == "AssetFolder")
            {
                m_asset_folder = m_root_folder / value;
            }
            else if (name == "SchemaFolder")
            {
                m_schema_folder = m_root_folder / value;
            }
            else if (name == "DefaultWorld")
            {
                m_default_world_url = value;
            }
            else if (name == "BigIconFile")
            {
                m_editor_big_icon_path = m_root_folder / value;
            }
            else if (name == "SmallIconFile")
            {
                m_editor_small_icon_path = m_root_folder / value;
            }
            else if (name == "FontFile")
            {
                m_editor_font_path = m_root_folder / value;
            }
            else if (name == "GlobalRenderingRes")
            {
                m_global_rendering_res_url = value;
            }
            else if (name == "GlobalParticleRes")
            {
                m_global_particle_res_url = value;
            }
#ifdef ENABLE_PHYSICS_DEBUG_RENDERER
            else if (name == "JoltAssetFolder")
            {
                m_jolt_physics_asset_folder = m_root_folder / value;
            }
#endif
        }
    }
}

1.2. Physics Manager初始化

Physics Manager的初始化主要与Physics Debug Renderer有关,用于加载调试用Renderer的相关资源,由于暂时不会启用该功能,而且该Renderer只能在windows平台使用,限制较多,所以内部的实现细节之后在研究:

void PhysicsManager::initialize()
{
#ifdef ENABLE_PHYSICS_DEBUG_RENDERER
    std::shared_ptr<ConfigManager> config_manager = g_runtime_global_context.m_config_manager;
    ASSERT(config_manager);

    Trace = TraceImpl;

    m_renderer = new Renderer;
    m_renderer->Initialize();

    m_font = new Font(m_renderer);
    m_font->Create("Arial", 24, config_manager->getJoltPhysicsAssetFolder());

    m_debug_renderer = new DebugRendererImp(m_renderer, m_font, config_manager->getJoltPhysicsAssetFolder());
#endif
}

1.3. World Manager初始化

World Manager初始化内部并没有加载资源的相关代码,只是将m_is_world_loaded置为了false,当游戏实际运行时,如果检测到没有加载游戏世界,则会调用loadWorld方法来加载世界。不过由于该加载方法是同步调用,因此每次第一次启动和重新加载世界时,Piccolo引擎总会卡顿。后续可以考虑将同步加载换成异步加载,不过这样的转变应该需要修改很多地方的引擎代码,并注意多线程问题。

void WorldManager::initialize()
{
    m_is_world_loaded   = false;
    m_current_world_url = g_runtime_global_context.m_config_manager->getDefaultWorldUrl();

    //debugger
    m_level_debugger = std::make_shared<LevelDebugger>();
}

1.4. Window System初始化

该部分没有太多可说的,就是一个标准的glfw创建窗口的过程,具体可以看这个Vulkan教程来学习,在这个类里面比较有意思的是它注册的监听函数,使用std::function来绑定监听,每种类型的监听都由一个函数列表存储,因此针对一个类型的输入回调函数,我们可以注册多个处理函数,来完成不同逻辑。

void WindowSystem::initialize(WindowCreateInfo create_info)
{
    if (!glfwInit())
    {
        LOG_FATAL(__FUNCTION__, "failed to initialize GLFW");
        return;
    }

    m_width  = create_info.width;
    m_height = create_info.height;

    glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API);
    m_window = glfwCreateWindow(create_info.width, create_info.height, create_info.title, nullptr, nullptr);
    if (!m_window)
    {
        LOG_FATAL(__FUNCTION__, "failed to create window");
        glfwTerminate();
        return;
    }

    // Setup input callbacks
    glfwSetWindowUserPointer(m_window, this);
    glfwSetKeyCallback(m_window, keyCallback);
    glfwSetCharCallback(m_window, charCallback);
    glfwSetCharModsCallback(m_window, charModsCallback);
    glfwSetMouseButtonCallback(m_window, mouseButtonCallback);
    glfwSetCursorPosCallback(m_window, cursorPosCallback);
    glfwSetCursorEnterCallback(m_window, cursorEnterCallback);
    glfwSetScrollCallback(m_window, scrollCallback);
    glfwSetDropCallback(m_window, dropCallback);
    glfwSetWindowSizeCallback(m_window, windowSizeCallback);
    glfwSetWindowCloseCallback(m_window, windowCloseCallback);

    glfwSetInputMode(m_window, GLFW_RAW_MOUSE_MOTION, GLFW_FALSE);
}

1.5. Input System初始化

该系统主要是向Window System中注册了两个监听函数,分别用于处理键盘输入与鼠标移动事件。键盘输入的信息由位操作编码到一个m_game_command变量中,并且在处理该输入前,会检测引擎运行状态,若引擎处于编辑器模式,则不会进行按键回调处理。由于m_game_command的长度有限,因此只能编码一定长度的指令,换句话说,就是限制了游戏内部玩家可以操作的键位数量,但是从另外一个方面考虑,使用单个m_game_command进行存储,降低了内存需求,同时处理起来更加快速。不过对于现代处理器来说,这一点性能损耗应该无关紧要,更关键的性能消耗应该在渲染、物理和AI等模块上。除此以外,游戏内的按键绑定现在是硬编码的,后续可以考虑使用命令模式等设计模式对其进行改编,使其支持自定义按键功能。

void InputSystem::initialize()
{
    std::shared_ptr<WindowSystem> window_system = g_runtime_global_context.m_window_system;
    ASSERT(window_system);

    window_system->registerOnKeyFunc(std::bind(&InputSystem::onKey,
                                                this,
                                                std::placeholders::_1,
                                                std::placeholders::_2,
                                                std::placeholders::_3,
                                                std::placeholders::_4));
    window_system->registerOnCursorPosFunc(
        std::bind(&InputSystem::onCursorPos, this, std::placeholders::_1, std::placeholders::_2));
}

1.6. Particle Manager初始化

当前版本的粒子系统应该只是一个初步版本,所有粒子共享一套全局设定,没有足够的自由度,后续版本可以考虑更加自由的自定义方法。

void ParticleManager::initialize()
{
    std::shared_ptr<ConfigManager> config_manager = g_runtime_global_context.m_config_manager;
    ASSERT(config_manager);
    std::shared_ptr<AssetManager> asset_manager = g_runtime_global_context.m_asset_manager;
    ASSERT(asset_manager);

    GlobalParticleRes  global_particle_res;
    const std::string& global_particle_res_url = config_manager->getGlobalParticleResUrl();
    asset_manager->loadAsset(global_particle_res_url, global_particle_res);

    if (global_particle_res.m_emit_gap < 0)
    {
        global_particle_res.m_emit_gap = s_default_particle_emit_gap;
    }
    if (global_particle_res.m_emit_gap % 2)
    {
        LOG_ERROR("emit_gap should be multiples of 2");
        global_particle_res.m_emit_gap = s_default_particle_emit_gap;
    }
    if (global_particle_res.m_time_step < 1e-6)
    {
        LOG_ERROR("time_step should be lager");
        global_particle_res.m_emit_gap = s_default_particle_time_step;
    }
    if (global_particle_res.m_max_life < global_particle_res.m_time_step)
    {
        LOG_ERROR("max_life should be larger");
        global_particle_res.m_max_life = s_default_particle_life_time * s_default_particle_time_step;
    }
    m_global_particle_res = global_particle_res;
}

1.7. Render System初始化

Render System的初始化可以算是整个引擎最复杂的初始化流程。首先是Vulkan RHI的初始化,随后是全局资源的加载,包括各种贴图,然后是Camera、Scene和Render Pipeline的初始化。

void RenderSystem::initialize(RenderSystemInitInfo init_info)
{
    std::shared_ptr<ConfigManager> config_manager = g_runtime_global_context.m_config_manager;
    ASSERT(config_manager);
    std::shared_ptr<AssetManager> asset_manager = g_runtime_global_context.m_asset_manager;
    ASSERT(asset_manager);

    // render context initialize
    RHIInitInfo rhi_init_info;
    rhi_init_info.window_system = init_info.window_system;

    m_rhi = std::make_shared<VulkanRHI>();
    m_rhi->initialize(rhi_init_info);

    // global rendering resource
    GlobalRenderingRes global_rendering_res;
    const std::string& global_rendering_res_url = config_manager->getGlobalRenderingResUrl();
    asset_manager->loadAsset(global_rendering_res_url, global_rendering_res);

    // upload ibl, color grading textures
    LevelResourceDesc level_resource_desc;
    level_resource_desc.m_ibl_resource_desc.m_skybox_irradiance_map = global_rendering_res.m_skybox_irradiance_map;
    level_resource_desc.m_ibl_resource_desc.m_skybox_specular_map   = global_rendering_res.m_skybox_specular_map;
    level_resource_desc.m_ibl_resource_desc.m_brdf_map              = global_rendering_res.m_brdf_map;
    level_resource_desc.m_color_grading_resource_desc.m_color_grading_map =
        global_rendering_res.m_color_grading_map;

    m_render_resource = std::make_shared<RenderResource>();
    m_render_resource->uploadGlobalRenderResource(m_rhi, level_resource_desc);

    // setup render camera
    const CameraPose& camera_pose = global_rendering_res.m_camera_config.m_pose;
    m_render_camera               = std::make_shared<RenderCamera>();
    m_render_camera->lookAt(camera_pose.m_position, camera_pose.m_target, camera_pose.m_up);
    m_render_camera->m_zfar  = global_rendering_res.m_camera_config.m_z_far;
    m_render_camera->m_znear = global_rendering_res.m_camera_config.m_z_near;
    m_render_camera->setAspect(global_rendering_res.m_camera_config.m_aspect.x /
                                global_rendering_res.m_camera_config.m_aspect.y);

    // setup render scene
    m_render_scene                  = std::make_shared<RenderScene>();
    m_render_scene->m_ambient_light = {global_rendering_res.m_ambient_light.toVector3()};
    m_render_scene->m_directional_light.m_direction =
        global_rendering_res.m_directional_light.m_direction.normalisedCopy();
    m_render_scene->m_directional_light.m_color = global_rendering_res.m_directional_light.m_color.toVector3();
    m_render_scene->setVisibleNodesReference();

    // initialize render pipeline
    RenderPipelineInitInfo pipeline_init_info;
    pipeline_init_info.enable_fxaa     = global_rendering_res.m_enable_fxaa;
    pipeline_init_info.render_resource = m_render_resource;

    m_render_pipeline        = std::make_shared<RenderPipeline>();
    m_render_pipeline->m_rhi = m_rhi;
    m_render_pipeline->initialize(pipeline_init_info);

    // descriptor set layout in main camera pass will be used when uploading resource
    std::static_pointer_cast<RenderResource>(m_render_resource)->m_mesh_descriptor_set_layout =
        &static_cast<RenderPass*>(m_render_pipeline->m_main_camera_pass.get())
                ->m_descriptor_infos[MainCameraPass::LayoutType::_per_mesh]
                .layout;
    std::static_pointer_cast<RenderResource>(m_render_resource)->m_material_descriptor_set_layout =
        &static_cast<RenderPass*>(m_render_pipeline->m_main_camera_pass.get())
                ->m_descriptor_infos[MainCameraPass::LayoutType::_mesh_per_material]
                .layout;
}

1.7.1. Vulkan RHI初始化

该RHI的初始化与Vulkan Tutorial中提到的Vulkan初始化流程基本一致,具体大致有以下几个步骤:

  • 创建一个VkInstance
  • 选择一个支持Vulkan的图形设备(VkPhysicsDevice)
  • 为绘制和显示操作创建VkDevice和VkQueue
  • 创建一个窗口,窗口表面和交换链
  • 将交换链图像包装进VkImageView
  • 创建一个渲染层指定渲染目标和使用方式
  • 为渲染层创建帧缓冲
  • 配置图形管线
  • 为每一个交换链图像分配指令缓冲
  • 从交换链获取图像进行绘制操作,提交图像对应的指令缓冲,返回图像到交换链

在Piccolo中还多了一个步骤,添加了AMD开发的Vulkan Memeory Allocator来提高GPU内存分配效率。还有一点就是Piccolo中使用了三缓冲,而不是二缓冲,该选择可能可以提高系统流畅度,但是会增加资源消耗,对于配置较低的电脑可能需要注意。

void VulkanRHI::initialize(RHIInitInfo init_info)
{
    m_window = init_info.window_system->getWindow();

    std::array<int, 2> window_size = init_info.window_system->getWindowSize();

    m_viewport = {0.0f, 0.0f, (float)window_size[0], (float)window_size[1], 0.0f, 1.0f};
    m_scissor  = {{0, 0}, {(uint32_t)window_size[0], (uint32_t)window_size[1]}};

#ifndef NDEBUG
    m_enable_validation_Layers = true;
    m_enable_debug_utils_label = true;
#else
    m_enable_validation_Layers  = false;
    m_enable_debug_utils_label  = false;
#endif

#if defined(__GNUC__) && defined(__MACH__)
    m_enable_point_light_shadow = false;
#else
    m_enable_point_light_shadow = true;
#endif

#if defined(__GNUC__)
    // https://gcc.gnu.org/onlinedocs/cpp/Common-Predefined-Macros.html
#if defined(__linux__)
    char const* vk_layer_path = PICCOLO_XSTR(PICCOLO_VK_LAYER_PATH);
    setenv("VK_LAYER_PATH", vk_layer_path, 1);
#elif defined(__MACH__)
    // https://developer.apple.com/library/archive/documentation/Porting/Conceptual/PortingUnix/compiling/compiling.html
    char const* vk_layer_path    = PICCOLO_XSTR(PICCOLO_VK_LAYER_PATH);
    char const* vk_icd_filenames = PICCOLO_XSTR(PICCOLO_VK_ICD_FILENAMES);
    setenv("VK_LAYER_PATH", vk_layer_path, 1);
    setenv("VK_ICD_FILENAMES", vk_icd_filenames, 1);
#else
#error Unknown Platform
#endif
#elif defined(_MSC_VER)
    // https://docs.microsoft.com/en-us/cpp/preprocessor/predefined-macros
    char const* vk_layer_path = PICCOLO_XSTR(PICCOLO_VK_LAYER_PATH);
    SetEnvironmentVariableA("VK_LAYER_PATH", vk_layer_path);
    SetEnvironmentVariableA("DISABLE_LAYER_AMD_SWITCHABLE_GRAPHICS_1", "1");
#else
#error Unknown Compiler
#endif

    createInstance();

    initializeDebugMessenger();

    createWindowSurface();

    initializePhysicalDevice();

    createLogicalDevice();

    createCommandPool();

    createCommandBuffers();

    createDescriptorPool();

    createSyncPrimitives();

    createSwapchain();

    createSwapchainImageViews();

    createFramebufferImageAndView();

    createAssetAllocator();
}

1.7.2. Render Pipeline初始化

Pipeline的初始化是将每一个Render Pass进行初始化,并且除了Point Light Shadow和Directional Light以外所有的Render Pass都作为子Render Pass绑定在Main Camera的RenderPass上。关于渲染部分的详细分析会在后面的文章中进行。

void RenderPipeline::initialize(RenderPipelineInitInfo init_info)
{
    m_point_light_shadow_pass = std::make_shared<PointLightShadowPass>();
    m_directional_light_pass  = std::make_shared<DirectionalLightShadowPass>();
    m_main_camera_pass        = std::make_shared<MainCameraPass>();
    m_tone_mapping_pass       = std::make_shared<ToneMappingPass>();
    m_color_grading_pass      = std::make_shared<ColorGradingPass>();
    m_ui_pass                 = std::make_shared<UIPass>();
    m_combine_ui_pass         = std::make_shared<CombineUIPass>();
    m_pick_pass               = std::make_shared<PickPass>();
    m_fxaa_pass               = std::make_shared<FXAAPass>();
    m_particle_pass           = std::make_shared<ParticlePass>();

    RenderPassCommonInfo pass_common_info;
    pass_common_info.rhi             = m_rhi;
    pass_common_info.render_resource = init_info.render_resource;

    m_point_light_shadow_pass->setCommonInfo(pass_common_info);
    m_directional_light_pass->setCommonInfo(pass_common_info);
    m_main_camera_pass->setCommonInfo(pass_common_info);
    m_tone_mapping_pass->setCommonInfo(pass_common_info);
    m_color_grading_pass->setCommonInfo(pass_common_info);
    m_ui_pass->setCommonInfo(pass_common_info);
    m_combine_ui_pass->setCommonInfo(pass_common_info);
    m_pick_pass->setCommonInfo(pass_common_info);
    m_fxaa_pass->setCommonInfo(pass_common_info);
    m_particle_pass->setCommonInfo(pass_common_info);

    m_point_light_shadow_pass->initialize(nullptr);
    m_directional_light_pass->initialize(nullptr);

    std::shared_ptr<MainCameraPass> main_camera_pass = std::static_pointer_cast<MainCameraPass>(m_main_camera_pass);
    std::shared_ptr<RenderPass>     _main_camera_pass = std::static_pointer_cast<RenderPass>(m_main_camera_pass);
    std::shared_ptr<ParticlePass> particle_pass = std::static_pointer_cast<ParticlePass>(m_particle_pass);

    ParticlePassInitInfo particle_init_info{};
    particle_init_info.m_particle_manager = g_runtime_global_context.m_particle_manager;
    m_particle_pass->initialize(&particle_init_info);

    main_camera_pass->m_point_light_shadow_color_image_view =
        std::static_pointer_cast<RenderPass>(m_point_light_shadow_pass)->getFramebufferImageViews()[0];
    main_camera_pass->m_directional_light_shadow_color_image_view =
        std::static_pointer_cast<RenderPass>(m_directional_light_pass)->m_framebuffer.attachments[0].view;

    MainCameraPassInitInfo main_camera_init_info;
    main_camera_init_info.enble_fxaa = init_info.enable_fxaa;
    main_camera_pass->setParticlePass(particle_pass);
    m_main_camera_pass->initialize(&main_camera_init_info);

    std::static_pointer_cast<ParticlePass>(m_particle_pass)->setupParticlePass();

    std::vector<RHIDescriptorSetLayout*> descriptor_layouts = _main_camera_pass->getDescriptorSetLayouts();
    std::static_pointer_cast<PointLightShadowPass>(m_point_light_shadow_pass)
        ->setPerMeshLayout(descriptor_layouts[MainCameraPass::LayoutType::_per_mesh]);
    std::static_pointer_cast<DirectionalLightShadowPass>(m_directional_light_pass)
        ->setPerMeshLayout(descriptor_layouts[MainCameraPass::LayoutType::_per_mesh]);

    m_point_light_shadow_pass->postInitialize();
    m_directional_light_pass->postInitialize();

    ToneMappingPassInitInfo tone_mapping_init_info;
    tone_mapping_init_info.render_pass = _main_camera_pass->getRenderPass();
    tone_mapping_init_info.input_attachment =
        _main_camera_pass->getFramebufferImageViews()[_main_camera_pass_backup_buffer_odd];
    m_tone_mapping_pass->initialize(&tone_mapping_init_info);

    ColorGradingPassInitInfo color_grading_init_info;
    color_grading_init_info.render_pass = _main_camera_pass->getRenderPass();
    color_grading_init_info.input_attachment =
        _main_camera_pass->getFramebufferImageViews()[_main_camera_pass_backup_buffer_even];
    m_color_grading_pass->initialize(&color_grading_init_info);

    UIPassInitInfo ui_init_info;
    ui_init_info.render_pass = _main_camera_pass->getRenderPass();
    m_ui_pass->initialize(&ui_init_info);

    CombineUIPassInitInfo combine_ui_init_info;
    combine_ui_init_info.render_pass = _main_camera_pass->getRenderPass();
    combine_ui_init_info.scene_input_attachment =
        _main_camera_pass->getFramebufferImageViews()[_main_camera_pass_backup_buffer_odd];
    combine_ui_init_info.ui_input_attachment =
        _main_camera_pass->getFramebufferImageViews()[_main_camera_pass_backup_buffer_even];
    m_combine_ui_pass->initialize(&combine_ui_init_info);

    PickPassInitInfo pick_init_info;
    pick_init_info.per_mesh_layout = descriptor_layouts[MainCameraPass::LayoutType::_per_mesh];
    m_pick_pass->initialize(&pick_init_info);

    FXAAPassInitInfo fxaa_init_info;
    fxaa_init_info.render_pass = _main_camera_pass->getRenderPass();
    fxaa_init_info.input_attachment =
        _main_camera_pass->getFramebufferImageViews()[_main_camera_pass_post_process_buffer_odd];
    m_fxaa_pass->initialize(&fxaa_init_info);
}

1.8. Debug Draw Manager初始化

Debug Draw Manager相当于是添加了额外的Render Pipeline,专门用于绘制Debug的效果显示,具体代码如下:

void DebugDrawManager::initialize()
{
    m_rhi = g_runtime_global_context.m_render_system->getRHI();
    setupPipelines();
}

void DebugDrawManager::setupPipelines()
{
    //setup pipelines
    for (uint8_t i = 0; i < DebugDrawPipelineType::_debug_draw_pipeline_type_count; i++)
    {
        m_debug_draw_pipeline[i] = new DebugDrawPipeline((DebugDrawPipelineType)i);
        m_debug_draw_pipeline[i]->initialize();
    }
    m_buffer_allocator = new DebugDrawAllocator();
    m_font = new DebugDrawFont();
    m_font->inialize();
    m_buffer_allocator->initialize(m_font);
}

2.编辑器部分初始化

编辑器的初始化比引擎稍微简单一点,是通过调用这个函数editor->initialize(engine);来进行编辑器的初始化。具体的初始化代码如下:

void PiccoloEditor::initialize(PiccoloEngine* engine_runtime)
{
    assert(engine_runtime);

    g_is_editor_mode = true;
    m_engine_runtime = engine_runtime;

    EditorGlobalContextInitInfo init_info = {g_runtime_global_context.m_window_system.get(),
                                                g_runtime_global_context.m_render_system.get(),
                                                engine_runtime};
    g_editor_global_context.initialize(init_info);
    g_editor_global_context.m_scene_manager->setEditorCamera(
        g_runtime_global_context.m_render_system->getRenderCamera());
    g_editor_global_context.m_scene_manager->uploadAxisResource();

    m_editor_ui                   = std::make_shared<EditorUI>();
    WindowUIInitInfo ui_init_info = {g_runtime_global_context.m_window_system,
                                        g_runtime_global_context.m_render_system};
    m_editor_ui->initialize(ui_init_info);
}

初始化主要由三个部分组成,第一个是设g_is_editor_mode标志位,这个标志在引擎运行时控制了很多子系统的运行逻辑,主要是引擎在编辑器模式下与游戏模式下运行的逻辑有很大区别,需要通过该标志进行区分,比如输入系统就需要通过这个标志来区分输入是给编辑器使用还是游戏使用。

2.1. Editor Global Context初始化

第二个部分是对g_editor_global_context进行初始化,内部主要包括m_scene_manager的初始化和m_input_manager的的初始化。m_scene_manager的初始化没有操作,m_input_manager的初始化注册了编辑器需要的输入监听。

void EditorGlobalContext::initialize(const EditorGlobalContextInitInfo& init_info)
{
    g_editor_global_context.m_window_system  = init_info.window_system;
    g_editor_global_context.m_render_system  = init_info.render_system;
    g_editor_global_context.m_engine_runtime = init_info.engine_runtime;

    m_scene_manager = new EditorSceneManager();
    m_input_manager = new EditorInputManager();
    m_scene_manager->initialize();
    m_input_manager->initialize();
}

随后是setEditorCamerauploadAxisResource两个部分。uploadAxisResource手动创建三个游戏内编辑用GO对象,分别是移动,旋转的缩放的轴GO对象,并使用Render System来加载GO所需资源。

void EditorSceneManager::uploadAxisResource()
{
    auto& instance_id_allocator   = g_editor_global_context.m_render_system->getGOInstanceIdAllocator();
    auto& mesh_asset_id_allocator = g_editor_global_context.m_render_system->getMeshAssetIdAllocator();

    // assign some value that won't be used by other game objects
    {
        GameObjectPartId axis_instance_id = {0xFFAA, 0xFFAA};
        MeshSourceDesc   mesh_source_desc = {"%%translation_axis%%"};

        m_translation_axis.m_instance_id   = instance_id_allocator.allocGuid(axis_instance_id);
        m_translation_axis.m_mesh_asset_id = mesh_asset_id_allocator.allocGuid(mesh_source_desc);
    }

    {
        GameObjectPartId axis_instance_id = {0xFFBB, 0xFFBB};
        MeshSourceDesc   mesh_source_desc = {"%%rotate_axis%%"};

        m_rotation_axis.m_instance_id   = instance_id_allocator.allocGuid(axis_instance_id);
        m_rotation_axis.m_mesh_asset_id = mesh_asset_id_allocator.allocGuid(mesh_source_desc);
    }

    {
        GameObjectPartId axis_instance_id = {0xFFCC, 0xFFCC};
        MeshSourceDesc   mesh_source_desc = {"%%scale_axis%%"};

        m_scale_aixs.m_instance_id   = instance_id_allocator.allocGuid(axis_instance_id);
        m_scale_aixs.m_mesh_asset_id = mesh_asset_id_allocator.allocGuid(mesh_source_desc);
    }

    g_editor_global_context.m_render_system->createAxis(
        {m_translation_axis, m_rotation_axis, m_scale_aixs},
        {m_translation_axis.m_mesh_data, m_rotation_axis.m_mesh_data, m_scale_aixs.m_mesh_data});
}

2.2. Editor UI初始化

最后一个就是对编辑器UI的初始化。编辑器的UI采用了imgui,渲染底层采用的是Vulkan,初始化的代码如下所示,与imgui官方的初始化代码很相似:

void EditorUI::initialize(WindowUIInitInfo init_info)
{
    std::shared_ptr<ConfigManager> config_manager = g_runtime_global_context.m_config_manager;
    ASSERT(config_manager);

    // create imgui context
    IMGUI_CHECKVERSION();
    ImGui::CreateContext();

    // set ui content scale
    float x_scale, y_scale;
    glfwGetWindowContentScale(init_info.window_system->getWindow(), &x_scale, &y_scale);
    float content_scale = fmaxf(1.0f, fmaxf(x_scale, y_scale));
    windowContentScaleUpdate(content_scale);
    glfwSetWindowContentScaleCallback(init_info.window_system->getWindow(), windowContentScaleCallback);

    // load font for imgui
    ImGuiIO& io = ImGui::GetIO();
    io.ConfigFlags |= ImGuiConfigFlags_DockingEnable;
    io.ConfigDockingAlwaysTabBar         = true;
    io.ConfigWindowsMoveFromTitleBarOnly = true;
    io.Fonts->AddFontFromFileTTF(
        config_manager->getEditorFontPath().generic_string().data(), content_scale * 16, nullptr, nullptr);
    io.Fonts->Build();

    ImGuiStyle& style     = ImGui::GetStyle();
    style.WindowPadding   = ImVec2(1.0, 0);
    style.FramePadding    = ImVec2(14.0, 2.0f);
    style.ChildBorderSize = 0.0f;
    style.FrameRounding   = 5.0f;
    style.FrameBorderSize = 1.5f;

    // set imgui color style
    setUIColorStyle();

    // setup window icon
    GLFWimage   window_icon[2];
    std::string big_icon_path_string   = config_manager->getEditorBigIconPath().generic_string();
    std::string small_icon_path_string = config_manager->getEditorSmallIconPath().generic_string();
    window_icon[0].pixels =
        stbi_load(big_icon_path_string.data(), &window_icon[0].width, &window_icon[0].height, 0, 4);
    window_icon[1].pixels =
        stbi_load(small_icon_path_string.data(), &window_icon[1].width, &window_icon[1].height, 0, 4);
    glfwSetWindowIcon(init_info.window_system->getWindow(), 2, window_icon);
    stbi_image_free(window_icon[0].pixels);
    stbi_image_free(window_icon[1].pixels);

    // initialize imgui vulkan render backend
    init_info.render_system->initializeUIRenderBackend(this);
}

关于render_system调用的UI后端初始化,最后是调用UI Pass的初始化方法:

void UIPass::initializeUIRenderBackend(WindowUI* window_ui)
{
    m_window_ui = window_ui;

    ImGui_ImplGlfw_InitForVulkan(std::static_pointer_cast<VulkanRHI>(m_rhi)->m_window, true);
    ImGui_ImplVulkan_InitInfo init_info = {};
    init_info.Instance                  = std::static_pointer_cast<VulkanRHI>(m_rhi)->m_instance;
    init_info.PhysicalDevice            = std::static_pointer_cast<VulkanRHI>(m_rhi)->m_physical_device;
    init_info.Device                    = std::static_pointer_cast<VulkanRHI>(m_rhi)->m_device;
    init_info.QueueFamily               = m_rhi->getQueueFamilyIndices().graphics_family.value();
    init_info.Queue                     = ((VulkanQueue*)m_rhi->getGraphicsQueue())->getResource();
    init_info.DescriptorPool            = std::static_pointer_cast<VulkanRHI>(m_rhi)->m_vk_descriptor_pool;
    init_info.Subpass                   = _main_camera_subpass_ui;
    
    // may be different from the real swapchain image count
    // see ImGui_ImplVulkanH_GetMinImageCountFromPresentMode
    init_info.MinImageCount = 3;
    init_info.ImageCount    = 3;
    ImGui_ImplVulkan_Init(&init_info, ((VulkanRenderPass*)m_framebuffer.render_pass)->getResource());

    uploadFonts();
}

void UIPass::uploadFonts()
{
    RHICommandBufferAllocateInfo allocInfo = {};
    allocInfo.sType                       = RHI_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
    allocInfo.level                       = RHI_COMMAND_BUFFER_LEVEL_PRIMARY;
    allocInfo.commandPool                 = m_rhi->getCommandPoor();
    allocInfo.commandBufferCount          = 1;

    RHICommandBuffer* commandBuffer = new VulkanCommandBuffer();
    if (RHI_SUCCESS != m_rhi->allocateCommandBuffers(&allocInfo, commandBuffer))
    {
        throw std::runtime_error("failed to allocate command buffers!");
    }

    RHICommandBufferBeginInfo beginInfo = {};
    beginInfo.sType                    = RHI_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
    beginInfo.flags                    = RHI_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;

    if (RHI_SUCCESS != m_rhi->beginCommandBuffer(commandBuffer, &beginInfo))
    {
        throw std::runtime_error("Could not create one-time command buffer!");
    }

    ImGui_ImplVulkan_CreateFontsTexture(((VulkanCommandBuffer*)commandBuffer)->getResource());

    if (RHI_SUCCESS != m_rhi->endCommandBuffer(commandBuffer))
    {
        throw std::runtime_error("failed to record command buffer!");
    }

    RHISubmitInfo submitInfo {};
    submitInfo.sType              = RHI_STRUCTURE_TYPE_SUBMIT_INFO;
    submitInfo.commandBufferCount = 1;
    submitInfo.pCommandBuffers    = &commandBuffer;

    m_rhi->queueSubmit(m_rhi->getGraphicsQueue(), 1, &submitInfo, RHI_NULL_HANDLE);
    m_rhi->queueWaitIdle(m_rhi->getGraphicsQueue());

    m_rhi->freeCommandBuffers(m_rhi->getCommandPoor(), 1, commandBuffer);

    ImGui_ImplVulkan_DestroyFontUploadObjects();
}

由上述两个部分的初始化程序可以看出,这个流程基本与官方初始化流程一致,imgui官方的初始化程序如下所示:

int main(int, char**)
{
    // Setup GLFW window
    ...

    // Setup Vulkan
    ...

    // Setup Dear ImGui context
    IMGUI_CHECKVERSION();
    ImGui::CreateContext();
    ImGuiIO& io = ImGui::GetIO(); (void)io;
    //io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard;     // Enable Keyboard Controls
    //io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad;      // Enable Gamepad Controls

    // Setup Dear ImGui style
    ImGui::StyleColorsDark();
    //ImGui::StyleColorsLight();

    // Setup Platform/Renderer backends
    ImGui_ImplGlfw_InitForVulkan(window, true);
    ImGui_ImplVulkan_InitInfo init_info = {};
    init_info.Instance = g_Instance;
    init_info.PhysicalDevice = g_PhysicalDevice;
    init_info.Device = g_Device;
    init_info.QueueFamily = g_QueueFamily;
    init_info.Queue = g_Queue;
    init_info.PipelineCache = g_PipelineCache;
    init_info.DescriptorPool = g_DescriptorPool;
    init_info.Subpass = 0;
    init_info.MinImageCount = g_MinImageCount;
    init_info.ImageCount = wd->ImageCount;
    init_info.MSAASamples = VK_SAMPLE_COUNT_1_BIT;
    init_info.Allocator = g_Allocator;
    init_info.CheckVkResultFn = check_vk_result;
    ImGui_ImplVulkan_Init(&init_info, wd->RenderPass);

    // Load Fonts
    // - If no fonts are loaded, dear imgui will use the default font. You can also load multiple fonts and use ImGui::PushFont()/PopFont() to select them.
    // - AddFontFromFileTTF() will return the ImFont* so you can store it if you need to select the font among multiple.
    // - If the file cannot be loaded, the function will return NULL. Please handle those errors in your application (e.g. use an assertion, or display an error and quit).
    // - The fonts will be rasterized at a given size (w/ oversampling) and stored into a texture when calling ImFontAtlas::Build()/GetTexDataAsXXXX(), which ImGui_ImplXXXX_NewFrame below will call.
    // - Use '#define IMGUI_ENABLE_FREETYPE' in your imconfig file to use Freetype for higher quality font rendering.
    // - Read 'docs/FONTS.md' for more instructions and details.
    // - Remember that in C/C++ if you want to include a backslash \ in a string literal you need to write a double backslash \\ !
    //io.Fonts->AddFontDefault();
    //io.Fonts->AddFontFromFileTTF("c:\\Windows\\Fonts\\segoeui.ttf", 18.0f);
    //io.Fonts->AddFontFromFileTTF("../../misc/fonts/DroidSans.ttf", 16.0f);
    //io.Fonts->AddFontFromFileTTF("../../misc/fonts/Roboto-Medium.ttf", 16.0f);
    //io.Fonts->AddFontFromFileTTF("../../misc/fonts/Cousine-Regular.ttf", 15.0f);
    //ImFont* font = io.Fonts->AddFontFromFileTTF("c:\\Windows\\Fonts\\ArialUni.ttf", 18.0f, NULL, io.Fonts->GetGlyphRangesJapanese());
    //IM_ASSERT(font != NULL);

    // Upload Fonts
    {
        // Use any command queue
        VkCommandPool command_pool = wd->Frames[wd->FrameIndex].CommandPool;
        VkCommandBuffer command_buffer = wd->Frames[wd->FrameIndex].CommandBuffer;

        err = vkResetCommandPool(g_Device, command_pool, 0);
        check_vk_result(err);
        VkCommandBufferBeginInfo begin_info = {};
        begin_info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
        begin_info.flags |= VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;
        err = vkBeginCommandBuffer(command_buffer, &begin_info);
        check_vk_result(err);

        ImGui_ImplVulkan_CreateFontsTexture(command_buffer);

        VkSubmitInfo end_info = {};
        end_info.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
        end_info.commandBufferCount = 1;
        end_info.pCommandBuffers = &command_buffer;
        err = vkEndCommandBuffer(command_buffer);
        check_vk_result(err);
        err = vkQueueSubmit(g_Queue, 1, &end_info, VK_NULL_HANDLE);
        check_vk_result(err);

        err = vkDeviceWaitIdle(g_Device);
        check_vk_result(err);
        ImGui_ImplVulkan_DestroyFontUploadObjects();
    }

    // Main loop
    ...

    // Cleanup
    ...

    return 0;
}