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 education • demo • template • engine
C++20 • Modular • Windows • Linux
- 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)
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
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.
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.
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...
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();
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
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);
});
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.
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
Run the lava executable to test above examples. Let it simply flow...
$ lava -t
- frame init
- run loop
- window input
- clear color
- color block
- forward shading
- gamepad
- imgui demo
$ lava 2
The driver starts the last test without command line arguments.
- C++20 compatible compiler
- CMake 3.15+
- Vulkan SDK
$ git clone https://github.com/liblava/liblava.git
$ cd liblava
$ git submodule update --init --recursive
$ mkdir build
$ cd build
$ cmake ..
$ make
- argh • 3-clause BSD
- bitmap • MIT
- glfw • zlib
- gli • MIT
- glm • MIT
- imgui • MIT
- json • MIT
- physfs • zlib
- selene • MIT
- spdlog • MIT
- stb • MIT
- tinyobjloader • MIT
- volk • MIT
- Vulkan-Headers • Apache 2.0
- VulkanMemoryAllocator • MIT
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.