From 17eca070eff29ae60b2fec2d7da0de4bca64bd30 Mon Sep 17 00:00:00 2001
From: Jens Koenen <koenen@vr.rwth-aachen.de>
Date: Wed, 23 Aug 2023 16:07:41 +0200
Subject: [PATCH] Moved the computation of the quad tree to the GPU

---
 CMakeLists.txt                                |   1 +
 .../mesh_based_reprojection_defines.inc       |   1 +
 .../mesh_based_reprojection_quad_tree.comp    |  43 ++
 src/strategy/mesh_based_reprojection.cpp      | 531 +++++++++++++-----
 src/strategy/mesh_based_reprojection.hpp      |  29 +-
 5 files changed, 462 insertions(+), 143 deletions(-)
 create mode 100644 res/dpr/strategy/mesh_based_reprojection/mesh_based_reprojection_quad_tree.comp

diff --git a/CMakeLists.txt b/CMakeLists.txt
index 139c9bcb..572b1932 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -791,6 +791,7 @@ message("=======================================================================
                 strategy/mesh_based_reprojection/mesh_based_reprojection_indirect.frag
                 strategy/mesh_based_reprojection/mesh_based_reprojection_indirect_shadow.frag
                 strategy/mesh_based_reprojection/mesh_based_reprojection_edge_detection.comp
+                strategy/mesh_based_reprojection/mesh_based_reprojection_quad_tree.comp
                 strategy/mesh_based_reprojection/mesh_based_reprojection_overlay.vert
                 strategy/mesh_based_reprojection/mesh_based_reprojection_overlay.frag
                 strategy/mesh_based_reprojection/mesh_based_reprojection_mesh_overlay.vert
diff --git a/res/dpr/strategy/mesh_based_reprojection/mesh_based_reprojection_defines.inc b/res/dpr/strategy/mesh_based_reprojection/mesh_based_reprojection_defines.inc
index 148a999f..1223ee0c 100644
--- a/res/dpr/strategy/mesh_based_reprojection/mesh_based_reprojection_defines.inc
+++ b/res/dpr/strategy/mesh_based_reprojection/mesh_based_reprojection_defines.inc
@@ -2,5 +2,6 @@
 #define SHADER_INCLUDE_MESH_BASED_REPROJECTION_DEFINES
 
 #define MESH_BASED_REPROJECTION_EDGE_DETECTION_GROUP_SIZE 16
+#define MESH_BASED_REPROJECTION_QUAD_TREE_GROUP_SIZE      16
 
 #endif
\ No newline at end of file
diff --git a/res/dpr/strategy/mesh_based_reprojection/mesh_based_reprojection_quad_tree.comp b/res/dpr/strategy/mesh_based_reprojection/mesh_based_reprojection_quad_tree.comp
new file mode 100644
index 00000000..18476a62
--- /dev/null
+++ b/res/dpr/strategy/mesh_based_reprojection/mesh_based_reprojection_quad_tree.comp
@@ -0,0 +1,43 @@
+#version 450 core
+#extension GL_GOOGLE_include_directive : require
+
+#include "mesh_based_reprojection_defines.inc"
+
+layout(local_size_x = MESH_BASED_REPROJECTION_QUAD_TREE_GROUP_SIZE, local_size_y = MESH_BASED_REPROJECTION_QUAD_TREE_GROUP_SIZE, local_size_z = 1) in;
+
+layout(set = 0, binding = 0, r32f) uniform image2D image_layer_edge_src;
+layout(set = 0, binding = 1, r32f) uniform image2D image_layer_edge_dst;
+
+void main()
+{
+    ivec2 src_resolution = imageSize(image_layer_edge_src);
+    ivec2 dst_resolution = imageSize(image_layer_edge_dst);
+
+    ivec2 dst_coord = ivec2(gl_GlobalInvocationID);
+    
+    if(any(greaterThanEqual(dst_coord, dst_resolution)))
+    {
+        return;
+    }
+
+    float dst_value = -1.0;
+
+    for(uint offset_y = 0; offset_y < 2; offset_y++)
+    {
+        for(uint offset_x = 0; offset_x < 2; offset_x++)
+        {
+            ivec2 src_coord = dst_coord * 2 + ivec2(offset_x, offset_y);
+
+            if(any(greaterThanEqual(src_coord, src_resolution)))
+            {
+                return;
+            }
+
+            float src_value = imageLoad(image_layer_edge_src, src_coord).x;
+
+            dst_value = max(dst_value, src_value);
+        }
+    }
+
+    imageStore(image_layer_edge_dst, dst_coord, vec4(dst_value));
+}
\ No newline at end of file
diff --git a/src/strategy/mesh_based_reprojection.cpp b/src/strategy/mesh_based_reprojection.cpp
index 698ebd90..97211fab 100644
--- a/src/strategy/mesh_based_reprojection.cpp
+++ b/src/strategy/mesh_based_reprojection.cpp
@@ -22,22 +22,24 @@ namespace glsl
 
 MeshBasedReprojectionQuadTreeLevel::MeshBasedReprojectionQuadTreeLevel(const glm::uvec2& resolution) : resolution(resolution)
 {
-    this->buffer.resize(resolution.x * resolution.y);
+
 }
 
-bool MeshBasedReprojectionQuadTreeLevel::fill(float* data, uint32_t size)
+bool MeshBasedReprojectionQuadTreeLevel::fill(lava::buffer::ptr buffer)
 {
-    if (this->buffer.size() * sizeof(float) != size)
+    uint32_t buffer_size = this->resolution.x * this->resolution.y * sizeof(float);
+
+    if (buffer->get_size() != buffer_size)
     {
         return false;
     }
 
-    memcpy(this->buffer.data(), data, size);
+    this->buffer = (float*)buffer->get_mapped_data();
 
     return true;
 }
 
-bool MeshBasedReprojectionQuadTreeLevel::set_pixel(const glm::ivec2& coord, float value)
+inline bool MeshBasedReprojectionQuadTreeLevel::set_pixel(const glm::ivec2& coord, float value)
 {
     if (coord.x < 0 || coord.x >= this->resolution.x)
     {
@@ -55,7 +57,7 @@ bool MeshBasedReprojectionQuadTreeLevel::set_pixel(const glm::ivec2& coord, floa
     return true;
 }
 
-bool MeshBasedReprojectionQuadTreeLevel::get_pixel(const glm::ivec2& coord, float& value) const
+inline bool MeshBasedReprojectionQuadTreeLevel::get_pixel(const glm::ivec2& coord, float& value) const
 {
     if (coord.x < 0 || coord.x >= this->resolution.x)
     {
@@ -104,49 +106,18 @@ MeshBasedReprojectionQuadTree::MeshBasedReprojectionQuadTree(const glm::uvec2& r
     this->levels.push_back(make_mesh_based_reprojection_quad_tree_level(level_resolution));
 }
 
-bool MeshBasedReprojectionQuadTree::fill(lava::buffer::ptr buffer)
+bool MeshBasedReprojectionQuadTree::fill(const std::vector<lava::buffer::ptr>& buffers)
 {
-    MeshBasedReprojectionQuadTreeLevel::Ptr base_level = this->levels.front();
-
-    if (!base_level->fill((float*) buffer->get_mapped_data(), buffer->get_size()))
+    if (buffers.size() != this->levels.size())
     {
         return false;
     }
 
-    for (uint32_t index = 1; index < this->levels.size(); index++)
+    for (uint32_t index = 0; index < this->levels.size(); index++)
     {
-        MeshBasedReprojectionQuadTreeLevel::Ptr src_level = this->levels[index - 1];
-        MeshBasedReprojectionQuadTreeLevel::Ptr dst_level = this->levels[index];
-        glm::uvec2 dst_resolution = dst_level->get_resolution();
-
-        for (uint32_t dst_y = 0; dst_y < dst_resolution.y; dst_y++)
+        if (!this->levels[index]->fill(buffers[index]))
         {
-            for (uint32_t dst_x = 0; dst_x < dst_resolution.x; dst_x++)
-            {
-                glm::ivec2 dst_coord = glm::ivec2(dst_x, dst_y);
-                float dst_value = -1.0f;
-
-                for (uint32_t offset_y = 0; offset_y < 2; offset_y++)
-                {
-                    for (uint32_t offset_x = 0; offset_x < 2; offset_x++)
-                    {
-                        glm::ivec2 src_coord = dst_coord * 2 + glm::ivec2(offset_x, offset_y);
-                        float src_value = 0.0f;
-
-                        if (!src_level->get_pixel(src_coord, src_value))
-                        {
-                            continue;
-                        }
-
-                        dst_value = glm::max(dst_value, src_value);
-                    }
-                }
-
-                if(!dst_level->set_pixel(dst_coord, dst_value))
-                {
-                    return false;
-                }
-            }
+            return false;
         }
     }
 
@@ -424,6 +395,11 @@ bool MeshBasedReprojection::on_create()
         return false;
     }
 
+    if (!this->create_quad_tree_pipeline())
+    {
+        return false;
+    }
+
     if (!this->create_overlay_pass())
     {
         return false;
@@ -444,10 +420,16 @@ bool MeshBasedReprojection::on_create()
         return false;
     }
 
-    this->statistic_edge_buffer_time = make_statistic("edge_buffer_time", UNIT_MILLISECONDS);
+    this->statistic_line_trace_time = make_statistic("line_trace_time", UNIT_MILLISECONDS);
+    this->statistic_triangulate_time = make_statistic("triangulate_time", UNIT_MILLISECONDS);
+    this->statistic_depth_sample_time = make_statistic("depth_sample_time", UNIT_MILLISECONDS);
+    this->statistic_metadata_bitrate = make_statistic("metadata_bitrate", UNIT_MBITS_PER_SECOND);
 
     StatisticLog::Ptr statistic_log = this->get_application()->get_statistic_log();
-    statistic_log->add_statistic(this->statistic_edge_buffer_time);
+    statistic_log->add_statistic(this->statistic_line_trace_time);
+    statistic_log->add_statistic(this->statistic_triangulate_time);
+    statistic_log->add_statistic(this->statistic_depth_sample_time);
+    statistic_log->add_statistic(this->statistic_metadata_bitrate);
 
     return true;
 }
@@ -498,7 +480,10 @@ bool MeshBasedReprojection::on_interface()
 
     ImGui::Separator();
 
-    this->statistic_edge_buffer_time->interface(128);
+    this->statistic_line_trace_time->interface(128);
+    this->statistic_triangulate_time->interface(128);
+    this->statistic_depth_sample_time->interface(128);
+    this->statistic_metadata_bitrate->interface(128);
 
     return true;
 }
@@ -508,12 +493,30 @@ bool MeshBasedReprojection::on_update(lava::delta delta_time)
     std::unique_lock<std::mutex> lock(this->edge_buffer_mutex);
     if (this->edge_buffer_process_count > 0)
     {
-        double edge_buffer_process_time = (double)this->edge_buffer_process_time_sum / this->edge_buffer_process_count;
+        double line_trace_time = this->line_trace_time_sum / (double) this->edge_buffer_process_count;
+        double triangulate_time = this->triangulate_time_sum / (double) this->edge_buffer_process_count;
+        double depth_sample_time = this->depth_sample_time_sum / (double) this->edge_buffer_process_count;
 
-        this->statistic_edge_buffer_time->add_sample(edge_buffer_process_time);
+        this->statistic_line_trace_time->add_sample(line_trace_time);
+        this->statistic_triangulate_time->add_sample(triangulate_time);
+        this->statistic_depth_sample_time->add_sample(depth_sample_time);
 
         this->edge_buffer_process_count = 0;
-        this->edge_buffer_process_time_sum = 0.0f;
+        this->line_trace_time_sum = 0.0;
+        this->triangulate_time_sum = 0.0;
+        this->depth_sample_time_sum = 0.0;
+    }
+
+    this->metadata_time += delta_time;
+
+    if (this->metadata_time > 1.0)
+    {
+        double metadata_bitrate = ((double) this->metadata_bits / (1000.0 * 1000.0)) / this->metadata_time;
+
+        this->statistic_metadata_bitrate->add_sample(metadata_bitrate);
+
+        this->metadata_time = 0.0;
+        this->metadata_bits = 0.0;
     }
     lock.unlock();
 
@@ -589,6 +592,10 @@ bool MeshBasedReprojection::on_render(VkCommandBuffer command_buffer, lava::inde
     this->process_edge_detection_pass(command_buffer);
     this->get_pass_timer()->end_pass(command_buffer);
 
+    this->get_pass_timer()->begin_pass(command_buffer, "quad_tree");
+    this->process_quad_tree_pass(command_buffer);
+    this->get_pass_timer()->end_pass(command_buffer);
+
     if (frame_number != FRAME_NUMBER_DROPPED)
     {
         if (!this->process_edge_buffer_copy(command_buffer, frame_number))
@@ -705,8 +712,8 @@ bool MeshBasedReprojection::on_render(VkCommandBuffer command_buffer, lava::inde
         this->get_companion_window()->submit_image(command_buffer, EYE_RIGHT, this->layer_normal_buffers[1], VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
         break;
     case MESH_BASED_REPROJECTION_OVERLAY_EDGE_DETECTION:
-        this->get_companion_window()->submit_image(command_buffer, EYE_LEFT, this->layer_edge_buffers[0], VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL);
-        this->get_companion_window()->submit_image(command_buffer, EYE_RIGHT, this->layer_edge_buffers[1], VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL);
+        this->get_companion_window()->submit_image(command_buffer, EYE_LEFT, this->layer_edge_buffers[0][0], VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL);
+        this->get_companion_window()->submit_image(command_buffer, EYE_RIGHT, this->layer_edge_buffers[1][0], VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL);
         break;
     case MESH_BASED_REPROJECTION_OVERLAY_EDGE:
     case MESH_BASED_REPROJECTION_OVERLAY_TRIANGLE:
@@ -744,7 +751,6 @@ bool MeshBasedReprojection::create_buffers()
 {
     glm::uvec2 resolution = this->get_headset()->get_framebuffer_resolution();
     uint32_t depth_image_buffer_size = resolution.x * resolution.y * sizeof(float);
-    uint32_t edge_image_buffer_size = resolution.x * resolution.y * sizeof(float);
     uint32_t edge_geometry_buffer_size = MESH_BASED_REPROJECTION_EDGE_GEOMETRY_BUFFER_SIZE * 2 * sizeof(glm::vec2);
     uint32_t triangle_geometry_buffer_size = MESH_BASED_REPROJECTION_TRIANGLE_GEOMETRY_BUFFER_SIZE * 3 * sizeof(glm::vec3);
 
@@ -834,17 +840,37 @@ bool MeshBasedReprojection::create_buffers()
 
     for (uint32_t layer = 0; layer < this->layer_edge_buffers.size(); layer++)
     {
-        lava::image::ptr edge_buffer = lava::make_image(VK_FORMAT_R32_SFLOAT);
-        edge_buffer->set_usage(VK_IMAGE_USAGE_STORAGE_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT);
+        this->layer_edge_buffers[layer].resize(this->layer_quad_tree_levels);
+        glm::uvec2 level_resolution = resolution;
 
-        if (!edge_buffer->create(this->get_device(), resolution))
+        for (uint32_t level = 0; level < this->layer_quad_tree_levels; level++)
         {
-            lava::log()->error("Mesh Based Reprojection: Can't create layer edge buffer!");
+            lava::image::ptr edge_buffer = lava::make_image(VK_FORMAT_R32_SFLOAT);
+            edge_buffer->set_usage(VK_IMAGE_USAGE_STORAGE_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT);
 
-            return false;
-        }
+            if (!edge_buffer->create(this->get_device(), level_resolution))
+            {
+                lava::log()->error("Mesh Based Reprojection: Can't create layer edge buffer!");
+
+                return false;
+            }
+
+            this->layer_edge_buffers[layer][level] = edge_buffer;
+
+            glm::uvec2 next_resolution = level_resolution / 2u;
+
+            if (level_resolution.x % 2 != 0)
+            {
+                next_resolution.x += 1;
+            }
+
+            if (level_resolution.y % 2 != 0)
+            {
+                next_resolution.y += 1;
+            }
 
-        this->layer_edge_buffers[layer] = edge_buffer;
+            level_resolution = next_resolution;
+        }
     }
 
     for (uint32_t layer = 0; layer < this->layer_overlay_color_buffers.size(); layer++)
@@ -906,7 +932,7 @@ bool MeshBasedReprojection::create_buffers()
         }
 
         std::array<lava::buffer::ptr, 2> depth_image_buffers;
-        std::array<lava::buffer::ptr, 2> edge_image_buffers;
+        std::array<std::vector<lava::buffer::ptr>, 2> edge_image_buffers;
 
         for (uint32_t layer = 0; layer < 2; layer++)
         {
@@ -919,13 +945,35 @@ bool MeshBasedReprojection::create_buffers()
                 return false;
             }
 
-            edge_image_buffers[layer] = lava::make_buffer();
+            edge_image_buffers[layer].resize(this->layer_quad_tree_levels);
+            glm::uvec2 level_resolution = resolution;
 
-            if (!edge_image_buffers[layer]->create_mapped(this->get_device(), nullptr, edge_image_buffer_size, VK_BUFFER_USAGE_TRANSFER_DST_BIT, VMA_MEMORY_USAGE_GPU_TO_CPU))
+            for (uint32_t level = 0; level < this->layer_quad_tree_levels; level++)
             {
-                lava::log()->error("Mesh based Reprojection: Can't create edge image buffer!");
+                uint32_t edge_image_buffer_size = level_resolution.x * level_resolution.y * sizeof(float);
+                
+                edge_image_buffers[layer][level] = lava::make_buffer();
 
-                return false;
+                if (!edge_image_buffers[layer][level]->create_mapped(this->get_device(), nullptr, edge_image_buffer_size, VK_BUFFER_USAGE_TRANSFER_DST_BIT, VMA_MEMORY_USAGE_GPU_TO_CPU))
+                {
+                    lava::log()->error("Mesh based Reprojection: Can't create edge image buffer!");
+
+                    return false;
+                }
+
+                glm::uvec2 next_resolution = level_resolution / 2u;
+
+                if (level_resolution.x % 2 != 0)
+                {
+                    next_resolution.x += 1;
+                }
+
+                if (level_resolution.y % 2 != 0)
+                {
+                    next_resolution.y += 1;
+                }
+
+                level_resolution = next_resolution;
             }
         }
 
@@ -982,13 +1030,13 @@ bool MeshBasedReprojection::create_descriptors()
     lava::VkDescriptorPoolSizes descriptor_type_count =
     {
         { VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 4 },
-        { VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, 2 },
+        { VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, 2 + 4 * (this->layer_quad_tree_levels - 1) },
         { VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, frame_count }
     };
 
     this->descriptor_pool = lava::make_descriptor_pool();
 
-    if (!this->descriptor_pool->create(this->get_device(), descriptor_type_count, frame_count + 4 + 2))
+    if (!this->descriptor_pool->create(this->get_device(), descriptor_type_count, frame_count + 4 + 2 + 2 * (this->layer_quad_tree_levels - 1)))
     {
         lava::log()->error("Mesh Based Reprojection: Can't create descriptor pool!");
 
@@ -1026,8 +1074,22 @@ bool MeshBasedReprojection::create_descriptors()
         return false;
     }
 
-    std::array<VkDescriptorImageInfo, 6> descriptor_image_infos;
+    this->quad_tree_descriptor = lava::make_descriptor();
+    this->quad_tree_descriptor->add_binding(0, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, VK_SHADER_STAGE_COMPUTE_BIT);
+    this->quad_tree_descriptor->add_binding(1, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, VK_SHADER_STAGE_COMPUTE_BIT);
+
+    if (!this->quad_tree_descriptor->create(this->get_device()))
+    {
+        lava::log()->error("Mesh Based Reprojection: Can't create quad tree descriptor!");
+
+        return false;
+    }
+
     std::vector<VkWriteDescriptorSet> descriptor_writes;
+    std::vector<VkDescriptorImageInfo> descriptor_image_infos;
+    descriptor_image_infos.resize(6 + 4 * (this->layer_quad_tree_levels - 1));
+
+    uint32_t descriptor_image_info_offset = 0;
 
     this->transform_descriptor_sets.resize(frame_count, VK_NULL_HANDLE);
 
@@ -1052,16 +1114,20 @@ bool MeshBasedReprojection::create_descriptors()
     {
         this->layer_descriptor_sets[layer] = this->layer_descriptor->allocate(this->descriptor_pool->get());
 
-        VkDescriptorImageInfo& depth_image_info = descriptor_image_infos[layer * 2];
+        VkDescriptorImageInfo& depth_image_info = descriptor_image_infos[descriptor_image_info_offset];
         depth_image_info.sampler = this->nearest_sampler;
         depth_image_info.imageView = this->layer_depth_buffers[layer]->get_view();
         depth_image_info.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
 
-        VkDescriptorImageInfo& normal_image_info = descriptor_image_infos[layer * 2 + 1];
+        descriptor_image_info_offset++;
+
+        VkDescriptorImageInfo& normal_image_info = descriptor_image_infos[descriptor_image_info_offset];
         normal_image_info.sampler = this->nearest_sampler;
         normal_image_info.imageView = this->layer_normal_buffers[layer]->get_view();
         normal_image_info.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
 
+        descriptor_image_info_offset++;
+
         VkWriteDescriptorSet& descriptor_write_depth = descriptor_writes.emplace_back();
         descriptor_write_depth.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
         descriptor_write_depth.pNext = nullptr;
@@ -1091,11 +1157,13 @@ bool MeshBasedReprojection::create_descriptors()
     {
         this->edge_descriptor_sets[layer] = this->edge_descriptor->allocate(this->descriptor_pool->get());
 
-        VkDescriptorImageInfo& descriptor_image_info = descriptor_image_infos[layer + 4];
+        VkDescriptorImageInfo& descriptor_image_info = descriptor_image_infos[descriptor_image_info_offset];
         descriptor_image_info.sampler = VK_NULL_HANDLE;
-        descriptor_image_info.imageView = this->layer_edge_buffers[layer]->get_view();
+        descriptor_image_info.imageView = this->layer_edge_buffers[layer][0]->get_view();
         descriptor_image_info.imageLayout = VK_IMAGE_LAYOUT_GENERAL;
 
+        descriptor_image_info_offset++;
+
         VkWriteDescriptorSet& descriptor_write = descriptor_writes.emplace_back();
         descriptor_write.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
         descriptor_write.pNext = nullptr;
@@ -1109,6 +1177,54 @@ bool MeshBasedReprojection::create_descriptors()
         descriptor_write.pTexelBufferView = nullptr;
     }
 
+    for (uint32_t layer = 0; layer < 2; layer++)
+    {
+        this->quad_tree_descriptor_sets[layer].resize(this->layer_quad_tree_levels - 1);
+
+        for (uint32_t level = 0; level < this->layer_quad_tree_levels - 1; level++)
+        {
+            this->quad_tree_descriptor_sets[layer][level] = this->quad_tree_descriptor->allocate(this->descriptor_pool->get());
+
+            VkDescriptorImageInfo& src_image_info = descriptor_image_infos[descriptor_image_info_offset];
+            src_image_info.sampler = VK_NULL_HANDLE;
+            src_image_info.imageView = this->layer_edge_buffers[layer][level]->get_view();
+            src_image_info.imageLayout = VK_IMAGE_LAYOUT_GENERAL;
+
+            descriptor_image_info_offset++;
+
+            VkDescriptorImageInfo& dst_image_info = descriptor_image_infos[descriptor_image_info_offset];
+            dst_image_info.sampler = VK_NULL_HANDLE;
+            dst_image_info.imageView = this->layer_edge_buffers[layer][level + 1]->get_view();
+            dst_image_info.imageLayout = VK_IMAGE_LAYOUT_GENERAL;
+
+            descriptor_image_info_offset++;
+
+            VkWriteDescriptorSet& descriptor_write_src = descriptor_writes.emplace_back();
+            descriptor_write_src.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
+            descriptor_write_src.pNext = nullptr;
+            descriptor_write_src.dstSet = this->quad_tree_descriptor_sets[layer][level];
+            descriptor_write_src.dstBinding = 0;
+            descriptor_write_src.dstArrayElement = 0;
+            descriptor_write_src.descriptorCount = 1;
+            descriptor_write_src.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_IMAGE;
+            descriptor_write_src.pImageInfo = &src_image_info;
+            descriptor_write_src.pBufferInfo = nullptr;
+            descriptor_write_src.pTexelBufferView = nullptr;
+
+            VkWriteDescriptorSet& descriptor_write_dst = descriptor_writes.emplace_back();
+            descriptor_write_dst.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
+            descriptor_write_dst.pNext = nullptr;
+            descriptor_write_dst.dstSet = this->quad_tree_descriptor_sets[layer][level];
+            descriptor_write_dst.dstBinding = 1;
+            descriptor_write_dst.dstArrayElement = 0;
+            descriptor_write_dst.descriptorCount = 1;
+            descriptor_write_dst.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_IMAGE;
+            descriptor_write_dst.pImageInfo = &dst_image_info;
+            descriptor_write_dst.pBufferInfo = nullptr;
+            descriptor_write_dst.pTexelBufferView = nullptr;
+        }
+    }
+
     vkUpdateDescriptorSets(this->get_device()->get(), descriptor_writes.size(), descriptor_writes.data(), 0, nullptr);
 
     return true;
@@ -1346,6 +1462,38 @@ bool MeshBasedReprojection::create_edge_detection_pipeline()
     return true;
 }
 
+bool MeshBasedReprojection::create_quad_tree_pipeline()
+{
+    this->quad_tree_pipeline_layout = lava::make_pipeline_layout();
+    this->quad_tree_pipeline_layout->add(this->quad_tree_descriptor);
+
+    if (!this->quad_tree_pipeline_layout->create(this->get_device()))
+    {
+        lava::log()->error("Mesh Based Reprojection: Can't create quad tree pipeline layout!");
+
+        return false;
+    }
+
+    this->quad_tree_pipeline = lava::make_compute_pipeline(this->get_device());
+    this->quad_tree_pipeline->set_layout(this->quad_tree_pipeline_layout);
+
+    if (!this->quad_tree_pipeline->set_shader_stage(lava::file_data("dpr/binary/mesh_based_reprojection_quad_tree_compute.spirv"), VK_SHADER_STAGE_COMPUTE_BIT))
+    {
+        lava::log()->error("Mesh Based Reprojection: Can't load compute shader for quad tree pipeline!");
+
+        return false;
+    }
+
+    if (!this->quad_tree_pipeline->create())
+    {
+        lava::log()->error("Mesh Based Reprojection: Can't create quad tree pipeline!");
+
+        return false;
+    }
+
+    return true;
+}
+
 bool MeshBasedReprojection::create_overlay_pass()
 {
     lava::attachment::ptr color_attachment = lava::make_attachment(this->get_headset()->get_format());
@@ -1720,6 +1868,27 @@ bool MeshBasedReprojection::build_projection()
     this->layer_head_to_eye_matrix = glm::translate(glm::mat4(1.0f), -center_point_combined);
     this->layer_projection_matrix = glm::perspective(horizontal_angle_combined, aspect_ratio_combined, near_combined, far_combined);
     this->layer_inv_projection_matrix = glm::inverse(this->layer_projection_matrix);
+    this->layer_quad_tree_levels = 1;
+
+    glm::uvec2 level_resolution = this->layer_resolution;
+
+    while (level_resolution.x > 1 && level_resolution.y > 1)
+    {
+        glm::uvec2 next_resolution = level_resolution / 2u;
+
+        if (level_resolution.x % 2 != 0)
+        {
+            next_resolution.x += 1;
+        }
+
+        if (level_resolution.y % 2 != 0)
+        {
+            next_resolution.y += 1;
+        }
+
+        level_resolution = next_resolution;
+        this->layer_quad_tree_levels++;
+    }
 
     return true;
 }
@@ -1880,24 +2049,29 @@ void MeshBasedReprojection::process_edge_detection_pass(VkCommandBuffer command_
         work_group_count.y += 1;
     }
 
-    std::array<VkImageMemoryBarrier, 2> image_barriers;
+    std::vector<VkImageMemoryBarrier> image_barriers;
 
     for (uint32_t layer = 0; layer < 2; layer++)
     {
-        image_barriers[layer].sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
-        image_barriers[layer].pNext = nullptr;
-        image_barriers[layer].srcAccessMask = 0;
-        image_barriers[layer].dstAccessMask = VK_ACCESS_SHADER_WRITE_BIT;
-        image_barriers[layer].oldLayout = VK_IMAGE_LAYOUT_UNDEFINED;
-        image_barriers[layer].newLayout = VK_IMAGE_LAYOUT_GENERAL;
-        image_barriers[layer].srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
-        image_barriers[layer].dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
-        image_barriers[layer].image = this->layer_edge_buffers[layer]->get();
-        image_barriers[layer].subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
-        image_barriers[layer].subresourceRange.baseMipLevel = 0;
-        image_barriers[layer].subresourceRange.levelCount = 1;
-        image_barriers[layer].subresourceRange.baseArrayLayer = 0;
-        image_barriers[layer].subresourceRange.layerCount = 1;
+        for (uint32_t level = 0; level < this->layer_quad_tree_levels; level++)
+        {
+            VkImageMemoryBarrier& image_barrier = image_barriers.emplace_back();
+
+            image_barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
+            image_barrier.pNext = nullptr;
+            image_barrier.srcAccessMask = 0;
+            image_barrier.dstAccessMask = VK_ACCESS_SHADER_WRITE_BIT;
+            image_barrier.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED;
+            image_barrier.newLayout = VK_IMAGE_LAYOUT_GENERAL;
+            image_barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
+            image_barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
+            image_barrier.image = this->layer_edge_buffers[layer][level]->get();
+            image_barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
+            image_barrier.subresourceRange.baseMipLevel = 0;
+            image_barrier.subresourceRange.levelCount = 1;
+            image_barrier.subresourceRange.baseArrayLayer = 0;
+            image_barrier.subresourceRange.layerCount = 1;
+        }
     }
 
     vkCmdPipelineBarrier(command_buffer, VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, 0, 0, nullptr, 0, nullptr, image_barriers.size(), image_barriers.data());
@@ -1921,15 +2095,65 @@ void MeshBasedReprojection::process_edge_detection_pass(VkCommandBuffer command_
     }
 }
 
+void MeshBasedReprojection::process_quad_tree_pass(VkCommandBuffer command_buffer)
+{
+    this->quad_tree_pipeline->bind(command_buffer);
+
+    for (uint32_t level = 0; level < this->layer_quad_tree_levels - 1; level++)
+    {
+        std::array<VkImageMemoryBarrier, 2> image_barriers;
+
+        for (uint32_t layer = 0; layer < 2; layer++)
+        {
+            image_barriers[layer].sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
+            image_barriers[layer].pNext = nullptr;
+            image_barriers[layer].srcAccessMask = VK_ACCESS_SHADER_WRITE_BIT;
+            image_barriers[layer].dstAccessMask = VK_ACCESS_SHADER_READ_BIT;
+            image_barriers[layer].oldLayout = VK_IMAGE_LAYOUT_GENERAL;
+            image_barriers[layer].newLayout = VK_IMAGE_LAYOUT_GENERAL;
+            image_barriers[layer].srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
+            image_barriers[layer].dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
+            image_barriers[layer].image = this->layer_edge_buffers[layer][level]->get();
+            image_barriers[layer].subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
+            image_barriers[layer].subresourceRange.baseMipLevel = 0;
+            image_barriers[layer].subresourceRange.levelCount = 1;
+            image_barriers[layer].subresourceRange.baseArrayLayer = 0;
+            image_barriers[layer].subresourceRange.layerCount = 1;
+        }
+
+        vkCmdPipelineBarrier(command_buffer, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, 0, 0, nullptr, 0, nullptr, image_barriers.size(), image_barriers.data());
+
+        glm::uvec2 level_resolution = this->layer_edge_buffers[0][level]->get_size();
+        glm::uvec2 work_group_count = level_resolution / glm::uvec2(MESH_BASED_REPROJECTION_QUAD_TREE_GROUP_SIZE);
+
+        if ((level_resolution.x % MESH_BASED_REPROJECTION_QUAD_TREE_GROUP_SIZE) != 0)
+        {
+            work_group_count.x += 1;
+        }
+
+        if ((level_resolution.y % MESH_BASED_REPROJECTION_QUAD_TREE_GROUP_SIZE) != 0)
+        {
+            work_group_count.y += 1;
+        }
+
+        for (uint32_t layer = 0; layer < 2; layer++)
+        {
+            vkCmdBindDescriptorSets(command_buffer, VK_PIPELINE_BIND_POINT_COMPUTE, this->quad_tree_pipeline_layout->get(), 0, 1, &this->quad_tree_descriptor_sets[layer][level], 0, nullptr);
+            
+            vkCmdDispatch(command_buffer, work_group_count.x, work_group_count.y, 1);
+        }
+    }
+}
+
 bool MeshBasedReprojection::process_edge_buffer_copy(VkCommandBuffer command_buffer, FrameNumber frame_number)
 {
     glm::uvec2 resolution = this->get_headset()->get_framebuffer_resolution();
 
-    std::array<VkImageMemoryBarrier, 4> image_barriers;
+    std::vector<VkImageMemoryBarrier> image_barriers;
 
     for (uint32_t layer = 0; layer < 2; layer++)
     {
-        VkImageMemoryBarrier& depth_barrier = image_barriers[layer * 2];
+        VkImageMemoryBarrier& depth_barrier = image_barriers.emplace_back();
         depth_barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
         depth_barrier.pNext = nullptr;
         depth_barrier.srcAccessMask = VK_ACCESS_SHADER_READ_BIT;
@@ -1945,21 +2169,24 @@ bool MeshBasedReprojection::process_edge_buffer_copy(VkCommandBuffer command_buf
         depth_barrier.subresourceRange.baseArrayLayer = 0;
         depth_barrier.subresourceRange.layerCount = 1;
 
-        VkImageMemoryBarrier& edge_barrier = image_barriers[layer * 2 + 1];
-        edge_barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
-        edge_barrier.pNext = nullptr;
-        edge_barrier.srcAccessMask = VK_ACCESS_SHADER_WRITE_BIT;
-        edge_barrier.dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT;
-        edge_barrier.oldLayout = VK_IMAGE_LAYOUT_GENERAL;
-        edge_barrier.newLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL;
-        edge_barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
-        edge_barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
-        edge_barrier.image = this->layer_edge_buffers[layer]->get();
-        edge_barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
-        edge_barrier.subresourceRange.baseMipLevel = 0;
-        edge_barrier.subresourceRange.levelCount = 1;
-        edge_barrier.subresourceRange.baseArrayLayer = 0;
-        edge_barrier.subresourceRange.layerCount = 1;
+        for (uint32_t level = 0; level < this->layer_quad_tree_levels; level++)
+        {
+            VkImageMemoryBarrier& edge_barrier = image_barriers.emplace_back();
+            edge_barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
+            edge_barrier.pNext = nullptr;
+            edge_barrier.srcAccessMask = VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT;
+            edge_barrier.dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT;
+            edge_barrier.oldLayout = VK_IMAGE_LAYOUT_GENERAL;
+            edge_barrier.newLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL;
+            edge_barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
+            edge_barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
+            edge_barrier.image = this->layer_edge_buffers[layer][level]->get();
+            edge_barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
+            edge_barrier.subresourceRange.baseMipLevel = 0;
+            edge_barrier.subresourceRange.levelCount = 1;
+            edge_barrier.subresourceRange.baseArrayLayer = 0;
+            edge_barrier.subresourceRange.layerCount = 1;
+        }
     }
 
     vkCmdPipelineBarrier(command_buffer, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 0, nullptr, 0, nullptr, image_barriers.size(), image_barriers.data());
@@ -1990,25 +2217,40 @@ bool MeshBasedReprojection::process_edge_buffer_copy(VkCommandBuffer command_buf
         depth_copy.imageExtent.height = resolution.y;
         depth_copy.imageExtent.depth = 1;
 
-        VkBufferImageCopy edge_copy = depth_copy;
-        edge_copy.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
-
         vkCmdCopyImageToBuffer(command_buffer, this->layer_depth_buffers[layer]->get(), VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, edge_buffer->depth_image_buffers[layer]->get(), 1, &depth_copy);
-        vkCmdCopyImageToBuffer(command_buffer, this->layer_edge_buffers[layer]->get(), VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, edge_buffer->edge_image_buffers[layer]->get(), 1, &edge_copy);
-
-        std::array<VkBufferMemoryBarrier, 2> buffer_barriers;
-        buffer_barriers[0].sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER;
-        buffer_barriers[0].pNext = 0;
-        buffer_barriers[0].srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
-        buffer_barriers[0].dstAccessMask = VK_ACCESS_HOST_READ_BIT;
-        buffer_barriers[0].srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
-        buffer_barriers[0].dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
-        buffer_barriers[0].buffer = edge_buffer->depth_image_buffers[layer]->get();
-        buffer_barriers[0].offset = 0;
-        buffer_barriers[0].size = VK_WHOLE_SIZE;
-
-        buffer_barriers[1] = buffer_barriers[0];
-        buffer_barriers[1].buffer = edge_buffer->edge_image_buffers[layer]->get();
+
+        for(uint32_t level = 0; level < this->layer_quad_tree_levels; level++)
+        {
+            lava::image::ptr src_image = this->layer_edge_buffers[layer][level];
+            lava::buffer::ptr dst_buffer = edge_buffer->edge_image_buffers[layer][level];
+
+            VkBufferImageCopy edge_copy = depth_copy;
+            edge_copy.imageExtent.width = src_image->get_size().x;
+            edge_copy.imageExtent.height = src_image->get_size().y;
+            edge_copy.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
+            
+            vkCmdCopyImageToBuffer(command_buffer, src_image->get(), VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, dst_buffer->get(), 1, &edge_copy);
+        }
+
+        std::vector<VkBufferMemoryBarrier> buffer_barriers;
+
+        VkBufferMemoryBarrier& depth_barrier = buffer_barriers.emplace_back();
+        depth_barrier.sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER;
+        depth_barrier.pNext = 0;
+        depth_barrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
+        depth_barrier.dstAccessMask = VK_ACCESS_HOST_READ_BIT;
+        depth_barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
+        depth_barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
+        depth_barrier.buffer = edge_buffer->depth_image_buffers[layer]->get();
+        depth_barrier.offset = 0;
+        depth_barrier.size = VK_WHOLE_SIZE;
+
+        for (uint32_t level = 0; level < this->layer_quad_tree_levels; level++)
+        {
+            VkBufferMemoryBarrier& edge_barrier = buffer_barriers.emplace_back();
+            edge_barrier = depth_barrier;
+            edge_barrier.buffer = edge_buffer->edge_image_buffers[layer][level]->get();
+        }
 
         vkCmdPipelineBarrier(command_buffer, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_HOST_BIT, 0, 0, nullptr, buffer_barriers.size(), buffer_barriers.data(), 0, nullptr);
     }
@@ -2144,10 +2386,14 @@ void MeshBasedReprojection::release_edge_buffer(MeshBasedReprojectionEdgeBuffer:
 
 void MeshBasedReprojection::worker_process_edge_buffer(MeshBasedReprojectionEdgeBuffer::Ptr edge_buffer, FrameNumber frame_number)
 {
-    std::chrono::high_resolution_clock::time_point start = std::chrono::high_resolution_clock::now();
+    double line_trace_time = 0.0;
+    double triangulate_time = 0.0;
+    double depth_sample_time = 0.0;
 
     for (uint32_t layer = 0; layer < 2; layer++)
     {
+        std::chrono::high_resolution_clock::time_point line_trace_start = std::chrono::high_resolution_clock::now();
+
         edge_buffer->edges[layer].clear();
         edge_buffer->triangles[layer].clear();
 
@@ -2220,6 +2466,11 @@ void MeshBasedReprojection::worker_process_edge_buffer(MeshBasedReprojectionEdge
             }
         }
 
+        std::chrono::high_resolution_clock::time_point line_trace_end = std::chrono::high_resolution_clock::now();
+        line_trace_time += std::chrono::duration_cast<std::chrono::duration<double, std::chrono::milliseconds::period>>(line_trace_end - line_trace_start).count();
+
+        std::chrono::high_resolution_clock::time_point triangulate_start = std::chrono::high_resolution_clock::now();
+
         Point2 border_point1 = Point2(1.0, 1.0);
         Point2 border_point2 = Point2(1.0, resolution.y-2);
         Point2 border_point3 = Point2(resolution.x-2, resolution.y-2);
@@ -2239,6 +2490,11 @@ void MeshBasedReprojection::worker_process_edge_buffer(MeshBasedReprojectionEdge
             triangulation.insert_constraint(edge_start, edge_end);
         }
 
+        std::chrono::high_resolution_clock::time_point triangulate_end = std::chrono::high_resolution_clock::now();
+        triangulate_time += std::chrono::duration_cast<std::chrono::duration<double, std::chrono::milliseconds::period>>(triangulate_end - triangulate_start).count();
+
+        std::chrono::high_resolution_clock::time_point depth_sample_start = std::chrono::high_resolution_clock::now();
+
         std::vector<MeshBasedReprojectionEdge> debug_edges;
 
         for (FaceHandle face_handle : triangulation.finite_face_handles())
@@ -2343,7 +2599,7 @@ void MeshBasedReprojection::worker_process_edge_buffer(MeshBasedReprojectionEdge
                         sample_direction.x = this->triangle_depth_offset * glm::cos(center_angle);
                         sample_direction.y = this->triangle_depth_offset * glm::sin(center_angle);
 
-                        for (const MeshBasedReprojectionEdge& edge : edge_buffer->edges[layer])
+                        /*for (const MeshBasedReprojectionEdge& edge : edge_buffer->edges[layer])
                         {
                             glm::vec2 edge_start = glm::vec2(edge.start) - sample_base;
                             glm::vec2 edge_end = glm::vec2(edge.end) - sample_base;
@@ -2383,7 +2639,7 @@ void MeshBasedReprojection::worker_process_edge_buffer(MeshBasedReprojectionEdge
                             {
                                 sample_direction = edge_intersect / 2.0f;
                             }
-                        }
+                        }*/
 
                         sample_offset = sample_direction;
                     }
@@ -2392,7 +2648,7 @@ void MeshBasedReprojection::worker_process_edge_buffer(MeshBasedReprojectionEdge
                 glm::ivec2 sample_point = glm::ivec2(sample_base) + sample_offset;
                 sample_point = glm::clamp(sample_point, glm::ivec2(0), glm::ivec2(resolution - 1u));
 
-                MeshBasedReprojectionEdge line1;
+                /*MeshBasedReprojectionEdge line1;
                 line1.start = glm::ivec2(sample_point) + glm::ivec2(3, 3);
                 line1.end = glm::ivec2(sample_point) - glm::ivec2(2, 2);
 
@@ -2401,7 +2657,7 @@ void MeshBasedReprojection::worker_process_edge_buffer(MeshBasedReprojectionEdge
                 line2.end = glm::ivec2(sample_point) - glm::ivec2(2, -2);
 
                 debug_edges.push_back(line1);
-                debug_edges.push_back(line2);
+                debug_edges.push_back(line2);*/
 
                 float sample_depth = depth_image_pointer[sample_point.y * resolution.x + sample_point.x];
                 sample_depth = glm::min(sample_depth, this->edge_detection_depth_threshold);
@@ -2409,7 +2665,7 @@ void MeshBasedReprojection::worker_process_edge_buffer(MeshBasedReprojectionEdge
                 points[index] = glm::vec3(vertex->point().x(), vertex->point().y(), sample_depth);
             }
 
-            glm::vec2 screen_coord1 = ((glm::vec2(points[0]) + glm::vec2(0.5f)) / glm::vec2(resolution)) * 2.0f - 1.0f;
+            /*glm::vec2 screen_coord1 = ((glm::vec2(points[0]) + glm::vec2(0.5f)) / glm::vec2(resolution)) * 2.0f - 1.0f;
             glm::vec2 screen_coord2 = ((glm::vec2(points[1]) + glm::vec2(0.5f)) / glm::vec2(resolution)) * 2.0f - 1.0f;
             glm::vec2 screen_coord3 = ((glm::vec2(points[2]) + glm::vec2(0.5f)) / glm::vec2(resolution)) * 2.0f - 1.0f;
 
@@ -2432,16 +2688,19 @@ void MeshBasedReprojection::worker_process_edge_buffer(MeshBasedReprojectionEdge
             }
 
             if (view_angle < this->triangle_angle)
-            {
+            {*/
                 MeshBasedReprojectionTriangle triangle;
                 triangle.point1 = points[0];
                 triangle.point2 = points[1];
                 triangle.point3 = points[2];   
 
                 edge_buffer->triangles[layer].push_back(triangle);
-            }
+            //}
         }
 
+        std::chrono::high_resolution_clock::time_point depth_sample_end = std::chrono::high_resolution_clock::now();
+        depth_sample_time += std::chrono::duration_cast<std::chrono::duration<double, std::chrono::milliseconds::period>>(depth_sample_end - depth_sample_start).count();
+
         if (!this->overlay_geometry_locked)
         {
             std::unique_lock<std::mutex> geometry_lock(this->geometry_mutex);
@@ -2454,14 +2713,6 @@ void MeshBasedReprojection::worker_process_edge_buffer(MeshBasedReprojectionEdge
             geometry_lock.unlock();
         }
     }
-
-    std::chrono::high_resolution_clock::time_point end = std::chrono::high_resolution_clock::now();
-    double time = std::chrono::duration_cast<std::chrono::duration<double, std::chrono::milliseconds::period>>(end - start).count();
-
-    std::unique_lock<std::mutex> edge_lock(this->edge_buffer_mutex);
-    this->edge_buffer_process_count += 1;
-    this->edge_buffer_process_time_sum += time;
-    edge_lock.unlock();
     
     if (!this->overlay_geometry_locked)
     {
@@ -2497,6 +2748,14 @@ void MeshBasedReprojection::worker_process_edge_buffer(MeshBasedReprojectionEdge
     }
 
     this->get_headset()->submit_metadata(frame_number, metadata);
+
+    std::unique_lock<std::mutex> edge_lock(this->edge_buffer_mutex);
+    this->edge_buffer_process_count += 1;
+    this->line_trace_time_sum += line_trace_time;
+    this->triangulate_time_sum += triangulate_time;
+    this->depth_sample_time_sum += depth_sample_time;
+    this->metadata_bits += 8 * metadata_size;
+    edge_lock.unlock();
 }
 
 bool MeshBasedReprojection::worker_traversal_lines(MeshBasedReprojectionEdgeBuffer::Ptr edge_buffer, uint32_t layer, const glm::ivec2& start_coord, std::vector<glm::ivec2>& edge_coords)
diff --git a/src/strategy/mesh_based_reprojection.hpp b/src/strategy/mesh_based_reprojection.hpp
index 213d1e21..4afced17 100644
--- a/src/strategy/mesh_based_reprojection.hpp
+++ b/src/strategy/mesh_based_reprojection.hpp
@@ -66,7 +66,7 @@ public:
 public:
     MeshBasedReprojectionQuadTreeLevel(const glm::uvec2& resolution);
 
-    bool fill(float* data, uint32_t size);
+    bool fill(lava::buffer::ptr buffer);
 
     bool set_pixel(const glm::ivec2& coord, float value);
     bool get_pixel(const glm::ivec2& coord, float& value) const;
@@ -75,7 +75,7 @@ public:
 
 private:
     glm::uvec2 resolution;
-    std::vector<float> buffer;
+    float* buffer = nullptr;
 };
 
 class MeshBasedReprojectionQuadTree
@@ -86,7 +86,7 @@ public:
 public:
     MeshBasedReprojectionQuadTree(const glm::uvec2& resolution);
 
-    bool fill(lava::buffer::ptr buffer);
+    bool fill(const std::vector<lava::buffer::ptr>& buffers);
     bool remove(const glm::ivec2& coord);
 
     bool find_global(float threshold, glm::ivec2& coord) const;
@@ -109,7 +109,7 @@ public:
     ExternFence::Ptr fence;
 
     std::array<lava::buffer::ptr, 2> depth_image_buffers;
-    std::array<lava::buffer::ptr, 2> edge_image_buffers;
+    std::array<std::vector<lava::buffer::ptr>, 2> edge_image_buffers;
 
     MeshBasedReprojectionQuadTree::Ptr quad_tree;
 
@@ -148,6 +148,7 @@ private:
     bool create_layer_pass();
     bool create_layer_pipeline();
     bool create_edge_detection_pipeline();
+    bool create_quad_tree_pipeline();
     bool create_overlay_pass();
     bool create_overlay_pipeline();
     bool create_mesh_overlay_pass();
@@ -160,6 +161,7 @@ private:
 
     void process_layer_pass(VkCommandBuffer command_buffer);
     void process_edge_detection_pass(VkCommandBuffer command_buffer);
+    void process_quad_tree_pass(VkCommandBuffer command_buffer);
     bool process_edge_buffer_copy(VkCommandBuffer command_buffer, FrameNumber frame_number);
     void process_edge_overlay_pass(VkCommandBuffer command_buffer);
     void process_triangle_overlay_pass(VkCommandBuffer command_buffer);
@@ -202,13 +204,14 @@ private:
     glm::mat4 layer_projection_matrix = glm::mat4(1.0f);
     glm::mat4 layer_head_to_eye_matrix = glm::mat4(1.0f);
     glm::mat4 layer_inv_projection_matrix = glm::mat4(1.0f);
+    uint32_t layer_quad_tree_levels = 0;
 
     std::vector<lava::buffer::ptr> transform_buffers;
     std::array<std::vector<lava::buffer::ptr>, 2> edge_geometry_buffers;
     std::array<std::vector<lava::buffer::ptr>, 2> triangle_geometry_buffers;
     std::array<lava::image::ptr, 2> layer_depth_buffers;
     std::array<lava::image::ptr, 2> layer_normal_buffers;
-    std::array<lava::image::ptr, 2> layer_edge_buffers;
+    std::array<std::vector<lava::image::ptr>, 2> layer_edge_buffers;
     std::array<lava::image::ptr, 2> layer_overlay_color_buffers;
     std::array<lava::image::ptr, 2> layer_overlay_depth_buffers;
 
@@ -216,7 +219,11 @@ private:
     std::vector<MeshBasedReprojectionEdgeBuffer::Ptr> edge_buffers;
     std::vector<MeshBasedReprojectionEdgeBuffer::Ptr> edge_buffer_pool;               //NOTE: Protected by edge_buffer_mutex
     uint32_t edge_buffer_process_count = 0;                                           //NOTE: Protected by edge_buffer_mutex
-    double edge_buffer_process_time_sum = 0.0;                                        //NOTE: Protected by edge_buffer_mutex
+    double line_trace_time_sum = 0.0;                                                 //NOTE: Protected by edge_buffer_mutex
+    double triangulate_time_sum = 0.0;                                                //NOTE: Protected by edge_buffer_mutex
+    double depth_sample_time_sum = 0.0;                                               //NOTE: Protected by edge_buffer_mutex
+    double metadata_time = 0.0;
+    uint32_t metadata_bits = 0;                                                       //NOTE: Protected by edge_buffer_mutex
     
     std::mutex geometry_mutex;
     glm::mat4 geometry_view_matrix;                                                   //NOTE: Protected by geometry_mutex
@@ -229,9 +236,11 @@ private:
     lava::descriptor::ptr transform_descriptor;
     lava::descriptor::ptr layer_descriptor;
     lava::descriptor::ptr edge_descriptor;
+    lava::descriptor::ptr quad_tree_descriptor;
     std::array<VkDescriptorSet, 2> layer_descriptor_sets;
     std::array<VkDescriptorSet, 2> edge_descriptor_sets;
     std::vector<VkDescriptorSet> transform_descriptor_sets;
+    std::array<std::vector<VkDescriptorSet>, 2> quad_tree_descriptor_sets;
 
     VkSampler nearest_sampler = VK_NULL_HANDLE;
 
@@ -242,6 +251,9 @@ private:
     lava::pipeline_layout::ptr edge_detection_pipeline_layout;
     lava::compute_pipeline::ptr edge_detection_pipeline;
 
+    lava::pipeline_layout::ptr quad_tree_pipeline_layout;
+    lava::compute_pipeline::ptr quad_tree_pipeline;
+
     lava::render_pass::ptr overlay_pass;
     lava::pipeline_layout::ptr overlay_pipeline_layout;
     lava::graphics_pipeline::ptr edge_overlay_pipeline;
@@ -252,7 +264,10 @@ private:
     lava::graphics_pipeline::ptr mesh_overlay_pipeline;
 
 private:
-    Statistic::Ptr statistic_edge_buffer_time;
+    Statistic::Ptr statistic_line_trace_time;
+    Statistic::Ptr statistic_triangulate_time;
+    Statistic::Ptr statistic_depth_sample_time;
+    Statistic::Ptr statistic_metadata_bitrate;
 };
 
 MeshBasedReprojection::Ptr make_mesh_based_reprojection();
-- 
GitLab