前言

在上一篇文章讨论了工程的基本结构,接下来要把Vulkan、日志系统和窗口系统植入到本框架。

以下是准备使用的版本约定:

第三方库 版本号 描述
spdlog 1.13.0 日志系统. Very fast, header-only/compiled, C++ logging library
glfw 3.3.8 窗口系统
Vulkan 1.3.204 渲染API

该工程的源码管理地址:https://gitee.com/xingchen0085/adiosy_engine

本文TAG:TAG_01.00

第三方库全部放在Platform/External目录下。

spdlog

从github下载spdlog 1.13.0版本。https://github.com/gabime/spdlog

因为spdlog支持Header-only,所以将include目录下的头文件加到我们的第三方库目录就可使用。

为了方便使用,我添加了一个AdLog的类来维护日志相关信息,然后使用宏来完成日志输出。

//Platform/Public/AdLog.h

#ifndef ADLOG_H
#define ADLOG_H

#include "AdEngine.h"

#define SPDLOG_ACTIVE_LEVEL SPDLOG_LEVEL_TRACE
#include "spdlog/spdlog.h"

namespace ade{
    class AdLog{
    public:
        AdLog() = delete;
        AdLog(const AdLog&) = delete;
        AdLog &operator=(const AdLog&) = delete;
        static void Init();

        static spdlog::logger* GetLoggerInstance(){
            assert(sLoggerInstance && "Logger instance is null, maybe you have not execute AdLog::Init().");
            return sLoggerInstance.get();
        }

    private:
        static std::shared_ptr<spdlog::logger> sLoggerInstance;
    };

#define LOG_T(...) SPDLOG_LOGGER_TRACE(ade::AdLog::GetLoggerInstance(), __VA_ARGS__)
#define LOG_D(...) SPDLOG_LOGGER_DEBUG(ade::AdLog::GetLoggerInstance(), __VA_ARGS__)
#define LOG_I(...) SPDLOG_LOGGER_INFO(ade::AdLog::GetLoggerInstance(), __VA_ARGS__)
#define LOG_W(...) SPDLOG_LOGGER_WARN(ade::AdLog::GetLoggerInstance(), __VA_ARGS__)
#define LOG_E(...) SPDLOG_LOGGER_ERROR(ade::AdLog::GetLoggerInstance(), __VA_ARGS__)
}

#endif

具体的日志配置放在.cpp文件中:

//Platform/Private/AdLog.cpp

#include "AdLog.h"
#include "spdlog/sinks/stdout_color_sinks.h"
#include "spdlog/async.h"

namespace ade{

    std::shared_ptr<spdlog::logger> AdLog::sLoggerInstance{};

    void AdLog::Init() {
        sLoggerInstance = spdlog::stdout_color_mt<spdlog::async_factory>("async_logger");
        sLoggerInstance->set_level(spdlog::level::trace);
        sLoggerInstance->set_pattern("%^%H:%M:%S:%e [%P-%t] [%1!L] [%20s:%-4#] - %v%$");
    }
}

这里的AdEngine.h是公共头引入。

//Platform/Public/AdEngine.h

#ifndef AD_ENGINE_H
#define AD_ENGINE_H

#include <iostream>
#include <cassert>
#include <memory>
#include <algorithm>
#include <functional>

#include <string>
#include <sstream>
#include <fstream>
#include <filesystem>
#include <vector>
#include <stack>
#include <queue>
#include <deque>
#include <set>
#include <unordered_map>

#ifdef AD_ENGINE_PLATFORM_WIN32
//Windows
#define VK_USE_PLATFORM_WIN32_KHR
#elif AD_ENGINE_PLATFORM_MACOS
//Macos
#define VK_USE_PLATFORM_MACOS_MVK
#elif AD_ENGINE_PLATFORM_LINUX
// Linux
#define VK_USE_PLATFORM_XCB_KHR

#else
    #error Unsuppots this Platform
#endif

#define AD_ENGINE_GRAPHIC_API_VULKAN

#endif

日志输出测试:

//Sample/SandBox/Main.cpp

#include <iostream>
#include "AdLog.h"

int main(){
    std::cout << "Hello adiosy engine." << std::endl;

    ade::AdLog::Init();
    LOG_T("Log system debug... {0}, {1}, {2}", 1, 0.1f, "abc");

    return EXIT_SUCCESS;
}

运行结果:

image-20240411151321600

窗口系统

从github下载glfw 3.38版本。https://github.com/glfw/glfw/releases/tag/3.3.8。

可以选择编译安装到本地,然后使用链接库。但我这里想用一个比较浪费编译时间的方法,就是将源码拷贝到Platform/External目录下,方便查看源码。如下图:

image-20240411152102872

然后在CMakeLists.txt通过add_subdirectory()作为一个子项目来管理。

#Platform/CMakeLists.txt

#GLFW
add_subdirectory(External/glfw)
target_link_libraries(adiosy_platform PUBLIC glfw)

接下来就是创建我们的窗口。在Platform中的实现,我设计为是一个父类,然后根据宏控制使用不同的子类。所以先创建一个抽象的窗口类。

//Platform/Public/AdWindow.h

#ifndef AD_WINDOW_H
#define AD_WINDOW_H

#include "AdEngine.h"

namespace ade{
    class AdWindow{
    public:
        AdWindow(const AdWindow&) = delete;
        AdWindow &operator=(const AdWindow&) = delete;
        virtual ~AdWindow() = default;

        static std::unique_ptr<AdWindow> Create(uint32_t width, uint32_t height, const char *title);

        virtual bool ShouldClose() = 0;
        virtual void PollEvents() = 0;
        virtual void SwapBuffer() = 0;
    protected:
        AdWindow() = default;
    };
}

#endif
//Platform/Private/AdWindow.cpp

#include "AdWindow.h"
#include "Window/AdGLFWwindow.h"

namespace ade{
    std::unique_ptr<AdWindow> AdWindow::Create(uint32_t width, uint32_t height, const char *title) {
#ifdef AD_ENGINE_PLATFORM_WIN32
        return std::make_unique<AdGLFWwindow>(width, height, title);
#elif AD_ENGINE_PLATFORM_MACOS
        return std::make_unique<AdGLFWwindow>(width, height, title);
#elif AD_ENGINE_PLATFORM_LINUX
        return std::make_unique<AdGLFWwindow>(width, height, title);
#endif
        return nullptr;
    }
}

在GLFWwindow中的实现:

//Platform/Public/Window/AdGLFWWindow.h

#ifndef AD_GLFWWINDOW_H
#define AD_GLFWWINDOW_H

#include "AdWindow.h"
#include "GLFW/glfw3.h"

namespace ade{
    class AdGLFWwindow : public AdWindow{
    public:
        AdGLFWwindow() = delete;
        AdGLFWwindow(uint32_t width, uint32_t height, const char *title);
        ~AdGLFWwindow() override;

        bool ShouldClose() override;
        void PollEvents() override;
        void SwapBuffer() override;
    private:
        GLFWwindow *mGLFWwindow;
    };
}

#endif
//Platform/Private/Window/AdGLFWWindow.cpp

#include "Window/AdGLFWwindow.h"
#include "AdLog.h"
#include "GLFW/glfw3native.h"

namespace ade{
    AdGLFWwindow::AdGLFWwindow(uint32_t width, uint32_t height, const char *title) {
        if(!glfwInit()){
            LOG_E("Failed to init glfw.");
            return;
        }
        glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API);
        glfwWindowHint(GLFW_VISIBLE, GL_FALSE);

        mGLFWwindow = glfwCreateWindow(width, height, title, nullptr, nullptr);
        if(!mGLFWwindow){
            LOG_E("Failed to create glfw window.");
            return;
        }

        GLFWmonitor *primaryMonitor = glfwGetPrimaryMonitor();
        if(primaryMonitor){
            int xPos, yPos, workWidth, workHeight;
            glfwGetMonitorWorkarea(primaryMonitor, &xPos, &yPos, &workWidth, &workHeight);
            glfwSetWindowPos(mGLFWwindow, workWidth / 2 - width / 2, workHeight / 2 - height / 2);
        }

        glfwSwapInterval(0);

        glfwMakeContextCurrent(mGLFWwindow);

        // show window
        glfwShowWindow(mGLFWwindow);
    }

    AdGLFWwindow::~AdGLFWwindow() {
        glfwDestroyWindow(mGLFWwindow);
        glfwTerminate();
        LOG_I("The application running end.");
    }

    bool AdGLFWwindow::ShouldClose() {
        return glfwWindowShouldClose(mGLFWwindow);
    }

    void AdGLFWwindow::PollEvents() {
        glfwPollEvents();
    }

    void AdGLFWwindow::SwapBuffer() {
        glfwSwapBuffers(mGLFWwindow);
    }
}

显示窗口:

std::unique_ptr<ade::AdWindow> window = ade::AdWindow::Create(800, 600, "SandBox");
while (!window->ShouldClose()){
    window->PollEvents();

    window->SwapBuffers();
}

image-20240411162259154

Vulkan

安装Vulkan可以参考vulkan-tutorial,这个教程非常详细,这里就不细述了。包括后面Vulkan的基础渲染,也会参考该教程。

在安装好Vulkan后,在CMakeLists.txt中加载进来。

#Platform/CMakeLists.txt

#Vulkan
find_package(Vulkan REQUIRED)
if(Vulkan_FOUND)
    message("Find vulkan success: ${Vulkan_INCLUDE_DIRS}")
    include_directories(${Vulkan_INCLUDE_DIRS})
endif()

image-20240411164509293

然后我们创建一个AdGraphicContext类,这是渲染API的上下文,提供渲染调用。

//Platform/Public/AdGraphicContext.h

#ifndef AD_GRAPHIC_CONTEXT_H
#define AD_GRAPHIC_CONTEXT_H

#include "AdEngine.h"

namespace ade{
    class AdGraphicContext{
    public:
        AdGraphicContext(const AdGraphicContext&) = delete;
        AdGraphicContext &operator=(const AdGraphicContext&) = delete;
        virtual ~AdGraphicContext() = default;

        static std::unique_ptr<AdGraphicContext> Create();
    protected:
        AdGraphicContext() = default;
    private:

    };
}

#endif
//Platform/Private/AdGraphicContext.cpp

#include "AdGraphicContext.h"
#include "Graphic/VK/AdVKGraphicContext.h"

namespace ade{
    std::shared_ptr<AdGraphicContext> AdGraphicContext::Create() {
#ifdef AD_ENGINE_GRAPHIC_API_VULKAN
        return std::make_shared<AdVKGraphicContext>();
#else
        return nullptr;
#endif
        return nullptr;
    }
}

为了测试Vulkan是否可用,我创建了AdVKGraphicContext,然后调用一个Vulkan API测试。

//Platform/Public/Graphic/VK/AdVKGraphicContext.h

#include "AdGraphicContext.h"
#include "Graphic/AdVKGraphicContext.h"

namespace ade{
    std::unique_ptr<AdGraphicContext> AdGraphicContext::Create() {
#ifdef AD_ENGINE_GRAPHIC_API_VULKAN
        return std::make_unique<AdVKGraphicContext>();
#endif
        return nullptr;
    }
}
//Platform/Private/Graphic/VK/AdVKGraphicContext.cpp

#include "Graphic/AdVKGraphicContext.h"
#include "AdLog.h"
#include <vulkan/vulkan.h>

namespace ade{
    AdVKGraphicContext::AdVKGraphicContext() {
        CreateInstance();
    }

    AdVKGraphicContext::~AdVKGraphicContext() {

    }

    void AdVKGraphicContext::CreateInstance() {
        uint32_t availableLayerCount;
        vkEnumerateInstanceLayerProperties(&availableLayerCount, nullptr);
        VkLayerProperties availableLayers[availableLayerCount];
        vkEnumerateInstanceLayerProperties(&availableLayerCount, availableLayers);

        LOG_D("---------------------------");
        LOG_D("Instance Layers: ");
        for(int i = 0; i < availableLayerCount; i++){
            LOG_D("\t {0}", availableLayers[i].layerName);
        }
        LOG_D("---------------------------");
    }
}

然后在我们的测试程序中尝试创建这个渲染API上下文。

//Sample/SandBox/Main.cpp

#include <iostream>
#include "AdLog.h"
#include "AdWindow.h"
#include "AdGraphicContext.h"

int main(){
    ...
    ...
 	std::unique_ptr<ade::AdWindow> window = ade::AdWindow::Create(800, 600, "SandBox");
    std::unique_ptr<ade::AdGraphicContext> graphicContext = ade::AdGraphicContext::Create();
    ...
    ...

    return EXIT_SUCCESS;
}

我在创建Vulkan上下文时候,只是打印了当前设备支持的所有实例Layer。所以运行结果是:

image-20240411180804475

我们可以在命令行工具中输入vulkaninfo,来对比这些Layer是否是一致的。

到目前位置,窗口系统GLFW和Vulkan环境搭建完毕。