2017年10月29日日曜日

Learning of Vulkan Part 00

はじめに

Vulkanを段階的に, 私のために学習していきます.
私のための学習ですので, プログラミング, CGの基礎, 概要は省略します.
始めにライブラリのロードを行いました.

IntelのチュートリアルAPI without Secretsを参考にしています.
コードはGitHubに置くことにします.

ライブラリロード

VulkanのAPIはOpenGLと同様に, APIを動的リンクライブラリからロードして使用するスタイルです.
Khronos GroupのVulkan Documentsに, API仕様がXMLファイルとして定義されています.
実装は各ベンダが提供します. C関数宣言としてのインターフェイスもXML仕様から生成されます.

とりあえず, Windows環境でデバイスの初期化を試してみます.

Vulkan (Ver. 1)のライブラリ名は"vulkan-1.dll"や"libvulkan.so.1"です.
"LoadLibrary"や"dlopen"でライブラリをロードし,
"GetProcAddress"や"dlsym"で関数アドレス取得,
"FreeLibrary"や"dlclose"でライブラリをアンロード, という流れになります.
#ifdef _WIN32
#define VK_USE_PLATFORM_WIN32_KHR 1

#define VLK_DLHANDLE HMODULE
#define VLK_DLOPEN(path) LoadLibrary((path))
#define VLK_DLSYM(handle, name) GetProcAddress((handle), (name))
#define VLK_DLCLOSE(handle) FreeLibrary((handle))

#define VLK_VULKANLIB ("vulkan-1.dll")

#else
#define VK_USE_PLATFORM_XLIB_KHR 1

#define VLK_DLHANDLE void*
#define VLK_DLOPEN(path) dlopen((path), RTLD_NOW)
#define VLK_DLSYM(handle, name) dlsym((handle), (name))
#define VLK_DLCLOSE(handle) dlclose((handle))

#define VLK_VULKANLIB ("libvulkan.so.1")

#endif
namespace vk
{
    //--------------------------------------------------------------
    //---
    //--- Lib
    //---
    //--------------------------------------------------------------
    class Lib
    {
    public:
        Lib();
        ~Lib();

        inline bool valid() const;

        VkResult initialize();
        void terminate();
    private:
        Lib(const Lib&) = delete;
        Lib& operator=(const Lib&) = delete;

        VLK_DLHANDLE handle_;
    };

    inline bool Lib::valid() const
    {
        return VLK_NULL != handle_;
    }

    Lib::Lib()
        :handle_(VLK_NULL)
    {
    }

    Lib::~Lib()
    {
        terminate();
    }

    VkResult Lib::initialize()
    {
        if(VLK_NULL != handle_){
            return VK_ERROR_INITIALIZATION_FAILED;
        }
        handle_ = VLK_DLOPEN(VLK_VULKANLIB);
        if(VLK_NULL == handle_){
            return VK_ERROR_INITIALIZATION_FAILED;
        }
        return VK_SUCCESS;
    }

    void Lib::terminate()
    {
        if(VLK_NULL != handle_){
            VLK_DLCLOSE(handle_);
            handle_ = VLK_NULL;
        }
    }
}

インスタンス, デバイス生成

次に関数ポインタをロードします.
関数のプロトタイプは, Vulkan Documentsにヘッダファイルとして宣言があります.
PFN_(name)の型で関数ポインタの型宣言があるため, 例えば以下のように文字列でアドレスを取得します.
#define VLK_GET_FUNCTION(name) name ## _ = (PFN_ ## name)VLK_DLSYM(handle_, #name);
APIが変更されることはほとんどないため, 手で全て書いてもよいですが面倒なので, vk.xmlから抽出します.
例えば, "VLK_EXPORTED_FUNCTION(vkCreateInstance)"として"VkFunctions.inc"に出力しておけば,
#define VLK_EXPORTED_FUNCTION(name) extern PFN_ ## name name ## _;
#include "VkFunctions.inc"

#define VLK_EXPORTED_FUNCTION(name) PFN_ ## name name ## _ = VLK_NULL;
#include "VkFunctions.inc

    VkResult Lib::initialize()
    {
        if(VLK_NULL != handle_){
            return VK_ERROR_INITIALIZATION_FAILED;
        }
        handle_ = VLK_DLOPEN(VLK_VULKANLIB);
        if(VLK_NULL == handle_){
            return VK_ERROR_INITIALIZATION_FAILED;
        }

#define VLK_EXPORTED_FUNCTION(name) VLK_GET_FUNCTION(name, "Error: cannot get exported %s\n")

#define VLK_GET_FUNCTION(name, message) name ## _ = (PFN_ ## name)VLK_DLSYM(handle_, #name);\
    if(VLK_NULL == name ## _){\
        PRINT1_ERR(message, #name);\
        return VK_ERROR_INITIALIZATION_FAILED;\
    }

#include "VkFunctions.inc"

#undef VLK_GET_FUNCTION

        return VK_SUCCESS;
    }
のようにしておくと, "(name)_"でグローバル関数として呼び出すことができます.
ここまでできると, 後は仕様どおりに初期化して使用するだけです.
とりあえず, デバイスの生成までできました.
クラスでwrapしていますが, vkCreateInstanceやvkCreateDeviceを呼び出しているだけです.
bool selectQueueFamily(vk::PhysicalDevice& selectedDevice, vk::u32& selectedQueueFamilty, vk::Instance& instance);

int main(int /*argc*/, char** /*argv*/)
{
    //Load library
    vk::Lib lib;
    if(VK_SUCCESS != lib.initialize()){
        fprintf(stderr, "Fail to initialize lib\n");
        return 0;
    }
    vk::Instance instance;
    vk::Device device;
    {
        // Create instance
        //---------------------------------------------------------------------
        VkApplicationInfo applicationInfo = {
            VK_STRUCTURE_TYPE_APPLICATION_INFO, //structure type
            VLK_NULL,
            "Tutorial Vulkan", //application name
            VK_MAKE_VERSION(1,0,0), //application version
            "Tutorial Engine", //engine name
            VK_MAKE_VERSION(1,0,0), //engine version
            VK_API_VERSION_1_0, //api version
        };
        VkInstanceCreateInfo instanceCreateInfo = {
            VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO, //structure type
            VLK_NULL,
            0, //instance creation flags
            &applicationInfo, //application info
            0, //enabled layer count
            VLK_NULL, //enabled layer names
            0, //enabled extension count
            VLK_NULL, //enabled extension names
        };
        if(VK_SUCCESS != lib.createInstance(instance, &instanceCreateInfo, VLK_NULL)){
            fprintf(stderr, "Fail to create instance\n");
            return 0;
        }

        // Create Device
        //---------------------------------------------------------------------
        vk::PhysicalDevice physicalDevice;
        vk::u32 queueFamily;
        if(!selectQueueFamily(physicalDevice, queueFamily, instance)){
            fprintf(stderr, "Fail to find queue family\n");
            return 0;
        }

        const float queuePriorities[] = {0.0f};
        VkDeviceQueueCreateInfo queueCreateInfo = {
            VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO, //structure type
            VLK_NULL,
            0, //device queue creation flags
            queueFamily, //selected queue family's index
            1, //queue count
            queuePriorities, //queue priorities
        };

        VkDeviceCreateInfo deviceCreateInfo = {
            VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO, //structure type
            VLK_NULL,
            0, //device creation flags
            1, //queue create info count,
            &queueCreateInfo, //queue create info
            0, //enabled layer count
            VLK_NULL, //enabled layer names
            0, //enabled extension count
            VLK_NULL, //enabled extension names
            VLK_NULL, //enabled physical device features
        };

        if(VK_SUCCESS != physicalDevice.createDevice(device, &deviceCreateInfo, VLK_NULL)){
            fprintf(stderr, "Fail to create device\n");
            return 0;
        }
    }
    device.destroy();
    instance.destroy();
    lib.terminate();
    return 0;
}

bool selectQueueFamily(vk::PhysicalDevice& selectedDevice, vk::u32& selectedQueueFamilty, vk::Instance& instance)
{
    static const vk::u32 MaxProperties = 128;
    VkQueueFamilyProperties properties[MaxProperties];

    vk::PhysicalDevices physicalDevices = instance.enumeratePhysicalDevices();
    for(vk::u32 i=0; i<physicalDevices.getNumDevices(); ++i){
        vk::PhysicalDevice& physicalDevice = physicalDevices.getDevice(i);
        vk::u32 countProperties = 0;
        physicalDevice.getPhysicalDeviceQueueFamilyProperties(&countProperties, VLK_NULL);
        countProperties = vk::minimum(countProperties, MaxProperties);
        physicalDevice.getPhysicalDeviceQueueFamilyProperties(&countProperties, properties);

        for(vk::u32 j=0; j<countProperties; ++j){
            if(0<properties[j].queueCount
                && (properties[j].queueFlags & VK_QUEUE_GRAPHICS_BIT))
            {
                selectedDevice = physicalDevice;
                selectedQueueFamilty = j;
                return true;
            }
        }
    }
    return false;
}

まとめ

vk.xmlから関数名を抽出し, デバイスの生成まで行いました.
APIの基本設計はOpenGLと変わらないように思います.
XMLで関数仕様が記述されているため, ヘッダから抽出するより楽になったと思います.

0 件のコメント:

コメントを投稿