#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 #include #include #include #include #include #include #include #include [[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(); 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(); 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 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 request::requested_meshes() const noexcept { return meshes; } std::vector> request::requested_lines() const { auto collected = std::vector>(); 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 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(); 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(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(); 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(mesh.indices.size()), }}); return id; } void renderer::do_ui(const std::function &cb) { ImGui_ImplGlfw_NewFrame(); ImGui::NewFrame(); cb(); ImGui::Render(); } } // namespace trb::render