diff --git a/CMakeLists.txt b/CMakeLists.txt
index 846ec7fd34c442f9327c9fad577f6c61fddc2cb5..2a4788b92f122add4d6188a4e73ee4724dd77f54 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -700,7 +700,7 @@ message("=======================================================================
             ${SRC_DIR}/strategy/stereo_strategy.hpp ${SRC_DIR}/strategy/stereo_strategy.cpp
             ${SRC_DIR}/strategy/native_stereo_forward.hpp ${SRC_DIR}/strategy/native_stereo_forward.cpp
             ${SRC_DIR}/strategy/native_stereo_deferred.hpp ${SRC_DIR}/strategy/native_stereo_deferred.cpp
-            # ${SRC_DIR}/strategy/multiview_stereo.hpp ${SRC_DIR}/strategy/multiview_stereo.cpp
+            ${SRC_DIR}/strategy/multi_view_stereo.hpp ${SRC_DIR}/strategy/multi_view_stereo.cpp
             ${SRC_DIR}/strategy/depth_peeling_reprojection_stereo.hpp ${SRC_DIR}/strategy/depth_peeling_reprojection_stereo.cpp
             
 			#Stream
@@ -734,8 +734,8 @@ message("=======================================================================
                 strategy/native_deferred/native_stereo_deferred.vert
                 strategy/native_deferred/native_stereo_deferred.frag
 
-                strategy/multiview/multiview_stereo.vert
-                strategy/multiview/multiview_stereo.frag
+                strategy/multi_view/multi_view_stereo.vert
+                strategy/multi_view/multi_view_stereo.frag
 
                 strategy/depth_peeling_reprojection/depth_peeling_reprojection_stereo.vert
                 strategy/depth_peeling_reprojection/depth_peeling_reprojection_stereo.geom
diff --git a/liblava/block/render_pass.cpp b/liblava/block/render_pass.cpp
index c38ad76959e46552e5fff10b7fee8e524b389df7..562654d9c909b84c98efefb3fbce21d7c9aff9b1 100644
--- a/liblava/block/render_pass.cpp
+++ b/liblava/block/render_pass.cpp
@@ -12,7 +12,7 @@ namespace lava {
         on_destroyed = [&]() { on_target_destroyed(); };
     }
 
-    bool render_pass::create(VkAttachmentsRef target_attachments, rect area) {
+    bool render_pass::create(VkAttachmentsRef target_attachments, rect area, void* next) {
         std::vector<VkAttachmentDescription> attachment_descriptions;
 
         for (auto& attachment : attachments)
@@ -30,6 +30,7 @@ namespace lava {
 
         VkRenderPassCreateInfo const create_info{
             .sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO,
+            .pNext = next,
             .attachmentCount = to_ui32(attachment_descriptions.size()),
             .pAttachments = attachment_descriptions.data(),
             .subpassCount = to_ui32(subpass_descriptions.size()),
diff --git a/liblava/block/render_pass.hpp b/liblava/block/render_pass.hpp
index 2ebbdcadf813d0fa0eaffbf44b9463b28ac68fda..c58cccd13df5a1329d9491187c9f0938df770905 100644
--- a/liblava/block/render_pass.hpp
+++ b/liblava/block/render_pass.hpp
@@ -16,7 +16,7 @@ namespace lava {
 
         explicit render_pass(device_ptr device);
 
-        bool create(VkAttachmentsRef target_attachments, rect area);
+        bool create(VkAttachmentsRef target_attachments, rect area, void* next = nullptr);
         void destroy();
 
         void process(VkCommandBuffer cmd_buf, index frame);
diff --git a/res/dpr/strategy/multi_view/multi_view_stereo.frag b/res/dpr/strategy/multi_view/multi_view_stereo.frag
new file mode 100644
index 0000000000000000000000000000000000000000..4b7eae36af1519a9667e17a95944680340a7f7e5
--- /dev/null
+++ b/res/dpr/strategy/multi_view/multi_view_stereo.frag
@@ -0,0 +1,57 @@
+#version 450 core
+#extension GL_GOOGLE_include_directive : require
+#extension GL_EXT_multiview : require
+
+#define MATERIAL_DESCRIPTOR_SET 3
+#define LIGHT_DESCRIPTOR_SET 4
+#define SHADOW_DESCRIPTOR_SET 5
+
+#include "math_library.glsl"
+#include "material_library.glsl"
+#include "light_library.glsl"
+
+#include "per_frame.inc"
+
+layout(location = 0) out vec4 outFragColor;
+
+layout(location = 0) in vec3 inPos;
+layout(location = 1) in vec3 inNormal;
+layout(location = 2) in vec3 inTangent;
+layout(location = 3) in vec2 inUV;
+
+layout(set = 0, binding = 0) uniform LeftFrame 
+{
+    PerFrameData left_frame;
+};
+
+layout(set = 1, binding = 0) uniform RightFrame
+{
+    PerFrameData right_frame;
+};
+
+void main()
+{
+    Material material = lookup_material(inUV);
+    
+    if (material.opacity < EPSILON)
+    {
+        discard;
+    }
+
+    vec3 eye_position = vec3(0.0);
+
+    if (gl_ViewIndex == 0) //Left Eye
+    {
+        eye_position = left_frame.eyePosition;
+    }
+
+    else if (gl_ViewIndex == 1) //Right Eye
+    {
+        eye_position = right_frame.eyePosition;
+    }
+    
+    vec3 normal = lookup_normal(inUV, inNormal, inTangent);
+    vec3 lighting = apply_local_lighting(eye_position, inPos, normal, material);
+
+    outFragColor = vec4(lighting, 1.0);
+}
diff --git a/res/dpr/strategy/multi_view/multi_view_stereo.vert b/res/dpr/strategy/multi_view/multi_view_stereo.vert
new file mode 100644
index 0000000000000000000000000000000000000000..e7849d5da5fb9e8b39694df7039ccfead112128c
--- /dev/null
+++ b/res/dpr/strategy/multi_view/multi_view_stereo.vert
@@ -0,0 +1,54 @@
+#version 450 core
+#extension GL_GOOGLE_include_directive : require
+#extension GL_EXT_multiview : require
+
+#include "mesh_data.inc"
+#include "per_frame.inc"
+
+layout(location = 0) out vec3 outPos;
+layout(location = 1) out vec3 outNormal;
+layout(location = 2) out vec3 outTangent;
+layout(location = 3) out vec2 outUV;
+
+out gl_PerVertex
+{
+    vec4 gl_Position;
+};
+
+layout(location = 0) in vec3 inPos;
+layout(location = 1) in vec3 inNormal;
+layout(location = 2) in vec3 inTangent;
+layout(location = 3) in vec2 inUV;
+
+layout(set = 0, binding = 0) uniform LeftFrame 
+{
+    PerFrameData left_frame;
+};
+
+layout(set = 1, binding = 0) uniform RightFrame
+{
+    PerFrameData right_frame;
+};
+
+layout(set = 2, binding = 0) uniform Mesh
+{
+    MeshData meshData;
+};
+
+void main()
+{
+    outPos = vec3(meshData.localToWorldSpace * vec4(inPos, 1.0));
+    outNormal = normalize((meshData.vectorToWorldSpace * vec4(inNormal, 0.0)).xyz);
+    outTangent = normalize((meshData.vectorToWorldSpace * vec4(inTangent, 0.0)).xyz);
+    outUV = inUV;
+
+    if (gl_ViewIndex == 0) //Left Eye
+    {
+        gl_Position = left_frame.viewProjection * vec4(outPos, 1.0);
+    }
+
+    else if (gl_ViewIndex == 1) //Right Eye
+    {
+        gl_Position = right_frame.viewProjection * vec4(outPos, 1.0);
+    }
+}
diff --git a/res/dpr/strategy/multiview/multiview_stereo.frag b/res/dpr/strategy/multiview/multiview_stereo.frag
deleted file mode 100644
index 1111d048c3ca4391f8db247a17e7274fe3a454e5..0000000000000000000000000000000000000000
--- a/res/dpr/strategy/multiview/multiview_stereo.frag
+++ /dev/null
@@ -1,31 +0,0 @@
-#version 450 core
-#extension GL_GOOGLE_include_directive : require
-
-#include "per_frame.inc"
-
-layout(location = 0) in vec3 inPos;
-layout(location = 1) in vec3 inNormal;
-layout(location = 2) in vec3 inTangent;
-layout(location = 3) in vec2 inUV;
-
-layout(set = 0, binding = 0) uniform Frame {
-    PerFrameData frame;
-};
-
-layout(set = 1, binding = 0) uniform sampler2D samplerDiffuse;
-layout(set = 1, binding = 1) uniform sampler2D samplerNormal;
-
-layout(location = 0) out vec4 outFragColor;
-
-void main() {
-    vec3 vertexNormal = normalize(inNormal);
-    vec3 vertexTangent = normalize(inTangent);
-    vec3 vertexBitangent = cross(vertexTangent, vertexNormal);
-    mat3 tangentToWorldSpace = mat3(vertexTangent, vertexBitangent, vertexNormal);
-    vec3 normalMapNormal = texture(samplerNormal, inUV).xyz * 2.0 - 1.0;
-    vec3 normal = tangentToWorldSpace * normalMapNormal;
-    vec4 diffuse = texture(samplerDiffuse, inUV);
-    float nDotL = dot(inNormal, normalize(vec3(0.1, -1.0, 0.13)));
-    outFragColor = vec4(max(0.23, nDotL) * diffuse.rgb, 1.0f);
-    // outFragColor = vec4(inNormal, 1.0);
-}
diff --git a/res/dpr/strategy/multiview/multiview_stereo.vert b/res/dpr/strategy/multiview/multiview_stereo.vert
deleted file mode 100644
index ac6f5f08f6faf9d5058d37f5d5ef8a986d864e9d..0000000000000000000000000000000000000000
--- a/res/dpr/strategy/multiview/multiview_stereo.vert
+++ /dev/null
@@ -1,30 +0,0 @@
-#version 450 core
-#extension GL_GOOGLE_include_directive : require
-
-#include "per_frame.inc"
-
-layout(location = 0) in vec3 inPos;
-layout(location = 1) in vec3 inNormal;
-layout(location = 2) in vec3 inTangent;
-layout(location = 3) in vec2 inUV;
-
-layout(set = 0, binding = 0) uniform Frame {
-    PerFrameData frame;
-};
-
-layout(location = 0) out vec3 outPos;
-layout(location = 1) out vec3 outNormal;
-layout(location = 2) out vec3 outTangent;
-layout(location = 3) out vec2 outUV;
-
-out gl_PerVertex {
-    vec4 gl_Position;
-};
-
-void main() {
-    gl_Position =  frame.viewProjection * vec4(inPos.xyz, 1.0);
-    outPos = inPos;
-    outNormal = inNormal;
-    outTangent = inTangent;
-    outUV = inUV;
-}
diff --git a/src/strategy/multi_view_stereo.cpp b/src/strategy/multi_view_stereo.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..73546895f37c7fe466a4ebe9114a650509777911
--- /dev/null
+++ b/src/strategy/multi_view_stereo.cpp
@@ -0,0 +1,413 @@
+#include "multi_view_stereo.hpp"
+#include "vr_application.hpp"
+
+bool MultiViewStereo::on_setup_instance(lava::frame_config& config)
+{
+    config.info.req_api_version = lava::api_version::v1_1;
+
+    return true;
+}
+
+bool MultiViewStereo::on_setup_device(lava::device::create_param& parameters)
+{
+    memset(&this->features, 0, sizeof(this->features));
+    this->features.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MULTIVIEW_FEATURES;
+    this->features.multiview = VK_TRUE;
+
+    parameters.next = &this->features;
+
+    return true;
+}
+
+bool MultiViewStereo::on_create()
+{
+    this->shadow_cache = make_shadow_cache();
+
+    if (!this->shadow_cache->create(this->get_scene(), ShadowCacheSettings()))
+    {
+        return false;
+    }
+
+    if (!this->create_pipeline_layout())
+    {
+        return false;
+    }
+
+    if (!this->create_render_pass())
+    {
+        return false;
+    }
+
+    if (!this->create_pipeline())
+    {
+        return false;
+    }
+
+    return true;
+}
+
+void MultiViewStereo::on_destroy()
+{
+    if (this->shadow_cache != nullptr)
+    {
+        this->shadow_cache->destroy();
+    }
+
+    if (this->pipeline_layout != nullptr)
+    {
+        this->pipeline_layout->destroy();
+    }
+
+    if (this->pipeline != nullptr)
+    {
+        this->pipeline->destroy();
+    }
+
+    if (this->render_pass != nullptr)
+    {
+        this->render_pass->destroy();
+    }
+
+    if (this->color_buffer != nullptr)
+    {
+        this->color_buffer->destroy();
+    }
+
+    if (this->depth_buffer != nullptr)
+    {
+        this->depth_buffer->destroy();
+    }
+}
+
+bool MultiViewStereo::on_interface()
+{
+    return true;
+}
+
+bool MultiViewStereo::on_update(lava::delta delta_time)
+{
+    return true;
+}
+
+bool MultiViewStereo::on_render(VkCommandBuffer command_buffer, lava::index frame)
+{
+    this->get_headset()->submit_metadata(FrameMetadata());
+
+    this->get_pass_timer()->begin_pass(command_buffer, "shadow");
+    this->shadow_cache->compute_shadow(command_buffer, frame);
+    this->get_pass_timer()->end_pass(command_buffer);
+
+    this->get_pass_timer()->begin_pass(command_buffer, "multi_eye");
+    this->render_pass->process(command_buffer, 0);
+    this->get_pass_timer()->end_pass(command_buffer);
+
+    VkImageMemoryBarrier left_barrier;
+    left_barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
+    left_barrier.pNext = nullptr;
+    left_barrier.srcAccessMask = 0;
+    left_barrier.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
+    left_barrier.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED;
+    left_barrier.newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
+    left_barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
+    left_barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
+    left_barrier.image = this->get_headset()->get_framebuffer(EYE_LEFT)->get();
+    left_barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
+    left_barrier.subresourceRange.baseMipLevel = 0;
+    left_barrier.subresourceRange.levelCount = 1;
+    left_barrier.subresourceRange.baseArrayLayer = 0;
+    left_barrier.subresourceRange.layerCount = 1;
+
+    VkImageMemoryBarrier right_barrier;
+    right_barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
+    right_barrier.pNext = nullptr;
+    right_barrier.srcAccessMask = 0;
+    right_barrier.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
+    right_barrier.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED;
+    right_barrier.newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
+    right_barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
+    right_barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
+    right_barrier.image = this->get_headset()->get_framebuffer(EYE_RIGHT)->get();
+    right_barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
+    right_barrier.subresourceRange.baseMipLevel = 0;
+    right_barrier.subresourceRange.levelCount = 1;
+    right_barrier.subresourceRange.baseArrayLayer = 0;
+    right_barrier.subresourceRange.layerCount = 1;
+
+    std::array<VkImageMemoryBarrier, 2> barriers = 
+    {
+        left_barrier,
+        right_barrier
+    };
+
+    vkCmdPipelineBarrier(command_buffer, VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 0, nullptr, 0, nullptr, barriers.size(), barriers.data());
+
+    VkImageSubresourceLayers subresource;
+    subresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
+    subresource.mipLevel = 0;
+    subresource.baseArrayLayer = 0;
+    subresource.layerCount = 1;
+
+    VkOffset3D offset;
+    offset.x = 0;
+    offset.y = 0;
+    offset.z = 0;
+
+    VkExtent3D extent;
+    extent.width = this->get_headset()->get_resolution().x;
+    extent.height = this->get_headset()->get_resolution().y;
+    extent.depth = 1;
+
+    VkImageCopy left_copy;
+    left_copy.srcSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
+    left_copy.srcSubresource.mipLevel = 0;
+    left_copy.srcSubresource.baseArrayLayer = 0;
+    left_copy.srcSubresource.layerCount = 1;
+    left_copy.srcOffset = offset;
+    left_copy.dstSubresource = subresource;
+    left_copy.dstOffset = offset;
+    left_copy.extent = extent;
+
+    VkImageCopy right_copy;
+    right_copy.srcSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
+    right_copy.srcSubresource.mipLevel = 0;
+    right_copy.srcSubresource.baseArrayLayer = 1;
+    right_copy.srcSubresource.layerCount = 1;
+    right_copy.srcOffset = offset;
+    right_copy.dstSubresource = subresource;
+    right_copy.dstOffset = offset;
+    right_copy.extent = extent;
+
+    vkCmdCopyImage(command_buffer, this->color_buffer->get(), VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, this->get_headset()->get_framebuffer(EYE_LEFT)->get(), VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, &left_copy);
+    vkCmdCopyImage(command_buffer, this->color_buffer->get(), VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, this->get_headset()->get_framebuffer(EYE_RIGHT)->get(), VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, &right_copy);
+
+    this->get_frame_capture()->capture_image(command_buffer, this->get_headset()->get_framebuffer(EYE_LEFT), VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, "left_eye");
+    this->get_companion_window()->submit_image(command_buffer, EYE_LEFT, this->get_headset()->get_framebuffer(EYE_LEFT), VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL);
+    this->get_headset()->submit_frame(command_buffer, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, EYE_LEFT);
+
+    this->get_frame_capture()->capture_image(command_buffer, this->get_headset()->get_framebuffer(EYE_RIGHT), VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, "right_eye");
+    this->get_companion_window()->submit_image(command_buffer, EYE_RIGHT, this->get_headset()->get_framebuffer(EYE_RIGHT), VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL);
+    this->get_headset()->submit_frame(command_buffer, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, EYE_RIGHT);
+
+    return true;
+}
+
+uint32_t MultiViewStereo::get_max_remote_frame_ids() const
+{
+    return 2;
+}
+
+const char* MultiViewStereo::get_name() const
+{
+    return "Multi-View Stereo";
+}
+
+bool MultiViewStereo::create_pipeline_layout()
+{
+    this->pipeline_layout = lava::make_pipeline_layout();
+    this->pipeline_layout->add(this->get_stereo_transform()->get_descriptor());
+    this->pipeline_layout->add(this->get_stereo_transform()->get_descriptor());
+    this->pipeline_layout->add(this->get_scene()->get_mesh_descriptor());
+    this->pipeline_layout->add(this->get_scene()->get_material_descriptor());
+    this->pipeline_layout->add(this->get_scene()->get_light_descriptor());
+    this->pipeline_layout->add(this->shadow_cache->get_descriptor());
+    
+    if (!this->pipeline_layout->create(this->get_device()))
+    {
+        lava::log()->error("Can't create pipeline layout for multi-view stereo!");
+
+        return false;
+    }
+
+    return true;
+}
+
+bool MultiViewStereo::create_render_pass()
+{
+    this->depth_buffer = lava::make_image(VK_FORMAT_D32_SFLOAT);
+    this->depth_buffer->set_usage(VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT);
+    this->depth_buffer->set_tiling(VK_IMAGE_TILING_OPTIMAL);
+    this->depth_buffer->set_view_type(VK_IMAGE_VIEW_TYPE_2D_ARRAY);
+    this->depth_buffer->set_layer_count(2);
+
+    if (!this->depth_buffer->create(this->get_device(), this->get_headset()->get_resolution()))
+    {
+        lava::log()->error("Can't create layered depth buffer for multi-view stereo!");
+
+        return false;
+    }
+
+    this->color_buffer = lava::make_image(this->get_headset()->get_format());
+    this->color_buffer->set_usage(VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT);
+    this->color_buffer->set_tiling(VK_IMAGE_TILING_OPTIMAL);
+    this->color_buffer->set_view_type(VK_IMAGE_VIEW_TYPE_2D_ARRAY);
+    this->color_buffer->set_layer_count(2);
+
+    if (!this->color_buffer->create(this->get_device(), this->get_headset()->get_resolution()))
+    {
+        lava::log()->error("Can't create layered color buffer for multi-view stereo!");
+
+        return false;
+    }
+
+    this->depth_attachment = lava::make_attachment(VK_FORMAT_D32_SFLOAT);
+    this->depth_attachment->set_op(VK_ATTACHMENT_LOAD_OP_CLEAR, VK_ATTACHMENT_STORE_OP_STORE);
+    this->depth_attachment->set_stencil_op(VK_ATTACHMENT_LOAD_OP_DONT_CARE, VK_ATTACHMENT_STORE_OP_DONT_CARE);
+    this->depth_attachment->set_layouts(VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL);
+
+    this->color_attachment = lava::make_attachment(this->get_headset()->get_format());
+    this->color_attachment->set_op(VK_ATTACHMENT_LOAD_OP_CLEAR, VK_ATTACHMENT_STORE_OP_STORE);
+    this->color_attachment->set_stencil_op(VK_ATTACHMENT_LOAD_OP_DONT_CARE, VK_ATTACHMENT_STORE_OP_DONT_CARE);
+    this->color_attachment->set_layouts(VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL);
+
+    VkClearValue depth_clear_value;
+    depth_clear_value.depthStencil.depth = 1.0f;
+    depth_clear_value.depthStencil.stencil = 0;
+
+    VkClearValue color_clear_value;
+    color_clear_value.color.float32[0] = 0.0f;
+    color_clear_value.color.float32[1] = 0.0f;
+    color_clear_value.color.float32[2] = 0.0f;
+    color_clear_value.color.float32[3] = 1.0f;
+
+    lava::subpass::ptr subpass = lava::make_subpass();
+    subpass->set_depth_stencil_attachment(0, VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL);
+    subpass->set_color_attachment(1, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL);
+
+    // wait for previous operations to finish reading before clearing attachments
+    // 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
+    lava::subpass_dependency::ptr subpass_begin_dependency = lava::make_subpass_dependency(VK_SUBPASS_EXTERNAL, 0);
+    subpass_begin_dependency->set_stage_mask(VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT | VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT);
+    subpass_begin_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);
+
+    // don't run any transfer operations before we're done writing to attachments
+    // make attachment writes visible to subsequent reads
+    lava::subpass_dependency::ptr subpass_end_dependency = lava::make_subpass_dependency(0, VK_SUBPASS_EXTERNAL);
+    subpass_end_dependency->set_stage_mask(VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT | VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT);
+    subpass_end_dependency->set_access_mask(VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT, VK_ACCESS_TRANSFER_READ_BIT);
+
+    this->render_pass = lava::make_render_pass(this->get_device());
+    this->render_pass->add(subpass);
+    this->render_pass->add(subpass_begin_dependency);
+    this->render_pass->add(subpass_end_dependency);
+    this->render_pass->add(this->depth_attachment);
+    this->render_pass->add(this->color_attachment);
+    this->render_pass->set_clear_values(
+    {
+        depth_clear_value,
+        color_clear_value
+    });
+
+    lava::rect framebuffer_area =
+    {
+        glm::vec2(0.0f),
+        this->get_headset()->get_resolution()
+    };
+
+    lava::VkImageViews framebuffer_views =
+    {
+        this->depth_buffer->get_view(),
+        this->color_buffer->get_view()
+    };
+
+    uint32_t view_mask = 0x03;         //NOTE: First and second bit set, meaning that the first and second layer of the framebuffer will be modified.
+    uint32_t correleation_mask = 0x03; //NOTE: First and second bit set, meaning that left and right eye should be rendered concurrently if possible.
+
+    VkRenderPassMultiviewCreateInfo multi_view_info;
+    multi_view_info.sType = VK_STRUCTURE_TYPE_RENDER_PASS_MULTIVIEW_CREATE_INFO;
+    multi_view_info.pNext = nullptr;
+    multi_view_info.subpassCount = 1;
+    multi_view_info.pViewMasks = &view_mask;
+    multi_view_info.dependencyCount = 0;
+    multi_view_info.pViewOffsets = nullptr;
+    multi_view_info.correlationMaskCount = 1;
+    multi_view_info.pCorrelationMasks = &correleation_mask;
+
+    if (!this->render_pass->create({ framebuffer_views }, framebuffer_area, &multi_view_info))
+    {
+        lava::log()->error("Can't create render pass for multi-view stereo!");
+
+        return false;
+    }
+
+    return true;
+}
+
+bool MultiViewStereo::create_pipeline()
+{
+    VkPipelineColorBlendAttachmentState blend_state;
+    blend_state.blendEnable = VK_FALSE;
+    blend_state.srcColorBlendFactor = VK_BLEND_FACTOR_ZERO;
+    blend_state.dstColorBlendFactor = VK_BLEND_FACTOR_ZERO;
+    blend_state.colorBlendOp = VK_BLEND_OP_ADD;
+    blend_state.srcAlphaBlendFactor = VK_BLEND_FACTOR_ZERO;
+    blend_state.dstAlphaBlendFactor = VK_BLEND_FACTOR_ZERO;
+    blend_state.alphaBlendOp = VK_BLEND_OP_ADD;
+    blend_state.colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT;
+
+    this->pipeline = lava::make_graphics_pipeline(this->get_device());
+    this->pipeline->set_layout(this->pipeline_layout);
+    this->pipeline->set_rasterization_cull_mode(VK_CULL_MODE_BACK_BIT);
+    this->pipeline->set_depth_test_and_write(true, true);
+    this->pipeline->set_depth_compare_op(VK_COMPARE_OP_LESS);
+    this->pipeline->add_color_blend_attachment(blend_state);
+
+    Scene::set_vertex_input(this->pipeline.get());
+
+    if (!this->pipeline->add_shader(lava::file_data("dpr/binary/multi_view_stereo_vertex.spirv"), VK_SHADER_STAGE_VERTEX_BIT))
+    {
+        lava::log()->error("Can't load vertex shader for multi-view stereo!");
+
+        return false;
+    }
+
+    if (!this->pipeline->add_shader(lava::file_data("dpr/binary/multi_view_stereo_fragment.spirv"), VK_SHADER_STAGE_FRAGMENT_BIT))
+    {
+        lava::log()->error("Can't load fragment shader for multi-view stereo!");
+
+        return false;
+    }
+
+    this->pipeline->on_process = [this](VkCommandBuffer command_buffer)
+    {
+        this->pipeline_function(command_buffer);
+    };
+
+    if (!this->pipeline->create(this->render_pass->get()))
+    {
+        lava::log()->error("Can't create pipeline for native stereo forward!");
+
+        return false;
+    }
+
+    this->render_pass->add_front(this->pipeline);
+
+    return true;
+}
+
+void MultiViewStereo::pipeline_function(VkCommandBuffer command_buffer)
+{
+    uint32_t frame_index = this->get_application()->get_frame_index();
+
+    this->pipeline_layout->bind(command_buffer, this->get_stereo_transform()->get_descriptor_set(EYE_LEFT, frame_index), 0);
+    this->pipeline_layout->bind(command_buffer, this->get_stereo_transform()->get_descriptor_set(EYE_RIGHT, frame_index), 1);
+    this->pipeline_layout->bind(command_buffer, this->get_scene()->get_light_descriptor_set(frame_index), 4);
+    this->pipeline_layout->bind(command_buffer, this->shadow_cache->get_descriptor_set(), 5);
+
+    const std::vector<SceneMaterial>& materials = this->get_scene()->get_materials();
+
+    for (const SceneMesh& mesh : this->get_scene()->get_meshes())
+    {
+        const SceneMaterial& material = materials[mesh.material_index];
+
+        this->pipeline_layout->bind(command_buffer, material.descriptor_set, 3);
+        this->pipeline_layout->bind(command_buffer, mesh.descriptor_set[frame_index], 2);
+        
+        mesh.mesh->bind_draw(command_buffer);
+    }
+}
+
+MultiViewStereo::Ptr make_multi_view_stereo()
+{
+    return std::make_shared<MultiViewStereo>();
+}
\ No newline at end of file
diff --git a/src/strategy/multi_view_stereo.hpp b/src/strategy/multi_view_stereo.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..895a6fb387f1baa0a180969b0b2b5b0d2c4682ca
--- /dev/null
+++ b/src/strategy/multi_view_stereo.hpp
@@ -0,0 +1,51 @@
+#pragma once
+#include <memory>
+#include <array>
+
+#include "stereo_strategy.hpp"
+#include "utility/shadow_cache.hpp"
+
+class MultiViewStereo final : public StereoStrategy
+{
+public:
+    typedef std::shared_ptr<MultiViewStereo> Ptr;
+
+public:
+    MultiViewStereo() = default;
+
+    bool on_setup_instance(lava::frame_config& config) override;
+    bool on_setup_device(lava::device::create_param& parameters) override;
+
+    bool on_create() override;
+    void on_destroy() override;
+
+    bool on_interface() override;
+    bool on_update(lava::delta delta_time) override;
+    bool on_render(VkCommandBuffer command_buffer, lava::index frame) override;
+
+    uint32_t get_max_remote_frame_ids() const override;
+    const char* get_name() const override;
+
+private:
+    bool create_pipeline_layout();
+    bool create_render_pass();
+    bool create_pipeline();
+
+    void pipeline_function(VkCommandBuffer command_buffer);
+
+private:
+    VkPhysicalDeviceMultiviewFeatures features;
+
+    ShadowCache::Ptr shadow_cache;
+    lava::pipeline_layout::ptr pipeline_layout;
+
+    lava::graphics_pipeline::ptr pipeline;
+    lava::render_pass::ptr render_pass;
+
+    lava::attachment::ptr depth_attachment;
+    lava::attachment::ptr color_attachment;
+    lava::image::ptr color_buffer;
+    lava::image::ptr depth_buffer;
+};
+
+MultiViewStereo::Ptr make_multi_view_stereo();
\ No newline at end of file
diff --git a/src/strategy/multiview_stereo.cpp b/src/strategy/multiview_stereo.cpp
deleted file mode 100644
index 8470eac20102c166cbd83d26406a5da5b6d681e7..0000000000000000000000000000000000000000
--- a/src/strategy/multiview_stereo.cpp
+++ /dev/null
@@ -1,93 +0,0 @@
-#include "multiview_stereo.hpp"
-#include "vr_app.hpp"
-
-using namespace lava;
-
-multiview_stereo::multiview_stereo(vr_app* app)
-: stereo_strategy(app) {
-}
-
-multiview_stereo::~multiview_stereo() {
-    pipeline_layout_->destroy();
-}
-
-bool multiview_stereo::create() {
-    pipeline_layout_ = make_pipeline_layout();
-    pipeline_layout_->add(app()->per_frame_descriptor());
-    pipeline_layout_->add(app()->scene_descriptor());
-    if (!pipeline_layout_->create(app()->device())) {
-        return false;
-    }
-
-    create_render_pass(app()->right_eye_framebuffer(), app()->right_eye_framebuffer());
-
-    return true;
-}
-
-void multiview_stereo::render(VkCommandBuffer command_buffer, lava::index frame) {
-    // begin
-    auto origin = area.get_origin();
-    auto size = area.get_size();
-
-    VkRenderPassBeginInfo const info{
-        .sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO,
-        .renderPass = vk_render_pass,
-        .framebuffer = framebuffers[frame],
-        .renderArea = { { origin.x, origin.y }, { size.x, size.y } },
-        .clearValueCount = to_ui32(clear_values.size()),
-        .pClearValues = clear_values.data(),
-    };
-
-    device().call().vkCmdBeginRenderPass(command_buffer, &info, VK_SUBPASS_CONTENTS_INLINE);
-
-    // process
-    ui32 count = 0;
-
-    for (auto& subpass : subpasses) {
-        if (count > 0)
-            device().call().vkCmdNextSubpass(command_buffer, VK_SUBPASS_CONTENTS_INLINE);
-
-        if (!subpass->activated())
-            continue;
-
-        subpass->process(command_buffer, area.get_size());
-
-        ++count;
-    }
-
-    // end
-    device().call().vkCmdEndRenderPass(command_buffer);
-}
-
-bool multiview_stereo::create_render_pass(const stereo_framebuffer::ptr& left_eye_framebuffer, const stereo_framebuffer::ptr& right_eye_framebuffer) {
-    for (auto& attachment : attachments) {
-        attachment_descriptions.push_back(attachment->get_description());
-    }
-
-    std::vector<VkSubpassDescription> subpass_descriptions;
-
-    for (auto& subpass : subpasses)
-        subpass_descriptions.push_back(subpass->get_description());
-
-    std::vector<VkSubpassDependency> subpass_dependencies;
-
-    for (auto& dependency : dependencies)
-        subpass_dependencies.push_back(dependency->get_dependency());
-
-    VkRenderPassCreateInfo const create_info{
-        .sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO,
-        .attachmentCount = to_ui32(attachment_descriptions.size()),
-        .pAttachments = attachment_descriptions.data(),
-        .subpassCount = to_ui32(subpass_descriptions.size()),
-        .pSubpasses = subpass_descriptions.data(),
-        .dependencyCount = to_ui32(subpass_dependencies.size()),
-        .pDependencies = subpass_dependencies.data(),
-    };
-
-    if (!check(device->call().vkCreateRenderPass(device->get(), &create_info, memory::alloc(), &vk_render_pass))) {
-        log()->error("create render pass");
-        return false;
-    }
-
-    return true;
-}
diff --git a/src/strategy/multiview_stereo.hpp b/src/strategy/multiview_stereo.hpp
deleted file mode 100644
index fe6abb16732003b293d386ff7db159eeac321677..0000000000000000000000000000000000000000
--- a/src/strategy/multiview_stereo.hpp
+++ /dev/null
@@ -1,22 +0,0 @@
-#pragma once
-
-#include "stereo_strategy.hpp"
-
-class multiview_stereo final : public stereo_strategy {
-public:
-    multiview_stereo(vr_app* app);
-    ~multiview_stereo();
-
-    bool create();
-    virtual void render(VkCommandBuffer command_buffer, lava::index frame) override;
-
-private:
-    lava::pipeline_layout::ptr pipeline_layout_;
-    VkRenderPass vk_render_pass = VK_NULL_HANDLE;
-    lava::attachment::list attachments;
-    lava::subpass_dependency::list dependencies;
-    lava::subpass::list subpasses;
-    lava::rect area;
-
-    bool create_render_pass(const stereo_framebuffer::ptr& left_eye_framebuffer, const stereo_framebuffer::ptr& right_eye_framebuffer);
-};
\ No newline at end of file
diff --git a/src/strategy/stereo_strategy.cpp b/src/strategy/stereo_strategy.cpp
index ce41253737f53b05660e4a8e984406313a7fa4e8..eb359580ddcbc0d0938a68a82b28c4acaeabfb79 100644
--- a/src/strategy/stereo_strategy.cpp
+++ b/src/strategy/stereo_strategy.cpp
@@ -1,6 +1,7 @@
 #include "stereo_strategy.hpp"
 #include "native_stereo_forward.hpp"
 #include "native_stereo_deferred.hpp"
+#include "multi_view_stereo.hpp"
 #include "depth_peeling_reprojection_stereo.hpp"
 
 #include "vr_application.hpp"
@@ -72,6 +73,9 @@ StereoStrategy::Ptr make_stereo_strategy(VRApplication* application, StereoStrat
     case STEREO_STRATEGY_TYPE_NATIVE_DEFERRED:
         strategy = make_native_stereo_deferred();
         break;
+    case STEREO_STRATEGY_TYPE_MULTI_VIEW:
+        strategy = make_multi_view_stereo();
+        break;
     case STEREO_STRATEGY_TYPE_DEPTH_PEELING_REPROJECTION:
         strategy = make_depth_peeling_reprojection_stereo();
         break;
@@ -90,6 +94,7 @@ std::vector<StereoStrategy::Ptr> make_all_stereo_strategies(VRApplication* appli
     std::vector<StereoStrategy::Ptr> strategies;
     strategies.push_back(make_native_stereo_forward());
     strategies.push_back(make_native_stereo_deferred());
+    strategies.push_back(make_multi_view_stereo());
     strategies.push_back(make_depth_peeling_reprojection_stereo());
 
     for (StereoStrategy::Ptr strategy : strategies)
diff --git a/src/strategy/stereo_strategy.hpp b/src/strategy/stereo_strategy.hpp
index 28eb54f6e78de9694085f75919beaa833dcb3142..2f565c26afde0fbaceb64fd53a3c66639f6ad988 100644
--- a/src/strategy/stereo_strategy.hpp
+++ b/src/strategy/stereo_strategy.hpp
@@ -29,6 +29,7 @@ enum StereoStrategyType
 {
     STEREO_STRATEGY_TYPE_NATIVE_FORWARD,
     STEREO_STRATEGY_TYPE_NATIVE_DEFERRED,
+    STEREO_STRATEGY_TYPE_MULTI_VIEW,
     STEREO_STRATEGY_TYPE_DEPTH_PEELING_REPROJECTION
 };
 
diff --git a/src/utility/command_parser.cpp b/src/utility/command_parser.cpp
index dfa2d4f309c881f77b3f6fe8b5ea643bafd01098..44c1294364a09becdde57e29591fb5db2445a2a3 100644
--- a/src/utility/command_parser.cpp
+++ b/src/utility/command_parser.cpp
@@ -263,7 +263,7 @@ void CommandParser::show_help()
     std::cout << "   --headset={headset}                  The headset that should be used." << std::endl;
     std::cout << "                                        Options: emulated (default), openvr, openxr, remote" << std::endl;
     std::cout << "   --stereo-strategy={strategy}         The stereo strategy that should be used." << std::endl;
-    std::cout << "                                        Options: native-forward (default), native-deferred, dpr" << std::endl;
+    std::cout << "                                        Options: native-forward (default), native-deferred, multi-view, dpr" << std::endl;
     std::cout << "   --animation={animation_name}         The name of the animation that should be played." << std::endl;
     std::cout << "                                        This parameter is only allowed during a benchmark." << std::endl;
     std::cout << "   --animation-index={animation_index}  The index of the animation that should be played." << std::endl;
@@ -325,6 +325,11 @@ bool CommandParser::set_stero_strategy(const std::string& name)
         this->stereo_strategy = STEREO_STRATEGY_TYPE_NATIVE_FORWARD;
     }
 
+    else if (name == "multi-view")
+    {
+        this->stereo_strategy = STEREO_STRATEGY_TYPE_MULTI_VIEW;
+    }
+
     else if (name == "dpr")
     {
         this->stereo_strategy = STEREO_STRATEGY_TYPE_DEPTH_PEELING_REPROJECTION;