diff --git a/display/lava-extras/display/DisplayOutput.cc b/display/lava-extras/display/DisplayOutput.cc
index 205f4d52b15c57229d3cf3872beefc5bee3021e5..b94ec6dec41ad35f3f99e21fa2f531b5dcdec8ea 100644
--- a/display/lava-extras/display/DisplayOutput.cc
+++ b/display/lava-extras/display/DisplayOutput.cc
@@ -29,15 +29,9 @@ DisplayOutput::instanceExtensions(const std::vector<const char *> &available) {
     return result;
 }
 
-void DisplayOutput::onInstanceCreated(Instance *instance) {}
-
 std::vector<const char *> DisplayOutput::deviceExtensions() {
     return {VK_KHR_SWAPCHAIN_EXTENSION_NAME};
 }
 
-void DisplayOutput::onPhysicalDeviceSelected(vk::PhysicalDevice phy) {}
-
-void DisplayOutput::onLogicalDeviceCreated(const SharedDevice &device) {}
-
 } // namespace display
 } // namespace lava
diff --git a/display/lava-extras/display/DisplayOutput.hh b/display/lava-extras/display/DisplayOutput.hh
index 1949c642f9679e29b8608eab151dcf3bfe21e5cf..401e04c638abe5735a246bd10d120f3635098706 100644
--- a/display/lava-extras/display/DisplayOutput.hh
+++ b/display/lava-extras/display/DisplayOutput.hh
@@ -18,19 +18,8 @@ class DisplayOutput : public lava::features::IFeature {
                                               uint32_t index);
 
     /* IFeature overrides */
-
     std::vector<const char *> instanceExtensions(std::vector<const char *> const& available) override;
-
-    void onInstanceCreated(Instance *instance) override;
-
     std::vector<const char *> deviceExtensions() override;
-    void onPhysicalDeviceSelected(vk::PhysicalDevice phy) override;
-    void onLogicalDeviceCreated(SharedDevice const &device) override;
-
-  protected:
-    lava::Instance *mInstance;
-    lava::Device *mDevice;
-    vk::PhysicalDevice mPhysicalDevice;
 };
 } // namespace display
 } // namespace lava
diff --git a/display/lava-extras/display/TiledDisplay.cc b/display/lava-extras/display/TiledDisplay.cc
new file mode 100644
index 0000000000000000000000000000000000000000..88d17bb6f6341fbf3ed92959e13fc00120ecd54f
--- /dev/null
+++ b/display/lava-extras/display/TiledDisplay.cc
@@ -0,0 +1,98 @@
+#include "TiledDisplay.hh"
+#include "DisplayOutput.hh"
+#include "DisplayWindow.hh"
+#include <lava/createinfos/Images.hh>
+#include <lava/objects/Device.hh>
+#include <lava/objects/Image.hh>
+#include <lava/objects/MemoryChunk.hh>
+
+namespace lava {
+
+namespace display {
+
+lava::display::TiledDisplay::TiledDisplay(
+    const std::vector<lava::SharedDevice> &devices,
+    const lava::display::TiledDisplay::Config &config)
+    : mConfig(config), mDevices(devices) {
+
+    auto output = devices[0]->get<DisplayOutput>();
+    if (!output)
+        throw std::logic_error("TiledDisplay: all devices need to have the "
+                               "DisplayOutput feature.");
+
+    for (auto const &c : config.tiles) {
+        mWindows.push_back(output->openWindow(mDevices[c.device], c.display));
+    }
+
+    auto ci = lava::ImageCreateInfo()
+                  .setTiling(vk::ImageTiling::eLinear)
+                  .setCombinedType(vk::ImageViewType::e2D)
+                  .setSize(config.sourceWidth, config.sourceHeight)
+                  .setFormat(config.sourceFormat)
+                  .setMipLevels(1);
+
+    for (auto const &d : devices) {
+        auto img = ci.create(d);
+        img->realizeRAM();
+        img->changeLayout(vk::ImageLayout::eGeneral);
+        mHostImages.push_back(img);
+    }
+}
+
+void TiledDisplay::submit(const SharedImage &image) {
+    auto master_idx =
+        std::find(begin(mDevices), end(mDevices), image->device()) -
+        begin(mDevices);
+    if (master_idx == mDevices.size())
+        throw std::logic_error(
+            "For now you should present to the original device, too");
+
+    image->blitTo(mHostImages[master_idx]);
+    auto master_map = mHostImages[master_idx]->memoryChunk()->map();
+
+    for (size_t i = 0; i < mHostImages.size(); i++) {
+        if (i == master_idx)
+            continue;
+        auto map = mHostImages[i]->memoryChunk()->map();
+        memcpy(map.data(), master_map.data(),
+               mHostImages[master_idx]->levelBytes(0));
+    }
+
+    for (size_t i = 0; i < mConfig.tiles.size(); i++) {
+        auto const &tile = mConfig.tiles[i];
+        auto const &window = mWindows[i];
+        auto frame = window->startFrame();
+        {
+            auto cmd =
+                mDevices[tile.device]->graphicsQueue().beginCommandBuffer();
+
+            auto sub = vk::ImageSubresourceLayers()
+                           .setAspectMask(vk::ImageAspectFlagBits::eColor)
+                           .setLayerCount(1);
+
+            auto src =
+                (tile.device == master_idx) ? image : mHostImages[tile.device];
+
+            src->blitTo(
+                frame.image(), cmd,
+                vk::ImageBlit()
+                    .setSrcOffsets(
+                        {{vk::Offset3D{tile.srcRect.offset.x,
+                                       tile.srcRect.offset.y, 0},
+                          vk::Offset3D(tile.srcRect.offset.x +
+                                           tile.srcRect.extent.width,
+                                       tile.srcRect.offset.y +
+                                           tile.srcRect.extent.height,
+                                       1)}})
+                    .setDstOffsets(
+                        {{vk::Offset3D{0, 0, 0},
+                          vk::Offset3D(window->width(), window->height(), 1)}})
+
+                    .setSrcSubresource(sub)
+                    .setDstSubresource(sub));
+        }
+    }
+}
+
+} // namespace display
+} // namespace lava
diff --git a/display/lava-extras/display/TiledDisplay.hh b/display/lava-extras/display/TiledDisplay.hh
new file mode 100644
index 0000000000000000000000000000000000000000..f10a68996b285abfa8e8884afdcc6791e42037cf
--- /dev/null
+++ b/display/lava-extras/display/TiledDisplay.hh
@@ -0,0 +1,39 @@
+#pragma once
+#include <vector>
+
+#include <lava/fwd.hh>
+#include <lava/common/vulkan.hh>
+#include "fwd.hh"
+
+namespace lava {
+namespace display {
+
+class TiledDisplay {
+  public:
+    struct TileConfig {
+        int device;
+        int display;
+
+        vk::Rect2D srcRect;
+    };
+
+    struct Config {
+        int sourceHeight, sourceWidth;
+        vk::Format sourceFormat;
+        std::vector<TileConfig> tiles;
+    };
+
+    TiledDisplay(std::vector<SharedDevice> const& devices, Config const &config);
+
+    void submit(SharedImage const &image);
+
+  protected:
+    Config mConfig;
+    std::vector<SharedDisplayWindow> mWindows;
+    std::vector<SharedDevice> mDevices;
+    std::vector<char> mBuffer;
+    std::vector<SharedImage> mHostImages;
+};
+
+} // namespace display
+} // namespace lava
diff --git a/display/lava-extras/display/fwd.hh b/display/lava-extras/display/fwd.hh
new file mode 100644
index 0000000000000000000000000000000000000000..57b01c68dc0f4c5000570673183540d420539a64
--- /dev/null
+++ b/display/lava-extras/display/fwd.hh
@@ -0,0 +1,18 @@
+#pragma once
+#include <memory>
+
+#define LAVA_FORWARD_DECLARE_CLASS(T) \
+    class T; \
+    using Shared ## T = std::shared_ptr<T>; \
+    using Weak ## T = std::weak_ptr<T>; \
+    using Unique ## T = std::unique_ptr<T>
+
+namespace lava {
+namespace display {
+    LAVA_FORWARD_DECLARE_CLASS(DisplayOutput);
+    LAVA_FORWARD_DECLARE_CLASS(DisplayWindow);
+    LAVA_FORWARD_DECLARE_CLASS(TiledDisplay);
+}
+}
+
+#undef LAVA_FORWARD_DECLARE_CLASS