LoginSignup
4
6

More than 3 years have passed since last update.

低レベル描画API(Vulkanなど)をなんとなく扱えるライブラリを作ってみた

Last updated at Posted at 2020-12-18

https://github.com/ichi-raven/Cutlass
こちらです.

動機

Vulkanで書いていて、コード量と落とし穴の多さがいやになったからです.

やたら隠蔽した理由

主な導入対象である弊部では、コードの互換性が重要だったからです.

バックエンドが書き換えやすいとは言ってない

開発目標

低レベル描画APIの設計上の利点を活かしながら、可能な限り抽象化・コード量削減を行います.
GLFWと連携してウィンドウ関連処理, 入出力処理もある程度行います.
ファイルからの画像読み込みも行います.
より下位層での抽象化を行うことで、アプリケーション実装時に柔軟さを持たせます.

依存環境

Vulkan
GLFW
stb
glm
(注:glmはライブラリ本体では使用していませんが, リソースタイプ等glmを基準として記述しているため、使用をお勧めします. )

サンプルコード

main.cpp
#include <Cutlass.hpp>

#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtc/quaternion.hpp>

#include <iostream>
#include <vector>
#include <chrono>
#include <numeric>

using namespace Cutlass;

//頂点型
struct Vertex
{
    glm::vec3 pos;
    glm::vec3 color;
    glm::vec3 normal;
    glm::vec2 UV;
};

//ユニフォームバッファ型
struct Uniform
{
    glm::mat4 world;
    glm::mat4 view;
    glm::mat4 proj;
};

int main()
{
    //定数
    constexpr uint32_t frameCount = 3;
    constexpr uint32_t width = 800, height = 600;

    //-----------------------------------------ジオメトリ定義
    const float k = 1.0f;
    const glm::vec3 red(1.0f, 0.0f, 0.0f);
    const glm::vec3 green(0.0f, 1.0f, 0.0f);
    const glm::vec3 blue(0.0f, 0.0f, 1.0f);
    const glm::vec3 white(1.0f);
    const glm::vec3 black(0.0f);
    const glm::vec3 yellow(1.0f, 1.0f, 0.0f);
    const glm::vec3 magenta(1.0f, 0.0f, 1.0f);
    const glm::vec3 cyan(0.0f, 1.0f, 1.0f);

    const glm::vec2 lb(0.0f, 0.0f);
    const glm::vec2 lt(0.0f, 1.0f);
    const glm::vec2 rb(1.0f, 0.0f);
    const glm::vec2 rt(1.0f, 1.0f);

    const glm::vec3 nf(0, 0, 1.f);
    const glm::vec3 nb(0, 0, -1.f);
    const glm::vec3 nr(1.f, 0, 0);
    const glm::vec3 nl(-1.f, 0, 0);
    const glm::vec3 nu(0, 1.f, 0);
    const glm::vec3 nd(0, -1.f, 0);

    std::vector<Vertex> vertices =
    {
        // 正面
        {glm::vec3(-k, k, k), yellow, nf, lb},
        {glm::vec3(-k, -k, k), red, nf, lt},
        {glm::vec3(k, k, k), white, nf, rb},
        {glm::vec3(k, -k, k), magenta, nf, rt},
        // 右
        {glm::vec3(k, k, k), white, nr, lb},
        {glm::vec3(k, -k, k), magenta, nr, lt},
        {glm::vec3(k, k, -k), cyan, nr, rb},
        {glm::vec3(k, -k, -k), blue, nr, rt},
        // 左
        {glm::vec3(-k, k, -k), green, nl, lb},
        {glm::vec3(-k, -k, -k), black, nl, lt},
        {glm::vec3(-k, k, k), yellow, nl, rb},
        {glm::vec3(-k, -k, k), red, nl, rt},
        // 裏
        {glm::vec3(k, k, -k), cyan, nb, lb},
        {glm::vec3(k, -k, -k), blue, nb, lt},
        {glm::vec3(-k, k, -k), green, nb, rb},
        {glm::vec3(-k, -k, -k), black, nb, rt},
        // 上
        {glm::vec3(-k, k, -k), green, nu, lb},
        {glm::vec3(-k, k, k), yellow, nu, lt},
        {glm::vec3(k, k, -k), cyan, nu, rb},
        {glm::vec3(k, k, k), white, nu, rt},
        // 底
        {glm::vec3(-k, -k, k), red, nd, lb},
        {glm::vec3(-k, -k, -k), black, nd, lt},
        {glm::vec3(k, -k, k), magenta, nd, rb},
        {glm::vec3(k, -k, -k), blue, nd, rt},
    };

    std::vector<uint32_t> indices =
    {
        0, 2, 1, 1, 2, 3,    // front
        4, 6, 5, 5, 6, 7,    // right
        8, 10, 9, 9, 10, 11, // left

        12, 14, 13, 13, 14, 15, // back
        16, 18, 17, 17, 18, 19, // top
        20, 22, 21, 21, 22, 23, // bottom
    };

    //-----------------------------------------------------

    //コンテキスト取得
    Context& context = Context::getInstance();

    {
        InitializeInfo ii("test", true);
        if (Result::eSuccess != context.initialize(ii))
            std::cout << "Failed to Initialize!\n";
    }

   HWindow window;
    {//ウィンドウ作成
        WindowInfo wi(width, height, frameCount, "CutlassTest", false, true);
        if (Result::eSuccess != context.createWindow(wi, window))
            std::cout << "Failed to create window!\n";
    }

    HBuffer vertexBuffer;
    {//頂点バッファ作成, 書き込み
        BufferInfo bi;
        bi.setVertexBuffer<decltype(vertices[0])>(vertices.size());
        if (Result::eSuccess != context.createBuffer(bi, vertexBuffer))
            std::cout << "Failed to create vertex buffer!\n";
        if (Result::eSuccess != context.writeBuffer(bi.size, vertices.data(), vertexBuffer))
            std::cout << "Failed to write vertex buffer!\n";
    }

    HBuffer indexBuffer;
    {//インデックスバッファ作成, 書き込み
        BufferInfo bi;
        bi.setIndexBuffer<decltype(indices[0])>(indices.size());
        if (Result::eSuccess != context.createBuffer(bi, indexBuffer))
            std::cout << "Failed to create index buffer!\n";
        if (Result::eSuccess != context.writeBuffer(bi.size, indices.data(), indexBuffer))
            std::cout << "Failed to write index buffer!\n";
    }

    HBuffer renderUB;
    std::vector<HBuffer> presentUBs(frameCount);
    {//ユニフォームバッファ作成
        BufferInfo bi;
        bi.setUniformBuffer<Uniform>();

        if (Result::eSuccess != context.createBuffer(bi, renderUB))
            std::cout << "Failed to create uniform\n";

        for (auto& ub : presentUBs)
            if (Result::eSuccess != context.createBuffer(bi, ub))
                std::cout << "Failed to create uniform\n";
    }

    HTexture texture;
    {//テクスチャ作成
        if (Result::eSuccess != context.createTextureFromFile("../Textures/texture.png", texture))
            std::cout << "Failed to create texture from file!\n";
    }

    HTexture target;
    {//描画先用テクスチャ作成
        TextureInfo ti;
        ti.setRTTex2D(width, height, ResourceType::eUNormVec4);
        if (Result::eSuccess != context.createTexture(ti, target))
            std::cout << "Failed to create render target texture!\n";
    }

    HRenderDST texDST;
    {//テクスチャ用描画先オブジェクト作成
        if (Result::eSuccess != context.createRenderDST({ target }, texDST))
            std::cout << "Failed to create texture renderDST\n";
    }

    HRenderDST renderDST;
    {//ウィンドウ用描画先オブジェクト作成
        if (Result::eSuccess != context.createRenderDST(window, true, renderDST))
            std::cout << "Failed to create renderDST\n";
    }

    HRenderPipeline renderPipeline, presentPipeline;
    {//テクスチャ描画用パス、ウィンドウ描画用パスを定義

        //頂点レイアウト定義
        VertexLayout vl;
        vl.set(ResourceType::eF32Vec3, "pos");
        vl.set(ResourceType::eF32Vec3, "color");
        vl.set(ResourceType::eF32Vec3, "normal");
        vl.set(ResourceType::eF32Vec2, "UV");

        //シェーダリソースレイアウト定義
        ShaderResourceDesc SRDesc;
        SRDesc.layout.allocForUniformBuffer(0);
        SRDesc.layout.allocForCombinedTexture(1);
        SRDesc.setCount = frameCount;

        RenderPipelineInfo rpi
        (
            vl,
            ColorBlend::eDefault,
            Topology::eTriangleList,
            RasterizerState(),
            MultiSampleState::eDefault,
            DepthStencilState::eNone,
            Shader("../Shaders/vert.spv", "main"),
            Shader("../Shaders/frag.spv", "main"),
            SRDesc,
            texDST
        );

        if (Result::eSuccess != context.createRenderPipeline(rpi, renderPipeline))
            std::cout << "Failed to create render pipeline!\n";

        //2パス目は対象とデプスバッファリングが変わる
        rpi.depthStencilState = DepthStencilState::eDepth;
        rpi.renderDST = renderDST;

        if (Result::eSuccess != context.createRenderPipeline(rpi, presentPipeline))
            std::cout << "Failed to create present pipeline!\n";
    }

    ShaderResourceSet renderSet;
    {//テクスチャレンダリングパスのリソースセット

        renderSet.setUniformBuffer(0, renderUB);
        renderSet.setCombinedTexture(1, texture);

    }

    std::vector<ShaderResourceSet> presentSets(frameCount);
    {//ウィンドウに描画するパスのリソースセット
        for (size_t i = 0; i < presentSets.size(); ++i)
        {
            presentSets[i].setUniformBuffer(0, presentUBs[i]);
            presentSets[i].setCombinedTexture(1, target);
        }
    }

    CommandList renderCL;
    std::vector<CommandList> presentCL(frameCount);

    {//コマンドリストを作成
        ColorClearValue ccv{ 0, 0.5f, 0, 0 };
        DepthClearValue dcv(1.f, 0);

        renderCL.bindVB(vertexBuffer);
        renderCL.bindIB(indexBuffer);

        renderCL.beginRenderPipeline(renderPipeline, ccv, dcv);
        renderCL.bindSRSet(renderSet);
        renderCL.renderIndexed(indices.size(), 1, 0, 0, 0);
        renderCL.endRenderPipeline();
        renderCL.sync();


        for(size_t i = 0; i < presentCL.size(); ++i)
        {
            presentCL[i].bindVB(vertexBuffer);
            presentCL[i].bindIB(indexBuffer);

            presentCL[i].beginRenderPipeline(presentPipeline);
            presentCL[i].bindSRSet(presentSets[i]);
            presentCL[i].renderIndexed(indices.size(), 1, 0, 0, 0);
            presentCL[i].endRenderPipeline();
            presentCL[i].present();
        }  
    }

    HCommandBuffer renderCB, presentCB;
    {//リストからGPUでバッファを構築
        if (Result::eSuccess != context.createCommandBuffer(renderCL, renderCB))
            std::cout << "Failed to create command buffer\n";
        if (Result::eSuccess != context.createCommandBuffer(presentCL, presentCB))
            std::cout << "Failed to create command buffer\n";
    }

    {//メインループ
        int frame = 0;

        //10F平均でFPSを計測
        std::array<double, 10> times;
        std::chrono::high_resolution_clock::time_point now, prev = std::chrono::high_resolution_clock::now();
        //カメラの移動スピード、座標
        constexpr double speed = 0.5f;
        glm::vec3 pos(0, 0, 10.f);

        //ウィンドウ破棄の通知もしくはEscキーで終了
        while (!context.shouldClose() && !context.getKey(Key::Escape))
        {
            //入出力更新
            if (Result::eSuccess != context.updateInput())
                std::cerr << "Failed to handle event!\n";

            {//各種情報表示
                now = std::chrono::high_resolution_clock::now();
                times[frame % 10] = std::chrono::duration_cast<std::chrono::microseconds>(now - prev).count() / 1000000.;
                std::cout << "now frame : " << frame << "\n";
                std::cout << "fps : " << 1. / (std::accumulate(times.begin(), times.end(), 0.) / 10.) << "\n";
                double x, y;
                context.getMouse(x, y);
                std::cout << "mouse x: " << x << " y: " << y << "\n";
            }

            {//カメラを移動してみる
                if (context.getKey(Key::W))
                    pos.z -= speed;
                if (context.getKey(Key::S))
                    pos.z += speed;
                if (context.getKey(Key::A))
                    pos.x -= speed;
                if (context.getKey(Key::D))
                    pos.x += speed;
                if (context.getKey(Key::Up))
                    pos.y += speed;
                if (context.getKey(Key::Down))
                    pos.y -= speed;
            }

            {//UBO書き込み
                Uniform ubo;
                ubo.world = glm::rotate(glm::identity<glm::mat4>(), glm::radians(3.f * frame), glm::vec3(0, 1.f, 0));
                ubo.view = glm::lookAtRH(pos, pos + glm::vec3(0, 0, -10.f), glm::vec3(0, 1.f, 0));
                ubo.proj = glm::perspective(glm::radians(45.f), 1.f * width / height, 1.f, 100.f);
                ubo.proj[1][1] *= -1;

                uint32_t frameIndex = context.getFrameBufferIndex(renderDST);
                if (Result::eSuccess != context.writeBuffer(sizeof(Uniform), &ubo, renderUB))
                    std::cout << "Failed to write uniform buffer!\n";
                if (Result::eSuccess != context.writeBuffer(sizeof(Uniform), &ubo, presentUBs[(frameIndex + 1) % frameCount]))
                    std::cout << "Failed to write uniform buffer!\n";
            }

            //コマンド実行
            if (Result::eSuccess != context.execute(renderCB))
                std::cerr << "Failed to execute command!\n";
            if (Result::eSuccess != context.execute(presentCB))
               std::cerr << "Failed to execute command!\n";

            {//更新
                ++frame;
                prev = now;
            }
        }
    }

    //破棄処理、明示的に行っているがユーザが行わなくてもよい
    context.destroy();

    return 0;
}
sample.vert
#version 450
#extension GL_ARB_separate_shader_objects : enable

layout(binding = 0) uniform UniformBufferObject 
{
    mat4 model;
    mat4 view;
    mat4 proj;
    vec4 lightDirection;
};

layout(location = 0) in vec3 inPosition;
layout(location = 1) in vec3 inColor;
layout(location = 2) in vec3 inNormal;
layout(location = 3) in vec2 inUV;

layout(location = 0) out vec3 fragColor;
layout(location = 1) out vec3 fragNormal;
layout(location = 2) out vec2 fragUV;
layout(location = 3) out vec3 debugPos;


void main() 
{
    vec4 inPos = vec4(inPosition, 1.0);
    inPos.z -= 10;
    fragColor = inColor;
    fragUV = inUV;
    fragNormal = inNormal;
    vec4 pos = proj * view * model * inPos;
    debugPos = inPosition;
    gl_Position = pos;
}
sample.frag
#version 450
#extension GL_ARB_separate_shader_objects : enable

layout(binding = 1) uniform sampler2D texSampler;

layout(location = 0) in vec3 fragColor;
layout(location = 1) in vec3 fragNormal;
layout(location = 2) in vec2 fragTexCoord;
layout(location = 3) in vec3 debugPos;

layout(location = 0) out vec4 outColor;

void main() 
{
     vec4 color = texture(texSampler, fragTexCoord);

    vec4 lightDirection = vec4(0, 1.f, 1.f, 0);
    vec3 normal = normalize(fragNormal);
    vec3 toLightDirection = normalize(lightDirection.xyz);
    float lmb = clamp(dot(toLightDirection, normalize(fragNormal)),0,1);

    vec4 ambient = vec4(0.5, 0.5, 0.5, 1.f);

    vec3 baseColor = color.rgb;
    color.rgb = baseColor * lmb;
    color.rgb += baseColor * ambient.xyz;

    color.w = 1.f;

    outColor = color;
}

texture.png
texture.png

実行結果

image.png

今後の開発目標

全体的にまだまだ開発途上です. すみません.

Windows版のインストーラは, 上記githubリポジトリにあります. 試してやろう!という方がいらっしゃれば、ぜひお願いします. (現在VS2019のみの対応です...)

本ライブラリを弊部で活用するため, 現在コンポーネントシステムを採用した本ライブラリによるゲームエンジンを開発中です.
ある程度使える状態まで完成したらまた書きたいと思います.

各種ウィンドウ処理, リソース型, コンピュート, シェーダリフレクション等実装が至らぬ点が未だ多々あります.
「こいつまたサボってんな」程度の目でコミットログを見守っていただけると幸いです.

4
6
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
6