Skip to content
Snippets Groups Projects
Select Git revision
  • 179304bc711e67152ff9e2489e61d800889862f7
  • develop default protected
  • feature/webrtc
  • feature/mesh-based-reprojection
  • feature/linux-fixes
  • feature/dual-layer-reprojection
  • feature/frame-invalidation
  • feature/plot-script
  • bug/jittering
  • feature/indirect-sky
  • feature/depth-peeling-reprojection protected
  • master
12 results

light.cpp

Blame
  • user avatar
    Pablo Escobar authored
    179304bc
    History
    light.cpp 19.70 KiB
    // file      : liblava-demo/light.cpp
    // copyright : Copyright (c) 2018-present, Lava Block OÜ and contributors
    // license   : MIT; see accompanying LICENSE file
    
    #include <imgui.h>
    #include <demo.hpp>
    
    using namespace lava;
    
    // structs for interfacing with shaders
    namespace glsl
    {
        using namespace glm;
        using uint = uint32_t;
    #include "res/light/data.inc"
    }
    
    glsl::UboData g_ubo;
    
    struct gbuffer_attachment {
        enum type : uint32_t {
            albedo = 0,
            normal,
            metallic_roughness,
            depth,
            count
        };
    
        VkFormats requested_formats;
        VkImageUsageFlags usage;
        image::ptr image_handle;
        attachment::ptr renderpass_attachment;
        VkAttachmentReference subpass_reference;
    
        bool create(uint32_t index);
    };
    
    using attachment_array = std::array<gbuffer_attachment, gbuffer_attachment::count>;
    attachment_array g_attachments = {
        gbuffer_attachment{ { VK_FORMAT_R8G8B8A8_UNORM }, VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT },
        gbuffer_attachment{ { VK_FORMAT_R16G16B16A16_SFLOAT }, VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT },
        gbuffer_attachment{ { VK_FORMAT_R16G16_SFLOAT }, VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT },
        gbuffer_attachment{ { VK_FORMAT_D32_SFLOAT, VK_FORMAT_D16_UNORM }, VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT },
    };
    
    using light_array = std::array<glsl::LightData, 3>;
    const light_array g_lights = {
        glsl::LightData{ { 2.0f, 2.0f, 2.5f }, 10.0f, { 30.0f, 10.0f, 10.0f } },
        glsl::LightData{ { -2.0f, -2.0f, -0.5f }, 10.0f, { 10.0f, 30.0f, 10.0f } },
        glsl::LightData{ { 0.0f, 0.0f, -1.5f }, 10.0f, { 10.0f, 10.0f, 30.0f } }
    };
    
    app* g_app = nullptr;
    
    render_pass::ptr create_gbuffer_renderpass(attachment_array& attachments);
    
    int main(int argc, char* argv[]) {
        app app("lava light", { argc, argv });
        if (!app.setup())
            return error::not_ready;
    
        target_callback resize_callback;
        app.target->add_callback(&resize_callback);
    
        g_app = &app;
    
        // create global immutable resources
        // destroyed in app.add_run_end
    
        mesh::ptr object = create_mesh(app.device, mesh_type::quad);
        if (!object)
            return error::create_failed;
    
        using object_array = std::array<mat4, 2>;
        object_array object_instances;
    
        texture::ptr tex_normal = load_texture(app.device, "light/normal.png");
        texture::ptr tex_roughness = load_texture(app.device, "light/roughness.png");
        if (!tex_normal || !tex_roughness)
            return error::create_failed;
    
        app.staging.add(tex_normal);
        app.staging.add(tex_roughness);
    
        buffer ubo_buffer;
        if (!ubo_buffer.create_mapped(app.device, nullptr, sizeof(g_ubo), VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT))
            return error::create_failed;
    
        buffer light_buffer;
        if (!light_buffer.create_mapped(app.device, g_lights.data(), sizeof(g_lights), VK_BUFFER_USAGE_STORAGE_BUFFER_BIT))
            return error::create_failed;
    
        const VkSamplerCreateInfo sampler_info = {
            .sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO,
            .magFilter = VK_FILTER_NEAREST,
            .minFilter = VK_FILTER_NEAREST,
            .mipmapMode = VK_SAMPLER_MIPMAP_MODE_NEAREST
        };
        VkSampler sampler;
        if (!app.device->vkCreateSampler(&sampler_info, &sampler))
            return error::create_failed;
    
        // pipeline-specific resources
        // created in app.on_create, destroyed in app.on_destroy
    
        descriptor::pool descriptor_pool;
    
        render_pass::ptr gbuffer_renderpass = make_render_pass(app.device);
        descriptor::ptr gbuffer_set_layout = make_descriptor();
        pipeline_layout::ptr gbuffer_pipeline_layout = make_pipeline_layout();
        graphics_pipeline::ptr gbuffer_pipeline = make_graphics_pipeline(app.device);
        VkDescriptorSet gbuffer_set = VK_NULL_HANDLE;
    
        descriptor::ptr lighting_set_layout = make_descriptor();
        pipeline_layout::ptr lighting_pipeline_layout = make_pipeline_layout();
        graphics_pipeline::ptr lighting_pipeline = make_graphics_pipeline(app.device);
        VkDescriptorSet lighting_set = VK_NULL_HANDLE;
    
        app.on_create = [&]() {
            const VkDescriptorPoolSizes pool_sizes = {
                { VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 1 * 2 }, // one uniform buffer for each pass (gbuffer + lighting)
                { VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1 }, // light buffer
                { VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 2 /* normal + roughness texture */ + g_attachments.size() },
            };
            constexpr ui32 max_sets = 2; // one for each pass
            if (!descriptor_pool.create(app.device, pool_sizes, max_sets))
                return false;
    
            // gbuffer pass
    
            gbuffer_set_layout->add_binding(0, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT);
            gbuffer_set_layout->add_binding(1, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, VK_SHADER_STAGE_FRAGMENT_BIT);
            gbuffer_set_layout->add_binding(2, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, VK_SHADER_STAGE_FRAGMENT_BIT);
            if (!gbuffer_set_layout->create(app.device))
                return false;
            gbuffer_set = gbuffer_set_layout->allocate(descriptor_pool.get());
            if (!gbuffer_set)
                return false;
    
            std::vector<VkWriteDescriptorSet> gbuffer_write_sets;
            for (const descriptor::binding::ptr& binding : gbuffer_set_layout->get_bindings()) {
                const VkDescriptorSetLayoutBinding& info = binding->get();
                gbuffer_write_sets.push_back({ .sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET,
                                               .dstSet = gbuffer_set,
                                               .dstBinding = info.binding,
                                               .descriptorCount = info.descriptorCount,
                                               .descriptorType = info.descriptorType });
            }
    
            gbuffer_write_sets[0].pBufferInfo = ubo_buffer.get_descriptor_info();
            gbuffer_write_sets[1].pImageInfo = tex_normal->get_descriptor_info();
            gbuffer_write_sets[2].pImageInfo = tex_roughness->get_descriptor_info();
    
            app.device->vkUpdateDescriptorSets(gbuffer_write_sets.size(), gbuffer_write_sets.data());
    
            gbuffer_pipeline_layout->add(gbuffer_set_layout);
            gbuffer_pipeline_layout->add_range({ VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT, 0, sizeof(glsl::PushConstantData) });
            if (!gbuffer_pipeline_layout->create(app.device))
                return false;
    
            const VkPipelineColorBlendAttachmentState gbuffer_blend_state = {
                .colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT
            };
    
            if (!gbuffer_pipeline->add_shader(file_data("light/gbuffer.vertex.spirv"), VK_SHADER_STAGE_VERTEX_BIT))
                return false;
            if (!gbuffer_pipeline->add_shader(file_data("light/gbuffer.fragment.spirv"), VK_SHADER_STAGE_FRAGMENT_BIT))
                return false;
            for (size_t i = 0; i < g_attachments.size() - 1; i++) {
                gbuffer_pipeline->add_color_blend_attachment(gbuffer_blend_state);
            }
            gbuffer_pipeline->set_depth_test_and_write(true, true);
            gbuffer_pipeline->set_depth_compare_op(VK_COMPARE_OP_LESS);
            gbuffer_pipeline->set_rasterization_cull_mode(VK_CULL_MODE_NONE);
            gbuffer_pipeline->set_vertex_input_binding({ 0, sizeof(vertex), VK_VERTEX_INPUT_RATE_VERTEX });
            gbuffer_pipeline->set_vertex_input_attributes({
                { 0, 0, VK_FORMAT_R32G32B32_SFLOAT, to_ui32(offsetof(vertex, position)) },
                { 1, 0, VK_FORMAT_R32G32_SFLOAT, to_ui32(offsetof(vertex, uv)) },
                { 2, 0, VK_FORMAT_R32G32B32_SFLOAT, to_ui32(offsetof(vertex, normal)) },
            });
            gbuffer_pipeline->set_layout(gbuffer_pipeline_layout);
            gbuffer_pipeline->set_auto_size(true);
    
            gbuffer_pipeline->on_process = [&](VkCommandBuffer cmd_buf) {
                scoped_label label(cmd_buf, "gbuffer");
    
                gbuffer_pipeline_layout->bind(cmd_buf, gbuffer_set);
                object->bind(cmd_buf);
    
                for (size_t i = 0; i < object_instances.size(); i++) {
                    const glsl::PushConstantData pc = {
                        .model = object_instances[i],
                        .color = v3(1.0f),
                        .metallic = float(i % 2),
                        .enableNormalMapping = 1 - (i % 2)
                    };
                    app.device->call().vkCmdPushConstants(cmd_buf, gbuffer_pipeline_layout->get(), VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT,
                                                          0, sizeof(pc), &pc);
                    object->draw(cmd_buf);
                }
            };
    
            gbuffer_renderpass = create_gbuffer_renderpass(g_attachments);
            gbuffer_renderpass->add_front(gbuffer_pipeline);
    
            // lighting pass
    
            for (size_t i = 0; i < g_attachments.size(); i++) {
                lighting_set_layout->add_binding(i, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, VK_SHADER_STAGE_FRAGMENT_BIT);
            }
            lighting_set_layout->add_binding(g_attachments.size() + 0, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, VK_SHADER_STAGE_FRAGMENT_BIT);
            lighting_set_layout->add_binding(g_attachments.size() + 1, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, VK_SHADER_STAGE_FRAGMENT_BIT);
            if (!lighting_set_layout->create(app.device))
                return false;
            lighting_set = lighting_set_layout->allocate(descriptor_pool.get());
            if (!lighting_set)
                return false;
    
            lighting_pipeline_layout->add(lighting_set_layout);
            if (!lighting_pipeline_layout->create(app.device))
                return false;
    
            const VkPipelineColorBlendAttachmentState lighting_blend_state = {
                .colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT
            };
    
            if (!lighting_pipeline->add_shader(file_data("light/lighting.vertex.spirv"), VK_SHADER_STAGE_VERTEX_BIT))
                return false;
            if (!lighting_pipeline->add_shader(file_data("light/lighting.fragment.spirv"), VK_SHADER_STAGE_FRAGMENT_BIT))
                return false;
            lighting_pipeline->add_color_blend_attachment(lighting_blend_state);
            lighting_pipeline->set_rasterization_cull_mode(VK_CULL_MODE_NONE);
            lighting_pipeline->set_layout(lighting_pipeline_layout);
            lighting_pipeline->set_auto_size(true);
    
            lighting_pipeline->on_process = [&](VkCommandBuffer cmd_buf) {
                scoped_label label(cmd_buf, "lighting");
    
                // run a fullscreen pass to calculate lighting, the shader loops over all lights
                // - this is NOT very performant, but simplifies the demo
                // - in a proper deferred renderer you most likely want to:
                //     - render light geometries (e.g. spheres) while depth testing against the gbuffer depth
                //     - use some kind of spatial acceleration structure for lights
    
                lighting_pipeline_layout->bind(cmd_buf, lighting_set);
                app.device->call().vkCmdDraw(cmd_buf, 3, 1, 0, 0);
            };
    
            // use lava's default backbuffer renderpass
            render_pass::ptr lighting_renderpass = app.shading.get_pass();
            lighting_renderpass->add_front(lighting_pipeline);
    
            // the resize callback creates the gbuffer images and renderpass, call it once manually
            if (!resize_callback.on_created({}, { { 0, 0 }, app.target->get_size() }))
                return false;
    
            // renderpasses have been created at this point, actually create the pipelines
            if (!gbuffer_pipeline->create(gbuffer_renderpass->get()))
                return false;
            if (!lighting_pipeline->create(lighting_renderpass->get()))
                return false;
    
            return true;
        };
    
        app.on_process = [&](VkCommandBuffer cmd_buf, index frame) {
            scoped_label label(cmd_buf, "on_process");
    
            // start custom renderpass, run on_process() for each pipeline added to the renderpass
            gbuffer_renderpass->process(cmd_buf);
        };
    
        app.on_update = [&](delta dt) {
            float seconds = to_delta(app.get_running_time());
            constexpr float distance = 1.25f;
            const float left = -distance * (object_instances.size() - 1) * 0.5f;
            for (size_t i = 0; i < object_instances.size(); i++) {
                float x = left + distance * i;
                v3 axis = v3(0.0f);
                axis[i % 3] = 1.0f;
                mat4 model = mat4(1.0f);
                model = glm::translate(model, { x, 0.0f, 0.0f });
                model = glm::rotate(model, glm::radians(std::fmod(seconds * 45.0f, 360.0f)), axis);
                model = glm::scale(model, { 0.5f, 0.5f, 0.5f });
                object_instances[i] = model;
            }
    
            return true;
        };
    
        // handle backbuffer resize
    
        resize_callback.on_created = [&](VkAttachmentsRef, rect area) {
            // update uniform buffer
            g_ubo.camPos = { 0.0f, 0.0f, -1.25f };
            g_ubo.lightCount = g_lights.size();
            g_ubo.view = glm::lookAtLH(g_ubo.camPos, { 0.0f, 0.0f, 0.0f }, { 0.0f, 1.0f, 0.0f });
            g_ubo.projection = perspective_matrix(area.get_size(), 90.0f, 3.0f);
            g_ubo.invProjection = glm::inverse(g_ubo.projection);
            g_ubo.resolution = area.get_size();
            *(decltype(g_ubo)*) ubo_buffer.get_mapped_data() = g_ubo;
    
            // (re-)create gbuffer attachments and collect views for framebuffer creation
            VkImageViews views;
            for (gbuffer_attachment& att : g_attachments) {
                if (!att.image_handle->create(app.device, area.get_size()))
                    return false;
                views.push_back(att.image_handle->get_view());
            }
    
            // update lighting descriptor set with new gbuffer image handles
            std::vector<VkWriteDescriptorSet> lighting_write_sets;
            for (const descriptor::binding::ptr& binding : lighting_set_layout->get_bindings()) {
                const VkDescriptorSetLayoutBinding& info = binding->get();
                lighting_write_sets.push_back({ .sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET,
                                                .dstSet = lighting_set,
                                                .dstBinding = info.binding,
                                                .descriptorCount = info.descriptorCount,
                                                .descriptorType = info.descriptorType });
            }
    
            std::array<VkDescriptorImageInfo, g_attachments.size()> lighting_images;
            for (size_t i = 0; i < g_attachments.size(); i++) {
                lighting_images[i] = {
                    .sampler = sampler,
                    .imageView = g_attachments[i].image_handle->get_view(),
                    .imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL
                };
                lighting_write_sets[i].pImageInfo = &lighting_images[i];
            }
            lighting_write_sets[g_attachments.size() + 0].pBufferInfo = ubo_buffer.get_descriptor_info();
            lighting_write_sets[g_attachments.size() + 1].pBufferInfo = light_buffer.get_descriptor_info();
    
            app.device->vkUpdateDescriptorSets(lighting_write_sets.size(), lighting_write_sets.data());
    
            // create framebuffer (and renderpass if necessary)
            if (gbuffer_renderpass->get() == VK_NULL_HANDLE)
                return gbuffer_renderpass->create({ views }, area);
            else
                return gbuffer_renderpass->on_created({ views }, area);
        };
    
        resize_callback.on_destroyed = [&]() {
            app.device->wait_for_idle();
            // destroy framebuffer
            gbuffer_renderpass->on_destroyed();
            // destroy gbuffer attachments
            for (gbuffer_attachment& att : g_attachments) {
                att.image_handle->destroy();
            }
        };
    
        app.imgui.on_draw = [&]() {
            ImGui::SetNextWindowPos(ImVec2(30, 30), ImGuiCond_FirstUseEver);
            ImGui::SetNextWindowSize(ImVec2(262, 262), ImGuiCond_FirstUseEver);
    
            ImGui::Begin(app.get_name());
    
            app.draw_about();
    
            ImGui::End();
        };
    
        app.on_destroy = [&]() {
            app.target->remove_callback(&resize_callback);
            resize_callback.on_destroyed();
    
            lighting_pipeline->destroy();
            lighting_pipeline_layout->destroy();
            lighting_set_layout->destroy();
    
            gbuffer_pipeline->destroy();
            gbuffer_pipeline_layout->destroy();
            gbuffer_set_layout->destroy();
            gbuffer_renderpass->destroy();
    
            descriptor_pool.destroy();
        };
    
        app.add_run_end([&]() {
            app.device->vkDestroySampler(sampler);
            sampler = VK_NULL_HANDLE;
    
            light_buffer.destroy();
            ubo_buffer.destroy();
    
            tex_roughness->destroy();
            tex_normal->destroy();
    
            object->destroy();
        });
    
        return app.run();
    }
    
    bool gbuffer_attachment::create(uint32_t index) {
        usage |= VK_IMAGE_USAGE_SAMPLED_BIT;
        std::optional<VkFormat> format = get_supported_format(g_app->device->get_vk_physical_device(), requested_formats, usage);
        if (!format.has_value())
            return false;
    
        image_handle = make_image(*format);
        image_handle->set_usage(usage);
    
        renderpass_attachment = make_attachment(*format);
        renderpass_attachment->set_op(VK_ATTACHMENT_LOAD_OP_CLEAR, VK_ATTACHMENT_STORE_OP_STORE);
        renderpass_attachment->set_stencil_op(VK_ATTACHMENT_LOAD_OP_DONT_CARE, VK_ATTACHMENT_STORE_OP_DONT_CARE);
        renderpass_attachment->set_layouts(VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
    
        subpass_reference.attachment = index;
        subpass_reference.layout = (usage & VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT)
                                       ? VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL
                                       : VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
    
        return true;
    }
    
    render_pass::ptr create_gbuffer_renderpass(attachment_array& attachments) {
        VkClearValues clear_values(attachments.size(), { .color = { 0.0f, 0.0f, 0.0f, 1.0f } });
        clear_values[gbuffer_attachment::depth] = { .depthStencil = { 1.0f, 0 } };
    
        render_pass::ptr pass = make_render_pass(g_app->device);
        pass->set_clear_values(clear_values);
    
        VkAttachmentReferences color_attachments;
        for (uint32_t i = 0; i < gbuffer_attachment::count; i++) {
            if (!attachments[i].create(i))
                return nullptr;
            pass->add(attachments[i].renderpass_attachment);
            if (i != gbuffer_attachment::depth)
                color_attachments.push_back(attachments[i].subpass_reference);
        }
    
        subpass::ptr sub = make_subpass();
        sub->set_color_attachments(color_attachments);
        sub->set_depth_stencil_attachment(attachments[gbuffer_attachment::depth].subpass_reference);
        pass->add(sub);
    
        subpass_dependency::ptr dependency = make_subpass_dependency(VK_SUBPASS_EXTERNAL, 0);
        // wait for previous fragment shader to finish reading before clearing attachments
        dependency->set_stage_mask(
            VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT,
            VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT | VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT);
        // we need a memory barrier because this isn't a standard write-after-read hazard
        // subpass deps have an implicit attachment layout transition, so the dst access mask must be correct
        dependency->set_access_mask(0,
                                    VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT);
        pass->add(dependency);
    
        dependency = make_subpass_dependency(pass->get_subpass_count() - 1, VK_SUBPASS_EXTERNAL);
        // don't run any fragment shader (sample attachments) before we're done writing to attachments
        dependency->set_stage_mask(VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT | VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT,
                                   VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT);
        // make attachment writes visible to subsequent reads
        dependency->set_access_mask(VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT,
                                    VK_ACCESS_SHADER_READ_BIT);
        pass->add(dependency);
    
        return pass;
    }