Compare commits

...

18 Commits

Author SHA1 Message Date
7213c85fdf Depth test. 2022-03-11 23:45:27 +01:00
9d4b85c940 Camera + fix swapchain crash. 2022-03-09 00:13:51 +01:00
3ce7962d4c Use indirect draw call. 2022-03-07 18:15:35 +01:00
5b363a8c57 VulkanTutorial refactoring. 2022-03-06 22:22:26 +01:00
b8a15406da Use Wrapper for most wrappers. 2022-03-05 12:03:48 +01:00
46b2698abb Improve wrapper. 2022-03-05 11:33:00 +01:00
8358558ddb DescriptorSet related wrappers. 2022-03-05 00:31:08 +01:00
f15ade11b7 ImageView wrapper. 2022-03-04 23:15:33 +01:00
b18f3719ed CommandPool & CommandBuffer wrappers. 2022-03-04 23:03:42 +01:00
b96155f1b8 Pipeline wrapper. 2022-03-04 19:43:39 +01:00
ad5e382d3a ShaderModule wrapper. 2022-03-03 23:30:18 +01:00
db9fdf5a7f Framebuffer wrapper. 2022-03-03 23:18:34 +01:00
b4cb6ebbbc is_valid -> is_null. 2022-03-03 22:26:20 +01:00
c73d773845 Semaphore wrapper. 2022-03-03 22:20:37 +01:00
63a87edc29 Wrapper around fences and render pass. 2022-03-01 23:53:32 +01:00
d06458454d Copyright notice + very basic Readme. 2022-02-27 23:51:40 +01:00
f03a0bd98b Some refactoring. 2022-02-27 23:45:20 +01:00
14caf36c53 Renamed Vulkan namespace as vk. 2022-02-27 20:34:09 +01:00
65 changed files with 3633 additions and 1291 deletions

View File

@@ -1,3 +1,5 @@
# Copyright 2022 Simon Boyé
cmake_minimum_required(VERSION 3.0.0) cmake_minimum_required(VERSION 3.0.0)
project(vk_expe VERSION 0.1.0) project(vk_expe VERSION 0.1.0)
@@ -30,17 +32,36 @@ function(add_shaders TARGET)
endfunction() endfunction()
add_executable(vk_expe add_executable(vk_expe
src/Vulkan/Context.cpp src/core/math.cpp
src/Vulkan/Swapchain.cpp src/core/utils.cpp
src/Vulkan/Memory.cpp src/core/Logger.cpp
src/Vulkan/Buffer.cpp
src/vk/Wrapper.cpp
src/vk/Context.cpp
src/vk/CommandPool.cpp
src/vk/CommandBuffer.cpp
src/vk/Fence.cpp
src/vk/Semaphore.cpp
src/vk/RenderPass.cpp
src/vk/Framebuffer.cpp
src/vk/ShaderModule.cpp
src/vk/Pipeline.cpp
src/vk/Memory.cpp
src/vk/Buffer.cpp
src/vk/Image.cpp
src/vk/ImageView.cpp
src/vk/DescriptorSetLayout.cpp
src/vk/PipelineLayout.cpp
src/vk/DescriptorPool.cpp
src/vk/DescriptorSet.cpp
src/vk/Swapchain.cpp
src/main.cpp src/main.cpp
src/utils.cpp src/Camera.cpp
src/Simplex.cpp src/Simplex.cpp
src/Logger.cpp
src/Planet.cpp src/Planet.cpp
src/VkExpe.cpp src/VkExpe.cpp
src/VulkanTutorial.cpp src/Renderer.cpp
) )
add_shaders(vk_expe add_shaders(vk_expe

View File

@@ -0,0 +1,2 @@
# vk_expe - A simple project to experiment with Vulkan

View File

@@ -1,3 +1,4 @@
// Copyright 2022 Simon Boyé
#version 450 #version 450
layout(location = 1) in vec3 in_normal; layout(location = 1) in vec3 in_normal;

View File

@@ -1,3 +1,4 @@
// Copyright 2022 Simon Boyé
#version 450 #version 450
layout(triangles) in; layout(triangles) in;
@@ -42,7 +43,7 @@ void main() {
vec2 v0 = p0 - p2; vec2 v0 = p0 - p2;
vec2 v1 = p1 - p2; vec2 v1 = p1 - p2;
out_edge_dist[i] = determinant(mat2(v0, v1)) / length(v1); out_edge_dist[i] = abs(determinant(mat2(v0, v1))) / length(v1);
EmitVertex(); EmitVertex();
} }

View File

@@ -1,3 +1,4 @@
// Copyright 2022 Simon Boyé
#version 450 #version 450
layout(binding = 0) uniform Uniforms { layout(binding = 0) uniform Uniforms {
@@ -17,11 +18,12 @@ layout(location = 1) out vec3 out_normal;
layout(location = 2) out vec3 out_color; layout(location = 2) out vec3 out_color;
void main() { void main() {
float lod = clamp( // float lod = clamp(
2.0 * dot(transpose(uniforms.scene_from_model)[0].xyz, in_position) + 0.5, // 2.0 * dot(transpose(uniforms.scene_from_model)[0].xyz, in_position) + 0.5,
0.0, 1.0 // 0.0, 1.0
); // );
vec3 position = mix(in_position2, in_position, lod); // vec3 position = mix(in_position2, in_position, lod);
vec3 position = in_position;
out_position = out_position =
uniforms.projection_from_scene * uniforms.projection_from_scene *
uniforms.scene_from_model * uniforms.scene_from_model *

70
src/Camera.cpp Normal file
View File

@@ -0,0 +1,70 @@
// Copyright 2022 Simon Boyé
#include <Camera.h>
Camera::Camera() noexcept
: m_left(-1)
, m_right(1)
, m_top(-1)
, m_bottom(1)
, m_near(0.1)
, m_far(100)
, m_position(Vector3::Zero())
, m_direction(Vector3::UnitZ())
, m_down(Vector3::UnitY())
{}
Camera::~Camera() = default;
void Camera::set_projection(Real h_fov, Real width_height_ratio) noexcept {
const Real hx = std::tan(h_fov / Real(2));
const Real hy = hx / width_height_ratio;
set_projection(
-hx, hx,
-hy, hy
);
}
void Camera::update_aspect_ratio(Real width_height_ratio) noexcept {
const Real prev_ratio = (m_right - m_left) / (m_bottom - m_top);
const Real next_ratio = width_height_ratio;
const Real sx = (Real(1) + Real(1) / prev_ratio) / (Real(1) + Real(1) / next_ratio);
const Real sy = (Real(1) + prev_ratio) / (Real(1) + next_ratio);
m_left *= sx;
m_right *= sx;
m_top *= sy;
m_bottom *= sy;
}
Matrix3 Camera::basis() const noexcept {
Matrix3 basis;
basis << m_down.cross(m_direction), m_down, m_direction;
return basis;
}
Matrix4 Camera::view_matrix() const noexcept {
Matrix3 linear_view;
linear_view <<
m_down.cross(m_direction).transpose(),
m_down.transpose(),
m_direction.transpose();
Matrix4 view;
view << linear_view, linear_view * -m_position,
Vector4::UnitW().transpose();
return view;
}
Matrix4 Camera::projection_matrix() const noexcept {
return ::projection_matrix(
m_near * m_left, m_near * m_right,
m_near * m_top, m_near * m_bottom,
m_near, m_far
);
}

92
src/Camera.h Normal file
View File

@@ -0,0 +1,92 @@
// Copyright 2022 Simon Boyé
#pragma once
#include <core/math.h>
class Camera {
public:
Camera() noexcept;
~Camera();
inline Real left() const noexcept {
return m_left;
}
inline Real right() const noexcept {
return m_right;
}
inline Real top() const noexcept {
return m_top;
}
inline Real bottom() const noexcept {
return m_bottom;
}
void set_projection(Real left, Real right, Real top, Real bottom) noexcept {
m_left = left;
m_right = right;
m_top = top;
m_bottom = bottom;
}
void set_projection(Real h_fov, Real width_height_ratio) noexcept;
void update_aspect_ratio(Real width_height_ratio) noexcept;
inline Real near() const noexcept {
return m_near;
}
inline Real far() const noexcept {
return m_far;
}
void set_clip_distances(Real near, Real far) noexcept {
m_near = near;
m_far = far;
}
inline Vector3 position() const noexcept {
return m_position;
}
inline void set_position(const Vector3& position) noexcept {
m_position = position;
}
inline Vector3 direction() const noexcept {
return m_direction;
}
inline void set_direction(const Vector3& direction) noexcept {
m_direction = direction;
}
inline Vector3 down() const noexcept {
return m_down;
}
inline void set_down(const Vector3& down) noexcept {
m_down = down;
}
Matrix3 basis() const noexcept;
Matrix4 view_matrix() const noexcept;
Matrix4 projection_matrix() const noexcept;
private:
Real m_left;
Real m_right;
Real m_top;
Real m_bottom;
Real m_near;
Real m_far;
Vector3 m_position;
Vector3 m_direction;
Vector3 m_down;
};

View File

@@ -1,6 +1,8 @@
// Copyright 2022 Simon Boyé
#include <Planet.h> #include <Planet.h>
#include <Logger.h>
#include <core/Logger.h>
#include <cassert> #include <cassert>
#include <vector> #include <vector>

View File

@@ -1,6 +1,8 @@
// Copyright 2022 Simon Boyé
#pragma once #pragma once
#include <core.h> #include <core/math.h>
#include <core/ArrayView.h>
#include <memory> #include <memory>
#include <cassert> #include <cassert>

825
src/Renderer.cpp Normal file
View File

@@ -0,0 +1,825 @@
// Copyright 2022 Simon Boyé
#include <Renderer.h>
#include <Planet.h>
#include <vk/ShaderModule.h>
#include <core/utils.h>
#include <core/Logger.h>
#include <SDL2/SDL_vulkan.h>
#include <Eigen/Geometry>
#include <cassert>
#include <stdexcept>
#include <array>
#include <vector>
#include <algorithm>
#include <cstring>
VkVertexInputBindingDescription vertex_binding_description() {
return {
.binding = 0,
.stride = sizeof(Vertex),
.inputRate = VK_VERTEX_INPUT_RATE_VERTEX,
};
}
std::array<VkVertexInputAttributeDescription, 4> vertex_attributes_description() {
return {
VkVertexInputAttributeDescription {
.location = 0,
.binding = 0,
.format = VK_FORMAT_R32G32B32_SFLOAT,
.offset = offsetof(Vertex, position),
},
VkVertexInputAttributeDescription {
.location = 1,
.binding = 0,
.format = VK_FORMAT_R32G32B32_SFLOAT,
.offset = offsetof(Vertex, position2),
},
VkVertexInputAttributeDescription {
.location = 2,
.binding = 0,
.format = VK_FORMAT_R32G32B32_SFLOAT,
.offset = offsetof(Vertex, normal),
},
{
.location = 3,
.binding = 0,
.format = VK_FORMAT_R32G32B32_SFLOAT,
.offset = offsetof(Vertex, color),
},
};
}
std::vector<Vertex> vertices = {
{{-1.0f, -1.0f, 0.0f}, {0.0f, 0.0f, 0.0f}, {0.0f, 0.0f, -1.0f}, {1.0f, 0.0f, 0.0f}},
{{ 1.0f, -1.0f, 0.0f}, {0.0f, 0.0f, 0.0f}, {0.0f, 0.0f, -1.0f}, {1.0f, 0.0f, 0.0f}},
{{-1.0f, 1.0f, 0.0f}, {0.0f, 0.0f, 0.0f}, {0.0f, 0.0f, -1.0f}, {1.0f, 0.0f, 0.0f}},
{{ 1.0f, 1.0f, 0.0f}, {0.0f, 0.0f, 0.0f}, {0.0f, 0.0f, -1.0f}, {1.0f, 0.0f, 0.0f}},
{{0.0f, -1.0f, -1.0f}, {0.0f, 0.0f, 0.0f}, {0.0f, 0.0f, -1.0f}, {0.0f, 1.0f, 0.0f}},
{{0.0f, -1.0f, 1.0f}, {0.0f, 0.0f, 0.0f}, {0.0f, 0.0f, -1.0f}, {0.0f, 1.0f, 0.0f}},
{{0.0f, 1.0f, -1.0f}, {0.0f, 0.0f, 0.0f}, {0.0f, 0.0f, -1.0f}, {0.0f, 1.0f, 0.0f}},
{{0.0f, 1.0f, 1.0f}, {0.0f, 0.0f, 0.0f}, {0.0f, 0.0f, -1.0f}, {0.0f, 1.0f, 0.0f}},
{{-1.0f, 0.0f, -1.0f}, {0.0f, 0.0f, 0.0f}, {0.0f, 0.0f, -1.0f}, {0.0f, 0.0f, 1.0f}},
{{ 1.0f, 0.0f, -1.0f}, {0.0f, 0.0f, 0.0f}, {0.0f, 0.0f, -1.0f}, {0.0f, 0.0f, 1.0f}},
{{-1.0f, 0.0f, 1.0f}, {0.0f, 0.0f, 0.0f}, {0.0f, 0.0f, -1.0f}, {0.0f, 0.0f, 1.0f}},
{{ 1.0f, 0.0f, 1.0f}, {0.0f, 0.0f, 0.0f}, {0.0f, 0.0f, -1.0f}, {0.0f, 0.0f, 1.0f}},
};
std::vector<uint32_t> indices = {
0, 1, 2,
2, 1, 3,
4, 5, 6,
6, 5, 7,
8, 9, 10,
10, 9, 11,
};
struct Uniforms {
alignas(16) Eigen::Matrix4f scene_from_model;
alignas(16) Eigen::Matrix4f projection_from_scene;
alignas(8) Eigen::Vector2f half_screen_size;
alignas(4) float lod;
};
Renderer::Renderer() {
m_swapchain.register_creation_callback(
std::bind(&Renderer::create_swapchain_objects, this));
// auto const subdiv_count = 4;
// vertices.resize(6 * Cell::vertex_count(subdiv_count));
// indices.resize(6 * 3 * Cell::triangle_count(subdiv_count));
// Vector3AV positions(
// vertices.size(), vertices.data(),
// sizeof(Vertex), offsetof(Vertex, position)
// );
// Vector3AV positions2(
// vertices.size(), vertices.data(),
// sizeof(Vertex), offsetof(Vertex, position2)
// );
// Vector3AV normals(
// vertices.size(), vertices.data(),
// sizeof(Vertex), offsetof(Vertex, normal)
// );
// Vector3AV colors(
// vertices.size(), vertices.data(),
// sizeof(Vertex), offsetof(Vertex, color)
// );
// TriangleAV triangles(
// indices.size() / 3,
// indices.data()
// );
// Planet planet;
// planet.build_mesh(positions, positions2, normals, triangles, subdiv_count);
// for (size_t vertex_index = 0; vertex_index < vertices.size(); vertex_index += 1) {
// colors[vertex_index] =
// positions[vertex_index] / 2.0f + Vector3::Constant(0.5f);
// }
// for(size_t vi = 0; vi < vertices.size(); vi += 1)
// logger.debug() << "v" << vi << ": "
// << vertices[vi].position.transpose();
// for(size_t ii = 0; 3 * ii < indices.size(); ii += 1)
// logger.debug() << "i" << ii << ": "
// << indices[3 * ii + 0] << ", "
// << indices[3 * ii + 1] << ", "
// << indices[3 * ii + 2];
// abort();
}
Renderer::~Renderer() {
if(m_context)
vkDeviceWaitIdle(m_context.device());
}
void Renderer::initialize(SDL_Window* window) {
auto const context_settings = vk::ContextSettings()
#if defined(VKEXPE_ENABLE_VALIDATION) || !defined(NDEBUG)
.with_debug(true)
#endif
.with_queue(GRAPHIC_QUEUE, VK_QUEUE_GRAPHICS_BIT)
.with_window(window);
m_context.initialize(context_settings);
create_command_pool();
create_descriptor_set_layout();
create_pipeline_layout();
create_render_pass();
// m_uniform_buffer_memory is allocated when uniform buffer is first created.
create_vertex_buffer();
create_index_buffer();
auto const swapchain_settings = vk::SwapchainSettings(&m_context)
.with_queue(m_context.queue_family(GRAPHIC_QUEUE));
m_swapchain.initialize(swapchain_settings);
}
void Renderer::set_camera(const Camera& camera) {
m_camera = camera;
}
void Renderer::draw_frame() {
m_swapchain.begin_frame();
const auto image_index = m_swapchain.current_image_index();
auto& image_states = m_image_states[image_index];
m_camera.update_aspect_ratio(Real(m_swapchain.extent().width) / Real(m_swapchain.extent().height));
const Matrix4 view = m_camera.view_matrix();
const Matrix4 proj = m_camera.projection_matrix();
Transform model = Transform::Identity();
const Uniforms uniforms = {
.scene_from_model = model.matrix(),
.projection_from_scene = proj * view,
.half_screen_size = {
0.5 * m_swapchain.extent().width,
0.5 * m_swapchain.extent().height,
},
// .lod = std::cos(alpha) * 0.5f + 0.5f,
};
void* uniform_buffer = m_uniform_buffer_memory.map(
m_context,
image_states.m_uniform_buffer_offset,
sizeof(Uniforms)
);
std::memcpy(uniform_buffer, &uniforms, sizeof(Uniforms));
m_uniform_buffer_memory.unmap(m_context);
std::vector<VkDrawIndexedIndirectCommand> draw_commands {
{
.indexCount = uint32_t(indices.size()),
.instanceCount = 1,
},
};
Byte* draw_buffer = image_states.m_draw_buffer.memory().map(
m_context,
0,
DrawBufferOffset + 1 * sizeof(VkDrawIndexedIndirectCommand)
);
*reinterpret_cast<uint32_t*>(draw_buffer) = uint32_t(draw_commands.size());
std::memcpy(
draw_buffer + DrawBufferOffset,
draw_commands.data(),
draw_commands.size() * sizeof(VkDrawIndexedIndirectCommand)
);
image_states.m_draw_buffer.memory().unmap(m_context);
VkSemaphore wait_semaphores[] = {
m_swapchain.ready_to_render(),
};
VkSemaphore done_semaphores[] = {
image_states.m_render_done,
};
VkPipelineStageFlags stages[] = {
VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT,
};
VkCommandBuffer command_buffers[] = {
image_states.m_command_buffer,
};
VkSubmitInfo submit_info {
.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO,
.waitSemaphoreCount = 1,
.pWaitSemaphores = wait_semaphores,
.pWaitDstStageMask = stages,
.commandBufferCount = 1,
.pCommandBuffers = command_buffers,
.signalSemaphoreCount = 1,
.pSignalSemaphores = done_semaphores,
};
if(vkQueueSubmit(
m_context.queue(GRAPHIC_QUEUE),
1, &submit_info,
m_swapchain.render_done()
))
throw std::runtime_error("failed to submit draw command buffer");
m_swapchain.swap_buffers(done_semaphores);
}
void Renderer::invalidate_swapchain() {
m_swapchain.invalidate();
}
void Renderer::create_swapchain_objects() {
m_image_states.clear();
m_swapchain_states.~SwapchainStates();
new(&m_swapchain_states) SwapchainStates();
initialize_swapchain_states();
m_image_states.resize(m_swapchain.image_count());
for(size_t image_index = 0; image_index < m_image_states.size(); image_index += 1)
initialize_image_states(image_index);
}
void Renderer::create_command_pool() {
m_command_pool = vk::CommandPool(
m_context,
m_context.queue_family(GRAPHIC_QUEUE)
);
}
void Renderer::create_descriptor_set_layout() {
VkDescriptorSetLayoutBinding binding[] {
{
.binding = 0,
.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER,
.descriptorCount = 1,
.stageFlags = VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_GEOMETRY_BIT,
}
};
m_descriptor_set_layout = vk::DescriptorSetLayout(m_context, binding);
}
void Renderer::create_pipeline_layout() {
VkDescriptorSetLayout set_layouts[] {
m_descriptor_set_layout,
};
m_pipeline_layout = vk::PipelineLayout(m_context, set_layouts);
}
void Renderer::create_render_pass() {
VkAttachmentDescription attachments[] {
{
.format = m_context.surface_format().format,
.samples = VK_SAMPLE_COUNT_1_BIT,
.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR,
.storeOp = VK_ATTACHMENT_STORE_OP_STORE,
.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE,
.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE,
.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED,
.finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR,
},
{
.format = VK_FORMAT_X8_D24_UNORM_PACK32,
.samples = VK_SAMPLE_COUNT_1_BIT,
.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR,
.storeOp = VK_ATTACHMENT_STORE_OP_DONT_CARE,
.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE,
.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE,
.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED,
.finalLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL,
},
};
VkAttachmentReference color_attachment_ref {
.attachment = 0,
.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL,
};
VkAttachmentReference depth_attachment_ref {
.attachment = 1,
.layout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL,
};
VkSubpassDescription subpass {
.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS,
.colorAttachmentCount = 1,
.pColorAttachments = &color_attachment_ref,
.pDepthStencilAttachment = &depth_attachment_ref,
};
VkSubpassDependency subpass_dep = {
.srcSubpass = VK_SUBPASS_EXTERNAL,
.dstSubpass = 0,
.srcStageMask =
VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT |
VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT,
.dstStageMask =
VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT |
VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT,
.srcAccessMask = 0,
.dstAccessMask =
VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT |
VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT,
};
VkRenderPassCreateInfo render_pass_info {
.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO,
.attachmentCount = uint32_t(std::extent_v<typeof(attachments)>),
.pAttachments = attachments,
.subpassCount = 1,
.pSubpasses = &subpass,
.dependencyCount = 1,
.pDependencies = &subpass_dep,
};
m_render_pass = vk::RenderPass(m_context, render_pass_info);
}
void Renderer::create_vertex_buffer() {
VkDeviceSize size = sizeof(vertices[0]) * vertices.size();
m_vertex_buffer = vk::Buffer(
m_context,
size,
VK_BUFFER_USAGE_VERTEX_BUFFER_BIT
| VK_BUFFER_USAGE_TRANSFER_DST_BIT,
VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT
);
m_vertex_buffer.upload(size, vertices.data(), m_context.queue_family(GRAPHIC_QUEUE));
}
void Renderer::create_index_buffer() {
VkDeviceSize size = sizeof(indices[0]) * indices.size();
m_index_buffer = vk::Buffer(
m_context,
size,
VK_BUFFER_USAGE_INDEX_BUFFER_BIT
| VK_BUFFER_USAGE_TRANSFER_DST_BIT,
VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT
);
m_index_buffer.upload(size, indices.data(), m_context.queue_family(GRAPHIC_QUEUE));
}
void Renderer::initialize_swapchain_states() {
create_descriptor_pool();
create_graphic_pipeline();
}
void Renderer::create_descriptor_pool() {
VkDescriptorPoolSize pool_sizes[] {
{
.type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER,
.descriptorCount = uint32_t(m_swapchain.image_count()),
}
};
m_swapchain_states.m_descriptor_pool = vk::DescriptorPool(
m_context,
uint32_t(m_swapchain.image_count()),
pool_sizes
);
}
void Renderer::create_graphic_pipeline() {
auto vertex_shader_module = vk::ShaderModule(m_context, "shaders/shader.vert.spv");
auto geometry_shader_module = vk::ShaderModule(m_context, "shaders/shader.geom.spv");
auto fragment_shader_module = vk::ShaderModule(m_context, "shaders/shader.frag.spv");
VkPipelineShaderStageCreateInfo shader_stage_infos[] = {
{
.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO,
.stage = VK_SHADER_STAGE_VERTEX_BIT,
.module = vertex_shader_module,
.pName = "main",
},
{
.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO,
.stage = VK_SHADER_STAGE_GEOMETRY_BIT,
.module = geometry_shader_module,
.pName = "main",
},
{
.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO,
.stage = VK_SHADER_STAGE_FRAGMENT_BIT,
.module = fragment_shader_module,
.pName = "main",
},
};
const VkVertexInputBindingDescription vertex_bindings[] = {
vertex_binding_description(),
};
const auto vertex_attributes = vertex_attributes_description();
VkPipelineVertexInputStateCreateInfo vertex_info {
.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO,
.vertexBindingDescriptionCount = 1,
.pVertexBindingDescriptions = vertex_bindings,
.vertexAttributeDescriptionCount = uint32_t(vertex_attributes.size()),
.pVertexAttributeDescriptions = vertex_attributes.data(),
};
VkPipelineInputAssemblyStateCreateInfo assembly_info {
.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO,
.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST,
.primitiveRestartEnable = VK_FALSE,
};
VkViewport viewport {
.x = 0.0f,
.y = 0.0f,
.width = float(m_swapchain.extent().width),
.height = float(m_swapchain.extent().height),
.minDepth = 0.0f,
.maxDepth = 1.0f,
};
VkRect2D scissor = {
.offset = { 0, 0 },
.extent = m_swapchain.extent(),
};
VkPipelineViewportStateCreateInfo viewport_info {
.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO,
.viewportCount = 1,
.pViewports = &viewport,
.scissorCount = 1,
.pScissors = &scissor,
};
VkPipelineRasterizationStateCreateInfo rasterization_info {
.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO,
.depthClampEnable = VK_FALSE,
.rasterizerDiscardEnable = VK_FALSE,
.polygonMode = VK_POLYGON_MODE_FILL,
.cullMode = VK_CULL_MODE_NONE,
// .cullMode = VK_CULL_MODE_BACK_BIT,
.frontFace = VK_FRONT_FACE_CLOCKWISE,
.depthBiasEnable = VK_FALSE,
.depthBiasConstantFactor = 0.0f,
.depthBiasClamp = 0.0f,
.depthBiasSlopeFactor = 0.0f,
.lineWidth = 1.0f,
};
VkPipelineMultisampleStateCreateInfo multisample_info {
.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO,
.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT,
.sampleShadingEnable = VK_FALSE,
.minSampleShading = 1.0f,
.pSampleMask = nullptr,
.alphaToCoverageEnable = VK_FALSE,
.alphaToOneEnable = VK_FALSE,
};
VkPipelineDepthStencilStateCreateInfo depth_stencil_info {
.sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO,
.depthTestEnable = VK_TRUE,
.depthWriteEnable = VK_TRUE,
.depthCompareOp = VK_COMPARE_OP_LESS,
};
VkPipelineColorBlendAttachmentState color_blend_attachment {
.blendEnable = VK_FALSE,
.srcColorBlendFactor = VK_BLEND_FACTOR_ONE,
.dstColorBlendFactor = VK_BLEND_FACTOR_ZERO,
.colorBlendOp = VK_BLEND_OP_ADD,
.srcAlphaBlendFactor = VK_BLEND_FACTOR_ONE,
.dstAlphaBlendFactor = VK_BLEND_FACTOR_ZERO,
.alphaBlendOp = VK_BLEND_OP_ADD,
.colorWriteMask =
VK_COLOR_COMPONENT_R_BIT |
VK_COLOR_COMPONENT_G_BIT |
VK_COLOR_COMPONENT_B_BIT |
VK_COLOR_COMPONENT_A_BIT,
};
VkPipelineColorBlendStateCreateInfo color_blend_info {
.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO,
.logicOpEnable = VK_FALSE,
.logicOp = VK_LOGIC_OP_COPY,
.attachmentCount = 1,
.pAttachments = &color_blend_attachment,
.blendConstants = { 0.0f, 0.0f, 0.0f, 0.0f },
};
VkGraphicsPipelineCreateInfo pipeline_info {
.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO,
.stageCount = 3,
.pStages = shader_stage_infos,
.pVertexInputState = &vertex_info,
.pInputAssemblyState = &assembly_info,
.pViewportState = &viewport_info,
.pRasterizationState = &rasterization_info,
.pMultisampleState = &multisample_info,
.pDepthStencilState = &depth_stencil_info,
.pColorBlendState = &color_blend_info,
.pDynamicState = nullptr,
.layout = m_pipeline_layout,
.renderPass = m_render_pass,
.subpass = 0,
.basePipelineHandle = VK_NULL_HANDLE,
.basePipelineIndex = -1,
};
m_swapchain_states.m_pipeline = vk::Pipeline(m_context, pipeline_info);
}
void Renderer::initialize_image_states(size_t image_index) {
auto& image_states = m_image_states[image_index];
image_states.m_image_index = image_index;
create_depth_buffer(image_states);
create_framebuffer(image_states);
create_uniform_buffer(image_states);
create_descriptor_set(image_states);
create_draw_buffer(image_states);
create_command_buffer(image_states);
image_states.m_render_done = vk::Semaphore(m_context);
}
void Renderer::create_depth_buffer(ImageStates& image_states)
{
image_states.m_depth_buffer = vk::Image(
m_context,
VkImageCreateInfo {
.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO,
.imageType = VK_IMAGE_TYPE_2D,
.format = VK_FORMAT_X8_D24_UNORM_PACK32,
.extent = {
m_swapchain.extent().width,
m_swapchain.extent().height,
1
},
.mipLevels = 1,
.arrayLayers = 1,
.samples = VK_SAMPLE_COUNT_1_BIT,
.tiling = VK_IMAGE_TILING_OPTIMAL,
.usage = VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT,
.sharingMode = VK_SHARING_MODE_EXCLUSIVE,
.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED,
},
VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT
);
image_states.m_depth_buffer_view = vk::ImageView(
m_context,
VkImageViewCreateInfo {
.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO,
.image = image_states.m_depth_buffer,
.viewType = VK_IMAGE_VIEW_TYPE_2D,
.format = VK_FORMAT_X8_D24_UNORM_PACK32,
.components = {
.r = VK_COMPONENT_SWIZZLE_R,
.g = VK_COMPONENT_SWIZZLE_G,
.b = VK_COMPONENT_SWIZZLE_B,
.a = VK_COMPONENT_SWIZZLE_A,
},
.subresourceRange = {
.aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT,
.baseMipLevel = 0,
.levelCount = 1,
.baseArrayLayer = 0,
.layerCount = 1,
}
}
);
}
void Renderer::create_framebuffer(ImageStates& image_states) {
VkImageView views[] {
m_swapchain.image_view(image_states.m_image_index),
image_states.m_depth_buffer_view,
};
image_states.m_framebuffer = vk::Framebuffer(
m_context,
m_render_pass,
views,
m_swapchain.extent()
);
}
void Renderer::create_uniform_buffer(ImageStates& image_states) {
const auto image_index = image_states.m_image_index;
image_states.m_uniform_buffer = vk::Buffer(
m_context,
sizeof(Uniforms),
VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT
);
auto& buffer = image_states.m_uniform_buffer;
if(!m_uniform_buffer_memory) {
VkMemoryRequirements memory_requirements =
buffer.memory_requirements();
m_uniform_buffer_memory = m_context.allocator().allocate(
memory_requirements.size * m_swapchain.image_count(),
memory_requirements.memoryTypeBits,
VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT
| VK_MEMORY_PROPERTY_HOST_COHERENT_BIT
);
for (size_t index = 0; index < m_swapchain.image_count(); index += 1)
m_image_states[index].m_uniform_buffer_offset =
index * memory_requirements.size;
}
buffer.bind_memory(
m_uniform_buffer_memory,
image_states.m_uniform_buffer_offset
);
}
void Renderer::create_descriptor_set(ImageStates& image_states) {
image_states.m_descriptor_set = vk::DescriptorSet(
m_context,
m_swapchain_states.m_descriptor_pool,
m_descriptor_set_layout
);
VkDescriptorBufferInfo buffer_info {
.buffer = image_states.m_uniform_buffer,
.offset = 0,
.range = sizeof(Uniforms),
};
VkWriteDescriptorSet write {
.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET,
.dstSet = image_states.m_descriptor_set,
.dstBinding = 0,
.dstArrayElement = 0,
.descriptorCount = 1,
.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER,
.pBufferInfo = &buffer_info,
};
vkUpdateDescriptorSets(
m_context.device(),
1, &write,
0, nullptr
);
}
void Renderer::create_draw_buffer(ImageStates& image_states) {
image_states.m_draw_buffer = vk::Buffer(
m_context,
DrawBufferOffset + MaxDrawTileCount * sizeof(VkDrawIndexedIndirectCommand),
VK_BUFFER_USAGE_INDIRECT_BUFFER_BIT,
VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT
| VK_MEMORY_PROPERTY_HOST_COHERENT_BIT
);
}
void Renderer::create_command_buffer(ImageStates& image_states) {
image_states.m_command_buffer = vk::CommandBuffer(m_context, m_command_pool);
VkCommandBufferBeginInfo begin_info {
.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO,
.flags = 0,
.pInheritanceInfo = nullptr,
};
logger.debug() << "record command buffer " << image_states.m_image_index
<< " (" << image_states.m_command_buffer << ")";
if(vkBeginCommandBuffer(
image_states.m_command_buffer,
&begin_info
) != VK_SUCCESS)
throw std::runtime_error("failed to begin command buffer");
VkClearValue clear_values[] = {
{
.color = {
.float32 = { 0.0f, 0.0f, 0.0f, 1.0f }
}
},
{
.depthStencil = {
.depth = 1.0f,
}
},
};
VkRenderPassBeginInfo pass_info {
.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO,
.renderPass = m_render_pass,
.framebuffer = image_states.m_framebuffer,
.renderArea = {
.offset = { 0, 0 },
.extent = m_swapchain.extent(),
},
.clearValueCount = uint32_t(std::extent_v<typeof(clear_values)>),
.pClearValues = clear_values,
};
vkCmdBeginRenderPass(
image_states.m_command_buffer,
&pass_info,
VK_SUBPASS_CONTENTS_INLINE
);
vkCmdBindPipeline(
image_states.m_command_buffer,
VK_PIPELINE_BIND_POINT_GRAPHICS,
m_swapchain_states.m_pipeline
);
VkBuffer vertex_buffers[] = {
m_vertex_buffer,
};
VkDeviceSize offsets[] = {
0,
};
vkCmdBindVertexBuffers(
image_states.m_command_buffer,
0,
1,
vertex_buffers,
offsets
);
vkCmdBindIndexBuffer(
image_states.m_command_buffer,
m_index_buffer,
0,
VK_INDEX_TYPE_UINT32
);
VkDescriptorSet descriptor_sets[] {
image_states.m_descriptor_set,
};
vkCmdBindDescriptorSets(
image_states.m_command_buffer,
VK_PIPELINE_BIND_POINT_GRAPHICS,
m_pipeline_layout,
0, 1, descriptor_sets,
0, nullptr
);
m_context.cmdDrawIndexedIndirectCount(
image_states.m_command_buffer,
image_states.m_draw_buffer,
DrawBufferOffset,
image_states.m_draw_buffer,
0,
MaxDrawTileCount,
sizeof(VkDrawIndexedIndirectCommand)
);
vkCmdEndRenderPass(image_states.m_command_buffer);
if(vkEndCommandBuffer(image_states.m_command_buffer) != VK_SUCCESS)
throw("failed to record command buffer");
}

140
src/Renderer.h Normal file
View File

@@ -0,0 +1,140 @@
// Copyright 2022 Simon Boyé
#pragma once
#include <Camera.h>
#include <vk/Swapchain.h>
#include <vk/DescriptorSet.h>
#include <vk/DescriptorPool.h>
#include <vk/PipelineLayout.h>
#include <vk/DescriptorSetLayout.h>
#include <vk/Image.h>
#include <vk/Buffer.h>
#include <vk/Pipeline.h>
#include <vk/Framebuffer.h>
#include <vk/RenderPass.h>
#include <vk/Semaphore.h>
#include <vk/CommandBuffer.h>
#include <vk/CommandPool.h>
#include <vk/Context.h>
#include <core/math.h>
#include <SDL2/SDL.h>
#include <vulkan/vulkan.h>
#include <Eigen/Dense>
#include <vector>
#include <chrono>
VkVertexInputBindingDescription vertex_binding_description();
std::array<VkVertexInputAttributeDescription, 4> vertex_attributes_description();
class Renderer {
public:
enum QueueIndex {
GRAPHIC_QUEUE,
};
using Clock = std::chrono::high_resolution_clock;
using TimePoint = Clock::time_point;
using Duration = Clock::duration;
using SecondsD = std::chrono::duration<double>;
public:
Renderer();
Renderer(const Renderer&) = delete;
~Renderer();
Renderer& operator=(const Renderer&) = delete;
void initialize(SDL_Window* window);
void set_camera(const Camera& camera);
void draw_frame();
void invalidate_swapchain();
private:
struct SwapchainStates;
struct ImageStates;
private:
void create_swapchain_objects();
void create_command_pool();
void create_descriptor_set_layout();
void create_pipeline_layout();
void create_render_pass();
void create_vertex_buffer();
void create_index_buffer();
void initialize_swapchain_states();
void create_descriptor_pool();
void create_graphic_pipeline();
void initialize_image_states(size_t image_index);
void create_depth_buffer(ImageStates& image_states);
void create_framebuffer(ImageStates& image_states);
void create_uniform_buffer(ImageStates& image_states);
void create_descriptor_set(ImageStates& image_states);
void create_draw_buffer(ImageStates& image_states);
void create_command_buffer(ImageStates& image_states);
private:
static constexpr uint32_t DrawBufferOffset = 4;
static constexpr uint32_t MaxDrawTileCount = 256;
private:
vk::Context m_context;
vk::CommandPool m_command_pool;
vk::DescriptorSetLayout m_descriptor_set_layout;
vk::PipelineLayout m_pipeline_layout;
vk::RenderPass m_render_pass;
vk::Buffer m_vertex_buffer;
vk::Buffer m_index_buffer;
vk::MemoryBlock m_uniform_buffer_memory;
vk::Swapchain m_swapchain;
struct SwapchainStates {
vk::DescriptorPool m_descriptor_pool;
vk::Pipeline m_pipeline;
};
SwapchainStates m_swapchain_states;
struct ImageStates {
size_t m_image_index;
vk::Image m_depth_buffer;
vk::ImageView m_depth_buffer_view;
vk::Framebuffer m_framebuffer;
vk::Buffer m_uniform_buffer;
VkDeviceSize m_uniform_buffer_offset;
vk::DescriptorSet m_descriptor_set;
vk::Buffer m_draw_buffer;
vk::CommandBuffer m_command_buffer;
vk::Semaphore m_render_done;
};
std::vector<ImageStates> m_image_states;
Camera m_camera;
};

View File

@@ -1,3 +1,4 @@
// Copyright 2022 Simon Boyé
#include <Simplex.h> #include <Simplex.h>

View File

@@ -1,6 +1,8 @@
// Copyright 2022 Simon Boyé
#pragma once #pragma once
#include <core.h> #include <core/math.h>
#include <core/ArrayView.h>
class Simplex { class Simplex {

View File

@@ -1,7 +1,8 @@
// Copyright 2022 Simon Boyé
#include <VkExpe.h> #include <VkExpe.h>
#include <utils.h> #include <core/utils.h>
#include <Logger.h> #include <core/Logger.h>
#include <stdexcept> #include <stdexcept>
#include <iostream> #include <iostream>
@@ -14,6 +15,12 @@ void SdlWindowDeleter::operator()(SDL_Window* window) const {
VkExpe::VkExpe(int argc, char** argv) { VkExpe::VkExpe(int argc, char** argv) {
m_camera.set_position(Vector3(0.0f, 0.0f, -3.0f));
m_camera.set_direction(Vector3::UnitZ());
m_camera.set_down(Vector3::UnitY());
m_camera.set_projection(M_PI / 3.0, 1.0);
m_camera.set_clip_distances(0.1, 10);
} }
VkExpe::~VkExpe() { VkExpe::~VkExpe() {
@@ -42,7 +49,7 @@ void VkExpe::initialize() {
} }
void VkExpe::shutdown() { void VkExpe::shutdown() {
m_vulkan.shutdown(); recreate_object(m_vulkan);
m_window.reset(); m_window.reset();
@@ -63,9 +70,22 @@ void VkExpe::run() {
auto last_time = std::chrono::high_resolution_clock::now(); auto last_time = std::chrono::high_resolution_clock::now();
SDL_Event event; // SDL_Event event;
std::vector<SDL_Event> events;
events.reserve(128);
while(m_running) { while(m_running) {
while(SDL_PollEvent(&event)) { SDL_PumpEvents();
int event_count = SDL_PeepEvents(nullptr, 0, SDL_PEEKEVENT, SDL_FIRSTEVENT, SDL_LASTEVENT);
if(event_count < 0) {
logger.error() << SDL_GetError();
break;
}
events.resize(event_count);
SDL_PeepEvents(events.data(), event_count, SDL_GETEVENT, SDL_FIRSTEVENT, SDL_LASTEVENT);
for(const auto& event: events) {
switch(event.type) { switch(event.type) {
case SDL_QUIT: case SDL_QUIT:
m_running = false; m_running = false;
@@ -108,7 +128,7 @@ void VkExpe::run() {
update(std::chrono::duration<double>(elapsed).count()); update(std::chrono::duration<double>(elapsed).count());
m_vulkan.set_camera(m_camera_position, m_camera_z, m_camera_y); m_vulkan.set_camera(m_camera);
m_vulkan.draw_frame(); m_vulkan.draw_frame();
m_mouse_offset = Vector2::Zero(); m_mouse_offset = Vector2::Zero();
@@ -127,14 +147,16 @@ void VkExpe::update(double elapsed) {
const Real x_sensi = 0.001; const Real x_sensi = 0.001;
const Real y_sensi = -0.001; const Real y_sensi = -0.001;
const Vector3 camera_x = m_camera_y.cross(m_camera_z); const Vector3 camera_z = m_camera.direction();
const Vector3 camera_y = m_camera.down();
const Vector3 camera_x = camera_y.cross(camera_z);
Vector3 axis = Vector3 axis =
x_sensi * m_mouse_offset[0] * m_camera_y + x_sensi * m_mouse_offset[0] * camera_y +
y_sensi * m_mouse_offset[1] * camera_x; y_sensi * m_mouse_offset[1] * camera_x;
Real rot_norm = axis.norm(); Real rot_norm = axis.norm();
AngleAxis rot(rot_norm, axis / rot_norm); AngleAxis rot(rot_norm, axis / rot_norm);
m_camera_y = rot * m_camera_y; m_camera.set_down(rot * camera_y);
m_camera_z = rot * m_camera_z; m_camera.set_direction(rot * camera_z);
} }
Vector3 walk_direction = Vector3::Zero(); Vector3 walk_direction = Vector3::Zero();
@@ -155,8 +177,9 @@ void VkExpe::update(double elapsed) {
walk_direction.normalize(); walk_direction.normalize();
const Real base_velocity = 1; const Real base_velocity = 1;
Matrix3 camera_basis; const Matrix3 basis = m_camera.basis();
camera_basis << m_camera_y.cross(m_camera_z), m_camera_y, m_camera_z; m_camera.set_position(
m_camera_position += elapsed * base_velocity * (camera_basis * walk_direction); m_camera.position() + elapsed * base_velocity * (basis * walk_direction)
);
} }
} }

View File

@@ -1,7 +1,10 @@
// Copyright 2022 Simon Boyé
#pragma once #pragma once
#include <core.h> #include <Renderer.h>
#include <VulkanTutorial.h> #include <Camera.h>
#include <core/math.h>
#include <SDL2/SDL.h> #include <SDL2/SDL.h>
@@ -30,11 +33,9 @@ public:
void update(double elapsed); void update(double elapsed);
private: private:
VulkanTutorial m_vulkan; Renderer m_vulkan;
Vector3 m_camera_position = Vector3(0.0f, 0.0f, -3.0f); Camera m_camera;
Vector3 m_camera_z = Vector3(0.0f, 0.0f, 1.0f);
Vector3 m_camera_y = Vector3(0.0f, 1.0f, 0.0f);
WindowUP m_window; WindowUP m_window;
bool m_running = false; bool m_running = false;

View File

@@ -1,861 +0,0 @@
#include <VulkanTutorial.h>
#include <utils.h>
#include <Logger.h>
#include <Planet.h>
#include <SDL2/SDL_vulkan.h>
#include <Eigen/Geometry>
#include <cassert>
#include <stdexcept>
#include <array>
#include <vector>
#include <algorithm>
#include <cstring>
VkVertexInputBindingDescription vertex_binding_description() {
return {
.binding = 0,
.stride = sizeof(Vertex),
.inputRate = VK_VERTEX_INPUT_RATE_VERTEX,
};
}
std::array<VkVertexInputAttributeDescription, 4> vertex_attributes_description() {
return {
VkVertexInputAttributeDescription {
.location = 0,
.binding = 0,
.format = VK_FORMAT_R32G32B32_SFLOAT,
.offset = offsetof(Vertex, position),
},
VkVertexInputAttributeDescription {
.location = 1,
.binding = 0,
.format = VK_FORMAT_R32G32B32_SFLOAT,
.offset = offsetof(Vertex, position2),
},
VkVertexInputAttributeDescription {
.location = 2,
.binding = 0,
.format = VK_FORMAT_R32G32B32_SFLOAT,
.offset = offsetof(Vertex, normal),
},
{
.location = 3,
.binding = 0,
.format = VK_FORMAT_R32G32B32_SFLOAT,
.offset = offsetof(Vertex, color),
},
};
}
std::vector<Vertex> vertices = {
// {{-0.5f, -0.5f, -0.5f}, {1.0f, 0.0f, 0.0f}},
// {{0.5f, -0.5f, -0.5f}, {0.0f, 1.0f, 0.0f}},
// {{-0.5f, 0.5f, -0.5f}, {0.0f, 0.0f, 1.0f}},
// {{0.5f, 0.5f, -0.5f}, {1.0f, 1.0f, 1.0f}},
// {{-0.5f, -0.5f, 0.5f}, {0.0f, 1.0f, 1.0f}},
// {{0.5f, -0.5f, 0.5f}, {1.0f, 0.0f, 1.0f}},
// {{-0.5f, 0.5f, 0.5f}, {1.0f, 1.0f, 0.0f}},
// {{0.5f, 0.5f, 0.5f}, {0.2f, 0.2f, 0.2f}},
};
std::vector<uint32_t> indices = {
// 0, 1, 2,
// 2, 1, 3,
// 1, 5, 3,
// 3, 5, 7,
// 5, 4, 7,
// 7, 4, 6,
// 4, 0, 6,
// 6, 0, 2,
// 0, 4, 1,
// 1, 4, 5,
// 2, 3, 6,
// 6, 3, 7,
};
struct Uniforms {
alignas(16) Eigen::Matrix4f scene_from_model;
alignas(16) Eigen::Matrix4f projection_from_scene;
alignas(8) Eigen::Vector2f half_screen_size;
alignas(4) float lod;
};
VulkanTutorial::VulkanTutorial() {
m_swapchain.register_creation_callback(
std::bind(&VulkanTutorial::create_swapchain_objects, this, std::placeholders::_1));
m_swapchain.register_destruction_callback(
std::bind(&VulkanTutorial::destroy_swapchain_objects, this));
auto const subdiv_count = 4;
vertices.resize(6 * Cell::vertex_count(subdiv_count));
indices.resize(6 * 3 * Cell::triangle_count(subdiv_count));
Vector3AV positions(
vertices.size(), vertices.data(),
sizeof(Vertex), offsetof(Vertex, position)
);
Vector3AV positions2(
vertices.size(), vertices.data(),
sizeof(Vertex), offsetof(Vertex, position2)
);
Vector3AV normals(
vertices.size(), vertices.data(),
sizeof(Vertex), offsetof(Vertex, normal)
);
Vector3AV colors(
vertices.size(), vertices.data(),
sizeof(Vertex), offsetof(Vertex, color)
);
TriangleAV triangles(
indices.size() / 3,
indices.data()
);
Planet planet;
planet.build_mesh(positions, positions2, normals, triangles, subdiv_count);
for (size_t vertex_index = 0; vertex_index < vertices.size(); vertex_index += 1) {
colors[vertex_index] =
positions[vertex_index] / 2.0f + Vector3::Constant(0.5f);
}
// for(size_t vi = 0; vi < vertices.size(); vi += 1)
// logger.debug() << "v" << vi << ": "
// << vertices[vi].position.transpose();
// for(size_t ii = 0; 3 * ii < indices.size(); ii += 1)
// logger.debug() << "i" << ii << ": "
// << indices[3 * ii + 0] << ", "
// << indices[3 * ii + 1] << ", "
// << indices[3 * ii + 2];
// abort();
}
VulkanTutorial::~VulkanTutorial() {
shutdown();
}
void VulkanTutorial::initialize(SDL_Window* window) {
auto const context_settings = Vulkan::ContextSettings()
#if defined(VKEXPE_ENABLE_VALIDATION) || !defined(NDEBUG)
.with_debug(true)
#endif
.with_queue(GRAPHIC_QUEUE, VK_QUEUE_GRAPHICS_BIT)
.with_window(window);
m_context.initialize(context_settings);
create_command_pool();
create_descriptor_set_layout();
create_vertex_buffer();
create_index_buffer();
auto const swapchain_settings = Vulkan::SwapchainSettings(&m_context)
.with_queue(m_context.queue_family(GRAPHIC_QUEUE));
m_swapchain.initialize(swapchain_settings);
}
void VulkanTutorial::shutdown() {
if(!m_context.instance())
return;
vkDeviceWaitIdle(m_context.device());
destroy_swapchain_objects();
m_context.destroy_command_pool(m_command_pool);
m_index_buffer.destroy();
m_vertex_buffer.destroy();
m_context.destroy_descriptor_set_layout(m_descriptor_set_layout);
for(VkSemaphore semaphore: m_render_done)
m_context.destroy_semaphore(semaphore);
m_render_done.clear();
m_swapchain.shutdown();
m_context.shutdown();
}
void VulkanTutorial::set_camera(const Vector3& camera_position, const Vector3& camera_z, const Vector3& camera_y) {
m_camera_position = camera_position;
m_camera_z = camera_z;
m_camera_y = camera_y;
}
void VulkanTutorial::draw_frame() {
m_swapchain.begin_frame();
auto const image_index = m_swapchain.current_image_index();
const auto now = Clock::now();
if(m_last_frame_time.time_since_epoch() != Duration(0)) {
m_time += now - m_last_frame_time;
}
m_last_frame_time = now;
Matrix3 linear_view;
linear_view <<
m_camera_y.cross(m_camera_z).transpose(),
m_camera_y.transpose(),
m_camera_z.transpose();
Matrix4 view;
view << linear_view, linear_view * -m_camera_position,
Vector4::UnitW().transpose();
const float fov = M_PI / 3.0f;
const float near = 0.1f;
const float far = 10.0f;
const float hx = near * std::tan(fov / 2.0f);
const float hy = hx * m_swapchain.extent().height / m_swapchain.extent().width;
const Eigen::Matrix4f proj = projection_matrix(
-hx, hx,
-hy, hy,
near, far
);
Transform model = Transform::Identity();
const Uniforms uniforms = {
.scene_from_model = model.matrix(),
.projection_from_scene = proj * view,
.half_screen_size = {
0.5 * m_swapchain.extent().width,
0.5 * m_swapchain.extent().height,
},
// .lod = std::cos(alpha) * 0.5f + 0.5f,
};
void* uniform_buffer = m_uniform_buffer_memory.map(
m_context,
m_uniform_buffer_offsets[image_index],
sizeof(Uniforms)
);
std::memcpy(uniform_buffer, &uniforms, sizeof(Uniforms));
m_uniform_buffer_memory.unmap(m_context);
VkSemaphore wait_semaphores[] = {
m_swapchain.ready_to_render(),
};
VkPipelineStageFlags stages[] = {
VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT,
};
VkSubmitInfo submit_info {
.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO,
.waitSemaphoreCount = 1,
.pWaitSemaphores = wait_semaphores,
.pWaitDstStageMask = stages,
.commandBufferCount = 1,
.pCommandBuffers = &m_command_buffers[image_index],
.signalSemaphoreCount = 1,
.pSignalSemaphores = &m_render_done[image_index],
};
if(vkQueueSubmit(
m_context.queue(GRAPHIC_QUEUE),
1, &submit_info,
m_swapchain.render_done()
))
throw std::runtime_error("failed to submit draw command buffer");
m_swapchain.swap_buffers({1, &m_render_done[image_index]});
}
void VulkanTutorial::invalidate_swapchain() {
m_swapchain.invalidate();
}
void VulkanTutorial::create_swapchain_objects(uint32_t image_count) {
create_render_pass();
create_framebuffers();
create_uniform_buffer();
create_descriptor_pool();
create_descriptor_sets();
create_graphic_pipeline();
create_command_buffers();
m_render_done.resize(m_swapchain.image_count());
VkSemaphoreCreateInfo semaphore_info {
.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO,
};
for(size_t index = 0; index < m_render_done.size(); index += 1) {
if(vkCreateSemaphore(
m_context.device(),
&semaphore_info,
nullptr,
&m_render_done[index]
) != VK_SUCCESS)
throw std::runtime_error("failed to create semaphore");
}
}
void VulkanTutorial::destroy_swapchain_objects() {
if(m_command_pool == VK_NULL_HANDLE)
return;
vkFreeCommandBuffers(
m_context.device(),
m_command_pool,
uint32_t(m_command_buffers.size()),
m_command_buffers.data()
);
m_command_buffers.clear();
for(auto& buffer: m_uniform_buffers)
buffer.destroy();
m_uniform_buffers.clear();
m_uniform_buffer_memory.free();
m_context.destroy_descriptor_pool(m_descriptor_pool);
for(VkFramebuffer framebuffer: m_framebuffers)
m_context.destroy_framebuffer(framebuffer);
m_framebuffers.clear();
m_context.destroy_pipeline(m_pipeline);
m_context.destroy_pipeline_layout(m_pipeline_layout);
m_context.destroy_render_pass(m_render_pass);
}
void VulkanTutorial::create_render_pass() {
VkAttachmentDescription color_attachment {
.format = m_context.surface_format().format,
.samples = VK_SAMPLE_COUNT_1_BIT,
.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR,
.storeOp = VK_ATTACHMENT_STORE_OP_STORE,
.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE,
.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE,
.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED,
.finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR,
};
VkAttachmentReference color_attachment_ref = {
.attachment = 0,
.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL,
};
VkSubpassDescription subpass {
.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS,
.colorAttachmentCount = 1,
.pColorAttachments = &color_attachment_ref,
};
VkSubpassDependency subpass_dep = {
.srcSubpass = VK_SUBPASS_EXTERNAL,
.dstSubpass = 0,
.srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT,
.dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT,
.srcAccessMask = 0,
.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT,
};
VkRenderPassCreateInfo render_pass_info {
.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO,
.attachmentCount = 1,
.pAttachments = &color_attachment,
.subpassCount = 1,
.pSubpasses = &subpass,
.dependencyCount = 1,
.pDependencies = &subpass_dep,
};
if(vkCreateRenderPass(
m_context.device(),
&render_pass_info,
nullptr,
&m_render_pass
) != VK_SUCCESS)
throw std::runtime_error("failed to create render pass");
}
void VulkanTutorial::create_descriptor_set_layout() {
VkDescriptorSetLayoutBinding binding {
.binding = 0,
.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER,
.descriptorCount = 1,
.stageFlags = VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_GEOMETRY_BIT,
};
VkDescriptorSetLayoutCreateInfo layout_info {
.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO,
.bindingCount = 1,
.pBindings = &binding,
};
if(vkCreateDescriptorSetLayout(
m_context.device(),
&layout_info,
nullptr,
&m_descriptor_set_layout
) != VK_SUCCESS)
throw std::runtime_error("failed to create descriptor set layout");
}
void VulkanTutorial::create_graphic_pipeline() {
auto const vertex_shader_module =
m_context.create_shader_module_from_file("shaders/shader.vert.spv");
auto const vertex_shader_guard = make_guard([&]{
vkDestroyShaderModule(m_context.device(), vertex_shader_module, nullptr);
});
auto const geometry_shader_module =
m_context.create_shader_module_from_file("shaders/shader.geom.spv");
auto const geometry_shader_guard = make_guard([&]{
vkDestroyShaderModule(m_context.device(), geometry_shader_module, nullptr);
});
auto const fragment_shader_module =
m_context.create_shader_module_from_file("shaders/shader.frag.spv");
auto const fragment_shader_guard = make_guard([&]{
vkDestroyShaderModule(m_context.device(), fragment_shader_module, nullptr);
});
VkPipelineShaderStageCreateInfo shader_stage_infos[] = {
{
.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO,
.stage = VK_SHADER_STAGE_VERTEX_BIT,
.module = vertex_shader_module,
.pName = "main",
},
{
.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO,
.stage = VK_SHADER_STAGE_GEOMETRY_BIT,
.module = geometry_shader_module,
.pName = "main",
},
{
.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO,
.stage = VK_SHADER_STAGE_FRAGMENT_BIT,
.module = fragment_shader_module,
.pName = "main",
},
};
const VkVertexInputBindingDescription vertex_bindings[] = {
vertex_binding_description(),
};
const auto vertex_attributes = vertex_attributes_description();
VkPipelineVertexInputStateCreateInfo vertex_info {
.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO,
.vertexBindingDescriptionCount = 1,
.pVertexBindingDescriptions = vertex_bindings,
.vertexAttributeDescriptionCount = uint32_t(vertex_attributes.size()),
.pVertexAttributeDescriptions = vertex_attributes.data(),
};
VkPipelineInputAssemblyStateCreateInfo assembly_info {
.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO,
.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST,
.primitiveRestartEnable = VK_FALSE,
};
VkViewport viewport {
.x = 0.0f,
.y = 0.0f,
.width = float(m_swapchain.extent().width),
.height = float(m_swapchain.extent().height),
.minDepth = 0.0f,
.maxDepth = 1.0f,
};
VkRect2D scissor = {
.offset = { 0, 0 },
.extent = m_swapchain.extent(),
};
VkPipelineViewportStateCreateInfo viewport_info {
.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO,
.viewportCount = 1,
.pViewports = &viewport,
.scissorCount = 1,
.pScissors = &scissor,
};
VkPipelineRasterizationStateCreateInfo rasterization_info {
.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO,
.depthClampEnable = VK_FALSE,
.rasterizerDiscardEnable = VK_FALSE,
.polygonMode = VK_POLYGON_MODE_FILL,
// .cullMode = VK_CULL_MODE_NONE,
.cullMode = VK_CULL_MODE_BACK_BIT,
.frontFace = VK_FRONT_FACE_CLOCKWISE,
.depthBiasEnable = VK_FALSE,
.depthBiasConstantFactor = 0.0f,
.depthBiasClamp = 0.0f,
.depthBiasSlopeFactor = 0.0f,
.lineWidth = 1.0f,
};
VkPipelineMultisampleStateCreateInfo multisample_info {
.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO,
.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT,
.sampleShadingEnable = VK_FALSE,
.minSampleShading = 1.0f,
.pSampleMask = nullptr,
.alphaToCoverageEnable = VK_FALSE,
.alphaToOneEnable = VK_FALSE,
};
VkPipelineColorBlendAttachmentState color_blend_attachment {
.blendEnable = VK_FALSE,
.srcColorBlendFactor = VK_BLEND_FACTOR_ONE,
.dstColorBlendFactor = VK_BLEND_FACTOR_ZERO,
.colorBlendOp = VK_BLEND_OP_ADD,
.srcAlphaBlendFactor = VK_BLEND_FACTOR_ONE,
.dstAlphaBlendFactor = VK_BLEND_FACTOR_ZERO,
.alphaBlendOp = VK_BLEND_OP_ADD,
.colorWriteMask =
VK_COLOR_COMPONENT_R_BIT |
VK_COLOR_COMPONENT_G_BIT |
VK_COLOR_COMPONENT_B_BIT |
VK_COLOR_COMPONENT_A_BIT,
};
VkPipelineColorBlendStateCreateInfo color_blend_info {
.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO,
.logicOpEnable = VK_FALSE,
.logicOp = VK_LOGIC_OP_COPY,
.attachmentCount = 1,
.pAttachments = &color_blend_attachment,
.blendConstants = { 0.0f, 0.0f, 0.0f, 0.0f },
};
VkPipelineLayoutCreateInfo layout_info = {
.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO,
.setLayoutCount = 1,
.pSetLayouts = &m_descriptor_set_layout,
.pushConstantRangeCount = 0,
.pPushConstantRanges = nullptr,
};
if(vkCreatePipelineLayout(
m_context.device(),
&layout_info,
nullptr,
&m_pipeline_layout
) != VK_SUCCESS)
throw std::runtime_error("failed to create pipeline layout");
VkGraphicsPipelineCreateInfo pipeline_info {
.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO,
.stageCount = 3,
.pStages = shader_stage_infos,
.pVertexInputState = &vertex_info,
.pInputAssemblyState = &assembly_info,
.pViewportState = &viewport_info,
.pRasterizationState = &rasterization_info,
.pMultisampleState = &multisample_info,
.pDepthStencilState = nullptr,
.pColorBlendState = &color_blend_info,
.pDynamicState = nullptr,
.layout = m_pipeline_layout,
.renderPass = m_render_pass,
.subpass = 0,
.basePipelineHandle = VK_NULL_HANDLE,
.basePipelineIndex = -1,
};
if(vkCreateGraphicsPipelines(
m_context.device(),
VK_NULL_HANDLE,
1, &pipeline_info,
nullptr,
&m_pipeline
) != VK_SUCCESS)
throw std::runtime_error("failed to create graphic pipeline");
}
void VulkanTutorial::create_framebuffers() {
m_framebuffers.resize(m_swapchain.image_count());
for(size_t index = 0; index < m_framebuffers.size(); index += 1) {
VkImageView view = m_swapchain.image_view(index);
VkFramebufferCreateInfo framebuffer_info {
.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO,
.renderPass = m_render_pass,
.attachmentCount = 1,
.pAttachments = &view,
.width = m_swapchain.extent().width,
.height = m_swapchain.extent().height,
.layers = 1,
};
if(vkCreateFramebuffer(
m_context.device(),
&framebuffer_info,
nullptr,
&m_framebuffers[index]
) != VK_SUCCESS)
throw std::runtime_error("failed to create framebuffer");
}
}
void VulkanTutorial::create_command_pool() {
if(m_command_pool != VK_NULL_HANDLE)
return;
VkCommandPoolCreateInfo pool_info {
.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO,
.flags = 0,
.queueFamilyIndex = m_context.queue_family(GRAPHIC_QUEUE),
};
if(vkCreateCommandPool(
m_context.device(),
&pool_info,
nullptr,
&m_command_pool
) != VK_SUCCESS)
throw std::runtime_error("failed to create command pool");
}
void VulkanTutorial::create_vertex_buffer() {
if(m_vertex_buffer)
return;
VkDeviceSize size = sizeof(vertices[0]) * vertices.size();
m_vertex_buffer = Vulkan::Buffer(
m_context,
size,
VK_BUFFER_USAGE_VERTEX_BUFFER_BIT
| VK_BUFFER_USAGE_TRANSFER_DST_BIT,
VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT
);
m_vertex_buffer.upload(size, vertices.data(), m_context.queue_family(GRAPHIC_QUEUE));
}
void VulkanTutorial::create_index_buffer() {
if(m_index_buffer)
return;
VkDeviceSize size = sizeof(indices[0]) * indices.size();
m_index_buffer = Vulkan::Buffer(
m_context,
size,
VK_BUFFER_USAGE_INDEX_BUFFER_BIT
| VK_BUFFER_USAGE_TRANSFER_DST_BIT,
VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT
);
m_index_buffer.upload(size, indices.data(), m_context.queue_family(GRAPHIC_QUEUE));
}
void VulkanTutorial::create_uniform_buffer() {
m_uniform_buffers.resize(m_swapchain.image_count());
for(size_t index = 0; index < m_uniform_buffers.size(); index += 1) {
m_uniform_buffers[index] = Vulkan::Buffer(
m_context,
sizeof(Uniforms),
VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT
);
}
VkMemoryRequirements memory_requirements =
m_uniform_buffers[0].memory_requirements();
m_uniform_buffer_memory = m_context.allocator().allocate(
memory_requirements.size * m_swapchain.image_count(),
memory_requirements.memoryTypeBits,
VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT
| VK_MEMORY_PROPERTY_HOST_COHERENT_BIT
);
m_uniform_buffer_offsets.resize(m_uniform_buffers.size());
for(size_t index = 0; index < m_uniform_buffers.size(); index += 1) {
m_uniform_buffer_offsets[index] = index * memory_requirements.size;
m_uniform_buffers[index].bind_memory(
m_uniform_buffer_memory,
m_uniform_buffer_offsets[index]
);
}
}
void VulkanTutorial::create_descriptor_pool() {
VkDescriptorPoolSize pool_size {
.type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER,
.descriptorCount = uint32_t(m_swapchain.image_count()),
};
VkDescriptorPoolCreateInfo pool_info {
.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO,
.maxSets = uint32_t(m_swapchain.image_count()),
.poolSizeCount = 1,
.pPoolSizes = &pool_size,
};
if(vkCreateDescriptorPool(
m_context.device(),
&pool_info,
nullptr,
&m_descriptor_pool
) != VK_SUCCESS)
throw std::runtime_error("failed to create descriptor pool");
}
void VulkanTutorial::create_descriptor_sets() {
const std::vector<VkDescriptorSetLayout> layouts(
m_swapchain.image_count(), m_descriptor_set_layout);
VkDescriptorSetAllocateInfo alloc_info {
.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO,
.descriptorPool = m_descriptor_pool,
.descriptorSetCount = uint32_t(layouts.size()),
.pSetLayouts = layouts.data(),
};
m_descriptor_sets.resize(m_swapchain.image_count());
if(vkAllocateDescriptorSets(
m_context.device(),
&alloc_info,
m_descriptor_sets.data()
) != VK_SUCCESS)
throw std::runtime_error("failed to allocate descriptor sets");
for(size_t index = 0; index < m_swapchain.image_count(); index += 1) {
VkDescriptorBufferInfo buffer_info {
.buffer = m_uniform_buffers[index],
.offset = 0,
.range = sizeof(Uniforms),
};
VkWriteDescriptorSet write {
.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET,
.dstSet = m_descriptor_sets[index],
.dstBinding = 0,
.dstArrayElement = 0,
.descriptorCount = 1,
.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER,
.pBufferInfo = &buffer_info,
};
vkUpdateDescriptorSets(
m_context.device(),
1, &write,
0, nullptr
);
}
}
void VulkanTutorial::create_command_buffers() {
m_command_buffers.resize(m_framebuffers.size());
VkCommandBufferAllocateInfo allocate_info {
.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO,
.commandPool = m_command_pool,
.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY,
.commandBufferCount = uint32_t(m_command_buffers.size()),
};
if(vkAllocateCommandBuffers(
m_context.device(),
&allocate_info,
m_command_buffers.data()
) != VK_SUCCESS)
throw std::runtime_error("failed to allocate command buffers");
for(size_t index = 0; index < m_command_buffers.size(); index += 1) {
VkCommandBuffer command_buffer = m_command_buffers[index];
VkCommandBufferBeginInfo begin_info {
.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO,
.flags = 0,
.pInheritanceInfo = nullptr,
};
logger.debug() << "record command buffer " << index << " (" << command_buffer << ")";
if(vkBeginCommandBuffer(
command_buffer,
&begin_info
) != VK_SUCCESS)
throw std::runtime_error("failed to begin command buffer");
VkClearValue clear_color = {
.color = {
.float32 = { 0.0f, 0.0f, 0.0f, 1.0f }
}
};
VkRenderPassBeginInfo pass_info {
.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO,
.renderPass = m_render_pass,
.framebuffer = m_framebuffers[index],
.renderArea = {
.offset = { 0, 0 },
.extent = m_swapchain.extent(),
},
.clearValueCount = 1,
.pClearValues = &clear_color,
};
vkCmdBeginRenderPass(
command_buffer,
&pass_info,
VK_SUBPASS_CONTENTS_INLINE
);
vkCmdBindPipeline(
command_buffer,
VK_PIPELINE_BIND_POINT_GRAPHICS,
m_pipeline
);
VkBuffer vertex_buffers[] = {
m_vertex_buffer,
};
VkDeviceSize offsets[] = {
0,
};
vkCmdBindVertexBuffers(
command_buffer,
0,
1,
vertex_buffers,
offsets
);
vkCmdBindIndexBuffer(
command_buffer,
m_index_buffer,
0,
VK_INDEX_TYPE_UINT32
);
vkCmdBindDescriptorSets(
command_buffer,
VK_PIPELINE_BIND_POINT_GRAPHICS,
m_pipeline_layout,
0, 1, &m_descriptor_sets[index],
0, nullptr
);
vkCmdDrawIndexed(
command_buffer,
uint32_t(indices.size()),
1,
0,
0,
0
);
vkCmdEndRenderPass(command_buffer);
if(vkEndCommandBuffer(command_buffer) != VK_SUCCESS)
throw("failed to record command buffer");
}
}

View File

@@ -1,90 +0,0 @@
#pragma once
#include <core.h>
#include <Vulkan/Context.h>
#include <Vulkan/Swapchain.h>
#include <Vulkan/Buffer.h>
#include <SDL2/SDL.h>
#include <vulkan/vulkan.h>
#include <Eigen/Dense>
#include <vector>
#include <chrono>
VkVertexInputBindingDescription vertex_binding_description();
std::array<VkVertexInputAttributeDescription, 4> vertex_attributes_description();
class VulkanTutorial {
public:
enum QueueIndex {
GRAPHIC_QUEUE,
};
using Clock = std::chrono::high_resolution_clock;
using TimePoint = Clock::time_point;
using Duration = Clock::duration;
using SecondsD = std::chrono::duration<double>;
public:
VulkanTutorial();
VulkanTutorial(const VulkanTutorial&) = delete;
~VulkanTutorial();
VulkanTutorial& operator=(const VulkanTutorial&) = delete;
void initialize(SDL_Window* window);
void shutdown();
void set_camera(const Vector3& camera_position, const Vector3& camera_z, const Vector3& camera_y);
void draw_frame();
void invalidate_swapchain();
private:
void create_swapchain_objects(uint32_t image_count);
void destroy_swapchain_objects();
void create_render_pass();
void create_descriptor_set_layout();
void create_graphic_pipeline();
void create_framebuffers();
void create_command_pool();
void create_vertex_buffer();
void create_index_buffer();
void create_uniform_buffer();
void create_descriptor_pool();
void create_descriptor_sets();
void create_command_buffers();
private:
Vulkan::Context m_context;
Vulkan::Swapchain m_swapchain;
VkQueue m_graphic_queue = nullptr;
VkQueue m_presentation_queue = nullptr;
VkRenderPass m_render_pass = VK_NULL_HANDLE;
VkDescriptorSetLayout m_descriptor_set_layout = VK_NULL_HANDLE;
VkPipelineLayout m_pipeline_layout = VK_NULL_HANDLE;
VkPipeline m_pipeline = VK_NULL_HANDLE;
std::vector<VkFramebuffer> m_framebuffers;
VkCommandPool m_command_pool = VK_NULL_HANDLE;
Vulkan::Buffer m_vertex_buffer;
Vulkan::Buffer m_index_buffer;
std::vector<Vulkan::Buffer> m_uniform_buffers;
std::vector<VkDeviceSize> m_uniform_buffer_offsets;
Vulkan::MemoryBlock m_uniform_buffer_memory;
VkDescriptorPool m_descriptor_pool = VK_NULL_HANDLE;
std::vector<VkDescriptorSet> m_descriptor_sets;
std::vector<VkCommandBuffer> m_command_buffers;
std::vector<VkSemaphore> m_render_done;
Vector3 m_camera_position = Vector3(0.0f, 0.0f, -3.0f);
Vector3 m_camera_z = Vector3(0.0f, 0.0f, 1.0f);
Vector3 m_camera_y = Vector3(0.0f, 1.0f, 0.0f);
TimePoint m_last_frame_time;
Duration m_time = Duration(0);
};

View File

@@ -1,51 +1,8 @@
// Copyright 2022 Simon Boyé
#pragma once #pragma once
#include <Eigen/Dense> #include <core/types.h>
#include <type_traits>
using Byte = unsigned char;
using Index = uint32_t;
using Real = float;
using Vector2 = Eigen::Matrix<Real, 2, 1>;
using Vector3 = Eigen::Matrix<Real, 3, 1>;
using Vector4 = Eigen::Matrix<Real, 4, 1>;
using Matrix2 = Eigen::Matrix<Real, 2, 2>;
using Matrix3 = Eigen::Matrix<Real, 3, 3>;
using Matrix4 = Eigen::Matrix<Real, 4, 4>;
using Transform = Eigen::Transform<Real, 3, Eigen::Affine>;
using AngleAxis = Eigen::AngleAxis<Real>;
using Quaternion = Eigen::Quaternion<Real>;
using Triangle = Eigen::Array<Index, 3, 1>;
template<typename Scalar, typename Derived0, typename Derived1>
auto lerp(
Scalar factor,
const Eigen::MatrixBase<Derived0>& m0,
const Eigen::MatrixBase<Derived1>& m1
) -> std::enable_if_t<
Derived0::RowsAtCompileTime == Derived1::RowsAtCompileTime &&
Derived0::ColsAtCompileTime == Derived1::ColsAtCompileTime,
Eigen::Matrix<
std::common_type_t<
Scalar,
typename Derived0::Scalar,
typename Derived1::Scalar
>,
Derived0::RowsAtCompileTime,
Derived0::ColsAtCompileTime
>
> {
return (Scalar(1) - factor) * m0 + factor * m1;
}
template<typename T> template<typename T>
@@ -85,12 +42,16 @@ public:
return m_stride; return m_stride;
} }
bool is_dense() const {
return m_stride == sizeof(T);
}
const T* data() const { const T* data() const {
return m_data; return reinterpret_cast<const T*>(m_data);
} }
T* data() { T* data() {
return m_data; return reinterpret_cast<T*>(m_data);
} }
const T& operator[](Index index) const { const T& operator[](Index index) const {
@@ -117,6 +78,3 @@ private:
Index m_size = 0; Index m_size = 0;
Index m_stride = sizeof(T); Index m_stride = sizeof(T);
}; };
using Vector3AV = ArrayView<Vector3>;
using TriangleAV = ArrayView<Triangle>;

View File

@@ -1,4 +1,5 @@
#include <Logger.h> // Copyright 2022 Simon Boyé
#include <core/Logger.h>
#include <iostream> #include <iostream>

View File

@@ -1,3 +1,4 @@
// Copyright 2022 Simon Boyé
#pragma once #pragma once
#include <ostream> #include <ostream>

View File

@@ -1,28 +1,6 @@
#include <utils.h> // Copyright 2022 Simon Boyé
#include <core/math.h>
#include <cstring>
#include <fstream>
Uuid make_uuid(uint8_t const* bytes) {
Uuid uuid;
std::memcpy(uuid.data(), bytes, UUID_SIZE);
return uuid;
}
std::vector<char> read_binary_file(const char* path) {
std::ifstream istream(path, std::ios_base::ate | std::ios_base::binary);
if(!istream.is_open())
throw std::runtime_error(cat("failed to open '", path, "'"));
std::vector<char> buffer(size_t(istream.tellg()));
istream.seekg(0);
istream.read(buffer.data(), buffer.size());
istream.close();
return buffer;
}
Eigen::Matrix4f projection_matrix(float x_min, float x_max, float y_min, float y_max, float z_min, float z_max) { Eigen::Matrix4f projection_matrix(float x_min, float x_max, float y_min, float y_max, float z_min, float z_max) {
auto const cx = x_max + x_min; auto const cx = x_max + x_min;
@@ -33,12 +11,12 @@ Eigen::Matrix4f projection_matrix(float x_min, float x_max, float y_min, float y
auto const dy = y_max - y_min; auto const dy = y_max - y_min;
auto const dz = z_max - z_min; auto const dz = z_max - z_min;
auto const pz = z_max * z_min; auto const sz = z_max / dz;
return (Eigen::Matrix4f() << return (Eigen::Matrix4f() <<
2.0f * z_min / dx, 0.0f, -cx / dx, 0.0f, 2.0f * z_min / dx, 0.0f, -cx / dx, 0.0f,
0.0f, 2.0f * z_min / dy, -cy / dy, 0.0f, 0.0f, 2.0f * z_min / dy, -cy / dy, 0.0f,
0.0f, 0.0f, cz / dz, -2.0f * pz / dz, 0.0f, 0.0f, sz, (1 - sz) * z_max,
0.0f, 0.0f, 1.0f, 0.0f 0.0f, 0.0f, 1.0f, 0.0f
).finished(); ).finished();
} }

60
src/core/math.h Normal file
View File

@@ -0,0 +1,60 @@
// Copyright 2022 Simon Boyé
#pragma once
#include <core/types.h>
#include <core/ArrayView.h>
#include <Eigen/Dense>
#include <type_traits>
using Real = float;
using Vector2 = Eigen::Matrix<Real, 2, 1>;
using Vector3 = Eigen::Matrix<Real, 3, 1>;
using Vector4 = Eigen::Matrix<Real, 4, 1>;
using Matrix2 = Eigen::Matrix<Real, 2, 2>;
using Matrix3 = Eigen::Matrix<Real, 3, 3>;
using Matrix4 = Eigen::Matrix<Real, 4, 4>;
using Transform = Eigen::Transform<Real, 3, Eigen::Affine>;
using AngleAxis = Eigen::AngleAxis<Real>;
using Quaternion = Eigen::Quaternion<Real>;
using Triangle = Eigen::Array<Index, 3, 1>;
using Vector3AV = ArrayView<Vector3>;
using TriangleAV = ArrayView<Triangle>;
template<typename Scalar, typename Derived0, typename Derived1>
auto lerp(
Scalar factor,
const Eigen::MatrixBase<Derived0>& m0,
const Eigen::MatrixBase<Derived1>& m1
) -> std::enable_if_t<
Derived0::RowsAtCompileTime == Derived1::RowsAtCompileTime &&
Derived0::ColsAtCompileTime == Derived1::ColsAtCompileTime,
Eigen::Matrix<
std::common_type_t<
Scalar,
typename Derived0::Scalar,
typename Derived1::Scalar
>,
Derived0::RowsAtCompileTime,
Derived0::ColsAtCompileTime
>
> {
return (Scalar(1) - factor) * m0 + factor * m1;
}
Eigen::Matrix4f projection_matrix(float x_min, float x_max, float y_min, float y_max, float z_min, float z_max);
Eigen::Matrix4f look_at_matrix(
const Eigen::Vector3f& cam_pos,
const Eigen::Vector3f& look_at,
const Eigen::Vector3f& up
);

11
src/core/types.h Normal file
View File

@@ -0,0 +1,11 @@
// Copyright 2022 Simon Boyé
#pragma once
#include <Eigen/Dense>
#include <type_traits>
using Byte = unsigned char;
using Index = uint32_t;

26
src/core/utils.cpp Normal file
View File

@@ -0,0 +1,26 @@
// Copyright 2022 Simon Boyé
#include <core/utils.h>
#include <cstring>
#include <fstream>
Uuid make_uuid(uint8_t const* bytes) {
Uuid uuid;
std::memcpy(uuid.data(), bytes, UUID_SIZE);
return uuid;
}
std::vector<char> read_binary_file(const char* path) {
std::ifstream istream(path, std::ios_base::ate | std::ios_base::binary);
if(!istream.is_open())
throw std::runtime_error(cat("failed to open '", path, "'"));
std::vector<char> buffer(size_t(istream.tellg()));
istream.seekg(0);
istream.read(buffer.data(), buffer.size());
istream.close();
return buffer;
}

View File

@@ -1,7 +1,6 @@
// Copyright 2022 Simon Boyé
#pragma once #pragma once
#include <Eigen/Dense>
#include <cassert> #include <cassert>
#include <vector> #include <vector>
#include <string> #include <string>
@@ -33,6 +32,15 @@ Guard<T> make_guard(T&& teardown) {
return Guard<T>(std::move(teardown)); return Guard<T>(std::move(teardown));
} }
template<typename T>
T& recreate_object(T& object) {
object.~T();
new(&object) T;
return object;
}
template<typename... Args> template<typename... Args>
std::string cat(Args&&... args) { std::string cat(Args&&... args) {
std::ostringstream ostream; std::ostringstream ostream;
@@ -40,8 +48,10 @@ std::string cat(Args&&... args) {
return ostream.str(); return ostream.str();
} }
std::vector<char> read_binary_file(const char* path); std::vector<char> read_binary_file(const char* path);
template<typename T> template<typename T>
class Array { class Array {
public: public:
@@ -55,6 +65,17 @@ public:
assert((size == 0 && m_data == nullptr) || (size != 0 && m_data != nullptr)); assert((size == 0 && m_data == nullptr) || (size != 0 && m_data != nullptr));
} }
// Array(T* item)
// : m_size(1)
// , m_data(item)
// {}
template<size_t Size>
Array(T (&array)[Size])
: m_size(Size)
, m_data(array)
{}
Array(const std::vector<T>& vector) Array(const std::vector<T>& vector)
: m_size(vector.size()) : m_size(vector.size())
, m_data(vector.data()) , m_data(vector.data())
@@ -76,10 +97,3 @@ private:
size_t m_size = 0; size_t m_size = 0;
T* m_data = nullptr; T* m_data = nullptr;
}; };
Eigen::Matrix4f projection_matrix(float x_min, float x_max, float y_min, float y_max, float z_min, float z_max);
Eigen::Matrix4f look_at_matrix(
const Eigen::Vector3f& cam_pos,
const Eigen::Vector3f& look_at,
const Eigen::Vector3f& up
);

View File

@@ -1,6 +1,8 @@
#include <Logger.h> // Copyright 2022 Simon Boyé
#include <VkExpe.h> #include <VkExpe.h>
#include <core/Logger.h>
int main(int argc, char** argv) { int main(int argc, char** argv) {
VkExpe vk_expe(argc, argv); VkExpe vk_expe(argc, argv);

View File

@@ -1,13 +1,16 @@
#include <Vulkan/Buffer.h> // Copyright 2022 Simon Boyé
#include <Vulkan/Context.h> #include <vk/Buffer.h>
#include <Logger.h> #include <vk/Context.h>
#include <core/Logger.h>
#include <stdexcept> #include <stdexcept>
#include <algorithm> #include <algorithm>
#include <cassert> #include <cassert>
#include <cstring>
namespace Vulkan { namespace vk {
Buffer::Buffer() noexcept { Buffer::Buffer() noexcept {
@@ -18,7 +21,7 @@ Buffer::Buffer(
VkDeviceSize size, VkDeviceSize size,
VkBufferUsageFlags usage VkBufferUsageFlags usage
) )
: m_context(&context) : Wrapper(context)
{ {
assert(*m_context); assert(*m_context);
@@ -48,35 +51,25 @@ Buffer::Buffer(
allocate_and_bind_memory(memory_properties); allocate_and_bind_memory(memory_properties);
} }
Buffer::Buffer(Buffer&& other) noexcept Buffer::Buffer(Buffer&& other) noexcept {
: m_context(other.m_context) swap(other);
, m_buffer(other.m_buffer)
, m_memory(std::move(other.m_memory))
{
other.m_context = nullptr;
other.m_buffer = VK_NULL_HANDLE;
} }
Buffer::~Buffer() noexcept { Buffer::~Buffer() noexcept {
if(is_valid()) { if(!is_null())
logger.warning() << "Buffer deleted before being destroyed";
destroy(); destroy();
} }
}
Buffer& Buffer::operator=(Buffer&& other) noexcept { Buffer& Buffer::operator=(Buffer&& other) noexcept {
if(&other != this) { swap(other);
using std::swap; if(other)
swap(m_context, other.m_context); other.destroy();
swap(m_buffer, other.m_buffer);
swap(m_memory, other.m_memory);
}
return *this; return *this;
} }
VkMemoryRequirements Buffer::memory_requirements() const noexcept { VkMemoryRequirements Buffer::memory_requirements() const noexcept {
assert(is_valid()); assert(!is_null());
assert(*m_context); assert(*m_context);
VkMemoryRequirements memory_requirements; VkMemoryRequirements memory_requirements;
@@ -91,7 +84,7 @@ VkMemoryRequirements Buffer::memory_requirements() const noexcept {
void Buffer::bind_memory(const MemoryBlock& memory_block, VkDeviceSize offset) { void Buffer::bind_memory(const MemoryBlock& memory_block, VkDeviceSize offset) {
assert(is_valid()); assert(!is_null());
assert(*m_context); assert(*m_context);
assert(memory_block); assert(memory_block);
@@ -111,7 +104,7 @@ void Buffer::bind_memory(MemoryBlock&& memory_block) {
} }
void Buffer::allocate_and_bind_memory(VkMemoryPropertyFlags memory_properties) { void Buffer::allocate_and_bind_memory(VkMemoryPropertyFlags memory_properties) {
assert(is_valid()); assert(!is_null());
assert(*m_context); assert(*m_context);
const auto memory_requirements = this->memory_requirements(); const auto memory_requirements = this->memory_requirements();
@@ -126,7 +119,7 @@ void Buffer::allocate_and_bind_memory(VkMemoryPropertyFlags memory_properties) {
void Buffer::upload(size_t size, void* src_buffer, uint32_t dst_queue_family) { void Buffer::upload(size_t size, void* src_buffer, uint32_t dst_queue_family) {
assert(is_valid()); assert(!is_null());
assert(*m_context); assert(*m_context);
const bool use_staging_buffer = const bool use_staging_buffer =
@@ -163,7 +156,7 @@ void Buffer::upload(size_t size, void* src_buffer, uint32_t dst_queue_family) {
void Buffer::destroy() noexcept { void Buffer::destroy() noexcept {
assert(is_valid()); assert(!is_null());
assert(*m_context); assert(*m_context);
if(m_memory) { if(m_memory) {

View File

@@ -1,9 +1,11 @@
// Copyright 2022 Simon Boyé
#pragma once #pragma once
#include <utils.h> #include <vk/forward.h>
#include <vk/Memory.h>
#include <vk/Wrapper.h>
#include <Vulkan/forward.h> #include <core/utils.h>
#include <Vulkan/Memory.h>
#include <vulkan/vulkan.h> #include <vulkan/vulkan.h>
@@ -11,10 +13,10 @@
#include <vector> #include <vector>
namespace Vulkan { namespace vk {
class Buffer { class Buffer: public Wrapper {
public: public:
Buffer() noexcept; Buffer() noexcept;
Buffer( Buffer(
@@ -28,27 +30,19 @@ public:
VkBufferUsageFlags usage, VkBufferUsageFlags usage,
VkMemoryPropertyFlags memory_properties VkMemoryPropertyFlags memory_properties
); );
Buffer(const Buffer&) = delete; Buffer(const Buffer&) = default;
Buffer(Buffer&& other) noexcept; Buffer(Buffer&& other) noexcept;
~Buffer() noexcept; ~Buffer() noexcept;
Buffer& operator=(const Buffer&) = delete; Buffer& operator=(const Buffer&) = default;
Buffer& operator=(Buffer&& other) noexcept; Buffer& operator=(Buffer&& other) noexcept;
inline explicit operator bool() const { inline explicit operator bool() const {
return is_valid(); return !is_null();
} }
inline bool is_valid() const { inline bool is_null() const {
return m_buffer != VK_NULL_HANDLE; return m_buffer == VK_NULL_HANDLE;
}
inline Context* context() noexcept {
return m_context;
}
inline const Context* context() const noexcept {
return m_context;
} }
inline operator VkBuffer() const noexcept { inline operator VkBuffer() const noexcept {
@@ -75,10 +69,20 @@ public:
void upload(size_t size, void* src_buffer, uint32_t dst_queue_family); void upload(size_t size, void* src_buffer, uint32_t dst_queue_family);
inline void swap(Buffer& other) noexcept {
using std::swap;
Wrapper::swap(other);
swap(m_buffer, other.m_buffer);
swap(m_memory, other.m_memory);
}
friend inline void swap(Buffer& buffer_0, Buffer& buffer_1) noexcept {
buffer_0.swap(buffer_1);
}
void destroy() noexcept; void destroy() noexcept;
private: private:
Context* m_context = nullptr;
VkBuffer m_buffer = VK_NULL_HANDLE; VkBuffer m_buffer = VK_NULL_HANDLE;
MemoryBlock m_memory; MemoryBlock m_memory;
}; };

70
src/vk/CommandBuffer.cpp Normal file
View File

@@ -0,0 +1,70 @@
// Copyright 2022 Simon Boyé
#include <vk/CommandBuffer.h>
#include <vk/Context.h>
#include <cassert>
namespace vk {
CommandBuffer::CommandBuffer() noexcept {
}
CommandBuffer::CommandBuffer(Context& context, VkCommandPool command_pool, VkCommandBufferLevel level)
: Wrapper(context)
, m_command_pool(command_pool)
{
assert(m_context);
assert(m_command_pool != VK_NULL_HANDLE);
VkCommandBufferAllocateInfo allocate_info {
.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO,
.commandPool = m_command_pool,
.level = level,
.commandBufferCount = 1,
};
if(vkAllocateCommandBuffers(
context.device(),
&allocate_info,
&m_command_buffer
) != VK_SUCCESS)
throw std::runtime_error("failed to allocate command pool");
}
CommandBuffer::CommandBuffer(CommandBuffer&& other) noexcept
{
swap(other);
}
CommandBuffer::~CommandBuffer() noexcept {
if(!is_null())
destroy();
}
CommandBuffer& CommandBuffer::operator=(CommandBuffer&& other) noexcept {
swap(other);
if(other)
other.destroy();
return *this;
}
void CommandBuffer::destroy() noexcept {
assert(!is_null());
assert(m_context);
vkFreeCommandBuffers(
m_context->device(),
m_command_pool,
1,
&m_command_buffer
);
m_command_buffer = nullptr;
}
}

67
src/vk/CommandBuffer.h Normal file
View File

@@ -0,0 +1,67 @@
// Copyright 2022 Simon Boyé
#pragma once
#include <vk/forward.h>
#include <vk/Wrapper.h>
#include <vulkan/vulkan.h>
namespace vk {
class CommandBuffer: public Wrapper {
public:
CommandBuffer() noexcept;
CommandBuffer(Context& context, VkCommandPool command_pool, VkCommandBufferLevel level=VK_COMMAND_BUFFER_LEVEL_PRIMARY);
CommandBuffer(const CommandBuffer&) = default;
CommandBuffer(CommandBuffer&& other) noexcept;
~CommandBuffer() noexcept;
CommandBuffer& operator=(const CommandBuffer&) = default;
CommandBuffer& operator=(CommandBuffer&& other) noexcept;
explicit inline operator bool() const noexcept {
return !is_null();
}
inline bool is_null() const noexcept {
return m_command_buffer == VK_NULL_HANDLE;
}
inline const VkCommandPool command_pool() const noexcept {
return m_command_pool;
}
inline VkCommandPool command_pool() noexcept {
return m_command_pool;
}
inline operator VkCommandBuffer() noexcept {
return m_command_buffer;
}
inline VkCommandBuffer command_buffer() noexcept {
return m_command_buffer;
}
inline void swap(CommandBuffer& other) noexcept {
using std::swap;
Wrapper::swap(other);
swap(m_command_pool, other.m_command_pool);
swap(m_command_buffer, other.m_command_buffer);
}
friend inline void swap(CommandBuffer& command_buffer_0, CommandBuffer& command_buffer_1) noexcept {
command_buffer_0.swap(command_buffer_1);
}
void destroy() noexcept;
private:
VkCommandPool m_command_pool = VK_NULL_HANDLE;
VkCommandBuffer m_command_buffer = VK_NULL_HANDLE;
};
}

67
src/vk/CommandPool.cpp Normal file
View File

@@ -0,0 +1,67 @@
// Copyright 2022 Simon Boyé
#include <vk/CommandPool.h>
#include <vk/Context.h>
#include <cassert>
namespace vk {
CommandPool::CommandPool() noexcept {
}
CommandPool::CommandPool(Context& context, uint32_t queue_family, VkCommandPoolCreateFlags flags)
: Wrapper(context)
{
assert(m_context);
VkCommandPoolCreateInfo create_info {
.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO,
.flags = flags,
.queueFamilyIndex = queue_family,
};
if(vkCreateCommandPool(
context.device(),
&create_info,
nullptr,
&m_command_pool
) != VK_SUCCESS)
throw std::runtime_error("failed to create command pool");
}
CommandPool::CommandPool(CommandPool&& other) noexcept
{
swap(other);
}
CommandPool::~CommandPool() noexcept {
if(!is_null())
destroy();
}
CommandPool& CommandPool::operator=(CommandPool&& other) noexcept {
swap(other);
if(other)
other.destroy();
return *this;
}
void CommandPool::destroy() noexcept {
assert(!is_null());
assert(m_context);
vkDestroyCommandPool(
m_context->device(),
m_command_pool,
nullptr
);
m_command_pool = nullptr;
}
}

57
src/vk/CommandPool.h Normal file
View File

@@ -0,0 +1,57 @@
// Copyright 2022 Simon Boyé
#pragma once
#include <vk/forward.h>
#include <vk/Wrapper.h>
#include <vulkan/vulkan.h>
namespace vk {
class CommandPool: public Wrapper {
public:
CommandPool() noexcept;
CommandPool(Context& context, uint32_t queue_family, VkCommandPoolCreateFlags flags=0);
CommandPool(const CommandPool&) = default;
CommandPool(CommandPool&& other) noexcept;
~CommandPool() noexcept;
CommandPool& operator=(const CommandPool&) = default;
CommandPool& operator=(CommandPool&& other) noexcept;
explicit inline operator bool() const noexcept {
return !is_null();
}
inline bool is_null() const noexcept {
return m_command_pool == VK_NULL_HANDLE;
}
inline operator VkCommandPool() noexcept {
return m_command_pool;
}
inline VkCommandPool command_pool() noexcept {
return m_command_pool;
}
inline void swap(CommandPool& other) noexcept {
using std::swap;
Wrapper::swap(other);
swap(m_command_pool, other.m_command_pool);
}
friend inline void swap(CommandPool& command_pool_0, CommandPool& command_pool_1) noexcept {
command_pool_0.swap(command_pool_1);
}
void destroy() noexcept;
private:
VkCommandPool m_command_pool = VK_NULL_HANDLE;
};
}

View File

@@ -1,8 +1,10 @@
#include <Vulkan/Context.h> // Copyright 2022 Simon Boyé
#include <Vulkan/Memory.h> #include <vk/Context.h>
#include <vk/Memory.h>
#include <vk/CommandBuffer.h>
#include <utils.h> #include <core/utils.h>
#include <Logger.h> #include <core/Logger.h>
#include <SDL2/SDL_vulkan.h> #include <SDL2/SDL_vulkan.h>
@@ -12,7 +14,7 @@
#include <tuple> #include <tuple>
namespace Vulkan { namespace vk {
ContextSettings::ContextSettings() { ContextSettings::ContextSettings() {
@@ -139,8 +141,8 @@ void Context::shutdown() {
m_allocator->free_all_pages(); m_allocator->free_all_pages();
m_allocator.reset(); m_allocator.reset();
destroy_fence(m_transfer_fence); m_transfer_fence.destroy();
destroy_command_pool(m_transfer_command_pool); m_transfer_command_pool.destroy();
destroy_device(m_device); destroy_device(m_device);
destroy_surface(m_surface); destroy_surface(m_surface);
@@ -281,18 +283,7 @@ VkShaderModule Context::create_shader_module_from_file(const char* path) {
} }
void Context::copy_buffer(VkBuffer dst, VkBuffer src, uint32_t dst_queue_family, VkDeviceSize size) { void Context::copy_buffer(VkBuffer dst, VkBuffer src, uint32_t dst_queue_family, VkDeviceSize size) {
VkCommandBufferAllocateInfo alloc_info { CommandBuffer command_buffer(*this, m_transfer_command_pool);
.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO,
.commandPool = m_transfer_command_pool,
.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY,
.commandBufferCount = 1,
};
VkCommandBuffer command_buffer;
vkAllocateCommandBuffers(m_device, &alloc_info, &command_buffer);
auto const command_buffer_guard = make_guard([this, command_buffer]() {
vkFreeCommandBuffers(m_device, m_transfer_command_pool, 1, &command_buffer);
});
VkCommandBufferBeginInfo begin_info { VkCommandBufferBeginInfo begin_info {
.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO, .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO,
@@ -329,19 +320,22 @@ void Context::copy_buffer(VkBuffer dst, VkBuffer src, uint32_t dst_queue_family,
vkEndCommandBuffer(command_buffer); vkEndCommandBuffer(command_buffer);
vkResetFences(m_device, 1, &m_transfer_fence); m_transfer_fence.reset();
VkCommandBuffer command_buffers[] = {
command_buffer
};
VkSubmitInfo submit_info { VkSubmitInfo submit_info {
.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO, .sType = VK_STRUCTURE_TYPE_SUBMIT_INFO,
.commandBufferCount = 1, .commandBufferCount = 1,
.pCommandBuffers = &command_buffer, .pCommandBuffers = command_buffers,
}; };
vkQueueSubmit( vkQueueSubmit(
queue(m_transfer_queue_index), queue(m_transfer_queue_index),
1, &submit_info, 1, &submit_info,
m_transfer_fence m_transfer_fence
); );
vkWaitForFences(m_device, 1, &m_transfer_fence, VK_TRUE, UINT64_MAX); m_transfer_fence.wait();
} }
@@ -568,7 +562,7 @@ void Context::create_instance(const ContextSettings& settings) {
if(vkCreateInstance(&instance_info, nullptr, &m_instance) != VK_SUCCESS) if(vkCreateInstance(&instance_info, nullptr, &m_instance) != VK_SUCCESS)
throw std::runtime_error("failed to create vulkan instance"); throw std::runtime_error("failed to create vulkan instance");
initialize_extension_functions(); initialize_instance_extension_functions();
if(settings.debug()) { if(settings.debug()) {
createDebugUtilsMessenger( createDebugUtilsMessenger(
@@ -739,7 +733,9 @@ void Context::create_device(const ContextSettings& settings) {
.geometryShader = true, .geometryShader = true,
}; };
std::vector<const char*> extensions; std::vector<const char*> extensions {
VK_KHR_DRAW_INDIRECT_COUNT_EXTENSION_NAME,
};
if(m_window) { if(m_window) {
extensions.push_back(VK_KHR_SWAPCHAIN_EXTENSION_NAME); extensions.push_back(VK_KHR_SWAPCHAIN_EXTENSION_NAME);
}; };
@@ -762,6 +758,8 @@ void Context::create_device(const ContextSettings& settings) {
) != VK_SUCCESS) ) != VK_SUCCESS)
throw std::runtime_error("failed to create logical device"); throw std::runtime_error("failed to create logical device");
initialize_device_extension_functions();
m_queues.resize(m_queue_families.size()); m_queues.resize(m_queue_families.size());
for(size_t index = 0; index < m_queue_families.size(); ++index) { for(size_t index = 0; index < m_queue_families.size(); ++index) {
vkGetDeviceQueue( vkGetDeviceQueue(
@@ -775,31 +773,18 @@ void Context::create_device(const ContextSettings& settings) {
} }
void Context::create_internal_objects() { void Context::create_internal_objects() {
VkCommandPoolCreateInfo pool_info { m_transfer_command_pool = CommandPool(
.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO, *this,
.flags = 0, queue_family(m_transfer_queue_index),
.queueFamilyIndex = queue_family(m_transfer_queue_index), VK_COMMAND_POOL_CREATE_TRANSIENT_BIT
}; );
if(vkCreateCommandPool( m_transfer_fence = Fence(*this);
m_device,
&pool_info,
nullptr,
&m_transfer_command_pool
) != VK_SUCCESS) {
throw std::runtime_error("failed to create command pool");
} }
VkFenceCreateInfo fence_info { void Context::initialize_instance_extension_functions() {
.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO,
};
if(vkCreateFence(m_device, &fence_info, nullptr, &m_transfer_fence) != VK_SUCCESS)
throw std::runtime_error("failed to create fence");
}
void Context::initialize_extension_functions() {
uint32_t errors_count = 0; uint32_t errors_count = 0;
#define GET_PROC_ADDR(ptr_name, func_name) \ #define GET_INSTANCE_PROC_ADDR(ptr_name, func_name) \
ptr_name = (PFN_vk ## func_name)vkGetInstanceProcAddr( \ ptr_name = (PFN_vk ## func_name)vkGetInstanceProcAddr( \
m_instance, "vk" #func_name); \ m_instance, "vk" #func_name); \
if(ptr_name == nullptr) \ if(ptr_name == nullptr) \
@@ -808,9 +793,27 @@ void Context::initialize_extension_functions() {
errors_count += 1; \ errors_count += 1; \
} }
GET_PROC_ADDR(createDebugUtilsMessenger, CreateDebugUtilsMessengerEXT) GET_INSTANCE_PROC_ADDR(createDebugUtilsMessenger, CreateDebugUtilsMessengerEXT)
GET_PROC_ADDR(destroyDebugUtilsMessenger, DestroyDebugUtilsMessengerEXT) GET_INSTANCE_PROC_ADDR(destroyDebugUtilsMessenger, DestroyDebugUtilsMessengerEXT)
GET_PROC_ADDR(setDebugUtilsObjectName, SetDebugUtilsObjectNameEXT) GET_INSTANCE_PROC_ADDR(setDebugUtilsObjectName, SetDebugUtilsObjectNameEXT)
if(errors_count != 0)
throw std::runtime_error("failed to load extensions");
}
void Context::initialize_device_extension_functions() {
uint32_t errors_count = 0;
#define GET_DEVICE_PROC_ADDR(ptr_name, func_name) \
ptr_name = (PFN_vk ## func_name)vkGetDeviceProcAddr( \
m_device, "vk" #func_name); \
if(ptr_name == nullptr) \
{ \
logger.error() << "failed to load extension function 'vk" #func_name "'"; \
errors_count += 1; \
}
GET_DEVICE_PROC_ADDR(cmdDrawIndexedIndirectCount, CmdDrawIndexedIndirectCountKHR)
if(errors_count != 0) if(errors_count != 0)
throw std::runtime_error("failed to load extensions"); throw std::runtime_error("failed to load extensions");
@@ -822,6 +825,9 @@ VKAPI_ATTR VkBool32 VKAPI_CALL Context::log_debug_message(
const VkDebugUtilsMessengerCallbackDataEXT* data, const VkDebugUtilsMessengerCallbackDataEXT* data,
void* user_data void* user_data
) { ) {
if(data->messageIdNumber == 0) // Loader message
return VK_FALSE;
{ {
auto stream = [severity]() { auto stream = [severity]() {
switch(severity) { switch(severity) {
@@ -853,7 +859,9 @@ VKAPI_ATTR VkBool32 VKAPI_CALL Context::log_debug_message(
} }
if(severity == VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT if(severity == VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT
&& data->messageIdNumber != 2094043421 // wrong swapchain extent && (
data->messageIdNumber != 2094043421 // wrong swapchain extent
)
) )
abort(); abort();

View File

@@ -1,10 +1,15 @@
// Copyright 2022 Simon Boyé
#pragma once #pragma once
#include <utils.h> #include <vk/forward.h>
#include <Logger.h> #include <vk/CommandPool.h>
#include <Vulkan/forward.h> #include <vk/Fence.h>
#include <core/utils.h>
#include <core/Logger.h>
#include <SDL2/SDL.h> #include <SDL2/SDL.h>
#include <vulkan/vulkan.h> #include <vulkan/vulkan.h>
#include <optional> #include <optional>
@@ -13,7 +18,7 @@
#include <functional> #include <functional>
namespace Vulkan { namespace vk {
class Context; class Context;
@@ -63,11 +68,11 @@ public:
Context& operator=(const Context&) = delete; Context& operator=(const Context&) = delete;
explicit inline operator bool() const { explicit inline operator bool() const {
return is_valid(); return !is_null();
} }
inline bool is_valid() const { inline bool is_null() const {
return m_instance != nullptr; return m_instance == nullptr;
} }
VkInstance instance(); VkInstance instance();
@@ -149,6 +154,8 @@ public:
PFN_vkDestroyDebugUtilsMessengerEXT destroyDebugUtilsMessenger = nullptr; PFN_vkDestroyDebugUtilsMessengerEXT destroyDebugUtilsMessenger = nullptr;
PFN_vkSetDebugUtilsObjectNameEXT setDebugUtilsObjectName = nullptr; PFN_vkSetDebugUtilsObjectNameEXT setDebugUtilsObjectName = nullptr;
PFN_vkCmdDrawIndexedIndirectCountKHR cmdDrawIndexedIndirectCount = nullptr;
private: private:
void create_instance(const ContextSettings& settings); void create_instance(const ContextSettings& settings);
void create_surface(const ContextSettings& settings); void create_surface(const ContextSettings& settings);
@@ -160,7 +167,8 @@ private:
void create_device(const ContextSettings& settings); void create_device(const ContextSettings& settings);
void create_internal_objects(); void create_internal_objects();
void initialize_extension_functions(); void initialize_instance_extension_functions();
void initialize_device_extension_functions();
static VKAPI_ATTR VkBool32 VKAPI_CALL log_debug_message( static VKAPI_ATTR VkBool32 VKAPI_CALL log_debug_message(
VkDebugUtilsMessageSeverityFlagBitsEXT severity, VkDebugUtilsMessageSeverityFlagBitsEXT severity,
@@ -190,8 +198,8 @@ private:
VkPresentModeKHR m_present_mode; VkPresentModeKHR m_present_mode;
uint32_t m_transfer_queue_index = UINT32_MAX; uint32_t m_transfer_queue_index = UINT32_MAX;
VkCommandPool m_transfer_command_pool = VK_NULL_HANDLE; CommandPool m_transfer_command_pool;
VkFence m_transfer_fence = VK_NULL_HANDLE; Fence m_transfer_fence;
std::vector<ContextDestructionCallback> m_context_destruction_callbacks; std::vector<ContextDestructionCallback> m_context_destruction_callbacks;

74
src/vk/DescriptorPool.cpp Normal file
View File

@@ -0,0 +1,74 @@
// Copyright 2022 Simon Boyé
#include <vk/DescriptorPool.h>
#include <vk/Context.h>
#include <cassert>
namespace vk {
DescriptorPool::DescriptorPool() noexcept {
}
DescriptorPool::DescriptorPool(
Context& context,
uint32_t max_sets,
Array<const VkDescriptorPoolSize> pool_sizes,
VkDescriptorPoolCreateFlags flags
)
: Wrapper(context)
{
assert(m_context);
VkDescriptorPoolCreateInfo create_info {
.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO,
.flags = flags,
.maxSets = max_sets,
.poolSizeCount = uint32_t(pool_sizes.size()),
.pPoolSizes = pool_sizes.data(),
};
if(vkCreateDescriptorPool(
context.device(),
&create_info,
nullptr,
&m_descriptor_pool
) != VK_SUCCESS)
throw std::runtime_error("failed to create descriptor pool");
}
DescriptorPool::DescriptorPool(DescriptorPool&& other) noexcept
{
swap(other);
}
DescriptorPool::~DescriptorPool() noexcept {
if(!is_null())
destroy();
}
DescriptorPool& DescriptorPool::operator=(DescriptorPool&& other) noexcept {
swap(other);
if(other)
other.destroy();
return *this;
}
void DescriptorPool::destroy() noexcept {
assert(!is_null());
assert(m_context);
vkDestroyDescriptorPool(
m_context->device(),
m_descriptor_pool,
nullptr
);
m_descriptor_pool = nullptr;
}
}

64
src/vk/DescriptorPool.h Normal file
View File

@@ -0,0 +1,64 @@
// Copyright 2022 Simon Boyé
#pragma once
#include <core/utils.h>
#include <vk/forward.h>
#include <vk/Wrapper.h>
#include <vulkan/vulkan.h>
namespace vk {
class DescriptorPool: public Wrapper {
public:
DescriptorPool() noexcept;
DescriptorPool(
Context& context,
uint32_t max_sets,
Array<const VkDescriptorPoolSize> pool_sizes,
VkDescriptorPoolCreateFlags flags=0
);
DescriptorPool(const DescriptorPool&) = default;
DescriptorPool(DescriptorPool&& other) noexcept;
~DescriptorPool() noexcept;
DescriptorPool& operator=(const DescriptorPool&) = default;
DescriptorPool& operator=(DescriptorPool&& other) noexcept;
explicit inline operator bool() const noexcept {
return !is_null();
}
inline bool is_null() const noexcept {
return m_descriptor_pool == VK_NULL_HANDLE;
}
inline operator VkDescriptorPool() noexcept {
return m_descriptor_pool;
}
inline VkDescriptorPool descriptor_pool() noexcept {
return m_descriptor_pool;
}
inline void swap(DescriptorPool& other) noexcept {
using std::swap;
Wrapper::swap(other);
swap(m_descriptor_pool, other.m_descriptor_pool);
}
friend inline void swap(DescriptorPool& descriptor_pool_0, DescriptorPool& descriptor_pool_1) noexcept {
descriptor_pool_0.swap(descriptor_pool_1);
}
void destroy() noexcept;
private:
VkDescriptorPool m_descriptor_pool = VK_NULL_HANDLE;
};
}

76
src/vk/DescriptorSet.cpp Normal file
View File

@@ -0,0 +1,76 @@
// Copyright 2022 Simon Boyé
#include <vk/DescriptorSet.h>
#include <vk/Context.h>
#include <cassert>
namespace vk {
DescriptorSet::DescriptorSet() noexcept {
}
DescriptorSet::DescriptorSet(
Context& context,
VkDescriptorPool descriptor_pool,
const VkDescriptorSetLayout& set_layout
)
: Wrapper(context, DontOwnUnderlyingObject)
, m_descriptor_pool(descriptor_pool)
{
assert(m_descriptor_pool != VK_NULL_HANDLE);
VkDescriptorSetAllocateInfo create_info {
.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO,
.descriptorPool = m_descriptor_pool,
.descriptorSetCount = 1,
.pSetLayouts = &set_layout,
};
if(vkAllocateDescriptorSets(
context.device(),
&create_info,
&m_descriptor_set
) != VK_SUCCESS)
throw std::runtime_error("failed to create descriptor_set");
}
DescriptorSet::DescriptorSet(DescriptorSet&& other) noexcept
{
swap(other);
}
DescriptorSet::~DescriptorSet() noexcept {
if(!is_null())
destroy();
}
DescriptorSet& DescriptorSet::operator=(DescriptorSet&& other) noexcept {
swap(other);
if(other)
other.destroy();
return *this;
}
void DescriptorSet::destroy() noexcept {
assert(!is_null());
assert(m_context);
if (!own_underlying_object())
return;
vkFreeDescriptorSets(
m_context->device(),
m_descriptor_pool,
1,
&m_descriptor_set
);
m_descriptor_set = nullptr;
}
}

71
src/vk/DescriptorSet.h Normal file
View File

@@ -0,0 +1,71 @@
// Copyright 2022 Simon Boyé
#pragma once
#include <vk/forward.h>
#include <vk/Wrapper.h>
#include <vulkan/vulkan.h>
namespace vk {
class DescriptorSet: public Wrapper {
public:
DescriptorSet() noexcept;
DescriptorSet(
Context& context,
VkDescriptorPool descriptor_pool,
const VkDescriptorSetLayout& set_layout
);
DescriptorSet(const DescriptorSet&) = default;
DescriptorSet(DescriptorSet&& other) noexcept;
~DescriptorSet() noexcept;
DescriptorSet& operator=(const DescriptorSet&) = default;
DescriptorSet& operator=(DescriptorSet&& other) noexcept;
explicit inline operator bool() const noexcept {
return !is_null();
}
inline bool is_null() const noexcept {
return m_descriptor_set == VK_NULL_HANDLE;
}
inline const VkDescriptorPool descriptor_pool() const noexcept {
return m_descriptor_pool;
}
inline VkDescriptorPool descriptor_pool() noexcept {
return m_descriptor_pool;
}
inline operator VkDescriptorSet() noexcept {
return m_descriptor_set;
}
inline VkDescriptorSet descriptor_set() noexcept {
return m_descriptor_set;
}
inline void swap(DescriptorSet& other) noexcept {
using std::swap;
Wrapper::swap(other);
swap(m_descriptor_pool, other.m_descriptor_pool);
swap(m_descriptor_set, other.m_descriptor_set);
}
friend inline void swap(DescriptorSet& descriptor_set_0, DescriptorSet& descriptor_set_1) noexcept {
descriptor_set_0.swap(descriptor_set_1);
}
void destroy() noexcept;
private:
VkDescriptorPool m_descriptor_pool = VK_NULL_HANDLE;
VkDescriptorSet m_descriptor_set = VK_NULL_HANDLE;
};
}

View File

@@ -0,0 +1,67 @@
// Copyright 2022 Simon Boyé
#include <vk/DescriptorSetLayout.h>
#include <vk/Context.h>
#include <cassert>
namespace vk {
DescriptorSetLayout::DescriptorSetLayout() noexcept {
}
DescriptorSetLayout::DescriptorSetLayout(Context& context, Array<const VkDescriptorSetLayoutBinding> bindings)
: Wrapper(context)
{
assert(m_context);
VkDescriptorSetLayoutCreateInfo create_info {
.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO,
.bindingCount = uint32_t(bindings.size()),
.pBindings = bindings.data(),
};
if(vkCreateDescriptorSetLayout(
context.device(),
&create_info,
nullptr,
&m_descriptor_set_layout
) != VK_SUCCESS)
throw std::runtime_error("failed to create descriptor set layout");
}
DescriptorSetLayout::DescriptorSetLayout(DescriptorSetLayout&& other) noexcept
{
swap(other);
}
DescriptorSetLayout::~DescriptorSetLayout() noexcept {
if(!is_null())
destroy();
}
DescriptorSetLayout& DescriptorSetLayout::operator=(DescriptorSetLayout&& other) noexcept {
swap(other);
if(other)
other.destroy();
return *this;
}
void DescriptorSetLayout::destroy() noexcept {
assert(!is_null());
assert(m_context);
vkDestroyDescriptorSetLayout(
m_context->device(),
m_descriptor_set_layout,
nullptr
);
m_descriptor_set_layout = nullptr;
}
}

View File

@@ -0,0 +1,59 @@
// Copyright 2022 Simon Boyé
#pragma once
#include <core/utils.h>
#include <vk/forward.h>
#include <vk/Wrapper.h>
#include <vulkan/vulkan.h>
namespace vk {
class DescriptorSetLayout: public Wrapper {
public:
DescriptorSetLayout() noexcept;
DescriptorSetLayout(Context& context, Array<const VkDescriptorSetLayoutBinding> bindings);
DescriptorSetLayout(const DescriptorSetLayout&) = default;
DescriptorSetLayout(DescriptorSetLayout&& other) noexcept;
~DescriptorSetLayout() noexcept;
DescriptorSetLayout& operator=(const DescriptorSetLayout&) = default;
DescriptorSetLayout& operator=(DescriptorSetLayout&& other) noexcept;
explicit inline operator bool() const noexcept {
return !is_null();
}
inline bool is_null() const noexcept {
return m_descriptor_set_layout == VK_NULL_HANDLE;
}
inline operator VkDescriptorSetLayout() noexcept {
return m_descriptor_set_layout;
}
inline VkDescriptorSetLayout descriptor_set_layout() noexcept {
return m_descriptor_set_layout;
}
inline void swap(DescriptorSetLayout& other) noexcept {
using std::swap;
Wrapper::swap(other);
swap(m_descriptor_set_layout, other.m_descriptor_set_layout);
}
friend inline void swap(DescriptorSetLayout& descriptor_set_layout_0, DescriptorSetLayout& descriptor_set_layout_1) noexcept {
descriptor_set_layout_0.swap(descriptor_set_layout_1);
}
void destroy() noexcept;
private:
VkDescriptorSetLayout m_descriptor_set_layout = VK_NULL_HANDLE;
};
}

123
src/vk/Fence.cpp Normal file
View File

@@ -0,0 +1,123 @@
// Copyright 2022 Simon Boyé
#include <vk/Fence.h>
#include <vk/Context.h>
#include <cassert>
namespace vk {
Fence::Fence() noexcept {
}
Fence::Fence(Context& context, VkFenceCreateFlags flags)
: Wrapper(context)
{
assert(m_context);
VkFenceCreateInfo create_info {
.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO,
.flags = flags,
};
if(vkCreateFence(
context.device(),
&create_info,
nullptr,
&m_fence
) != VK_SUCCESS)
throw std::runtime_error("failed to create fence");
}
Fence::Fence(Fence&& other) noexcept
{
swap(other);
}
Fence::~Fence() noexcept {
if(!is_null())
destroy();
}
Fence& Fence::operator=(Fence&& other) noexcept {
swap(other);
if(other)
other.destroy();
return *this;
}
VkResult Fence::get_status() const {
assert(m_context);
assert(!is_null());
VkResult result = vkGetFenceStatus(
m_context->device(),
m_fence
);
if(result < 0)
throw std::runtime_error("failed to get fence status");
return result;
}
void Fence::reset() {
assert(m_context);
assert(!is_null());
if(vkResetFences(
m_context->device(),
1,
&m_fence
) != VK_SUCCESS)
throw std::runtime_error("failed to reset fence");
}
void Fence::wait(uint64_t timeout) const {
assert(m_context);
assert(!is_null());
wait_for_fences(
m_context->device(),
ArrayView<VkFence>(1, const_cast<VkFence*>(&m_fence)),
true,
timeout
);
}
void Fence::destroy() noexcept {
assert(!is_null());
assert(m_context);
vkDestroyFence(
m_context->device(),
m_fence,
nullptr
);
m_fence = nullptr;
}
void wait_for_fences(VkDevice device, ArrayView<VkFence> fences, bool wait_all, uint64_t timeout) {
assert(fences.is_dense());
if(fences.size() == 0)
return;
if(vkWaitForFences(
device,
fences.size(),
fences.data(),
wait_all,
timeout
) != VK_SUCCESS)
throw std::runtime_error("failed to wait for fences");
}
}

65
src/vk/Fence.h Normal file
View File

@@ -0,0 +1,65 @@
// Copyright 2022 Simon Boyé
#pragma once
#include <core/ArrayView.h>
#include <vk/forward.h>
#include <vk/Wrapper.h>
#include <vulkan/vulkan.h>
namespace vk {
class Fence: public Wrapper {
public:
Fence() noexcept;
Fence(Context& context, VkFenceCreateFlags flags=0);
Fence(const Fence&) = default;
Fence(Fence&& other) noexcept;
~Fence() noexcept;
Fence& operator=(const Fence&) = default;
Fence& operator=(Fence&& other) noexcept;
explicit inline operator bool() const noexcept {
return !is_null();
}
inline bool is_null() const noexcept {
return m_fence == VK_NULL_HANDLE;
}
inline operator VkFence() noexcept {
return m_fence;
}
inline VkFence fence() noexcept {
return m_fence;
}
VkResult get_status() const;
void reset();
void wait(uint64_t timeout=UINT64_MAX) const;
inline void swap(Fence& other) noexcept {
using std::swap;
Wrapper::swap(other);
swap(m_fence, other.m_fence);
}
friend inline void swap(Fence& fence_0, Fence& fence_1) noexcept {
fence_0.swap(fence_1);
}
void destroy() noexcept;
private:
VkFence m_fence = VK_NULL_HANDLE;
};
void wait_for_fences(VkDevice device, ArrayView<VkFence> fences, bool wait_all=true, uint64_t timeout=UINT64_MAX);
}

86
src/vk/Framebuffer.cpp Normal file
View File

@@ -0,0 +1,86 @@
// Copyright 2022 Simon Boyé
#include <vk/Framebuffer.h>
#include <vk/Context.h>
#include <cassert>
namespace vk {
Framebuffer::Framebuffer() noexcept {
}
Framebuffer::Framebuffer(
Context& context,
VkRenderPass render_pass,
Array<VkImageView> attachments,
uint32_t width, uint32_t height, uint32_t layers
)
: Wrapper(context)
{
assert(m_context);
assert(render_pass);
VkFramebufferCreateInfo create_info {
.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO,
.renderPass = render_pass,
.attachmentCount = uint32_t(attachments.size()),
.pAttachments = attachments.data(),
.width = width,
.height = height,
.layers = layers,
};
if(vkCreateFramebuffer(
context.device(),
&create_info,
nullptr,
&m_framebuffer
) != VK_SUCCESS)
throw std::runtime_error("failed to create framebuffer");
}
Framebuffer::Framebuffer(
Context& context,
VkRenderPass render_pass,
Array<VkImageView> attachments,
VkExtent2D extent, uint32_t layers
)
: Framebuffer(context, render_pass, attachments, extent.width, extent.height, layers)
{}
Framebuffer::Framebuffer(Framebuffer&& other) noexcept
{
swap(other);
}
Framebuffer::~Framebuffer() noexcept {
if(!is_null())
destroy();
}
Framebuffer& Framebuffer::operator=(Framebuffer&& other) noexcept {
swap(other);
if(other)
other.destroy();
return *this;
}
void Framebuffer::destroy() noexcept {
assert(!is_null());
assert(m_context);
vkDestroyFramebuffer(
m_context->device(),
m_framebuffer,
nullptr
);
m_framebuffer = nullptr;
}
}

70
src/vk/Framebuffer.h Normal file
View File

@@ -0,0 +1,70 @@
// Copyright 2022 Simon Boyé
#pragma once
#include <core/utils.h>
#include <vk/forward.h>
#include <vk/Wrapper.h>
#include <vulkan/vulkan.h>
namespace vk {
class Framebuffer: public Wrapper {
public:
Framebuffer() noexcept;
Framebuffer(
Context& context,
VkRenderPass render_pass,
Array<VkImageView> attachments,
uint32_t width, uint32_t height, uint32_t layers=1
);
Framebuffer(
Context& context,
VkRenderPass render_pass,
Array<VkImageView> attachments,
VkExtent2D extent, uint32_t layers=1
);
Framebuffer(const Framebuffer&) = default;
Framebuffer(Framebuffer&& other) noexcept;
~Framebuffer() noexcept;
Framebuffer& operator=(const Framebuffer&) = default;
Framebuffer& operator=(Framebuffer&& other) noexcept;
explicit inline operator bool() const noexcept {
return !is_null();
}
inline bool is_null() const noexcept {
return m_framebuffer == VK_NULL_HANDLE;
}
inline operator VkFramebuffer() noexcept {
return m_framebuffer;
}
inline VkFramebuffer framebuffer() noexcept {
return m_framebuffer;
}
inline void swap(Framebuffer& other) noexcept {
using std::swap;
Wrapper::swap(other);
swap(m_framebuffer, other.m_framebuffer);
}
friend inline void swap(Framebuffer& framebuffer_0, Framebuffer& framebuffer_1) noexcept {
framebuffer_0.swap(framebuffer_1);
}
void destroy() noexcept;
private:
VkFramebuffer m_framebuffer = VK_NULL_HANDLE;
};
}

124
src/vk/Image.cpp Normal file
View File

@@ -0,0 +1,124 @@
// Copyright 2022 Simon Boyé
#include <vk/Image.h>
#include <vk/Context.h>
#include <cassert>
namespace vk {
Image::Image() noexcept {
}
Image::Image(Context& context, VkImageCreateInfo create_info)
: Wrapper(context)
{
assert(m_context);
if(vkCreateImage(
context.device(),
&create_info,
nullptr,
&m_image
) != VK_SUCCESS)
throw std::runtime_error("failed to create image");
}
Image::Image(Context& context, VkImageCreateInfo create_info, VkMemoryPropertyFlags memory_properties)
: Image(context, create_info)
{
allocate_and_bind_memory(memory_properties);
}
Image::Image(Image&& other) noexcept
{
swap(other);
}
Image::~Image() noexcept {
if(!is_null())
destroy();
}
Image& Image::operator=(Image&& other) noexcept {
swap(other);
if(other)
other.destroy();
return *this;
}
VkMemoryRequirements Image::memory_requirements() const noexcept {
assert(!is_null());
assert(*m_context);
VkMemoryRequirements memory_requirements;
vkGetImageMemoryRequirements(
m_context->device(),
m_image,
&memory_requirements
);
return memory_requirements;
}
void Image::bind_memory(const MemoryBlock& memory_block, VkDeviceSize offset) {
assert(!is_null());
assert(*m_context);
assert(memory_block);
// m_memory = std::move(memory_block);
if(vkBindImageMemory(
m_context->device(),
m_image,
memory_block.device_memory(),
memory_block.offset() + offset
) != VK_SUCCESS)
throw std::runtime_error("failed to bind image memory");
}
void Image::bind_memory(MemoryBlock&& memory_block) {
bind_memory(memory_block);
m_memory = std::move(memory_block);
}
void Image::allocate_and_bind_memory(VkMemoryPropertyFlags memory_properties) {
assert(!is_null());
assert(*m_context);
const auto memory_requirements = this->memory_requirements();
m_memory = m_context->allocator().allocate(
memory_requirements.size,
memory_requirements.memoryTypeBits,
memory_properties
);
bind_memory(m_memory);
}
void Image::destroy() noexcept {
assert(!is_null());
assert(m_context);
if(m_memory) {
m_memory.free();
m_memory = MemoryBlock();
}
vkDestroyImage(
m_context->device(),
m_image,
nullptr
);
m_context = nullptr;
m_image = VK_NULL_HANDLE;
}
}

66
src/vk/Image.h Normal file
View File

@@ -0,0 +1,66 @@
// Copyright 2022 Simon Boyé
#pragma once
#include <vk/forward.h>
#include <vk/Wrapper.h>
#include <vk/Memory.h>
#include <vulkan/vulkan.h>
namespace vk {
class Image: public Wrapper {
public:
Image() noexcept;
Image(Context& context, VkImageCreateInfo create_info);
Image(Context& context, VkImageCreateInfo create_info, VkMemoryPropertyFlags memory_properties);
Image(const Image&) = default;
Image(Image&& other) noexcept;
~Image() noexcept;
Image& operator=(const Image&) = default;
Image& operator=(Image&& other) noexcept;
explicit inline operator bool() const noexcept {
return !is_null();
}
inline bool is_null() const noexcept {
return m_image == VK_NULL_HANDLE;
}
inline operator VkImage() noexcept {
return m_image;
}
inline VkImage image() noexcept {
return m_image;
}
VkMemoryRequirements memory_requirements() const noexcept;
void bind_memory(const MemoryBlock& memory_block, VkDeviceSize offset=0);
void bind_memory(MemoryBlock&& memory_block);
void allocate_and_bind_memory(VkMemoryPropertyFlags memory_properties);
inline void swap(Image& other) noexcept {
using std::swap;
Wrapper::swap(other);
swap(m_image, other.m_image);
}
friend inline void swap(Image& image_0, Image& image_1) noexcept {
image_0.swap(image_1);
}
void destroy() noexcept;
private:
VkImage m_image = VK_NULL_HANDLE;
MemoryBlock m_memory;
};
}

62
src/vk/ImageView.cpp Normal file
View File

@@ -0,0 +1,62 @@
// Copyright 2022 Simon Boyé
#include <vk/ImageView.h>
#include <vk/Context.h>
#include <cassert>
namespace vk {
ImageView::ImageView() noexcept {
}
ImageView::ImageView(Context& context, const VkImageViewCreateInfo& create_info)
: Wrapper(context)
{
assert(m_context);
if(vkCreateImageView(
context.device(),
&create_info,
nullptr,
&m_image_view
) != VK_SUCCESS)
throw std::runtime_error("failed to create image view");
}
ImageView::ImageView(ImageView&& other) noexcept
{
swap(other);
}
ImageView::~ImageView() noexcept {
if(!is_null())
destroy();
}
ImageView& ImageView::operator=(ImageView&& other) noexcept {
swap(other);
if(other)
other.destroy();
return *this;
}
void ImageView::destroy() noexcept {
assert(!is_null());
assert(m_context);
vkDestroyImageView(
m_context->device(),
m_image_view,
nullptr
);
m_image_view = nullptr;
}
}

57
src/vk/ImageView.h Normal file
View File

@@ -0,0 +1,57 @@
// Copyright 2022 Simon Boyé
#pragma once
#include <vk/forward.h>
#include <vk/Wrapper.h>
#include <vulkan/vulkan.h>
namespace vk {
class ImageView: public Wrapper {
public:
ImageView() noexcept;
ImageView(Context& context, const VkImageViewCreateInfo& create_info);
ImageView(const ImageView&) = default;
ImageView(ImageView&& other) noexcept;
~ImageView() noexcept;
ImageView& operator=(const ImageView&) = default;
ImageView& operator=(ImageView&& other) noexcept;
explicit inline operator bool() const noexcept {
return !is_null();
}
inline bool is_null() const noexcept {
return m_image_view == VK_NULL_HANDLE;
}
inline operator VkImageView() noexcept {
return m_image_view;
}
inline VkImageView image_view() noexcept {
return m_image_view;
}
inline void swap(ImageView& other) noexcept {
using std::swap;
Wrapper::swap(other);
swap(m_image_view, other.m_image_view);
}
friend inline void swap(ImageView& image_view_0, ImageView& image_view_1) noexcept {
image_view_0.swap(image_view_1);
}
void destroy() noexcept;
private:
VkImageView m_image_view = VK_NULL_HANDLE;
};
}

View File

@@ -1,12 +1,13 @@
#include <Vulkan/Memory.h> // Copyright 2022 Simon Boyé
#include <Vulkan/Context.h> #include <vk/Memory.h>
#include <vk/Context.h>
#include <stdexcept> #include <stdexcept>
#include <algorithm> #include <algorithm>
#include <cassert> #include <cassert>
namespace Vulkan { namespace vk {
MemoryBlock::MemoryBlock() noexcept { MemoryBlock::MemoryBlock() noexcept {
@@ -41,11 +42,9 @@ MemoryBlock::MemoryBlock(MemoryBlock&& other) noexcept
} }
MemoryBlock::~MemoryBlock() noexcept { MemoryBlock::~MemoryBlock() noexcept {
if(is_valid()) { if(!is_null())
logger.warning() << "MemoryBlock deleted before being freed";
free(); free();
} }
}
MemoryBlock& MemoryBlock::operator=(MemoryBlock&& other) noexcept { MemoryBlock& MemoryBlock::operator=(MemoryBlock&& other) noexcept {
if(&other != this) { if(&other != this) {
@@ -61,7 +60,7 @@ MemoryBlock& MemoryBlock::operator=(MemoryBlock&& other) noexcept {
VkMemoryType MemoryBlock::memory_type_info(Context& context) const noexcept { VkMemoryType MemoryBlock::memory_type_info(Context& context) const noexcept {
assert(is_valid()); assert(!is_null());
assert(context); assert(context);
return context.memory_properties().memoryTypes[m_memory_type]; return context.memory_properties().memoryTypes[m_memory_type];
@@ -69,7 +68,7 @@ VkMemoryType MemoryBlock::memory_type_info(Context& context) const noexcept {
void MemoryBlock::free() noexcept { void MemoryBlock::free() noexcept {
assert(is_valid()); assert(!is_null());
m_memory_page->p_free(*this); m_memory_page->p_free(*this);
@@ -81,12 +80,12 @@ void MemoryBlock::free() noexcept {
} }
void* MemoryBlock::map(Context& context) { Byte* MemoryBlock::map(Context& context) {
return map(context, 0, m_size); return map(context, 0, m_size);
} }
void* MemoryBlock::map(Context& context, VkDeviceSize offset, VkDeviceSize size) { Byte* MemoryBlock::map(Context& context, VkDeviceSize offset, VkDeviceSize size) {
assert(is_valid()); assert(!is_null());
assert(context); assert(context);
assert(offset + size <= m_size); assert(offset + size <= m_size);
@@ -101,11 +100,11 @@ void* MemoryBlock::map(Context& context, VkDeviceSize offset, VkDeviceSize size)
) != VK_SUCCESS) ) != VK_SUCCESS)
throw std::runtime_error("failed to map memory"); throw std::runtime_error("failed to map memory");
return ptr; return reinterpret_cast<Byte*>(ptr);
} }
void MemoryBlock::unmap(Context& context) noexcept { void MemoryBlock::unmap(Context& context) noexcept {
assert(is_valid()); assert(!is_null());
vkUnmapMemory( vkUnmapMemory(
context.device(), context.device(),
@@ -115,7 +114,7 @@ void MemoryBlock::unmap(Context& context) noexcept {
void MemoryBlock::flush(Context& context) { void MemoryBlock::flush(Context& context) {
assert(is_valid()); assert(!is_null());
VkMappedMemoryRange range { VkMappedMemoryRange range {
.sType = VK_STRUCTURE_TYPE_MAPPED_MEMORY_RANGE, .sType = VK_STRUCTURE_TYPE_MAPPED_MEMORY_RANGE,
@@ -132,7 +131,7 @@ void MemoryBlock::flush(Context& context) {
} }
void MemoryBlock::invalidate(Context& context) { void MemoryBlock::invalidate(Context& context) {
assert(is_valid()); assert(!is_null());
VkMappedMemoryRange range { VkMappedMemoryRange range {
.sType = VK_STRUCTURE_TYPE_MAPPED_MEMORY_RANGE, .sType = VK_STRUCTURE_TYPE_MAPPED_MEMORY_RANGE,
@@ -175,6 +174,8 @@ MemoryBlock MemoryPage::allocate(VkDeviceSize size) noexcept {
if(block_it == m_blocks.end()) if(block_it == m_blocks.end())
return MemoryBlock(); return MemoryBlock();
const auto offset = block_it->offset;
block_it[0].is_free = false; block_it[0].is_free = false;
if (block_it[0].offset != block_it[1].offset) { if (block_it[0].offset != block_it[1].offset) {
m_blocks.emplace(std::next(block_it), Block{ m_blocks.emplace(std::next(block_it), Block{
@@ -182,7 +183,7 @@ MemoryBlock MemoryPage::allocate(VkDeviceSize size) noexcept {
}); });
} }
return MemoryBlock(m_device_memory, size, block_it[0].offset, this, m_memory_type); return MemoryBlock(m_device_memory, size, offset, this, m_memory_type);
} }
void MemoryPage::p_free(MemoryBlock& block) noexcept { void MemoryPage::p_free(MemoryBlock& block) noexcept {

View File

@@ -1,6 +1,9 @@
// Copyright 2022 Simon Boyé
#pragma once #pragma once
#include <Vulkan/forward.h> #include <core/types.h>
#include <vk/forward.h>
#include <vulkan/vulkan.h> #include <vulkan/vulkan.h>
@@ -8,7 +11,7 @@
#include <vector> #include <vector>
namespace Vulkan { namespace vk {
class MemoryBlock { class MemoryBlock {
@@ -32,8 +35,8 @@ public:
return m_size != 0; return m_size != 0;
} }
inline bool is_valid() const noexcept { inline bool is_null() const noexcept {
return m_size != 0; return m_size == 0;
} }
inline VkDeviceSize size() const noexcept { inline VkDeviceSize size() const noexcept {
@@ -60,8 +63,8 @@ public:
void free() noexcept; void free() noexcept;
void* map(Context& context); Byte* map(Context& context);
void* map(Context& context, VkDeviceSize offset, VkDeviceSize size); Byte* map(Context& context, VkDeviceSize offset, VkDeviceSize size);
void unmap(Context& context) noexcept; void unmap(Context& context) noexcept;
void flush(Context& context); void flush(Context& context);
@@ -91,11 +94,11 @@ public:
MemoryPage& operator=(MemoryPage&&); MemoryPage& operator=(MemoryPage&&);
explicit inline operator bool() const noexcept { explicit inline operator bool() const noexcept {
return is_valid(); return !is_null();
} }
inline bool is_valid() const noexcept { inline bool is_null() const noexcept {
return m_size != 0; return m_size == 0;
} }
inline VkDeviceSize size() const noexcept { inline VkDeviceSize size() const noexcept {

71
src/vk/Pipeline.cpp Normal file
View File

@@ -0,0 +1,71 @@
// Copyright 2022 Simon Boyé
#include <vk/Pipeline.h>
#include <vk/Context.h>
#include <cassert>
namespace vk {
Pipeline::Pipeline() noexcept {
}
Pipeline::Pipeline(Context& context, VkPipeline pipeline)
: Wrapper(context)
, m_pipeline(pipeline)
{
assert(m_context);
assert(m_pipeline != VK_NULL_HANDLE);
}
Pipeline::Pipeline(Context& context, VkGraphicsPipelineCreateInfo create_info)
: Wrapper(context)
{
assert(m_context);
if(vkCreateGraphicsPipelines(
m_context->device(),
VK_NULL_HANDLE,
1, &create_info,
nullptr,
&m_pipeline
) != VK_SUCCESS)
throw std::runtime_error("failed to create graphic pipeline");
}
Pipeline::Pipeline(Pipeline&& other) noexcept
{
swap(other);
}
Pipeline::~Pipeline() noexcept {
if(!is_null())
destroy();
}
Pipeline& Pipeline::operator=(Pipeline&& other) noexcept {
swap(other);
if(other)
other.destroy();
return *this;
}
void Pipeline::destroy() noexcept {
assert(!is_null());
assert(m_context);
vkDestroyPipeline(
m_context->device(),
m_pipeline,
nullptr
);
m_pipeline = nullptr;
}
}

58
src/vk/Pipeline.h Normal file
View File

@@ -0,0 +1,58 @@
// Copyright 2022 Simon Boyé
#pragma once
#include <vk/forward.h>
#include <vk/Wrapper.h>
#include <vulkan/vulkan.h>
namespace vk {
class Pipeline: public Wrapper {
public:
Pipeline() noexcept;
Pipeline(Context& context, VkPipeline pipeline);
Pipeline(Context& context, VkGraphicsPipelineCreateInfo create_info);
Pipeline(const Pipeline&) = default;
Pipeline(Pipeline&& other) noexcept;
~Pipeline() noexcept;
Pipeline& operator=(const Pipeline&) = default;
Pipeline& operator=(Pipeline&& other) noexcept;
explicit inline operator bool() const noexcept {
return !is_null();
}
inline bool is_null() const noexcept {
return m_pipeline == VK_NULL_HANDLE;
}
inline operator VkPipeline() noexcept {
return m_pipeline;
}
inline VkPipeline pipeline() noexcept {
return m_pipeline;
}
inline void swap(Pipeline& other) noexcept {
using std::swap;
Wrapper::swap(other);
swap(m_pipeline, other.m_pipeline);
}
friend inline void swap(Pipeline& pipeline_0, Pipeline& pipeline_1) noexcept {
pipeline_0.swap(pipeline_1);
}
void destroy() noexcept;
private:
VkPipeline m_pipeline = VK_NULL_HANDLE;
};
}

73
src/vk/PipelineLayout.cpp Normal file
View File

@@ -0,0 +1,73 @@
// Copyright 2022 Simon Boyé
#include <vk/PipelineLayout.h>
#include <vk/Context.h>
#include <cassert>
namespace vk {
PipelineLayout::PipelineLayout() noexcept {
}
PipelineLayout::PipelineLayout(
Context& context,
Array<const VkDescriptorSetLayout> set_layouts,
Array<const VkPushConstantRange> push_constant_ranges
)
: Wrapper(context)
{
assert(m_context);
VkPipelineLayoutCreateInfo create_info {
.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO,
.setLayoutCount = uint32_t(set_layouts.size()),
.pSetLayouts = set_layouts.data(),
.pushConstantRangeCount = uint32_t(push_constant_ranges.size()),
.pPushConstantRanges = push_constant_ranges.data(),
};
if(vkCreatePipelineLayout(
context.device(),
&create_info,
nullptr,
&m_pipeline_layout
) != VK_SUCCESS)
throw std::runtime_error("failed to create pipeline layout");
}
PipelineLayout::PipelineLayout(PipelineLayout&& other) noexcept
{
swap(other);
}
PipelineLayout::~PipelineLayout() noexcept {
if(!is_null())
destroy();
}
PipelineLayout& PipelineLayout::operator=(PipelineLayout&& other) noexcept {
swap(other);
if(other)
other.destroy();
return *this;
}
void PipelineLayout::destroy() noexcept {
assert(!is_null());
assert(m_context);
vkDestroyPipelineLayout(
m_context->device(),
m_pipeline_layout,
nullptr
);
m_pipeline_layout = nullptr;
}
}

63
src/vk/PipelineLayout.h Normal file
View File

@@ -0,0 +1,63 @@
// Copyright 2022 Simon Boyé
#pragma once
#include <core/utils.h>
#include <vk/forward.h>
#include <vk/Wrapper.h>
#include <vulkan/vulkan.h>
namespace vk {
class PipelineLayout: public Wrapper {
public:
PipelineLayout() noexcept;
PipelineLayout(
Context& context,
Array<const VkDescriptorSetLayout> set_layouts,
Array<const VkPushConstantRange> push_constant_ranges={}
);
PipelineLayout(const PipelineLayout&) = default;
PipelineLayout(PipelineLayout&& other) noexcept;
~PipelineLayout() noexcept;
PipelineLayout& operator=(const PipelineLayout&) = default;
PipelineLayout& operator=(PipelineLayout&& other) noexcept;
explicit inline operator bool() const noexcept {
return !is_null();
}
inline bool is_null() const noexcept {
return m_pipeline_layout == VK_NULL_HANDLE;
}
inline operator VkPipelineLayout() noexcept {
return m_pipeline_layout;
}
inline VkPipelineLayout pipeline_layout() noexcept {
return m_pipeline_layout;
}
inline void swap(PipelineLayout& other) noexcept {
using std::swap;
Wrapper::swap(other);
swap(m_pipeline_layout, other.m_pipeline_layout);
}
friend inline void swap(PipelineLayout& pipeline_layout_0, PipelineLayout& pipeline_layout_1) noexcept {
pipeline_layout_0.swap(pipeline_layout_1);
}
void destroy() noexcept;
private:
VkPipelineLayout m_pipeline_layout = VK_NULL_HANDLE;
};
}

63
src/vk/RenderPass.cpp Normal file
View File

@@ -0,0 +1,63 @@
// Copyright 2022 Simon Boyé
#include <vk/RenderPass.h>
#include <vk/Context.h>
#include <cassert>
namespace vk {
RenderPass::RenderPass() noexcept {
}
RenderPass::RenderPass(Context& context, const VkRenderPassCreateInfo& create_info)
: Wrapper(context)
{
assert(m_context);
if(vkCreateRenderPass(
context.device(),
&create_info,
nullptr,
&m_render_pass
) != VK_SUCCESS)
throw std::runtime_error("failed to create render pass");
}
RenderPass::RenderPass(RenderPass&& other) noexcept
{
swap(other);
}
RenderPass::~RenderPass() noexcept {
if(!is_null())
destroy();
}
RenderPass& RenderPass::operator=(RenderPass&& other) noexcept {
swap(other);
if(other)
other.destroy();
return *this;
}
void RenderPass::destroy() noexcept {
assert(!is_null());
assert(m_context);
vkDestroyRenderPass(
m_context->device(),
m_render_pass,
nullptr
);
m_render_pass = nullptr;
}
}

57
src/vk/RenderPass.h Normal file
View File

@@ -0,0 +1,57 @@
// Copyright 2022 Simon Boyé
#pragma once
#include <vk/forward.h>
#include <vk/Wrapper.h>
#include <vulkan/vulkan.h>
namespace vk {
class RenderPass: public Wrapper {
public:
RenderPass() noexcept;
RenderPass(Context& context, const VkRenderPassCreateInfo& create_info);
RenderPass(const RenderPass&) = default;
RenderPass(RenderPass&& other) noexcept;
~RenderPass() noexcept;
RenderPass& operator=(const RenderPass&) = default;
RenderPass& operator=(RenderPass&& other) noexcept;
explicit inline operator bool() const noexcept {
return !is_null();
}
inline bool is_null() const noexcept {
return m_render_pass == VK_NULL_HANDLE;
}
inline operator VkRenderPass() noexcept {
return m_render_pass;
}
inline VkRenderPass render_pass() noexcept {
return m_render_pass;
}
inline void swap(RenderPass& other) noexcept {
using std::swap;
Wrapper::swap(other);
swap(m_render_pass, other.m_render_pass);
}
friend inline void swap(RenderPass& render_pass_0, RenderPass& render_pass_1) noexcept {
render_pass_0.swap(render_pass_1);
}
void destroy() noexcept;
private:
VkRenderPass m_render_pass = VK_NULL_HANDLE;
};
}

65
src/vk/Semaphore.cpp Normal file
View File

@@ -0,0 +1,65 @@
// Copyright 2022 Simon Boyé
#include <vk/Semaphore.h>
#include <vk/Context.h>
#include <cassert>
namespace vk {
Semaphore::Semaphore() noexcept {
}
Semaphore::Semaphore(Context& context)
: Wrapper(context)
{
assert(m_context);
VkSemaphoreCreateInfo create_info {
.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO,
};
if(vkCreateSemaphore(
context.device(),
&create_info,
nullptr,
&m_semaphore
) != VK_SUCCESS)
throw std::runtime_error("failed to create semaphore");
}
Semaphore::Semaphore(Semaphore&& other) noexcept
{
swap(other);
}
Semaphore::~Semaphore() noexcept {
if(!is_null())
destroy();
}
Semaphore& Semaphore::operator=(Semaphore&& other) noexcept {
swap(other);
if(other)
other.destroy();
return *this;
}
void Semaphore::destroy() noexcept {
assert(!is_null());
assert(m_context);
vkDestroySemaphore(
m_context->device(),
m_semaphore,
nullptr
);
m_semaphore = nullptr;
}
}

57
src/vk/Semaphore.h Normal file
View File

@@ -0,0 +1,57 @@
// Copyright 2022 Simon Boyé
#pragma once
#include <vk/forward.h>
#include <vk/Wrapper.h>
#include <vulkan/vulkan.h>
namespace vk {
class Semaphore: public Wrapper {
public:
Semaphore() noexcept;
Semaphore(Context& context);
Semaphore(const Semaphore&) = default;
Semaphore(Semaphore&& other) noexcept;
~Semaphore() noexcept;
Semaphore& operator=(const Semaphore&) = default;
Semaphore& operator=(Semaphore&& other) noexcept;
explicit inline operator bool() const noexcept {
return !is_null();
}
inline bool is_null() const noexcept {
return m_semaphore == VK_NULL_HANDLE;
}
inline operator VkSemaphore() noexcept {
return m_semaphore;
}
inline VkSemaphore semaphore() noexcept {
return m_semaphore;
}
inline void swap(Semaphore& other) noexcept {
using std::swap;
Wrapper::swap(other);
swap(m_semaphore, other.m_semaphore);
}
friend inline void swap(Semaphore& semaphore_0, Semaphore& semaphore_1) noexcept {
semaphore_0.swap(semaphore_1);
}
void destroy() noexcept;
private:
VkSemaphore m_semaphore = VK_NULL_HANDLE;
};
}

71
src/vk/ShaderModule.cpp Normal file
View File

@@ -0,0 +1,71 @@
// Copyright 2022 Simon Boyé
#include <vk/ShaderModule.h>
#include <vk/Context.h>
#include <cassert>
namespace vk {
ShaderModule::ShaderModule() noexcept {
}
ShaderModule::ShaderModule(Context& context, const std::vector<char>& code)
: Wrapper(context)
{
assert(m_context);
VkShaderModuleCreateInfo create_info {
.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO,
.codeSize = code.size(),
.pCode = (uint32_t*)code.data(),
};
if(vkCreateShaderModule(
context.device(),
&create_info,
nullptr,
&m_shader_module
) != VK_SUCCESS)
throw std::runtime_error("failed to create shader module");
}
ShaderModule::ShaderModule(Context& context, const char* path)
: ShaderModule(context, read_binary_file(path))
{}
ShaderModule::ShaderModule(ShaderModule&& other) noexcept
{
swap(other);
}
ShaderModule::~ShaderModule() noexcept {
if(!is_null())
destroy();
}
ShaderModule& ShaderModule::operator=(ShaderModule&& other) noexcept {
swap(other);
if(other)
other.destroy();
return *this;
}
void ShaderModule::destroy() noexcept {
assert(!is_null());
assert(m_context);
vkDestroyShaderModule(
m_context->device(),
m_shader_module,
nullptr
);
m_shader_module = nullptr;
}
}

60
src/vk/ShaderModule.h Normal file
View File

@@ -0,0 +1,60 @@
// Copyright 2022 Simon Boyé
#pragma once
#include <vk/forward.h>
#include <vk/Wrapper.h>
#include <vulkan/vulkan.h>
#include <vector>
namespace vk {
class ShaderModule: public Wrapper {
public:
ShaderModule() noexcept;
ShaderModule(Context& context, const std::vector<char>& code);
ShaderModule(Context& context, const char* path);
ShaderModule(const ShaderModule&) = default;
ShaderModule(ShaderModule&& other) noexcept;
~ShaderModule() noexcept;
ShaderModule& operator=(const ShaderModule&) = default;
ShaderModule& operator=(ShaderModule&& other) noexcept;
explicit inline operator bool() const noexcept {
return !is_null();
}
inline bool is_null() const noexcept {
return m_shader_module == VK_NULL_HANDLE;
}
inline operator VkShaderModule() noexcept {
return m_shader_module;
}
inline VkShaderModule shader_module() noexcept {
return m_shader_module;
}
inline void swap(ShaderModule& other) noexcept {
using std::swap;
Wrapper::swap(other);
swap(m_shader_module, other.m_shader_module);
}
friend inline void swap(ShaderModule& shader_module_0, ShaderModule& shader_module_1) noexcept {
shader_module_0.swap(shader_module_1);
}
void destroy() noexcept;
private:
VkShaderModule m_shader_module = VK_NULL_HANDLE;
};
}

View File

@@ -1,6 +1,7 @@
#include <Vulkan/Swapchain.h> // Copyright 2022 Simon Boyé
#include <vk/Swapchain.h>
#include <Logger.h> #include <core/Logger.h>
#include <SDL2/SDL_vulkan.h> #include <SDL2/SDL_vulkan.h>
@@ -10,7 +11,7 @@
#include <tuple> #include <tuple>
namespace Vulkan { namespace vk {
SwapchainSettings::SwapchainSettings(Context* context) SwapchainSettings::SwapchainSettings(Context* context)
@@ -79,12 +80,12 @@ VkImageView Swapchain::image_view(size_t image_index) {
return m_image_resources[image_index].view; return m_image_resources[image_index].view;
} }
VkSemaphore Swapchain::ready_to_render() { Semaphore& Swapchain::ready_to_render() {
return m_frame_resources[m_frame_resources_index].ready_to_render; return m_frame_resources[m_frame_resources_index].ready_to_render;
} }
VkFence Swapchain::render_done() { Fence& Swapchain::render_done() {
return m_frame_resources[m_frame_resources_index].render_done; return *m_frame_resources[m_frame_resources_index].render_done;
} }
void Swapchain::initialize(const SwapchainSettings& settings) { void Swapchain::initialize(const SwapchainSettings& settings) {
@@ -111,13 +112,8 @@ void Swapchain::begin_frame() {
// frame_resources are used cyclically. We wait for the current one to be // frame_resources are used cyclically. We wait for the current one to be
// done rendering to not render more that MAX_FRAMES_IN_FLIGHT frames at // done rendering to not render more that MAX_FRAMES_IN_FLIGHT frames at
// the same time. // the same time.
if(frame_resources.render_done != VK_NULL_HANDLE) { if(frame_resources.render_done) {
vkWaitForFences( frame_resources.render_done->wait();
m_context->device(),
1, &frame_resources.render_done,
VK_TRUE,
UINT64_MAX
);
} }
bool acquired_image = false; bool acquired_image = false;
@@ -141,27 +137,22 @@ void Swapchain::begin_frame() {
} }
} }
logger.info() << "begin frame " << m_frame_index // logger.info() << "begin frame " << m_frame_index
<< ": image " << m_current_image_index // << ": image " << m_current_image_index
<< ", frame: " << m_frame_resources_index; // << ", frame: " << m_frame_resources_index;
auto& image_resources = m_image_resources[m_current_image_index]; auto& image_resources = m_image_resources[m_current_image_index];
// In case vkAcquireNextImageKHR doesn't return images in-order, we wait // In case vkAcquireNextImageKHR doesn't return images in-order, we wait
// again to be sure. // again to be sure.
if(image_resources.render_done != VK_NULL_HANDLE) { if(image_resources.render_done != VK_NULL_HANDLE) {
vkWaitForFences( image_resources.render_done.wait();
m_context->device(),
1, &image_resources.render_done,
VK_TRUE,
UINT64_MAX
);
} }
// Remember the right frame to wait for. // Remember the right frame to wait for.
frame_resources.render_done = image_resources.render_done; frame_resources.render_done = &image_resources.render_done;
vkResetFences(m_context->device(), 1, &frame_resources.render_done); frame_resources.render_done->reset();
} }
void Swapchain::swap_buffers(Array<VkSemaphore> wait_semaphores) { void Swapchain::swap_buffers(Array<VkSemaphore> wait_semaphores) {
@@ -296,13 +287,13 @@ void Swapchain::create() {
swapchain_images swapchain_images
); );
m_image_resources.resize(image_count, {});
for(size_t index = 0; index < image_count; index += 1) { for(size_t index = 0; index < image_count; index += 1) {
auto& image_resources = m_image_resources[index]; m_image_resources.emplace_back();
auto& image_resources = m_image_resources.back();
image_resources.image = swapchain_images[index]; image_resources.image = swapchain_images[index];
VkImageViewCreateInfo view_info { image_resources.view = ImageView(*m_context, VkImageViewCreateInfo {
.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO, .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO,
.image = image_resources.image, .image = image_resources.image,
.viewType = VK_IMAGE_VIEW_TYPE_2D, .viewType = VK_IMAGE_VIEW_TYPE_2D,
@@ -320,66 +311,28 @@ void Swapchain::create() {
.baseArrayLayer = 0, .baseArrayLayer = 0,
.layerCount = 1, .layerCount = 1,
}, },
}; });
if(vkCreateImageView( image_resources.render_done = Fence(*m_context, VK_FENCE_CREATE_SIGNALED_BIT);
m_context->device(),
&view_info,
nullptr,
&image_resources.view
) != VK_SUCCESS) {
throw std::runtime_error("failed to create swapchain image view");
} }
VkFenceCreateInfo fence_info { m_frame_resources.clear();
.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO, for(int index = 0; index < MAX_FRAMES_IN_FLIGHT; index += 1) {
.flags = VK_FENCE_CREATE_SIGNALED_BIT, m_frame_resources.emplace_back(FrameResources {
}; .ready_to_render = Semaphore(*m_context),
if(vkCreateFence( });
m_context->device(),
&fence_info,
nullptr,
&image_resources.render_done
) != VK_SUCCESS)
throw std::runtime_error("failed to create fence");
}
m_frame_resources.resize(MAX_FRAMES_IN_FLIGHT, {});
int index = 0;
for(auto& frame_resources: m_frame_resources) {
VkSemaphoreCreateInfo semaphore_info {
.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO,
};
if(vkCreateSemaphore(
m_context->device(),
&semaphore_info,
nullptr,
&frame_resources.ready_to_render
) != VK_SUCCESS)
throw std::runtime_error("failed to create semaphore");
index += 1;
} }
for(auto& callback: m_creation_callbacks) for(auto& callback: m_creation_callbacks)
callback(image_count); callback();
} }
void Swapchain::destroy() { void Swapchain::destroy() {
for(auto& callback: m_destruction_callbacks) for(auto& callback: m_destruction_callbacks)
callback(); callback();
for(auto& frame_resources: m_frame_resources) { m_frame_resources.clear();
m_context->destroy_semaphore(frame_resources.ready_to_render); m_image_resources.clear();
// This is a reference to an ImageResources.render_done
frame_resources.render_done = VK_NULL_HANDLE;
}
for(auto& image_resources: m_image_resources) {
m_context->destroy_fence(image_resources.render_done);
m_context->destroy_image_view(image_resources.view);
}
m_context->destroy_swapchain(m_swapchain); m_context->destroy_swapchain(m_swapchain);
} }

View File

@@ -1,9 +1,14 @@
// Copyright 2022 Simon Boyé
#pragma once #pragma once
#include <Vulkan/Context.h> #include <vk/Context.h>
#include <utils.h> #include <vk/Semaphore.h>
#include <vk/ImageView.h>
#include <core/utils.h>
#include <SDL2/SDL.h> #include <SDL2/SDL.h>
#include <vulkan/vulkan.h> #include <vulkan/vulkan.h>
#include <optional> #include <optional>
@@ -12,7 +17,7 @@
#include <functional> #include <functional>
namespace Vulkan { namespace vk {
class Swapchain; class Swapchain;
@@ -37,7 +42,7 @@ public:
static constexpr uint32_t CURRENT_IMAGE_INDEX = UINT32_MAX; static constexpr uint32_t CURRENT_IMAGE_INDEX = UINT32_MAX;
static constexpr uint32_t MAX_FRAMES_IN_FLIGHT = 2; static constexpr uint32_t MAX_FRAMES_IN_FLIGHT = 2;
using CreationCallback = std::function<void(uint32_t)>; using CreationCallback = std::function<void()>;
using DestructionCallback = std::function<void()>; using DestructionCallback = std::function<void()>;
public: public:
@@ -55,8 +60,8 @@ public:
VkImage image(size_t image_index); VkImage image(size_t image_index);
VkImageView image_view(); VkImageView image_view();
VkImageView image_view(size_t image_index); VkImageView image_view(size_t image_index);
VkSemaphore ready_to_render(); Semaphore& ready_to_render();
VkFence render_done(); Fence& render_done();
void initialize(const SwapchainSettings& settings); void initialize(const SwapchainSettings& settings);
void shutdown(); void shutdown();
@@ -72,13 +77,13 @@ public:
private: private:
struct ImageResources { struct ImageResources {
VkImage image = VK_NULL_HANDLE; VkImage image = VK_NULL_HANDLE;
VkImageView view = VK_NULL_HANDLE; ImageView view;
VkFence render_done = VK_NULL_HANDLE; Fence render_done;
}; };
struct FrameResources { struct FrameResources {
VkSemaphore ready_to_render = VK_NULL_HANDLE; Semaphore ready_to_render;
VkFence render_done = VK_NULL_HANDLE; Fence* render_done = nullptr;
}; };
private: private:

49
src/vk/Wrapper.cpp Normal file
View File

@@ -0,0 +1,49 @@
// Copyright 2022 Simon Boyé
#include <vk/Wrapper.h>
#include <vk/Context.h>
#include <cassert>
namespace vk {
Wrapper::Wrapper() noexcept {
}
Wrapper::Wrapper(Context& context, Flags flags) noexcept
: m_context(&context)
, m_flags(flags)
{
assert(m_context);
}
Wrapper::Wrapper(const Wrapper& other) noexcept
: m_context(other.m_context)
{}
Wrapper::Wrapper(Wrapper&& other) noexcept
{
swap(other);
}
Wrapper::~Wrapper() noexcept {
}
Wrapper& Wrapper::operator=(const Wrapper& other) noexcept {
if (&other != this) {
m_context = other.m_context;
m_flags = other.m_flags & ~OwnUnderlyingObject;
}
return *this;
}
Wrapper& Wrapper::operator=(Wrapper&& other) noexcept {
swap(other);
return *this;
}
}

65
src/vk/Wrapper.h Normal file
View File

@@ -0,0 +1,65 @@
// Copyright 2022 Simon Boyé
#pragma once
#include <vk/forward.h>
#include <vulkan/vulkan.h>
namespace vk {
class Wrapper {
public:
enum Flag {
DontOwnUnderlyingObject = 0x00,
OwnUnderlyingObject = 0x01,
};
using Flags = unsigned;
public:
Wrapper() noexcept;
Wrapper(Context& context, Flags flags=OwnUnderlyingObject) noexcept;
Wrapper(const Wrapper& other) noexcept;
Wrapper(Wrapper&& other) noexcept;
~Wrapper() noexcept;
Wrapper& operator=(const Wrapper& other) noexcept;
Wrapper& operator=(Wrapper&& other) noexcept;
inline const Context* context() const noexcept {
return m_context;
}
inline Context* context() noexcept {
return m_context;
}
inline bool own_underlying_object() const noexcept {
return (m_flags & OwnUnderlyingObject) != 0;
}
inline void set_own_underlying_object(bool own_underlying_object) noexcept {
if(own_underlying_object)
m_flags |= OwnUnderlyingObject;
else
m_flags &= ~OwnUnderlyingObject;
}
inline void swap(Wrapper& other) noexcept {
using std::swap;
swap(m_context, other.m_context);
swap(m_flags, other.m_flags);
}
friend inline void swap(Wrapper& wrapper_0, Wrapper& wrapper_1) noexcept {
wrapper_0.swap(wrapper_1);
}
protected:
Context* m_context = nullptr;
Flags m_flags = 0;
};
}

View File

@@ -1,8 +1,9 @@
// Copyright 2022 Simon Boyé
#include <memory> #include <memory>
namespace Vulkan { namespace vk {
class MemoryPage; class MemoryPage;
@@ -14,5 +15,9 @@ using MemoryPageSP = std::shared_ptr<MemoryBlock>;
class Context; class Context;
class ContextSettings; class ContextSettings;
class Buffer;
class RenderPass;
class Fence;
} }