Files
Towards/external/loft/examples/viewer/src/main.cpp
T
Martin Slachta a04f0dc262 initial
2026-07-18 14:31:15 +02:00

606 lines
20 KiB
C++

#include <random>
#include <stdexcept>
#include <iostream>
#include <memory>
#include <chrono>
#include <volk.h>
#include "Instance.hpp"
#include "RenderPass.hpp"
#include "SamplerBuilder.hpp"
#include "ShaderManager.hpp"
#include "Swapchain.hpp"
#include "SDLWindow.h"
#include "RenderGraphBuilder.hpp"
#include "cglm/vec3.h"
#include "imgui.h"
#include "backends/imgui_impl_vulkan.h"
#include "backends/imgui_impl_sdl2.h"
#include "cglm/types.h"
#include "io/gltfSceneLoader.hpp"
#include "io/path.hpp"
#include "mesh/runtime/Scene.h"
#include "resources/GpuAllocator.h"
#include "runtime/Camera.h"
#include "scene/Light.h"
#include "shaders/ComputePipelineBuilder.hpp"
#include "shaders/Pipeline.hpp"
#include "shaders/PipelineBuilder.h"
#include "shaders/ShaderInputSetLayoutBuilder.hpp"
#include "shaders/SpirvShaderBuilder.hpp"
void lft_dbg_callback(lft::dbg::LogMessageSeverity severity,
lft::dbg::LogMessageType type,
const char *__restrict format,
va_list args) {
const char* titles[3] = {
"\033[0;34m[info]:",
"\033[0;33m[warn]:",
"\033[0;31m[fail]:"
};
fwrite(titles[severity], 14, 1, stdout);
if(args != nullptr) {
vfprintf(stdout, format, args);
} else {
fprintf(stdout, format);
}
fwrite("\033[0m", 4, 1, stdout);
printf("\n");
}
struct GBufferContext {
VkPipelineLayout layout;
Pipeline pipeline;
VkDescriptorSetLayout input_layout;
VkDescriptorSetLayout scene_input_layout;
VkDescriptorSet* global_input_set;
Scene *scene;
GBufferContext() :
layout(VK_NULL_HANDLE),
pipeline(VK_NULL_HANDLE, VK_NULL_HANDLE) {
}
};
struct ShadingContext {
VkDescriptorSetLayout shading_set_layout;
VkPipelineLayout layout;
Pipeline pipeline;
VkSampler sampler;
VkDescriptorSet* global_input_set;
std::vector<VkDescriptorSet> input_sets;
ShadingContext() :
layout(VK_NULL_HANDLE),
pipeline(VK_NULL_HANDLE, VK_NULL_HANDLE) {
}
};
struct ParticleContext {
VkDescriptorSetLayout compute_input_set_layout;
Pipeline compute_pipeline;
VkDescriptorSetLayout draw_input_set_layout;
Pipeline draw_pipeline;
VkDescriptorSet* global_input_set;
std::vector<VkDescriptorSet> compute_input_sets;
std::vector<VkDescriptorSet> draw_input_sets;
ParticleContext() :
compute_pipeline(VK_NULL_HANDLE, VK_NULL_HANDLE),
draw_pipeline(VK_NULL_HANDLE, VK_NULL_HANDLE) {
}
};
struct ImGuiContext {
VkRenderPass rp = VK_NULL_HANDLE;
bool is_initialized = false;
};
int main(int argc, char** argv) {
VkExtent2D extent = {
.width = 1024,
.height = 1024
};
/**
* Sets the executable path to find shaders and assets
*/
io::path::setup_exe_path(argv[0]);
const std::string engine_name = "loft";
const std::string application_name = "loft";
/**
* Opens up a window
*/
std::unique_ptr<lft::win::Window> window = std::make_unique<lft::win::SDLWindow>(application_name, (VkRect2D){
0, 0,
extent.width, extent.height
});
/**
* Different platforms has different extension needs.
*/
std::vector<std::string> required_extensions = window->get_required_extensions();
std::vector<std::string> required_layers = {
"VK_LAYER_KHRONOS_validation"
};
/**
* Instance initializes a connection with Vulkan driver
*/
auto instance = std::make_unique<const Instance>(
application_name, engine_name,
required_extensions,
required_layers,
lft_dbg_callback);
/*
* Surface is a way to tell window:
* Hey, I am going to render to you from GPU. I need some surface to render to.
*/
auto surface = window->create_surface(instance.get());
/**
* Gpu manages stuff around rendering. Needed for most graphics operations.
*/
auto gpu = std::make_unique<Gpu>(instance.get(), &surface);
/**
* Create camera for scene
*/
Camera camera(gpu.get(), (float)extent.width / extent.height);
/**
* Swapchain is a queue storage of images to render to for the Window we opened.
*/
Swapchain swapchain(gpu.get(), extent, &surface);
auto global_input_set_layout = ShaderInputSetLayoutBuilder()
.uniform_buffer(0)
.build(gpu.get());
auto global_input_set = ShaderInputSetBuilder()
.buffer(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 0, camera.buffer(), 0, sizeof(mat4) * 2 + sizeof(vec4))
.build(gpu.get(), global_input_set_layout);
std::vector<VkDescriptorSet> input_sets(1);
auto sceneData = GltfSceneLoader().from_file(argv[1]);
Scene scene(gpu.get(), &sceneData);
vec3 position = {20.0f, 250.0f, 50.0f};
vec3 direction = {0.0f, 0.0f, 0.0f};
vec4 color = {1.0f, 1.0f, 1.0f, 1.0f};
std::vector<Light> lights = {
Light::directional(1.0f, 1000.0f, position, direction, color)
};
MemoryAllocationInfo memoryInfo = {
.usage = MEMORY_USAGE_AUTO_PREFER_DEVICE,
};
BufferCreateInfo lightInfoBuffer = {
.size = sizeof(Light),
.usage = VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT,
.isExclusive = true,
};
Buffer light_buffer;
gpu->memory()->create_buffer(&lightInfoBuffer, &memoryInfo, &light_buffer);
void *pPtr;
gpu->memory()->map(light_buffer.allocation, &pPtr);
memcpy(pPtr, lights.data(), lightInfoBuffer.size);
gpu->memory()->unmap(light_buffer.allocation);
ShaderManager shader_manager(gpu.get(), io::path::shader(""));
VkSampler sampler = lft::SamplerBuilder()
.address_mode(VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE)
.filter(VK_FILTER_LINEAR)
.mipmap_mode(VK_SAMPLER_MIPMAP_MODE_LINEAR)
.border_color(VK_BORDER_COLOR_FLOAT_OPAQUE_WHITE)
.build(gpu.get());
lft::rg::Builder builder(gpu.get(), ImageChain::from_swapchain(swapchain), "swapchain");
// keeps all the drawn images -> to be available for debuggers like RenderDoc
builder.store_all_images();
GBufferContext* context = new GBufferContext();
context->global_input_set = &global_input_set;
context->scene = &scene;
context->input_layout = ShaderInputSetLayoutBuilder().build(gpu.get());
context->scene_input_layout = scene.input_layout()
.build(gpu.get());
auto task1 = lft::rg::render_task<GBufferContext>(
"task1", context,
[&](const lft::rg::TaskBuildInfo& info,
GBufferContext* context) {
input_sets.resize(info.num_buffers());
input_sets[info.buffer_idx()] = ShaderInputSetBuilder()
.build(info.gpu(), context->input_layout);
if(context->pipeline.pipeline() == VK_NULL_HANDLE) {
context->layout = PipelineLayoutBuilder()
.push_constant_range(0, sizeof(uint32_t) * 2,
VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT)
.input_set(0, global_input_set_layout)
.input_set(1, context->input_layout)
.input_set(2, context->scene_input_layout)
.build(info.gpu());
auto vertex_shader = shader_manager.get("Opaque.vert.spirv");
auto fragment_shader = shader_manager.get("Opaque.frag.spirv");
context->pipeline = PipelineBuilder(info.gpu(), info.viewport(),
context->layout, info.renderpass(),
4, // number of attachments, because each needs to have a blending
// TODO: How to do this automatically
vertex_shader, fragment_shader)
.set_vertex_input_info(Vertex::bindings(), Vertex::attributes())
.build();
context->pipeline.set_debug_name(gpu.get(), "offscreen_pipeline");
}
}, [](const lft::rg::TaskRecordInfo& info, GBufferContext* context) {
lft::RecordingBindPoint pipeline_bind = info.recording()
.bind_graphics_pipeline(context->pipeline)
.bind_descriptor_set(0, *context->global_input_set);
context->scene->draw(info.recording(), pipeline_bind);
});
task1.add_color_output("col_gbuf", VK_FORMAT_R8G8B8A8_SRGB);
task1.add_color_output("norm_gbuf", VK_FORMAT_R16G16B16A16_SFLOAT);
task1.add_color_output("pos_gbuf", VK_FORMAT_R16G16B16A16_SFLOAT);
task1.add_color_output("pbr_gbuf", VK_FORMAT_R8G8B8A8_UNORM);
task1.set_depth_output("depth_gbuf", VK_FORMAT_D32_SFLOAT_S8_UINT);
builder.add_task(task1.build());
auto shading_context = new ShadingContext();
shading_context->global_input_set = &global_input_set;
shading_context->sampler = sampler;
shading_context->shading_set_layout = ShaderInputSetLayoutBuilder()
.binding(0, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 1, VK_SHADER_STAGE_FRAGMENT_BIT)
.image(1, VK_SHADER_STAGE_FRAGMENT_BIT)
.image(2, VK_SHADER_STAGE_FRAGMENT_BIT)
.image(3, VK_SHADER_STAGE_FRAGMENT_BIT)
.image(4, VK_SHADER_STAGE_FRAGMENT_BIT)
.build(gpu.get());
auto shading_task = lft::rg::render_task<ShadingContext>(
"shading", shading_context,
[&](const lft::rg::TaskBuildInfo& info,
ShadingContext* context) {
context->input_sets.resize(info.num_buffers());
context->input_sets[info.buffer_idx()] = ShaderInputSetBuilder()
.buffer(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 0, light_buffer, 0, sizeof(Light))
.image(1, info.get_resource("col_gbuf").image_view, context->sampler)
.image(2, info.get_resource("norm_gbuf").image_view, context->sampler)
.image(3, info.get_resource("pos_gbuf").image_view, context->sampler)
.image(4, info.get_resource("pbr_gbuf").image_view, context->sampler)
.build(info.gpu(), context->shading_set_layout);
if(context->pipeline.pipeline() == VK_NULL_HANDLE) {
auto layout = PipelineLayoutBuilder()
.input_set(0, global_input_set_layout)
.input_set(1, context->shading_set_layout)
.build(info.gpu());
const Shader* offscreen_vertex = shader_manager.get("Offscreen.vert.spirv");
const Shader* shading_fragment = shader_manager.get("Shading.frag.spirv");
context->pipeline = PipelineBuilder(info.gpu(), info.viewport(),
layout, info.renderpass(),
1, offscreen_vertex, shading_fragment)
.set_vertex_input_info({}, {})
.build();
context->pipeline.set_debug_name(gpu.get(), "shading_pipeline");
}
},
[&](const lft::rg::TaskRecordInfo& info, ShadingContext* context) {
info.recording().bind_graphics_pipeline(context->pipeline)
.bind_descriptor_set(0, global_input_set)
.bind_descriptor_set(1, context->input_sets[info.buffer_idx()]);
info.recording().draw(3, 1, 0, 0);
});
shading_task.add_color_output("swapchain", swapchain.format().format, swapchain.extent(),
{ 0.0f, 0.0f, 0.0f, 1.0f });
shading_task.add_dependency("col_gbuf");
shading_task.add_dependency("norm_gbuf");
shading_task.add_dependency("pos_gbuf");
shading_task.add_dependency("pbr_gbuf");
builder.add_task(shading_task.build());
auto particle_context = new ParticleContext();
struct Particle {
vec4 position;
vec4 velocity;
vec4 color;
};
BufferCreateInfo particle_buffer_info = {
.size = 1000 * sizeof(Particle),
.usage = VK_BUFFER_USAGE_VERTEX_BUFFER_BIT | VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT,
.isExclusive = true,
};
MemoryAllocationInfo memory_info2 = {
.usage = MEMORY_USAGE_AUTO_PREFER_HOST
};
Buffer particle_buffer;
gpu->memory()->create_buffer(&particle_buffer_info, &memory_info2, &particle_buffer);
std::default_random_engine rndEngine((unsigned)time(nullptr));
std::uniform_real_distribution<float> rndDist(0.0f, 1.0f);
// Initial particle positions on a circle
std::vector<Particle> particles(1000);
for (auto& particle : particles) {
float r = 0.25f * sqrt(rndDist(rndEngine));
float theta = rndDist(rndEngine) * 2 * 3.14159265358979323846;
float x = r * cos(theta) * 100 / 100;
float y = r * sin(theta);
particle.position[0] = x;
particle.position[1] = y;
particle.position[2] = 0.0f;
particle.position[3] = 1.0f;
glm_normalize_to(particle.position, particle.velocity);
glm_vec3_scale(particle.velocity, 0.00025f, particle.velocity);
particle.color[0] = rndDist(rndEngine);
particle.color[1] = rndDist(rndEngine);
particle.color[2] = rndDist(rndEngine);
particle.color[3] = 1.0f;
// particle.velocity = glm::normalize(glm::vec2(x,y)) * 0.00025f;
// particle.color = glm::vec4(rndDist(rndEngine), rndDist(rndEngine), rndDist(rndEngine), 1.0f);
}
auto particle_task = lft::rg::compute_task<ParticleContext>(
"particle", particle_context,
[&](const lft::rg::TaskBuildInfo& info, ParticleContext* context) {
context->compute_input_set_layout = ShaderInputSetLayoutBuilder()
.binding(0, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1, VK_SHADER_STAGE_COMPUTE_BIT)
.build(info.gpu());
for(uint32_t i = 0; i < info.num_buffers(); i++) {
context->compute_input_sets.push_back(ShaderInputSetBuilder()
.buffer(VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 0, particle_buffer, 0, 1000 * sizeof(Particle))
.build(info.gpu(), context->compute_input_set_layout));
}
const Shader* particle_move_shader = shader_manager.get("Particles.comp.spirv");
VkPipelineLayout pipeline_layout = PipelineLayoutBuilder()
.input_set(0, context->compute_input_set_layout)
.build(info.gpu());
context->compute_pipeline = lft::ComputePipelineBuilder(particle_move_shader, pipeline_layout)
.build(info.gpu());
}, [&](const lft::rg::TaskRecordInfo& info, ParticleContext* context) {
info.recording()
.bind_compute_pipeline(context->compute_pipeline)
.bind_descriptor_set(0, context->compute_input_sets[0]);
info.recording().dispatch(1000 / 256, 1, 1);
}).add_buffer_output("particle_buffer", 0)
.build();
std::vector<Buffer> buffers;
buffers.push_back(particle_buffer);
builder.add_buffer_resource("particle_buffer", buffers, 1000 * sizeof(Particle));
builder.add_task(particle_task);
auto particle_draw_task = lft::rg::render_task<ParticleContext>(
"particle_draw", particle_context,
[&](const lft::rg::TaskBuildInfo& info, ParticleContext* context) {
if(context->draw_pipeline.pipeline() == VK_NULL_HANDLE) {
VkPipelineLayout draw_pipeline_layout = PipelineLayoutBuilder()
.input_set(0, global_input_set_layout)
.build(info.gpu());
const Shader* particle_draw_vertex = shader_manager.get("ParticleDraw.vert.spirv");
const Shader* particle_draw_fragment = shader_manager.get("ParticleDraw.frag.spirv");
context->draw_pipeline = PipelineBuilder(info.gpu(), info.viewport(),
draw_pipeline_layout, info.renderpass(),
1, particle_draw_vertex, particle_draw_fragment)
.set_vertex_input_info({
{
.binding = 0,
.stride = sizeof(Particle),
.inputRate = VK_VERTEX_INPUT_RATE_VERTEX
}
}, {
{ 0, 0, VK_FORMAT_R32G32B32A32_SFLOAT, offsetof(Particle, position) },
{ 1, 0, VK_FORMAT_R32G32B32A32_SFLOAT, offsetof(Particle, color) }
})
.topology(VK_PRIMITIVE_TOPOLOGY_POINT_LIST)
.build();
}
context->draw_input_sets.resize(info.num_buffers());
/* context->draw_input_sets[info.buffer_idx()] = ShaderInputSetBuilder(1)
.buffer(0, particle_buffer, 0, 1000 * sizeof(Particle))
.build(info.gpu(), context->draw_input_set_layout); */
}, [&](const lft::rg::TaskRecordInfo& info, ParticleContext* context) {
info.recording().bind_graphics_pipeline(context->draw_pipeline)
.bind_descriptor_set(0, global_input_set);
info.recording().bind_vertex_buffers({particle_buffer}, {0});
info.recording().draw(10, 1, 0, 0);
}).add_dependency("particle_buffer")
.add_dependency("shading")
.add_color_output("swapchain", swapchain.format().format, extent, {0.0f, 0.0f, 0.0f, 0.0f})
.build();
builder.add_task(particle_draw_task);
auto ins = gpu->instance()->instance();
ImGui_ImplVulkan_LoadFunctions([](const char *function_name, void *vulkan_instance) {
return vkGetInstanceProcAddr(*(reinterpret_cast<VkInstance *>(vulkan_instance)), function_name);
}, &ins);
auto imgui_context = new ImGuiContext();
auto imgui_task = lft::rg::render_task<ImGuiContext>(
"imgui", imgui_context,
[&](const lft::rg::TaskBuildInfo& info,
ImGuiContext* context) {
if(!context->is_initialized) {
ImGui::CreateContext();
ImGuiIO& io = ImGui::GetIO(); (void)io;
io.Fonts->AddFontFromFileTTF(io::path::asset("fonts/ProggyClean.ttf").c_str(), 32.0f);
ImGui_ImplSDL2_InitForVulkan(((lft::win::SDLWindow*)(window.get()))->get_handle());
ImGui_ImplVulkan_InitInfo init_info = {
.Instance = info.gpu()->instance()->instance(),
.PhysicalDevice = gpu->gpu(),
.Device = gpu->dev(),
.QueueFamily = 0,
.Queue = gpu->graphics_queue(),
.PipelineCache = nullptr,
.DescriptorPool = gpu->descriptor_pool(),
.Subpass = 0,
.MinImageCount = 2,
.ImageCount = 2,
.MSAASamples = VK_SAMPLE_COUNT_1_BIT,
.Allocator = nullptr,
.CheckVkResultFn = nullptr,
};
ImGui_ImplVulkan_Init(&init_info, info.renderpass());
context->is_initialized = true;
}
}, [&](const lft::rg::TaskRecordInfo& info, ImGuiContext* context) {
ImGui::Render();
ImDrawData* draw_data = ImGui::GetDrawData();
ImGui_ImplVulkan_RenderDrawData(draw_data, info.recording().cmdbuf());
});
imgui_task.set_output_to_final();
imgui_task.add_dependency("shading");
imgui_task.add_dependency("particle_draw");
auto imgui = imgui_task.build();
builder.add_task(imgui_task.build());
std::chrono::high_resolution_clock::time_point start = std::chrono::high_resolution_clock::now();
/**
* Builds the render graph.
*/
auto render_graph = builder.build();
VkSemaphore wait_on_image_semaphore = gpu->create_semaphore();
VkFence wait_on_image_fence = gpu->create_fence(false);
bool is_open = true;
bool is_imgui = true;
vec3 velocity = {0.0f, 0.0f, 0.0f};
/**
* Main loop
*/
while(is_open) {
uint32_t imageIdx = 0;
auto result = swapchain.get_next_image_idx(VK_NULL_HANDLE, wait_on_image_fence, &imageIdx);
if (result == VK_ERROR_OUT_OF_DATE_KHR) {
throw std::runtime_error("failed to acquire swap chain image!");
} else if (result != VK_SUCCESS && result != VK_SUBOPTIMAL_KHR) {
throw std::runtime_error("failed to acquire swap chain image!");
}
if(is_imgui) {
ImGui_ImplVulkan_NewFrame();
ImGui_ImplSDL2_NewFrame();
ImGui::NewFrame();
ImGui::ShowDemoWindow();
}
SDL_Event event;
bool change_imgui = false;
while(window->poll_event(&event)) {
if(is_imgui) {
ImGui_ImplSDL2_ProcessEvent(&event);
}
switch(event.type) {
case SDL_QUIT:
is_open = false;
break;
case SDL_FINGERMOTION:
camera.rotate(event.tfinger.dx * 10.0f, event.tfinger.dy * 10.0f);
break;
case SDL_MOUSEMOTION:
camera.rotate(event.motion.xrel * 0.2f, event.motion.yrel * 0.2f);
break;
case SDL_KEYDOWN:
case SDL_KEYUP:
switch(event.key.keysym.sym) {
case SDLK_p:
change_imgui = !event.key.state;
break;
case SDLK_w:
velocity[1] = (float)event.key.state;
break;
case SDLK_s:
velocity[1] = -(float)event.key.state;
break;
case SDLK_a:
velocity[0] = (float)event.key.state;
break;
case SDLK_d:
velocity[0] = -(float)event.key.state;
break;
}
}
}
render_graph.run(imageIdx, VK_NULL_HANDLE, wait_on_image_fence);
swapchain.present({ render_graph.buffer(0).final_signal(imageIdx) }, imageIdx);
camera.move(velocity);
camera.update();
// Implement turning ImGui task on and off
if(change_imgui) {
if(!is_imgui) {
builder.add_task(imgui);
} else {
builder.remove_task(imgui.name());
}
render_graph = builder.build();
is_imgui = !is_imgui;
}
}
return 0;
}