/liblava

A modern and easy-to-use library for the Vulkan API

Primary LanguageC++MIT LicenseMIT

liblava is a modern and easy-to-use library for the Vulkan® API

lava is a lean framework that provides essentials for low-level graphics and is specially well suited for prototyping, tooling and educationdemotemplateengine

C++20ModularWindowsLinux

Version Build status Build Status License Donate Twitter URL

Features

  • written in modern C++ with latest Vulkan support
  • run loop abstraction for window and input handling
  • plain renderer and command buffer model
  • texture and mesh loading from virtual file system
  • camera, gui, logging, test driver and much more...
Download latest demo (Feb. 20, 2020)

Modules

core util base resource frame block app engine

hello frame

Let's write Hello World in Vulkan...

"a simple app that renders a colored window"

All we need is a window, device and renderer.

Vulkan is a low-level, verbose graphics API and such a program can take several hundred lines of code.

The good news is that liblava will help you...

#include <liblava/lava.hpp>

using namespace lava;

Here are a few examples to get to know lava

1. frame init

int main(int argc, char* argv[]) {

    frame frame( {argc, argv} );

    return frame.ready() ? 0 : error::not_ready;
}

This is how to initialize lava::frame with command line arguments.

2. run loop

frame frame(argh);
if (!frame.ready())
    return error::not_ready;

auto count = 0;

frame.add_run([&]() {

    sleep(seconds(1));
    count++;

    log()->debug("{} - running {} sec", count, to_sec(frame.get_running_time()));

    if (count == 3)
        frame.shut_down();

    return true;
});

return frame.run();

The last line performs a loop with the run we added before. If count reaches 3 that loop will exit.

3. window input

Here is another example that shows how to create a lava::window and handle lava::input

frame frame(argh);
if (!frame.ready())
    return error::not_ready;

window window;
if (!window.create())
    return error::create_failed;

input input;
window.assign(&input);

input.key.listeners.add([&](key_event::ref event) {

    if (event.pressed(key::escape))
        frame.shut_down();
});

frame.add_run([&]() {

    input.handle_events();

    if (window.close_request())
        frame.shut_down();

    return true;
});

return frame.run();

Straightforward - with this knowledge in hand let's write "hello frame" now...

4. clear color

frame frame(argh);
if (!frame.ready())
    return error::not_ready;

window window;
if (!window.create())
    return error::create_failed;

input input;
window.assign(&input);

input.key.listeners.add([&](key_event::ref event) {

    if (event.pressed(key::escape))
        frame.shut_down();
});

auto device = frame.create_device();
if (!device)
    return error::create_failed;

auto render_target = create_target(&window, device);
if (!render_target)
    return error::create_failed;

renderer plotter;
if (!plotter.create(render_target->get_swapchain()))
    return error::create_failed;

auto frame_count = render_target->get_frame_count();

VkCommandPool cmd_pool;
VkCommandBuffers cmd_bufs(frame_count);

auto build_cmd_bufs = [&]() {

    if (!device->vkCreateCommandPool(device->graphics_queue().family, &cmd_pool))
        return false;

    if (!device->vkAllocateCommandBuffers(cmd_pool, frame_count, cmd_bufs.data()))
        return false;

    VkCommandBufferBeginInfo const begin_info
    {
        .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO,
        .flags = VK_COMMAND_BUFFER_USAGE_SIMULTANEOUS_USE_BIT,
    };

    VkClearColorValue const clear_color = { random(0.f, 1.f), random(0.f, 1.f), random(0.f, 1.f), 0.f };

    VkImageSubresourceRange const image_range
    {
        .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
        .levelCount = 1,
        .layerCount = 1,
    };

    for (auto i = 0u; i < frame_count; i++) {

        auto cmd_buf = cmd_bufs[i];
        auto frame_image = render_target->get_image(i);

        if (failed(device->call().vkBeginCommandBuffer(cmd_buf, &begin_info)))
            return false;

        insert_image_memory_barrier(device, cmd_buf, frame_image,
                                    VK_ACCESS_MEMORY_READ_BIT, VK_ACCESS_TRANSFER_WRITE_BIT,
                                    VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
                                    VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT,
                                    image_range);

        device->call().vkCmdClearColorImage(cmd_buf, frame_image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
                                            &clear_color, 1, &image_range);

        insert_image_memory_barrier(device, cmd_buf, frame_image,
                                    VK_ACCESS_TRANSFER_WRITE_BIT, VK_ACCESS_MEMORY_READ_BIT,
                                    VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_PRESENT_SRC_KHR,
                                    VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT,
                                    image_range);

        if (failed(device->call().vkEndCommandBuffer(cmd_buf)))
            return false;
    }

    return true;
};

auto clean_cmd_bufs = [&]() {

    device->vkFreeCommandBuffers(cmd_pool, frame_count, cmd_bufs.data());
    device->vkDestroyCommandPool(cmd_pool);
};

if (!build_cmd_bufs())
    return error::create_failed;

render_target->on_swapchain_start = build_cmd_bufs;
render_target->on_swapchain_stop = clean_cmd_bufs;

frame.add_run([&]() {

    input.handle_events();

    if (window.close_request())
        return frame.shut_down();

    if (window.resize_request())
        return window.handle_resize();

    auto frame_index = plotter.begin_frame();
    if (!frame_index)
        return true;

    return plotter.end_frame({ cmd_bufs[*frame_index] });
});

frame.add_run_end([&]() {

    clean_cmd_bufs();

    plotter.destroy();
    render_target->destroy();
});

return frame.run();
Welcome on Planet Vulkan. That's a lot to display a colored window.

Phew! Take a closer look at the build_cmd_bufs function:

  • We create a command pool and command buffers for each frame of the render target
  • And set each command buffer to clear the frame image with some random color

Watch out the VK_COMMAND_BUFFER_USAGE_SIMULTANEOUS_USE_BIT flag that specifies the reusage of command buffers.

clean_cmd_bufs frees all buffers and destroys the command pool. In case of swap chain restoration we simply recreate command buffers with a new random color. This happens for example on window resize.

After all, this is a very static example. Vulkan supports a more dynamic and common usage by resetting a command pool before recording new commands.

Ok, it's time for... lava::block

5. color block

block block;

if (!block.create(device, frame_count, device->graphics_queue().family))
    return error::create_failed;

block.add_command([&](VkCommandBuffer cmd_buf) {

    VkClearColorValue const clear_color = { random(0.f, 1.f), random(0.f, 1.f), random(0.f, 1.f), 0.f };

    VkImageSubresourceRange const image_range
    {
        .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
        .levelCount = 1,
        .layerCount = 1,
    };

    auto frame_image = render_target->get_image(block.get_current_frame());

    insert_image_memory_barrier(device, cmd_buf, frame_image,
                                VK_ACCESS_MEMORY_READ_BIT, VK_ACCESS_TRANSFER_WRITE_BIT,
                                VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
                                VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT,
                                image_range);

    device->call().vkCmdClearColorImage(cmd_buf, frame_image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
                                        &clear_color, 1, &image_range);

    insert_image_memory_barrier(device, cmd_buf, frame_image,
                                VK_ACCESS_TRANSFER_WRITE_BIT, VK_ACCESS_MEMORY_READ_BIT,
                                VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_PRESENT_SRC_KHR,
                                VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT,
                                image_range);
});
Nice, this is much more simpler than before.

We create a lava::block and add one command that clears the current frame image.

All we need to do is to process the block in the run loop...

if (!block.process(*frame_index))
    return false;

return plotter.end_frame(block.get_buffers());

... and call the renderer with our recorded command buffers.

Don't forget to clean it up:

block.destroy();
New to Vulkan? Take a look at this Vulkan Guide

Check Awesome Vulkan ecosystem for tutorials, samples and books.

8. imgui demo

Out of blocks, lava::app supports the awesome Dear ImGui for tooling and easy prototyping.

int main(int argc, char* argv[]) {

    app app("demo", { argc, argv });
    if (!app.setup())
        return error::not_ready;

    app.gui.on_draw = []() {

        ImGui::ShowDemoWindow();
    };

    return app.run();
}
What's next? - Check demonstration projects and clone starter template

Tests

Run the lava executable to test above examples. Let it simply flow...

List all tests:
$ lava -t
  1. frame init
  2. run loop
  3. window input
  4. clear color
  5. color block
  6. forward shading
  7. gamepad
  8. imgui demo
Run test 2 for example:
$ lava 2

The driver starts the last test without command line arguments.

Requirements

  • C++20 compatible compiler
  • CMake 3.15+
  • Vulkan SDK

Build

$ git clone https://github.com/liblava/liblava.git
$ cd liblava

$ git submodule update --init --recursive

$ mkdir build
$ cd build

$ cmake ..
$ make

Third-Party

License

liblava is licensed under MIT License which allows you to use the software for any purpose you might like, including commercial and for-profit use.

However, this library includes several third-party Open Source libraries, which are licensed under their own respective licenses. They all allow static linking with closed source software.

All copies of liblava must include a copy of the MIT License terms and the copyright notice.

Vulkan and the Vulkan logo are trademarks of the Khronos Group Inc.
Copyright (c) 2018-present, Lava Block OÜ