Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 29 additions & 18 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,34 +1,45 @@
# Vulkan-CPP

Modern abstraction layer for Vulkan using C++23 to simplify and modernize development for graphical applications.
Custom Vulkan abstraction layer with native C++20 modules support using LLVM.

![NOTE]
> vulkan-cpp assumes you have some knowledge of computer graphics and API's such as with OpenGL or Direct3D
> [!TIP]
> vulkan-cpp assumes you have some knowledge of graphics APIs such as OpenGL or Direct3D

# Why another abstraction around Vulkan?
## Getting Started

I chose to make this vulkan abstraction for developing graphics applications using Vulkan much simpler. This involved providing ways for specifying operations that had quite a bit of boilerplate being done in raw Vulkan.
Before buidling the demos, make sure to check the [getting started](https://engine3d-dev.github.io/0.1/getting_started) page beforehand.

Examples of these are renderpass attachments and setting up descriptor set handles. These take up extroadinary amount of code to implement. Though there are not always a one size fit all situation.
## Building Demos

These demos are isolated and attempt at following the Vulkan tutorial, specifically to using the vulkan-cpp APIs. Building the demo is quite easy, assuming you already setup the development environment from the getting started page linked above.

## How to Build
Here is how to build any of the demos:

For building our projects we use Conan, the C++ package manager to manage our dependencies and build vulkan-cpp demos.
> [!NOTE]
> `-s build_type=Debug` to build in debug mode

Required to start at the [getting started](https://engine3d-dev.github.io/0.1/getting_started) page for setting up the development environment.
```bash
conan atlas build demos/<demo-dir> -s build_type=Debug
```

## Example Demos
Example for building demo 6:

The demos are meant to reflect closely enough to the Vulkan tutorial documentation site. Where they show you how to learn how to use Vulkan.
```bash
conan atlas build demos/6-graphics-pipeline -s build_type=Debug
```

These demos are supposed to enable you in learning more about how vulkan-cpp works, and how you can effectively build a renderer using the current API's of vulkan-cpp.
## Running the Demos

## Shader Samples
The build directory will be located in the demo that you built.

These are shader samples used by the specific demos that you see referenced below. Which demo utilizes those specific shader samples.
Executable path will be as the following:

* sample 1 -- used by demo 6 and 7
* sample 2 -- used by demo 8
* sample 3 -- used by demo 9 (descriptors + camera uniforms)
* sample 4 -- used by demo 10 (textures)
```bash
./demo/<demo-dir>/build/Debug/<executable>
```

For demo 6 this is where the executable is located:

```bash
./demo/6-graphics-pipeline/build/Debug/graphics-pipeline
```
2 changes: 1 addition & 1 deletion demos/1-instance/conanfile.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ def requirements(self):
self.requires("glm/1.0.1")
self.requires("stb/cci.20230920")
self.requires("tinyobjloader/2.0.0-rc10")
self.requires("vulkan-cpp/5.0")
self.requires("vulkan-cpp/6.0")

def build(self):
cmake = CMake(self)
Expand Down
156 changes: 99 additions & 57 deletions demos/10-textures/application.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,7 @@ main() {
// We provide a selection of format support that we want to check is
// supported on current hardware device.
VkFormat depth_format =
vk::select_depth_format(physical_device, format_support);
physical_device.request_depth_format(format_support);

vk::queue_indices queue_indices = physical_device.family_indices();
std::println("Graphics Queue Family Index = {}", queue_indices.graphics);
Expand Down Expand Up @@ -170,7 +170,7 @@ main() {
std::println("Starting implementation of the swapchain!!!");

vk::surface_params surface_properties =
vk::enumerate_surface(physical_device, window_surface);
physical_device.request_surface(window_surface);

if (surface_properties.format.format != VK_FORMAT_UNDEFINED) {
std::println("Surface Format.format is not undefined!!!");
Expand Down Expand Up @@ -206,11 +206,12 @@ main() {
.extent = { .width = swapchain_extent.width,
.height = swapchain_extent.height },
.format = surface_properties.format.format,
.memory_mask = physical_device.memory_properties(
vk::memory_property::device_local_bit),
.aspect = vk::image_aspect_flags::color_bit,
.usage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT,
.mip_levels = 1,
.layer_count = 1,
.phsyical_memory_properties = physical_device.memory_properties(),
};

swapchain_images[i] =
Expand All @@ -221,11 +222,12 @@ main() {
.extent = { .width = swapchain_extent.width,
.height = swapchain_extent.height },
.format = depth_format,
.memory_mask = physical_device.memory_properties(
vk::memory_property::device_local_bit),
.aspect = vk::image_aspect_flags::depth_bit,
.usage = VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT,
.mip_levels = 1,
.layer_count = 1,
.phsyical_memory_properties = physical_device.memory_properties(),
};
swapchain_depth_images[i] =
vk::sample_image(logical_device, image_config);
Expand Down Expand Up @@ -446,69 +448,100 @@ main() {
.normals = { 0.f, 0.f, 0.f },
.uv = { 1.0f, 1.0f } }
};
vk::vertex_params vertex_info = {
.phsyical_memory_properties = physical_device.memory_properties(),
.vertices = vertices,
//! @brief Creating vertex/index buffers with host visibility flags
const auto property_flags =
static_cast<vk::memory_property>(vk::memory_property::host_visible_bit |
vk::memory_property::host_cached_bit);

vk::buffer_parameters vertex_params = {
.memory_mask = physical_device.memory_properties(property_flags),
.property_flags = vk::memory_property::device_local_bit,
.usage = static_cast<uint32_t>(vk::buffer_usage::transfer_dst_bit) |
static_cast<uint32_t>(vk::buffer_usage::vertex_buffer_bit),
};
vk::vertex_buffer test_vbo(logical_device, vertex_info);
std::println("vertex_buffer.alive() = {}", test_vbo.alive());
vk::vertex_buffer test_vbo(logical_device, vertices, vertex_params);

std::array<uint32_t, 6> indices = { 0, 1, 2, 2, 3, 0 };

vk::index_params index_info = {
.phsyical_memory_properties = physical_device.memory_properties(),
.indices = indices,
vk::buffer_parameters index_params = {
.memory_mask = physical_device.memory_properties(property_flags),
.property_flags = static_cast<vk::memory_property>(
vk::memory_property::host_visible_bit |
vk::memory_property::host_cached_bit),
.usage = static_cast<uint32_t>(vk::buffer_usage::index_buffer_bit),
};
vk::index_buffer test_ibo(logical_device, index_info);
std::println("index_buffer.alive() = {}", test_ibo.alive());
vk::index_buffer test_ibo(logical_device, indices, index_params);

// Setting up descriptor sets for handling uniforms
vk::uniform_params test_ubo_info = { .phsyical_memory_properties =
physical_device.memory_properties(),
.size_bytes = sizeof(global_uniform) };
vk::uniform_buffer test_ubo =
vk::uniform_buffer(logical_device, test_ubo_info);

std::array<vk::write_buffer, 1> uniforms0 = { vk::write_buffer{
.buffer = test_ubo, .offset = 0, .range = test_ubo.size_bytes() } };
vk::buffer_parameters uniform_params = {
.memory_mask =
physical_device.memory_properties(static_cast<vk::memory_property>(
vk::memory_property::host_visible_bit |
vk::memory_property::host_cached_bit)),
.usage = static_cast<uint32_t>(vk::buffer_usage::uniform_buffer_bit),
};
vk::uniform_buffer test_ubo = vk::uniform_buffer(
logical_device, sizeof(global_uniform), uniform_params);

std::array<vk::write_buffer, 1> uniforms0 = {
vk::write_buffer{
.buffer = test_ubo,
.offset = 0,
.range = static_cast<uint32_t>(test_ubo.size_bytes()),
},
};

std::array<vk::write_buffer_descriptor, 1> uniforms = {
vk::write_buffer_descriptor{ .dst_binding = 0, .uniforms = uniforms0 }
};

// Loading a texture -- for testing
vk::texture_info config_texture = {
.phsyical_memory_properties = physical_device.memory_properties(),
.filepath =
std::filesystem::path("asset_samples/container_diffuse.png"),
vk::texture_params config_texture = {
.memory_mask =
physical_device.memory_properties(static_cast<vk::memory_property>(
vk::memory_property::host_visible_bit |
vk::memory_property::host_cached_bit)),
};
vk::texture texture1(logical_device, config_texture);
vk::texture texture1(
logical_device,
std::filesystem::path("asset_samples/container_diffuse.png"),
config_texture);

std::println("texture1.valid = {}", texture1.loaded());

// Moving update call here because now we add textures to set0
vk::uniform_params material_ubfo_info = {
.phsyical_memory_properties = physical_device.memory_properties(),
.size_bytes = sizeof(material_uniform)
vk::buffer_parameters material_ubfo_info = {
.memory_mask =
physical_device.memory_properties(static_cast<vk::memory_property>(
vk::memory_property::host_visible_bit |
vk::memory_property::host_cached_bit)),
.usage = static_cast<uint32_t>(vk::buffer_usage::uniform_buffer_bit),
};
vk::uniform_buffer material_ubo = vk::uniform_buffer(
logical_device, sizeof(material_uniform), material_ubfo_info);

std::array<vk::write_buffer, 1> set1_uniforms0 = {
vk::write_buffer{
.buffer = material_ubo,
.offset = 0,
.range = static_cast<uint32_t>(material_ubo.size_bytes()),
},
};
vk::uniform_buffer material_ubo =
vk::uniform_buffer(logical_device, material_ubfo_info);

std::array<vk::write_buffer, 1> set1_uniforms0 = { vk::write_buffer{
.buffer = material_ubo,
.offset = 0,
.range = material_ubo.size_bytes() } };

std::array<vk::write_buffer_descriptor, 1> uniforms_set1 = {
vk::write_buffer_descriptor{ .dst_binding = 0,
.uniforms = set1_uniforms0 }
vk::write_buffer_descriptor{
.dst_binding = 0,
.uniforms = set1_uniforms0,
},
};

std::array<vk::write_image, 1> set1_samplers = { vk::write_image{
.sampler = texture1.image().sampler(),
.view = texture1.image().image_view(),
.layout = vk::image_layout::shader_read_only_optimal,
} };
std::array<vk::write_image, 1> set1_samplers = {
vk::write_image{
.sampler = texture1.image().sampler(),
.view = texture1.image().image_view(),
.layout = vk::image_layout::shader_read_only_optimal,
},
};

std::array<vk::write_image_descriptor, 1> sample_images = {
vk::write_image_descriptor{ .dst_binding = 1,
Expand All @@ -527,21 +560,27 @@ main() {

// renderpass begin/end must be within a recording command buffer
vk::renderpass_begin_params begin_renderpass = {
.current_command = current,
.extent = swapchain_extent,
.current_framebuffer = swapchain_framebuffers[current_frame],
.color = color,
.subpass = vk::subpass_contents::inline_bit
};
main_renderpass.begin(begin_renderpass);
main_renderpass.begin(current, begin_renderpass);

// Binding a graphics pipeline -- before drawing stuff
// Inside of this graphics pipeline bind, is where you want to do the
// drawing stuff to
main_graphics_pipeline.bind(current);

test_vbo.bind(current);
test_ibo.bind(current);
// test_vbo.bind(current);
// test_ibo.bind(current);
const VkBuffer vertex = test_vbo;
uint64_t offset = 0;
current.bind_vertex_buffers(std::span<const VkBuffer>(&vertex, 1),
std::span<uint64_t>(&offset, 1));
if (!indices.empty()) {
current.bind_index_buffers32(test_ibo);
}

static auto start_time = std::chrono::high_resolution_clock::now();

Expand All @@ -565,16 +604,19 @@ main() {
10.0f)
};
ubo.proj[1][1] *= -1;
test_ubo.update(&ubo);

// Before we can send stuff to the GPU, since we already updated the
// descriptor set 0 beforehand, we must bind that descriptor resource
// before making any of the draw calls Something to note: You cannot
// update descriptor sets in the process of a current-recording command
// buffers or else that becomes undefined behavior
set0_resource.bind(current, main_graphics_pipeline.layout());

test_ubo.transfer<global_uniform>(
std::span<const global_uniform>(&ubo, 1));

std::array<const VkDescriptorSet, 1> descriptors = {
set0_resource,
};

current.bind_descriptors(main_graphics_pipeline.layout(),
VK_PIPELINE_BIND_POINT_GRAPHICS,
descriptors);

// Drawing-call to render actual triangle to the screen
// vkCmdDraw(current, 3, 1, 0, 0);
vkCmdDrawIndexed(
current, static_cast<uint32_t>(indices.size()), 1, 0, 0, 0);

Expand Down
2 changes: 1 addition & 1 deletion demos/10-textures/conanfile.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ def requirements(self):
self.requires("glm/1.0.1")
self.requires("stb/cci.20230920")
self.requires("tinyobjloader/2.0.0-rc10")
self.requires("vulkan-cpp/5.0")
self.requires("vulkan-cpp/6.0")

def build(self):
cmake = CMake(self)
Expand Down
Loading
Loading