Skip to content

liblava/liblava.github.io

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

1 Commit
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation



  Documentation

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


Guide   •   Test   •   Keyboard Shortcuts   •   Command-Line Arguments   •   Benchmark   •   Build   •   Third-Party


Need help?   Please feel free to ask us on ➜ Discord

New to Vulkan?   Take a look at this ➜ Vulkan Guide
Check Awesome Vulkan ecosystem for tutorials, samples and books.

Latest Vulkan documentation: Specification and Proposals



Tutorial

Hello World in Vulkan?   Let's go!

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 can help you...


#include "liblava/lava.hpp"

using namespace lava;

Here are a few examples to get to know lava


1. frame

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

    lava::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

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

ui32 count = 0;

frame.add_run([&](id::ref run_id) {
    sleep(one_second);
    count++;

    logger()->debug("{} - running {} sec", 
                    count, frame.get_running_time_sec());

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

    return run_continue;
});

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 lava window and handle lava input.

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

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

lava::input input;
window.assign(&input);

input.key.listeners.add([&](key_event::ref event) {
    if (event.pressed(key::escape))
        return frame.shut_down();
    
    return input_ignore;
});

frame.add_run([&](id::ref run_id) {
    input.handle_events();

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

    return run_continue;
});

return frame.run();

Straightforward ➜ With this knowledge in hand let's write our Hello World...


4. clear color

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

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

lava::input input;
window.assign(&input);

input.key.listeners.add([&](key_event::ref event) {
    if (event.pressed(key::escape))
        return frame.shut_down();

    return input_ignore;
});

lava::device::ptr device = frame.platform.create_device();
if (!device)
    return error::create_failed;

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

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

ui32 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 build_failed;

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

    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(1.f), random(1.f), random(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) {
        VkCommandBuffer cmd_buf = cmd_bufs[i];
        VkImage frame_image = render_target->get_image(i);

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

        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 build_failed;
    }

    return build_done;
};

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([&](id::ref run_id) {
    input.handle_events();

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

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

    optional_index current_frame = renderer.begin_frame();
    if (!current_frame.has_value())
        return run_continue;

    return renderer.end_frame({ cmd_bufs[*current_frame] });
});

frame.add_run_end([&]() {
    clean_cmd_bufs();

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

return frame.run();

Welcome on Planet Vulkan - That's a lot to display a colored window

Take a closer look at the build_cmd_bufs function:

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

clean_cmd_bufs frees and destroys all buffers in the command pool.

In case of swap chain restoration we simply recreate command buffers with a new random color - This happens for example when the window gets resized.

The flag VK_COMMAND_BUFFER_USAGE_SIMULTANEOUS_USE_BIT specifies the usage of command buffers in such a way that they can no longer be changed - Therefore it 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

lava::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(1.f), random(1.f), random(1.f), 0.f
    };

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

    VkImage 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);
});

This is much more simpler than before!

➜ We create a block with a command that clears the current frame image.

All we need to do now is to process that block in the run loop:

if (!block.process(*current_frame))
    return run_abort;

return renderer.end_frame(block.collect_buffers());

And call the renderer with our recorded command buffers.

Don't forget to clean it up when the run ends:

block.destroy();

6. imgui demo

lava app supports Dear ImGui for tooling and easy prototyping.

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

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

    app.imgui.layers.add("demo window", []() {

        ImGui::ShowDemoWindow();
    });

    return app.run();
}


What's next? ➜ Check some demo or use the template and start coding...



Guide

1. Lifetime of an Object

Before you create new objects or use existing ones, you should get familiar with the lifetime of objects.

It is basically possible to create all objects in liblava on the stack or on the heap.

But be careful. You have to take care of the lifetime yourself.


make   ➜   create   ➜   destroy

This is the general pattern that is used in this library:

  1. make   Use constructor or factory method (static function to get a shared pointer)
  2. create   Build the respective object
  3. destroy   Discard it after your use

The destructor calls the destroy method if it was not called before.


Example: buffer object

void use_buffer_on_stack() {

    buffer buf; // make

    auto created = buf.create(device, data, size, usage);
    if (created) {
        // ...

        buf.destroy();
    }
}

Or look at this method where it is returned as a shared pointer:

buffer::s_ptr use_buffer_on_heap() {

    auto buf = buffer::make();

    if (buf->create(device, data, size, usage))
        return buf;

    return nullptr;
}

2. Making Meshes

liblava provides a mesh struct that contains a list of vertices and optionally a list of indices.

It is made this way:

mesh::s_ptr my_mesh = mesh::make();

my_mesh->add_data( /* Pass in a lava::mesh_data object */ );
my_mesh->create(device);

liblava prepares a create_mesh() function to simplify the creation of primitives.

It takes a mesh_type argument to specify what kind of primitive to build:

cube   triangle   quad   hexagon   none


The function is called in this way:

mesh::s_ptr cube;
cube = create_mesh(device, mesh_type::cube);

By default, vertices in a mesh are of type vertex which has the following layout:

struct vertex {
    v3 position;
    v4 color;
    v2 uv;
    v3 normal;
}

Meshes are templated and can represent any vertex struct definition, like here:

struct int_vertex {
    std::array<i32, 3> position;
    v4 color;
};
mesh_template<int_vertex>::s_ptr int_triangle;

create_mesh() can generate primitives for arbitrary vertex structs too. Provided that the struct contains an array or vector member named position:

int_triangle = create_mesh<int_vertex>(device, mesh_type::triangle);

create_mesh() may also initialize Color, Normal, and UV data automatically.

However, it will only initialize these if there are corresponding color, normal, and/or uv fields defined in the vertex struct.

By default, it will initialize everything automatically. But if generating any of this data is not desired, the fields can be individually disabled by template arguments in this order:

  1. Color
  2. Normal
  3. UV
struct custom_vertex {
    v3 position;
    v3 color;
    v3 normal;
    v2 uv;
};
mesh_template<custom_vertex>::s_ptr triangle;

// Generate three vertices with positions and uvs, but not colors or normals
triangle = create_mesh<custom_vertex, false, false, true>
                      (device, mesh_type::triangle);

Cubes generated this way have a special case. If they are initialized with normal data, they will be represented by 24 vertices. Otherwise, only 8 vertices will be initialized.



Test

Run the lava executable to test our Tutorial examples ➜ so called stages.


List all stages

lava -ls

lava --stages
  1. frame
  2. run loop
  3. window input
  4. clear color
  5. color block
  6. imgui demo
  7. forward shading
  8. gamepad

The last stages in this list are further examples.


Run imgui demo

lava -st=6

lava --stage=6

If you run lava without arguments - the stage driver is started.


Unit testing

In addition run lava-test to check some unit tests with Catch2


Template

Put your code in the src/ folder and begin to code in main.cpp


You can change the project name in CMakeLIBLAVA_TEMPLATE_NAME

cmake -DLIBLAVA_TEMPLATE_NAME="My-Project" ..

Keyboard Shortcuts

lava defines some shortcuts for common actions:


shortcut action default config.json
alt + enter fullscreen off window/fullscreen
alt + backspace v-sync off app/v-sync
control + tab imgui on app/imgui
control + space pause off app/paused
control + ^ hud menu off
control + b benchmark
control + p screenshot
control + q quit

You can disable these actions by simply turning them off:

app.config.handle_key_events = false;

Command-Line Arguments

app

--clean, -c
  • clean preferences folder

--clean_cache, -cc
  • clean cache folder

--v_sync={0|1}, -vs={0|1}
  • 0   vertical sync off
  • 1   vertical sync on

--triple_buffering={0|1}, -tb={0|1}
  • 0   triple buffering off
  • 1   triple buffering on

--fps_cap={n}, -fps={n}
  • n   frames per second cap   disable: n = 0

--physical_device={n}, -pd={n}
  • n   physical device index   default: n = 0

--identification={str}, -id={str}
  • str   config save name   for example: "test profile"

--resource={str}, -res={str}
  • str   resource file or path (relative to app directory)   for example: mod.zip

--paused={0|1}, -p={0|1}
  • 0   running
  • 1   paused

--delta={n}, -dt={n}
  • n   fixed delta in milliseconds   disable: n = 0

--speed={n}, -s={n}
  • n   runtime speed   default: n = 1.0

--imgui={0|1}, -ig={0|1}
  • 0   hide imgui
  • 1   show imgui

--fullscreen={0|1}, -wf={0|1}
  • 0   windowed mode
  • 1   fullscreen mode

--x_pos={n}, -wx={n}
  • n   window x position

--y_pos={n}, -wy={n}
  • n   window y position

--width={n}, -ww={n}
  • n   window width

--height={n}, -wh={n}
  • n   window height

--center, -wc
  • center window on the monitor

--title, -wt
  • show window save title

frame

You need the Vulkan SDK installed for debugging.


--debug, -d

--utils, -u

--renderdoc, -r
  • enable RenderDoc capture layer   VK_LAYER_RENDERDOC_Capture

--log={0|1|2|3|4|5|6}, -l={0|1|2|3|4|5|6}
  • level 0   trace   verbose logging
  • level 1   debug
  • level 2   info
  • level 3   warn
  • level 4   error
  • level 5   critical
  • level 6   off   logging disabled


Benchmark

lava app writes frame times (durations in milliseconds) into a json file to analyze them further for automated workflows like benchmarks:

{
  "benchmark": {
    "avg": 16.02839111337229,
    "count": 622,
    "max": 45,
    "min": 12,
    "offset": 5000,
    "time": 10000
  },
  "frames": [
    12,
    14,
    16,
    16
  ],
  "timestamps": [
    5,
    17,
    31,
    47,
    63
  ]
}

--benchmark, -bm
  • activate benchmark mode

--benchmark_time={n}, -bmt={n}
  • n   benchmark duration in milliseconds   default: n = 10000 ms

--benchmark_offset={n}, -bmo={n}
  • n   warm up time in milliseconds   default: n = 5000 ms

--benchmark_file={str}, -bmf={str}
  • str   output file   default: str = benchmark.json

--benchmark_path={str}, -bmp={str}
  • str   output path   default: preferences folder

--benchmark_exit={0|1}, -bmx={0|1}
  • 0   keep running after benchmark
  • 1   close app after benchmark   default

--benchmark_buffer={n}, -bmb={n}
  • n   pre-allocated buffer size for results   default: n = 100000


Build

CMake (Linux, Windows)

Requirements


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

mkdir build
cd build

cmake ..
cmake --build . --parallel

Problems building or running on Ubuntu? - Try this:

sudo apt-get install libxi-dev libatomic-ops-dev libatomic1

Install

You can use liblava as a git submodule in your project:

git submodule add https://github.com/liblava/liblava.git

Add this to your CMakeLists.txt

add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/liblava ${CMAKE_CURRENT_BINARY_DIR}/liblava)

...

target_link_libraries(${PROJECT_NAME} PRIVATE lava::engine ${LIBLAVA_ENGINE_LIBRARIES})

Package setup

Alternatively ➜ compile and install a specific version for multiple projects:

mkdir build
cd build

cmake -D CMAKE_BUILD_TYPE=Release -D CMAKE_INSTALL_PREFIX=../lava-install ..
cmake --build . --config Release --target install --parallel

First find the package in your CMakeLists.txt

find_package(lava 0.8.1 REQUIRED)

...

target_link_libraries(${PROJECT_NAME} PRIVATE lava::engine ${LIBLAVA_ENGINE_LIBRARIES})

And then build your project with install path ➜ lava_DIR

mkdir build
cd build

cmake -D lava_DIR=path/to/lava-install/lib/cmake/lava ..
cmake --build . --parallel

Installing and using Vcpkg

Vcpkg integration with 2 options ➜ use this registry and port


Conan Package Manager

If you are familiar with Conan ➜ build this package recipe



Third-Party

  • argh   Argh! A minimalist argument handler   3-clause BSD
  • Catch2   A modern, C++-native, header-only, test framework for unit-tests, TDD and BDD   BSL 1.0
  • CPM.cmake   A small CMake script for setup-free, cross-platform, reproducible dependency management   MIT
  • glfw   A multi-platform library for OpenGL, OpenGL ES, Vulkan, window and input   zlib
  • gli   OpenGL Image (GLI)   MIT
  • glm   OpenGL Mathematics (GLM)   MIT
  • glslang   Khronos-reference front end for GLSL/ESSL, partial front end for HLSL, and a SPIR-V generator   3-clause BSD
  • IconFontCppHeaders   C, C++ headers and C# classes for icon fonts   zlib
  • imgui   Dear ImGui - Bloat-free Graphical User interface for C++ with minimal dependencies   MIT
  • json   JSON for Modern C++   MIT
  • physfs   A portable, flexible file i/o abstraction   zlib
  • PicoSHA2   A header-file-only SHA256 hash generator in C++   MIT
  • shaderc   A collection of tools, libraries, and tests for Vulkan shader compilation   Apache 2.0
  • spdlog   Fast C++ logging library   MIT
  • SPIRV-Headers   SPIRV Headers   MIT
  • SPIRV-Tools   SPIRV Tools   Apache 2.0
  • stb   Single-file public domain libraries for C/C++   MIT
  • tinyobjloader   Tiny but powerful single file wavefront obj loader   MIT
  • volk   Meta loader for Vulkan API   MIT
  • Vulkan-Headers   Vulkan Header files and API registry   Apache 2.0
  • VulkanMemoryAllocator   Easy to integrate Vulkan memory allocation library   MIT

Demo

You can find the demonstration projects in the liblava-demo/ folder.


  • Roboto   ➜   Roboto-Regular.ttf   Website   •   GitHub   Apache License, Version 2.0
  • Font Awesome   ➜   fa-solid-900.ttf   Website   •   GitHub   Font Awesome Free License
  • Barbarella   ➜   lamp.frag   Website   Shader by Weyland Yutani
  • Spawn Model   ➜   lava-spawn-game.obj + lava-spawn-game.mtl   Website   CC BY-SA 3.0
  • Mationi - Colored Border   ➜   demo.frag   Website   Shader by juanpetrik

Documentation

  • Doxygen   Generate documentation from source code   GPL-2.0
  • Doxybook2   Doxygen XML to Markdown (or JSON)   MIT
  • docsify   A magical documentation site generator   MIT

Creative Commons License
liblava Documentation is licensed under a Creative Commons Attribution 4.0 International License.