diff --git a/liblava/resource/texture.hpp b/liblava/resource/texture.hpp
index 7990d0854f119dd63df35fdd8c70e0e6207a8b03..93632755df85fa682cacf745ffd3dfa9e80f9ab4 100644
--- a/liblava/resource/texture.hpp
+++ b/liblava/resource/texture.hpp
@@ -72,6 +72,10 @@ namespace lava {
             return img ? img->get_format() : VK_FORMAT_UNDEFINED;
         }
 
+        const layer::list& get_layers() const {
+            return layers;
+        }
+
     private:
         image::ptr img;
 
diff --git a/src/scene.cpp b/src/scene.cpp
index 099625ecbc2cff7f3a25ee77ebdc5878b14dbdc3..c8882f3a6d7e7354a941cdc9c3af0d209ff88392 100644
--- a/src/scene.cpp
+++ b/src/scene.cpp
@@ -8,6 +8,7 @@
 #include <glm/gtx/matrix_operation.hpp>
 #include <imgui.h>
 #include <stb_image.h>
+#include <gli/gli.hpp>
 #include <filesystem>
 #include <cmath>
 
@@ -19,7 +20,7 @@ bool Scene::create(const SceneConfig& config, lava::device_ptr device, lava::sta
 
     if (!config.scene_file.empty())
     {
-        if (!this->load_scene(config.scene_file, config.scene_unit, config.scene_orientation, device, staging, node_indices))
+        if (!this->load_scene(config.scene_file, config.scene_unit, config.scene_orientation, device, node_indices))
         {
             return false;
         }
@@ -34,7 +35,7 @@ bool Scene::create(const SceneConfig& config, lava::device_ptr device, lava::sta
 
     if (!config.controller_left_file.empty())
     {
-        if (!this->load_controller(config.controller_left_file, config.controller_left_unit, config.controller_left_orientation, device, staging, this->controller_left, node_indices))
+        if (!this->load_controller(config.controller_left_file, config.controller_left_unit, config.controller_left_orientation, device, this->controller_left, node_indices))
         {
             return false;
         }
@@ -44,7 +45,7 @@ bool Scene::create(const SceneConfig& config, lava::device_ptr device, lava::sta
 
     if (!config.controller_right_file.empty())
     {
-        if (!this->load_controller(config.controller_right_file, config.controller_right_unit, config.controller_right_orientation, device, staging, this->controller_right, node_indices))
+        if (!this->load_controller(config.controller_right_file, config.controller_right_unit, config.controller_right_orientation, device, this->controller_right, node_indices))
         {
             return false;
         }
@@ -57,7 +58,7 @@ bool Scene::create(const SceneConfig& config, lava::device_ptr device, lava::sta
         //NOTE: Need to compute scene bounds for sky-sphere
         this->reset_transforms();
 
-        if (!this->load_sky_sphere(config.sky_sphere_file, config.sky_sphere_rings, config.sky_sphere_segments, device, staging))
+        if (!this->load_sky_sphere(config.sky_sphere_file, config.sky_sphere_rings, config.sky_sphere_segments, device))
         {
             return false;
         }
@@ -301,38 +302,16 @@ bool Scene::update(lava::delta delta_time, Headset::Ptr headset)
     return true;
 }
 
-bool Scene::write(lava::index frame)
+bool Scene::stage(VkCommandBuffer command_buffer, lava::index frame)
 {
-    glsl::MeshData* mesh_data = (glsl::MeshData*)this->mesh_data_buffer[frame]->get_mapped_data();
-
-    for (uint32_t index = 0; index < this->meshes.size(); index++)
+    if (!this->stage_buffers(frame))
     {
-        const SceneMesh& mesh = this->meshes[index];
-
-        memcpy(&mesh_data[index], &mesh.data, sizeof(glsl::MeshData));
-    }
-
-    this->mesh_data_buffer[frame]->flush();
-
-    glsl::LightData* light_data = (glsl::LightData*)this->light_data_buffer[frame]->get_mapped_data();
-
-    for (uint32_t index = 0; index < this->lights.size(); index++)
-    {
-        const SceneLight& light = this->lights[index];
-
-        memcpy(&light_data[index], &light.data, sizeof(glsl::LightData));
+        return false;
     }
 
-    this->light_data_buffer[frame]->flush();
-
-    if (this->exposure_changed)
+    if (!this->stage_textures(command_buffer, frame))
     {
-        //NOTE: The Buffer could be still in used during writing. Ignore this issure, since the exposure is not changed frequently.
-        glsl::LightParameter* light_parameter = (glsl::LightParameter*)this->light_parameter_buffer->get_mapped_data();
-        light_parameter->exposure = this->exposure;
-        this->light_parameter_buffer->flush();
-
-        this->exposure_changed = false;
+        return false;
     }
 
     return true;
@@ -479,6 +458,11 @@ const std::vector<SceneMaterial>& Scene::get_materials() const
     return this->materials;
 }
 
+const std::vector<SceneTexture>& Scene::get_textures() const
+{
+    return this->textures;
+}
+
 const std::vector<SceneAnimation>& Scene::get_animations() const
 {
     return this->animations;
@@ -827,7 +811,7 @@ bool Scene::create_mesh_buffer(lava::device_ptr device, uint32_t frame_count)
     return true;
 }
 
-bool Scene::load_scene(const std::string& file_name, SceneUnit unit, SceneOrientation orientation, lava::device_ptr device, lava::staging& staging, std::map<std::string, SceneNodeIndex>& node_indices)
+bool Scene::load_scene(const std::string& file_name, SceneUnit unit, SceneOrientation orientation, lava::device_ptr device, std::map<std::string, SceneNodeIndex>& node_indices)
 {
     uint32_t post_processes = 0;
     float scale = 1.0f;
@@ -860,7 +844,7 @@ bool Scene::load_scene(const std::string& file_name, SceneUnit unit, SceneOrient
     this->materials.reserve(scene->mNumMaterials);
     this->animations.reserve(scene->mNumAnimations);
 
-    if (!this->load_materials(scene, file_name, device, staging))
+    if (!this->load_materials(scene, file_name, device))
     {
         return false;
     }
@@ -893,7 +877,7 @@ bool Scene::load_scene(const std::string& file_name, SceneUnit unit, SceneOrient
     return true;
 }
 
-bool Scene::load_controller(const std::string& file_name, SceneUnit unit, SceneOrientation orientation, lava::device_ptr device, lava::staging& staging, SceneNodeIndex& node_index, std::map<std::string, SceneNodeIndex>& node_indices)
+bool Scene::load_controller(const std::string& file_name, SceneUnit unit, SceneOrientation orientation, lava::device_ptr device, SceneNodeIndex& node_index, std::map<std::string, SceneNodeIndex>& node_indices)
 {
     uint32_t post_processes = 0;
     float scale = 1.0f;
@@ -926,7 +910,7 @@ bool Scene::load_controller(const std::string& file_name, SceneUnit unit, SceneO
     this->meshes.reserve(scene->mNumMeshes);
     this->materials.reserve(scene->mNumMaterials);
 
-    if (!this->load_materials(scene, file_name, device, staging))
+    if (!this->load_materials(scene, file_name, device))
     {
         return false;
     }
@@ -946,68 +930,17 @@ bool Scene::load_controller(const std::string& file_name, SceneUnit unit, SceneO
     return true;
 }
 
-bool Scene::load_sky_sphere(const std::string& file_name, uint32_t ring_count, uint32_t segment_count, lava::device_ptr device, lava::staging& staging)
+bool Scene::load_sky_sphere(const std::string& file_name, uint32_t ring_count, uint32_t segment_count, lava::device_ptr device)
 {
     lava::log()->info("Loading sky-sphere file '{}'", file_name.c_str());
 
-    lava::file file(file_name.c_str());
-
-    int32_t width = 0;
-    int32_t height = 0;
-    float* pixel_data = nullptr;
-
-    if (file.opened())
-    {
-        lava::unique_data image_data(file.get_size(), false);
-
-        if (!image_data.allocate())
-        {
-            lava::log()->error("Can't allocate memory for sky-sphere image!");
-
-            return false;
-        }
-
-        if (lava::file_error(file.read(image_data.ptr)))
-        {
-            lava::log()->error("Can't read image for sky-sphere!");
-
-            return false;
-        }
-
-        pixel_data = stbi_loadf_from_memory((const stbi_uc*)image_data.ptr, image_data.size, &width, &height, nullptr, STBI_rgb_alpha);
-    }
-
-    else
-    {
-        pixel_data = stbi_loadf(file_name.c_str(), &width, &height, nullptr, STBI_rgb_alpha);
-    }
-
-    if (pixel_data == nullptr)
-    {
-        lava::log()->error("Faild to load sky-sphere file '{}'", file_name.c_str());
-
-        return false;
-    }
-
-    this->sky_emissive_texture = lava::make_texture();
-
-    if (!this->sky_emissive_texture->create(device, glm::uvec2(width, height), VK_FORMAT_R32G32B32A32_SFLOAT))
-    {
-        lava::log()->error("Can't create texture for sky-sphere!");
-
-        return false;
-    }
-
-    if (!this->sky_emissive_texture->upload(pixel_data, width * height * 4 * sizeof(float)))
+    if (!this->load_texture(file_name, device, true, false, this->sky_emissive_texture))
     {
-        lava::log()->error("Can't upload texture for sky-sphere!!");
+        lava::log()->error("Faild to load sky-sphere '{}'", file_name.c_str());
 
         return false;
     }
 
-    stbi_image_free(pixel_data);
-    staging.add(this->sky_emissive_texture);
-
     uint32_t sky_material_index = this->materials.size();
     SceneMaterial& sky_material = this->materials.emplace_back();
     sky_material.diffuse = this->dummy_diffuse_texture;    
@@ -1358,7 +1291,7 @@ bool Scene::load_meshes(const aiScene* scene, float scale, uint32_t base_materia
     return true;
 }
 
-bool Scene::load_materials(const aiScene* scene, const std::string& base_name, lava::device_ptr device, lava::staging& staging)
+bool Scene::load_materials(const aiScene* scene, const std::string& base_name, lava::device_ptr device)
 {
     for (uint32_t index = 0; index < scene->mNumMaterials; index++)
     {
@@ -1393,7 +1326,7 @@ bool Scene::load_materials(const aiScene* scene, const std::string& base_name, l
         material_data.padding4 = glm::mat4(0);
         material_data.padding5 = glm::mat4(0);
 
-        if (!this->load_texture(material, base_name, aiTextureType_DIFFUSE, device, staging, scene_material.diffuse))
+        if (!this->load_texture(material, base_name, aiTextureType_DIFFUSE, device, scene_material.diffuse))
         {
             scene_material.diffuse = this->dummy_diffuse_texture;
 
@@ -1401,7 +1334,7 @@ bool Scene::load_materials(const aiScene* scene, const std::string& base_name, l
             material_data.opacity = glm::clamp(opacity, 0.0f, 1.0f);
         }
 
-        if (!this->load_texture(material, base_name, aiTextureType_SPECULAR, device, staging, scene_material.specular))
+        if (!this->load_texture(material, base_name, aiTextureType_SPECULAR, device, scene_material.specular))
         {
             scene_material.specular = this->dummy_specular_texture;
 
@@ -1409,7 +1342,7 @@ bool Scene::load_materials(const aiScene* scene, const std::string& base_name, l
             material_data.metallic = glm::clamp(reflectivity, 0.0f, 1.0f);
         }
 
-        if (!this->load_texture(material, base_name, aiTextureType_NORMALS, device, staging, scene_material.normal))
+        if (!this->load_texture(material, base_name, aiTextureType_NORMALS, device, scene_material.normal))
         {
             scene_material.normal = this->dummy_normal_texture;
         }
@@ -1418,7 +1351,7 @@ bool Scene::load_materials(const aiScene* scene, const std::string& base_name, l
     return true;
 }
 
-bool Scene::load_texture(const aiMaterial* material, const std::string& base_name, aiTextureType type, lava::device_ptr device, lava::staging& staging, lava::texture::ptr& texture)
+bool Scene::load_texture(const aiMaterial* material, const std::string& base_name, aiTextureType type, lava::device_ptr device, lava::texture::ptr& texture)
 {
     aiString texture_path;
 
@@ -1437,28 +1370,412 @@ bool Scene::load_texture(const aiMaterial* material, const std::string& base_nam
     }
 
     std::filesystem::path base_path = std::filesystem::path(base_name).parent_path();
-    std::string path_string = (base_path / texture_path.C_Str()).string();
-    std::replace(path_string.begin(), path_string.end(), '\\', '/');
+    std::string file_name = (base_path / texture_path.C_Str()).string();
+    std::replace(file_name.begin(), file_name.end(), '\\', '/');
+    
+    if (!this->load_texture(file_name, device, false, true, texture))
+    {
+        lava::log()->warn("Faild to load texture file '{}'. Using dummy texture instead.", file_name.c_str());
 
-    lava::log()->info("Loading texture file '{}'", path_string.c_str());
+        return false;
+    }
 
-    texture = lava::load_texture(device, path_string, VK_FORMAT_UNDEFINED);
+    return true;
+}
 
-    if (texture == nullptr)
+bool Scene::load_texture(const std::string& file_name, lava::device_ptr device, bool use_float, bool use_mipmaps, lava::texture::ptr& texture)
+{
+    for (const SceneTexture& scene_texture : this->textures)
+    {
+        if (scene_texture.file_name == file_name)
+        {
+            texture = scene_texture.texture;
+
+            return true;
+        }
+    }
+    
+    lava::log()->info("Loading texture file '{}'", file_name.c_str());
+
+    lava::file file(file_name.c_str());
+
+    if (!file.opened())
     {
-        lava::log()->warn("Faild to load texture file '{}'. Using dummy texture instead.", path_string.c_str());
+        lava::log()->error("Can't open texture: " + file_name + " !");
 
         return false;
     }
 
-    if (texture->get_size() == glm::uvec2(1, 1))
+    lava::unique_data image_data(file.get_size(), false);
+
+    if (!image_data.allocate())
     {
-        lava::log()->warn("Loaded texture '{}' has invalid dimensions. Using dummy texture instead.", path_string.c_str());
+        lava::log()->error("Can't allocate memory for texture: " + file_name + " !");
 
         return false;
     }
 
-    staging.add(texture);
+    if (lava::file_error(file.read(image_data.ptr)))
+    {
+        lava::log()->error("Can't read texture: " + file_name + " !");
+
+        return false;
+    }
+
+    VkFormat format = VK_FORMAT_UNDEFINED;
+    int32_t width = 0;
+    int32_t height = 0;
+    lava::texture::layer layer;
+
+    lava::buffer::ptr stage_buffer = lava::make_buffer();
+    uint32_t stage_levels = 0;
+
+    bool use_gli = lava::extension(file_name.c_str(), { "DDS", "KTX", "KMG" });
+    bool use_stbi = lava::extension(file_name.c_str(), { "JPG", "PNG", "TGA", "BMP", "PSD", "GIF", "HDR", "PIC" });
+
+    if (use_gli)
+    {
+        gli::texture texture = gli::load(image_data.ptr, image_data.size);
+
+        if(texture.empty())
+        {
+            lava::log()->error("Can't load texture: " + file_name + " !");
+
+            return false;
+        }
+
+        switch (texture.format())
+        {
+        case gli::FORMAT_RGBA_DXT1_UNORM_BLOCK8:
+            format = VK_FORMAT_BC1_RGBA_UNORM_BLOCK;
+            break;
+        case gli::FORMAT_RGBA_DXT5_UNORM_BLOCK16:
+            format = VK_FORMAT_BC3_UNORM_BLOCK;
+            break;
+        case gli::FORMAT_RG_ATI2N_UNORM_BLOCK16:
+            format = VK_FORMAT_BC5_UNORM_BLOCK;
+            break;
+        default:
+            lava::log()->error("Unkown texture format. Can't load texture: " + file_name + " !");
+            return false;
+        }
+
+        width = texture.extent().x;
+        height = texture.extent().y;
+        stage_levels = (use_mipmaps) ? texture.levels() : 1;
+
+        uint32_t data_size = 0;
+
+        for (uint32_t index = 0; index < stage_levels; index++)
+        {
+            lava::texture::mip_level level;
+            level.extent = texture.extent(index);
+            level.size = texture.size(index);
+
+            layer.levels.push_back(level);
+            data_size += level.size;
+        }
+
+        if (!stage_buffer->create_mapped(device, texture.data(), data_size, VK_BUFFER_USAGE_TRANSFER_SRC_BIT, VMA_MEMORY_USAGE_CPU_ONLY))
+        {
+            lava::log()->error("Can't create staging buffer for texture: " + file_name + " !");
+            
+            return false;
+        }
+    }
+
+    else if (use_stbi)
+    {
+        uint8_t* data_pointer = nullptr;
+        uint32_t data_bitdepth = 0;
+
+        if (use_float)
+        {
+            format = VK_FORMAT_R32G32B32A32_SFLOAT;
+
+            data_pointer = (uint8_t*) stbi_loadf_from_memory((const stbi_uc*) image_data.ptr, image_data.size, &width, &height, nullptr, STBI_rgb_alpha);
+            data_bitdepth = 4 * sizeof(float);
+        }
+
+        else
+        {
+            format = VK_FORMAT_R8G8B8A8_SRGB;
+
+            data_pointer = (uint8_t*) stbi_load_from_memory((const stbi_uc*) image_data.ptr, image_data.size, &width, &height, nullptr, STBI_rgb_alpha);
+            data_bitdepth = 4 * sizeof(uint8_t);
+        }
+
+        if (data_pointer == nullptr)
+        {
+            lava::log()->error("Can't load texture: " + file_name + " !");
+
+            return false;
+        }
+
+        uint32_t data_size = width * height * data_bitdepth;
+        stage_levels = 1;
+
+        if (!stage_buffer->create_mapped(device, data_pointer, data_size, VK_BUFFER_USAGE_TRANSFER_SRC_BIT, VMA_MEMORY_USAGE_CPU_ONLY))
+        {
+            lava::log()->error("Can't create staging buffer for texture: " + file_name + " !");
+            stbi_image_free(data_pointer);
+
+            return false;
+        }
+
+        stbi_image_free(data_pointer);
+
+        if (use_mipmaps)
+        {
+            uint32_t mipmap_width = width;
+            uint32_t mipmap_height = height;
+
+            while (mipmap_width > 1 || mipmap_width > 1)
+            {
+                lava::texture::mip_level level;
+                level.extent = glm::uvec2(mipmap_width, mipmap_height);
+                level.size = mipmap_width * mipmap_height * data_bitdepth;
+
+                layer.levels.push_back(level);
+                
+                mipmap_width = glm::max((uint32_t)1, mipmap_width >> 1);
+                mipmap_height = glm::max((uint32_t)1, mipmap_height >> 1);
+            }
+        }
+
+        else
+        {
+            lava::texture::mip_level level;
+            level.extent = glm::uvec2(width, height);
+            level.size = width * height * data_bitdepth;
+
+            layer.levels.push_back(level);
+        }
+    }
+
+    else
+    {
+        lava::log()->error("Unsupported texture format. Can't load texture: " + file_name + " !");
+
+        return false;
+    }
+
+    texture = lava::make_texture();
+
+    if(!texture->create(device, glm::uvec2(width, height), format, {layer}, lava::texture_type::tex_2d))
+    {
+        lava::log()->error("Can't create texture: " + file_name + " !");
+
+        return false;
+    }
+
+    SceneTexture scene_texture;
+    scene_texture.file_name = file_name;
+    scene_texture.texture = texture;
+    scene_texture.stage_levels = stage_levels;
+    scene_texture.stage_buffer = stage_buffer;
+
+    this->textures.push_back(scene_texture);
+
+    return true;
+}
+
+bool Scene::stage_buffers(lava::index frame)
+{
+    glsl::MeshData* mesh_data = (glsl::MeshData*)this->mesh_data_buffer[frame]->get_mapped_data();
+
+    for (uint32_t index = 0; index < this->meshes.size(); index++)
+    {
+        const SceneMesh& mesh = this->meshes[index];
+
+        memcpy(&mesh_data[index], &mesh.data, sizeof(glsl::MeshData));
+    }
+
+    this->mesh_data_buffer[frame]->flush();
+
+    glsl::LightData* light_data = (glsl::LightData*)this->light_data_buffer[frame]->get_mapped_data();
+
+    for (uint32_t index = 0; index < this->lights.size(); index++)
+    {
+        const SceneLight& light = this->lights[index];
+
+        memcpy(&light_data[index], &light.data, sizeof(glsl::LightData));
+    }
+
+    this->light_data_buffer[frame]->flush();
+
+    if (this->exposure_changed)
+    {
+        //NOTE: The Buffer could be still in used during writing. Ignore this issure, since the exposure is not changed frequently.
+        glsl::LightParameter* light_parameter = (glsl::LightParameter*)this->light_parameter_buffer->get_mapped_data();
+        light_parameter->exposure = this->exposure;
+        this->light_parameter_buffer->flush();
+
+        this->exposure_changed = false;
+    }
+
+    return true;
+}
+
+bool Scene::stage_textures(VkCommandBuffer command_buffer, lava::index frame)
+{
+    for (SceneTexture& scene_texture : this->textures)
+    {
+        if (scene_texture.stage_active && scene_texture.stage_frame == frame)
+        {
+            scene_texture.stage_buffer->destroy();
+            scene_texture.stage_buffer = nullptr;
+
+            scene_texture.stage_active = false;
+            scene_texture.stage_frame = 0;
+            scene_texture.stage_levels = 0;
+        }
+    }
+
+    for (SceneTexture& scene_texture : this->textures)
+    {
+        lava::texture::ptr texture = scene_texture.texture;
+        lava::buffer::ptr stage_buffer = scene_texture.stage_buffer;
+
+        if (!scene_texture.stage_active && stage_buffer != nullptr)
+        {
+            VkImageMemoryBarrier begin_barrier;
+            begin_barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
+            begin_barrier.pNext = nullptr;
+            begin_barrier.srcAccessMask = 0;
+            begin_barrier.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
+            begin_barrier.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED;
+            begin_barrier.newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
+            begin_barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
+            begin_barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
+            begin_barrier.image = texture->get_image()->get();
+            begin_barrier.subresourceRange = texture->get_image()->get_subresource_range();
+
+            vkCmdPipelineBarrier(command_buffer, VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 0, nullptr, 0, nullptr, 1, &begin_barrier);
+
+            const std::vector<lava::texture::mip_level>& levels = texture->get_layers().front().levels;
+            uint32_t stage_levels = scene_texture.stage_levels;
+            uint32_t total_levels = texture->get_image()->get_info().mipLevels;
+
+            std::vector<VkBufferImageCopy> regions;
+            uint32_t buffer_offset = 0;
+
+            for (uint32_t index = 0; index < scene_texture.stage_levels; index++)
+            {
+                VkImageSubresourceLayers subresource;
+                subresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
+                subresource.mipLevel = index;
+                subresource.baseArrayLayer = 0;
+                subresource.layerCount = 1;
+
+                VkOffset3D offset;
+                offset.x = 0;
+                offset.y = 0;
+                offset.z = 0;
+
+                VkExtent3D extent;
+                extent.width = levels[index].extent.x;
+                extent.height = levels[index].extent.y;
+                extent.depth = 1;
+
+                VkBufferImageCopy region;
+                region.bufferOffset = buffer_offset;
+                region.bufferRowLength = 0;
+                region.bufferImageHeight = 0;
+                region.imageSubresource = subresource;
+                region.imageOffset = offset;
+                region.imageExtent = extent;
+
+                regions.push_back(region);
+                buffer_offset += levels[index].size;
+            }
+
+            vkCmdCopyBufferToImage(command_buffer, stage_buffer->get(), texture->get_image()->get(), VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, regions.size(), regions.data());
+
+            for (uint32_t index = stage_levels; index < total_levels; index++)
+            {
+                glm::uvec2 src_size = levels[index - 1].extent;
+                glm::uvec2 dst_size = levels[index].extent;
+
+                VkImageMemoryBarrier src_begin_barrier;
+                src_begin_barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
+                src_begin_barrier.pNext = nullptr;
+                src_begin_barrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
+                src_begin_barrier.dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT;
+                src_begin_barrier.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
+                src_begin_barrier.newLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL;
+                src_begin_barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
+                src_begin_barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
+                src_begin_barrier.image = texture->get_image()->get();
+                src_begin_barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
+                src_begin_barrier.subresourceRange.baseMipLevel = index - 1;
+                src_begin_barrier.subresourceRange.levelCount = 1;
+                src_begin_barrier.subresourceRange.baseArrayLayer = 0;
+                src_begin_barrier.subresourceRange.layerCount = 1;
+
+                vkCmdPipelineBarrier(command_buffer, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 0, nullptr, 0, nullptr, 1, &src_begin_barrier);
+
+                VkImageBlit region;
+                region.srcSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
+                region.srcSubresource.mipLevel = index - 1;
+                region.srcSubresource.baseArrayLayer = 0;
+                region.srcSubresource.layerCount = 1;
+                region.srcOffsets[0].x = 0;
+                region.srcOffsets[0].y = 0;
+                region.srcOffsets[0].z = 0;
+                region.srcOffsets[1].x = src_size.x;
+                region.srcOffsets[1].y = src_size.y;
+                region.srcOffsets[1].z = 1;
+                region.dstSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
+                region.dstSubresource.mipLevel = index;
+                region.dstSubresource.baseArrayLayer = 0;
+                region.dstSubresource.layerCount = 1;
+                region.dstOffsets[0].x = 0;
+                region.dstOffsets[0].y = 0;
+                region.dstOffsets[0].z = 0;
+                region.dstOffsets[1].x = dst_size.x;
+                region.dstOffsets[1].y = dst_size.y;
+                region.dstOffsets[1].z = 1;
+
+                vkCmdBlitImage(command_buffer, texture->get_image()->get(), VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, texture->get_image()->get(), VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, &region, VK_FILTER_LINEAR);
+
+                VkImageMemoryBarrier src_end_barrier;
+                src_end_barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
+                src_end_barrier.pNext = nullptr;
+                src_end_barrier.srcAccessMask = VK_ACCESS_TRANSFER_READ_BIT;
+                src_end_barrier.dstAccessMask = 0;
+                src_end_barrier.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL;
+                src_end_barrier.newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
+                src_end_barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
+                src_end_barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
+                src_end_barrier.image = texture->get_image()->get();
+                src_end_barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
+                src_end_barrier.subresourceRange.baseMipLevel = index - 1;
+                src_end_barrier.subresourceRange.levelCount = 1;
+                src_end_barrier.subresourceRange.baseArrayLayer = 0;
+                src_end_barrier.subresourceRange.layerCount = 1;
+
+                vkCmdPipelineBarrier(command_buffer, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, 0, 0, nullptr, 0, nullptr, 1, &src_end_barrier);
+            }
+
+            VkImageMemoryBarrier end_barrier;
+            end_barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
+            end_barrier.pNext = nullptr;
+            end_barrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
+            end_barrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT;
+            end_barrier.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
+            end_barrier.newLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
+            end_barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
+            end_barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
+            end_barrier.image = texture->get_image()->get();
+            end_barrier.subresourceRange = texture->get_image()->get_subresource_range();
+
+            vkCmdPipelineBarrier(command_buffer, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, 0, 0, nullptr, 0, nullptr, 1, &end_barrier);
+
+            scene_texture.stage_active = true;
+            scene_texture.stage_frame = frame;
+        }
+    }
 
     return true;
 }
diff --git a/src/scene.hpp b/src/scene.hpp
index a53d398aab611caa1c9b0145f14754afeb304310..591b0d261a98b96a5434fc05682c917b87821faa 100644
--- a/src/scene.hpp
+++ b/src/scene.hpp
@@ -108,6 +108,17 @@ struct SceneMaterial
     VkDescriptorSet descriptor_set = VK_NULL_HANDLE;
 };
 
+struct SceneTexture
+{
+    std::string file_name;
+    lava::texture::ptr texture;
+
+    bool stage_active = false;
+    uint32_t stage_frame = 0;
+    uint32_t stage_levels = 0;
+    lava::buffer::ptr stage_buffer;
+};
+
 struct SceneAnimationChannel
 {
     SceneNodeIndex node_index = INVALID_NODE;
@@ -137,7 +148,7 @@ public:
 
     bool interface();
     bool update(lava::delta delta_time, Headset::Ptr headset);
-    bool write(lava::index frame);
+    bool stage(VkCommandBuffer command_buffer, lava::index frame);
 
     void reset_animation();
     void set_animation_active(bool active);
@@ -163,6 +174,7 @@ public:
     const std::vector<SceneMesh>& get_meshes() const;
     const std::vector<SceneLight>& get_lights() const;
     const std::vector<SceneMaterial>& get_materials() const;
+    const std::vector<SceneTexture>& get_textures() const;
     const std::vector<SceneAnimation>& get_animations() const;
 
     const glm::vec3& get_scene_min() const;
@@ -180,17 +192,21 @@ private:
     bool create_light_buffer(lava::device_ptr device, uint32_t frame_count);
     bool create_mesh_buffer(lava::device_ptr device, uint32_t frame_count);
 
-    bool load_scene(const std::string& file_name, SceneUnit unit, SceneOrientation orientation, lava::device_ptr device, lava::staging& staging, std::map<std::string, SceneNodeIndex>& node_indices);
-    bool load_controller(const std::string& file_name, SceneUnit unit, SceneOrientation orientation, lava::device_ptr device, lava::staging& staging, SceneNodeIndex& node_index, std::map<std::string, SceneNodeIndex>& node_indices);
-    bool load_sky_sphere(const std::string& file_name, uint32_t ring_count, uint32_t segment_count, lava::device_ptr device, lava::staging& staging);
+    bool load_scene(const std::string& file_name, SceneUnit unit, SceneOrientation orientation, lava::device_ptr device, std::map<std::string, SceneNodeIndex>& node_indices);
+    bool load_controller(const std::string& file_name, SceneUnit unit, SceneOrientation orientation, lava::device_ptr device, SceneNodeIndex& node_index, std::map<std::string, SceneNodeIndex>& node_indices);
+    bool load_sky_sphere(const std::string& file_name, uint32_t ring_count, uint32_t segment_count, lava::device_ptr device);
 
     bool load_nodes(const aiNode* node, float scale, uint32_t base_mesh, SceneNodeIndex parent_index, std::map<std::string, SceneNodeIndex>& node_indices);
     bool load_cameras(const aiScene* scene, float scale, const std::map<std::string, SceneNodeIndex>& node_indices);
     bool load_animations(const aiScene* scene, float scale, const std::map<std::string, SceneNodeIndex>& node_indices);
     bool load_lights(const aiScene* scene, float scale, const std::map<std::string, SceneNodeIndex>& node_indices);
     bool load_meshes(const aiScene* scene, float scale, uint32_t base_material, bool scene_bounds, lava::device_ptr device);
-    bool load_materials(const aiScene* scene, const std::string& base_name, lava::device_ptr device, lava::staging& staging);
-    bool load_texture(const aiMaterial* material, const std::string& base_name, aiTextureType type, lava::device_ptr device, lava::staging& staging, lava::texture::ptr& texture);
+    bool load_materials(const aiScene* scene, const std::string& base_name, lava::device_ptr device);
+    bool load_texture(const aiMaterial* material, const std::string& base_name, aiTextureType type, lava::device_ptr device, lava::texture::ptr& texture);
+    bool load_texture(const std::string& file_name, lava::device_ptr device, bool use_float, bool use_mipmaps, lava::texture::ptr& texture);
+
+    bool stage_buffers(lava::index frame);
+    bool stage_textures(VkCommandBuffer command_buffer, lava::index frame);
 
     void compute_scene_bounds();
     void compute_mesh_bounds(SceneMesh& mesh, const glm::mat4& local_to_world);
@@ -221,7 +237,7 @@ private:
     glm::vec3 scene_min = glm::vec3(0.0f);
     glm::vec3 scene_max = glm::vec3(0.0f);
 
-    const glm::vec3 ambient_color = glm::vec3(0.0f); //NOTE: Indirect should be compute using an indirect cache.
+    const glm::vec3 ambient_color = glm::vec3(0.0f); //NOTE: Indirect should be computed using an indirect cache.
     float exposure = 1.0f;
     bool exposure_changed = false;
 
@@ -234,6 +250,7 @@ private:
     std::vector<SceneMesh> meshes;
     std::vector<SceneLight> lights;
     std::vector<SceneMaterial> materials;
+    std::vector<SceneTexture> textures;
     std::vector<SceneAnimation> animations;
 
     std::vector<lava::buffer::ptr> mesh_data_buffer;
diff --git a/src/vr_application.cpp b/src/vr_application.cpp
index c1e56d438310a074c508522cc6a32c69af7ef0d0..d01d36c9858ccc0cdb9bdf5811da0144a0847da8 100644
--- a/src/vr_application.cpp
+++ b/src/vr_application.cpp
@@ -63,6 +63,7 @@ bool VRApplication::setup(lava::name name, argh::parser cmd_line)
             }
         }
 
+        parameters.features.samplerAnisotropy = true;
         parameters.features.fillModeNonSolid = true;
         parameters.features.multiViewport = true;
         parameters.features.geometryShader = true;
@@ -673,11 +674,17 @@ bool VRApplication::on_update(lava::delta delta_time)
 bool VRApplication::on_render(VkCommandBuffer command_buffer, lava::index frame)
 {
     this->frame_capture->next_frame(frame);
-    this->pass_timer->next_frame(command_buffer, frame, app->device);
+    this->pass_timer->next_frame(command_buffer, frame, this->app->device);
 
-    this->scene->write(frame);
     this->stereo_transform->write(frame);
 
+    if(!this->scene->stage(command_buffer, frame))
+    {
+        lava::log()->error("Error during scene stage!");
+
+        return false;
+    }
+
     if (!this->cache_filled)
     {
         this->shadow_cache->compute_shadow(command_buffer, frame);