Files
tanks-reborn/source/render/renderer.cpp

461 lines
16 KiB
C++

#include "renderer.hpp"
#include "game/model.hpp"
#include "ui/display.hpp"
#include "vuk/ImageAttachment.hpp"
#include "vuk/RenderGraph.hpp"
#include "vuk/Types.hpp"
#include "vuk/Value.hpp"
#include "vuk/runtime/CommandBuffer.hpp"
#include "vuk/runtime/vk/Allocator.hpp"
#include "vuk/runtime/vk/DeviceFrameResource.hpp"
#include "vuk/runtime/vk/Image.hpp"
#include "vuk/runtime/vk/VkRuntime.hpp"
#include "vuk/vsl/Core.hpp"
#include <backends/imgui_impl_glfw.h>
#include <cstdint>
#include <vuk/extra/ImGuiIntegration.hpp>
#include <vuk/runtime/vk/Pipeline.hpp>
#include <vuk/runtime/vk/PipelineTypes.hpp>
#include <vuk/vsl/BindlessArray.hpp>
#include <glm/glm.hpp>
#include <span>
#include <string_view>
[[nodiscard]] auto create_ia(std::string_view name, vuk::Format format) {
auto image = vuk::declare_ia(name);
image->format = format;
image->layer_count = 1;
image->sample_count = vuk::SampleCountFlagBits::e1;
return std::move(image);
}
[[nodiscard]] auto render_lines(std::uint32_t count) {
return vuk::make_pass("line", [count](vuk::CommandBuffer &command_buffer,
VUK_BA(vuk::eMemoryRead) camera,
VUK_BA(vuk::eVertexRead) vertices,
VUK_IA(vuk::eColorWrite) target) {
auto attributes = std::vector<vuk::VertexInputAttributeDescription>();
attributes.push_back({
.location = 0,
.binding = 0,
.format = vuk::Format::eR32G32B32Sfloat,
.offset = 0,
});
attributes.push_back({
.location = 1,
.binding = 0,
.format = vuk::Format::eR32G32B32Sfloat,
.offset = sizeof(float) * 3,
});
command_buffer.bind_graphics_pipeline("debug_line")
.set_dynamic_state(vuk::DynamicStateFlagBits::eScissor |
vuk::DynamicStateFlagBits::eViewport)
.set_viewport(0, vuk::Rect2D::relative(0.0f, 1.0f, 1.0f, -1.0f))
.set_scissor(0, vuk::Rect2D::framebuffer())
.set_primitive_topology(vuk::PrimitiveTopology::eLineList)
.set_rasterization({})
.set_depth_stencil({
.depthTestEnable = false,
.depthWriteEnable = false,
.depthCompareOp = vuk::CompareOp::eLess,
})
.bind_vertex_buffer(0, vertices, attributes, sizeof(float) * 6)
.bind_buffer(0, 0, camera)
.set_color_blend(target, {})
.draw(count, 1, 0, 0);
return std::make_tuple(std::move(target), std::move(camera));
});
}
[[nodiscard]] auto composite() {
return vuk::make_pass("composite", [](vuk::CommandBuffer &command_buffer,
VUK_IA(vuk::eFragmentSampled) normals,
VUK_IA(vuk::eFragmentSampled) albedos,
VUK_IA(vuk::eFragmentSampled) gui,
VUK_IA(vuk::eColorWrite) target) {
command_buffer.bind_graphics_pipeline("composite")
.set_dynamic_state(vuk::DynamicStateFlagBits::eScissor |
vuk::DynamicStateFlagBits::eViewport)
.set_viewport(0, vuk::Rect2D::framebuffer())
.set_scissor(0, vuk::Rect2D::framebuffer())
.set_rasterization({})
.set_depth_stencil({
.depthTestEnable = false,
.depthWriteEnable = false,
.depthCompareOp = vuk::CompareOp::eLess,
})
.bind_image(0, 0, normals)
.bind_sampler(0, 0, {})
.bind_image(0, 1, albedos)
.bind_sampler(0, 1, {})
.bind_image(0, 2, gui)
.bind_sampler(0, 2, {})
.set_color_blend(target, {})
.draw(3, 1, 0, 0);
return std::make_tuple(std::move(target));
});
}
[[nodiscard]] auto render_mesh(trb::render::renderer::loaded_mesh *mesh,
std::uint32_t instance_count) {
return vuk::make_pass(
"render mesh",
[mesh, instance_count](vuk::CommandBuffer &command_buffer,
VUK_BA(vuk::eMemoryRead) camera_buffer,
VUK_BA(vuk::eVertexRead) vertex_buffer,
VUK_BA(vuk::eIndexRead) index_buffer,
VUK_BA(vuk::eVertexRead) transform_buffer,
VUK_IA(vuk::eColorWrite) normals,
VUK_IA(vuk::eColorWrite) albedos,
VUK_IA(vuk::eDepthStencilRW) depth_target) {
auto attributes = std::vector<vuk::VertexInputAttributeDescription>();
attributes.push_back({
.location = 0,
.binding = 0,
.format = vuk::Format::eR32G32B32Sfloat,
.offset = 0,
});
attributes.push_back({
.location = 1,
.binding = 0,
.format = vuk::Format::eR32G32B32Sfloat,
.offset = sizeof(glm::vec3),
});
attributes.push_back({
.location = 2,
.binding = 0,
.format = vuk::Format::eR32G32Sfloat,
.offset = sizeof(glm::vec3) + sizeof(glm::vec3),
});
command_buffer.bind_graphics_pipeline("mesh")
.set_dynamic_state(vuk::DynamicStateFlagBits::eScissor |
vuk::DynamicStateFlagBits::eViewport)
.set_viewport(0, vuk::Rect2D::relative(0.0f, 1.0f, 1.0f, -1.0f))
.set_scissor(0, vuk::Rect2D::framebuffer())
.set_rasterization({
//.polygonMode = vuk::PolygonMode::eLine,
})
.set_depth_stencil({
.depthTestEnable = true,
.depthWriteEnable = true,
.depthCompareOp = vuk::CompareOp::eLess,
})
.set_color_blend(normals, vuk::PipelineColorBlendAttachmentState())
.set_color_blend(albedos, vuk::PipelineColorBlendAttachmentState())
.broadcast_color_blend({})
.bind_vertex_buffer(0, vertex_buffer, attributes, sizeof(float) * 8)
.bind_index_buffer(index_buffer, vuk::IndexType::eUint32)
.bind_buffer(0, 0, camera_buffer)
.bind_image(0, 1, *mesh->albedo_view)
.bind_buffer(0, 2, transform_buffer)
.bind_sampler(0, 1,
{
.magFilter = vuk::Filter::eLinear,
.minFilter = vuk::Filter::eLinear,
})
.draw_indexed(mesh->index_count, instance_count, 0, 0, 0);
return std::make_tuple(std::move(camera_buffer), std::move(normals),
std::move(albedos), std::move(depth_target));
});
}
namespace trb::render {
request request::add_mesh(std::uint32_t render_id) {
if (current_mesh) {
commit();
}
current_mesh = mesh_transforms{
.id = render_id,
};
return *this;
}
request request::with_transform(glm::mat4 transform) {
if (!current_mesh) {
throw std::runtime_error(
"no mesh in current operation to apply transform to");
}
current_mesh->transforms.push_back(transform);
return *this;
}
request request::commit() {
if (!current_mesh) {
throw std::runtime_error("no mesh in current operation to commit");
}
if (current_mesh->transforms.empty()) {
current_mesh->transforms.push_back(glm::mat4(1.0f));
}
meshes.push_back(std::move(*current_mesh));
current_mesh = std::nullopt;
return *this;
}
request request::add_lines(std::span<const glm::vec3> line_vertices,
glm::vec3 color) {
auto &dst = lines.emplace_back();
dst.reserve(line_vertices.size() * 2);
for (auto &v : line_vertices) {
dst.push_back(v);
dst.push_back(color);
}
return *this;
}
request request::add_circle(glm::vec2 pos, float radius) {
circles.emplace_back(pos, radius);
return *this;
}
std::span<const mesh_transforms> request::requested_meshes() const noexcept {
return meshes;
}
std::vector<std::span<const glm::vec3>> request::requested_lines() const {
auto collected = std::vector<std::span<const glm::vec3>>();
for (const auto &line : lines) {
collected.emplace_back(line);
}
return collected;
}
std::uint32_t renderer::next_mesh_id() {
// Is there someway to do this with a do while?
auto generated_id = id_dist(rng);
while (registered_meshes.contains(generated_id)) [[unlikely]] {
generated_id = id_dist(rng);
}
return generated_id;
}
vuk::PipelineBaseCreateInfo
renderer::create_compute_pipeline(std::string_view comp_module,
std::string_view comp_entry) {
const auto comp_bytes = compiler.compile(comp_module, comp_entry);
auto pipeline_info = vuk::PipelineBaseCreateInfo();
pipeline_info.add_spirv(comp_bytes, comp_module.data(), comp_entry.data());
return pipeline_info;
}
vuk::PipelineBaseCreateInfo renderer::create_graphics_pipeline(
std::string_view vs_module, std::string_view vs_entry,
std::string_view fs_module, std::string_view fs_entry) {
const auto vs_bytes = compiler.compile(vs_module, vs_entry);
const auto fs_bytes = compiler.compile(fs_module, fs_entry);
auto pipeline_info = vuk::PipelineBaseCreateInfo();
pipeline_info.add_spirv(vs_bytes, vs_module.data(), vs_entry.data());
pipeline_info.add_spirv(fs_bytes, fs_module.data(), fs_entry.data());
return pipeline_info;
}
vuk::PipelineBaseCreateInfo
renderer::create_graphics_pipeline(std::string_view module_name) {
return create_graphics_pipeline(module_name, "vert_main", module_name,
"frag_main");
}
void renderer::create_pipelines() {
context.runtime.create_named_pipeline(
"mesh", create_graphics_pipeline("render_mesh.slang"));
context.runtime.create_named_pipeline(
"composite", create_graphics_pipeline("composite.slang"));
context.runtime.create_named_pipeline(
"debug_line", create_graphics_pipeline("debug_line.slang"));
}
vuk::Value<vuk::ImageAttachment> renderer::get_swap_target() {
auto imported_swapchain = vuk::acquire_swapchain(context.swapchain);
auto swapchain_image =
vuk::acquire_next_image("swp_img", std::move(imported_swapchain));
return vuk::clear_image(std::move(swapchain_image),
vuk::ClearColor{0.0f, 0.0f, 1.0f, 1.0f});
}
renderer::vuk_frame_resources renderer::get_next_frame_resources() {
auto &frame_resource = context.superframe_resource.get_next_frame();
context.runtime.next_frame();
return {
.allocator = vuk::Allocator(frame_resource),
.swap_target = get_swap_target(),
};
}
renderer::renderer(ui::display *display)
: context(display), rng(std::random_device()()) {
create_pipelines();
IMGUI_CHECKVERSION();
ImGui::CreateContext();
ImGui_ImplGlfw_InitForOther(display->raw(), true);
imgui_data = vuk::extra::ImGui_ImplVuk_Init(context.superframe_allocator);
}
void renderer::render(request req, glm::mat4 view, glm::mat4 projection) {
auto [frame_allocator, frame_target] = get_next_frame_resources();
auto depth_image =
vuk::clear_image(create_ia("depth", vuk::Format::eD32Sfloat),
vuk::ClearDepthStencil{1.0f, 0});
auto normal_buffer =
vuk::clear_image(create_ia("normals", vuk::Format::eR32G32B32A32Sfloat),
vuk::ClearColor(0.0f, 0.0f, 0.0f, 1.0f));
auto albedo_buffer =
vuk::clear_image(create_ia("albedos", vuk::Format::eR32G32B32A32Sfloat),
vuk::ClearColor(0.0f, 0.0f, 0.0f, 1.0f));
auto imgui_buffer =
vuk::clear_image(create_ia("imgui", vuk::Format::eR32G32B32A32Sfloat),
vuk::ClearColor(0.0f, 0.0f, 0.0f, 0.0f));
imgui_buffer->level_count = 1;
normal_buffer.same_extent_as(frame_target);
albedo_buffer.same_extent_as(frame_target);
imgui_buffer.same_extent_as(frame_target);
depth_image.same_extent_as(frame_target);
struct {
glm::mat4 view;
glm::mat4 projection;
} camera_data{
.view = view,
.projection = projection,
};
auto [camera_buffer, camera_future] = vuk::create_buffer(
frame_allocator, vuk::MemoryUsage::eCPUtoGPU,
vuk::DomainFlagBits::eTransferOperation, std::span(&camera_data, 1));
for (const auto [id, transforms] : req.requested_meshes()) {
auto &mesh = registered_meshes.at(id);
auto matrices = std::vector<glm::mat4>();
for (const auto &transform : transforms) {
const auto model_matrix = transform * mesh.transform;
const auto normal_matrix =
glm::mat4(glm::transpose(glm::inverse(glm::mat3(model_matrix))));
matrices.push_back(normal_matrix);
matrices.push_back(model_matrix);
}
auto [transform_buffer, transform_future] = vuk::create_buffer(
frame_allocator, vuk::MemoryUsage::eCPUtoGPU,
vuk::DomainFlagBits::eTransferOperation, std::span(matrices));
std::tie(camera_future, normal_buffer, albedo_buffer, depth_image) =
render_mesh(&mesh, transforms.size())(
camera_future, vuk::make_constant("mesh vertices", *mesh.vertices),
vuk::make_constant("mesh indices", *mesh.indices),
std::move(transform_future), std::move(normal_buffer),
std::move(albedo_buffer), std::move(depth_image));
}
imgui_buffer = vuk::extra::ImGui_ImplVuk_Render(
frame_allocator, std::move(imgui_buffer), imgui_data);
std::tie(frame_target) =
composite()(std::move(normal_buffer), std::move(albedo_buffer),
std::move(imgui_buffer), std::move(frame_target));
for (const auto line_vertices : req.requested_lines()) {
auto [line_vertex_buffer, line_vertex_future] = vuk::create_buffer(
frame_allocator, vuk::MemoryUsage::eCPUtoGPU,
vuk::DomainFlagBits::eTransferOperation, std::span(line_vertices));
std::tie(frame_target, camera_future) =
render_lines(static_cast<std::uint32_t>(line_vertices.size()))(
std::move(camera_future), std::move(line_vertex_future),
std::move(frame_target));
}
auto entire_thing = vuk::enqueue_presentation(frame_target);
entire_thing.submit(frame_allocator, context.compiler);
}
std::uint32_t renderer::register_mesh(const game::mesh_node &mesh,
const game::texture_source &albedo) {
const auto id = next_mesh_id();
auto [vertex_buffer, vertex_future] = vuk::create_buffer(
context.superframe_allocator, vuk::MemoryUsage::eGPUonly,
vuk::DomainFlagBits::eTransferOperation, std::span(mesh.vertices));
auto [index_buffer, index_future] = vuk::create_buffer(
context.superframe_allocator, vuk::MemoryUsage::eGPUonly,
vuk::DomainFlagBits::eTransferOperation, std::span(mesh.indices));
auto albedo_ia = vuk::ImageAttachment();
albedo_ia.format = vuk::Format::eR8G8B8A8Srgb;
albedo_ia.extent = vuk::Extent3D{
.width = albedo.extent.x,
.height = albedo.extent.y,
.depth = 1,
};
albedo_ia.sample_count = vuk::Samples::e1;
albedo_ia.allow_srgb_unorm_mutable = true;
albedo_ia.usage |=
vuk::ImageUsageFlagBits::eTransferDst | vuk::ImageUsageFlagBits::eSampled;
albedo_ia.base_level = 0;
albedo_ia.level_count = 1;
albedo_ia.base_layer = 0;
albedo_ia.layer_count = 1;
albedo_ia.view_type = vuk::ImageViewType::e2D;
auto [albedo_image, albedo_view, albedo_future] =
vuk::create_image_and_view_with_data(
context.superframe_allocator, vuk::DomainFlagBits::eTransferOperation,
albedo_ia, albedo.pixels.get());
auto futures = std::vector<vuk::UntypedValue>();
futures.push_back(std::move(vertex_future));
futures.push_back(std::move(index_future));
futures.push_back(std::move(albedo_future.as_released(
vuk::Access::eFragmentSampled, vuk::DomainFlagBits::eGraphicsQueue)));
// TODO: Make async?
vuk::wait_for_values_explicit(context.superframe_allocator, context.compiler,
std::span(futures));
registered_meshes.insert(
{id, loaded_mesh{
.vertices = std::move(vertex_buffer),
.indices = std::move(index_buffer),
.albedo = std::move(albedo_image),
.albedo_view = std::move(albedo_view),
.transform = mesh.transform,
.index_count = static_cast<std::uint32_t>(mesh.indices.size()),
}});
return id;
}
void renderer::do_ui(const std::function<void()> &cb) {
ImGui_ImplGlfw_NewFrame();
ImGui::NewFrame();
cb();
ImGui::Render();
}
} // namespace trb::render