diff --git a/CMakeLists.txt b/CMakeLists.txt
index bc2b9c25edaac17a99e238496743d50fdd3aa684..a32145ae5595b340090f99acc54a87be81802d83 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -191,7 +191,25 @@ add_library(lava.resource STATIC
         ${LIBLAVA_DIR}/resource/texture.hpp
         )
 
-target_include_directories(lava.resource PUBLIC
+target_link_libraries(lava.resource
+        lava::base
+        )
+
+set_property(TARGET lava.resource PROPERTY EXPORT_NAME resource)
+add_library(lava::resource ALIAS lava.resource)
+
+message(">> lava::asset")
+
+add_library(lava.asset STATIC
+        ${LIBLAVA_DIR}/asset/mesh_loader.cpp
+        ${LIBLAVA_DIR}/asset/mesh_loader.hpp
+        ${LIBLAVA_DIR}/asset/scope_image.cpp
+        ${LIBLAVA_DIR}/asset/scope_image.hpp
+        ${LIBLAVA_DIR}/asset/texture_loader.cpp
+        ${LIBLAVA_DIR}/asset/texture_loader.hpp
+        )
+
+target_include_directories(lava.asset PUBLIC
         $<BUILD_INTERFACE:${LIBLAVA_EXT_DIR}/stb>
         $<BUILD_INTERFACE:${LIBLAVA_EXT_DIR}/gli>
         $<BUILD_INTERFACE:${LIBLAVA_EXT_DIR}/tinyobjloader>
@@ -209,13 +227,13 @@ add_subdirectory(${LIBLAVA_EXT_DIR}/selene selene EXCLUDE_FROM_ALL)
 
 message("<<< selene")
 
-target_link_libraries(lava.resource
-        lava::base
+target_link_libraries(lava.asset
         lava::file
+        lava::resource
         )
 
-set_property(TARGET lava.resource PROPERTY EXPORT_NAME resource)
-add_library(lava::resource ALIAS lava.resource)
+set_property(TARGET lava.asset PROPERTY EXPORT_NAME asset)
+add_library(lava::asset ALIAS lava.asset)
 
 message(">> lava::frame")
 
@@ -251,7 +269,8 @@ add_subdirectory(${LIBLAVA_EXT_DIR}/glfw glfw EXCLUDE_FROM_ALL)
 message("<<< glfw")
 
 target_link_libraries(lava.frame
-        lava::resource
+        lava::base
+        lava::file
         glfw
         ${GLFW_LIBRARIES}
         )
@@ -305,6 +324,7 @@ target_include_directories(lava.app PUBLIC
         )
 
 target_link_libraries(lava.app
+        lava::asset
         lava::block
         lava::frame
         )
@@ -336,6 +356,7 @@ install(TARGETS
         lava.file
         lava.base
         lava.resource
+        lava.asset
         lava.frame
         lava.block
         lava.app
diff --git a/DOCS.md b/DOCS.md
index 67dc6cffdf6f3a0919d478e7d5e6f447de58a695..9dc7960207a5860063ef4ff3e60fd2c79bde0422 100644
--- a/DOCS.md
+++ b/DOCS.md
@@ -335,13 +335,17 @@ int main(int argc, char* argv[]) {
 
 [![app](https://img.shields.io/badge/lava-app-brightgreen.svg)](https://github.com/liblava/liblava/tree/master/liblava/app/app.hpp) [![camera](https://img.shields.io/badge/lava-camera-brightgreen.svg)](https://github.com/liblava/liblava/tree/master/liblava/app/camera.hpp) [![forward_shading](https://img.shields.io/badge/lava-forward_shading-brightgreen.svg)](https://github.com/liblava/liblava/tree/master/liblava/app/forward_shading.hpp) [![gui](https://img.shields.io/badge/lava-gui-brightgreen.svg)](https://github.com/liblava/liblava/tree/master/liblava/app/gui.hpp)
 
+#### lava [block](https://github.com/liblava/liblava/tree/master/liblava/block)
+
+[![attachment](https://img.shields.io/badge/lava-attachment-red.svg)](https://github.com/liblava/liblava/tree/master/liblava/block/attachment.hpp) [![block](https://img.shields.io/badge/lava-block-red.svg)](https://github.com/liblava/liblava/tree/master/liblava/block/block.hpp) [![descriptor](https://img.shields.io/badge/lava-descriptor-red.svg)](https://github.com/liblava/liblava/tree/master/liblava/block/descriptor.hpp) [![pipeline](https://img.shields.io/badge/lava-pipeline-red.svg)](https://github.com/liblava/liblava/tree/master/liblava/block/pipeline.hpp) [![render_pass](https://img.shields.io/badge/lava-render_pass-red.svg)](https://github.com/liblava/liblava/tree/master/liblava/block/render_pass.hpp) [![subpass](https://img.shields.io/badge/lava-subpass-red.svg)](https://github.com/liblava/liblava/tree/master/liblava/block/subpass.hpp)
+
 #### lava [frame](https://github.com/liblava/liblava/tree/master/liblava/frame)
 
 [![frame](https://img.shields.io/badge/lava-frame-red.svg)](https://github.com/liblava/liblava/tree/master/liblava/frame/frame.hpp) [![input](https://img.shields.io/badge/lava-input-red.svg)](https://github.com/liblava/liblava/tree/master/liblava/frame/input.hpp) [![render_target](https://img.shields.io/badge/lava-render_target-red.svg)](https://github.com/liblava/liblava/tree/master/liblava/frame/render_target.hpp) [![renderer](https://img.shields.io/badge/lava-renderer-red.svg)](https://github.com/liblava/liblava/tree/master/liblava/frame/renderer.hpp) [![swapchain](https://img.shields.io/badge/lava-swapchain-red.svg)](https://github.com/liblava/liblava/tree/master/liblava/frame/swapchain.hpp) [![window](https://img.shields.io/badge/lava-window-red.svg)](https://github.com/liblava/liblava/tree/master/liblava/frame/window.hpp)
 
-#### lava [block](https://github.com/liblava/liblava/tree/master/liblava/block) 
+#### lava [asset](https://github.com/liblava/liblava/tree/master/liblava/asset)
 
-[![attachment](https://img.shields.io/badge/lava-attachment-red.svg)](https://github.com/liblava/liblava/tree/master/liblava/block/attachment.hpp) [![block](https://img.shields.io/badge/lava-block-red.svg)](https://github.com/liblava/liblava/tree/master/liblava/block/block.hpp) [![descriptor](https://img.shields.io/badge/lava-descriptor-red.svg)](https://github.com/liblava/liblava/tree/master/liblava/block/descriptor.hpp) [![pipeline](https://img.shields.io/badge/lava-pipeline-red.svg)](https://github.com/liblava/liblava/tree/master/liblava/block/pipeline.hpp) [![render_pass](https://img.shields.io/badge/lava-render_pass-red.svg)](https://github.com/liblava/liblava/tree/master/liblava/block/render_pass.hpp) [![subpass](https://img.shields.io/badge/lava-subpass-red.svg)](https://github.com/liblava/liblava/tree/master/liblava/block/subpass.hpp) 
+[![mesh_loader](https://img.shields.io/badge/lava-mesh_loader-yellow.svg)](https://github.com/liblava/liblava/tree/master/liblava/asset/mesh_loader.hpp) [![scope_image](https://img.shields.io/badge/lava-scope_image-yellow.svg)](https://github.com/liblava/liblava/tree/master/liblava/asset/scope_image.hpp) [![texture_loader](https://img.shields.io/badge/lava-texture_loader-yellow.svg)](https://github.com/liblava/liblava/tree/master/liblava/asset/texture_loader.hpp)
 
 #### lava [resource](https://github.com/liblava/liblava/tree/master/liblava/resource)
 
diff --git a/README.md b/README.md
index e4c11a96e2f147c0958ab8a1a224f71e46632c1b..7805c8b03343fbb26863c1c0c68f34e4272bd692 100644
--- a/README.md
+++ b/README.md
@@ -30,9 +30,9 @@
 
 <br />
 
-##### [Modules](DOCS.md/#modules)
+#### [Modules](DOCS.md/#modules)
 
-[![core](https://img.shields.io/badge/lava-core-blue.svg)](https://github.com/liblava/liblava/tree/master/liblava/core) [![util](https://img.shields.io/badge/lava-util-blue.svg)](https://github.com/liblava/liblava/tree/master/liblava/util) [![file](https://img.shields.io/badge/lava-file-blue.svg)](https://github.com/liblava/liblava/tree/master/liblava/file) [![base](https://img.shields.io/badge/lava-base-yellow.svg)](https://github.com/liblava/liblava/tree/master/liblava/base) [![resource](https://img.shields.io/badge/lava-resource-yellow.svg)](https://github.com/liblava/liblava/tree/master/liblava/resource) [![frame](https://img.shields.io/badge/lava-frame-red.svg)](https://github.com/liblava/liblava/tree/master/liblava/frame) [![block](https://img.shields.io/badge/lava-block-red.svg)](https://github.com/liblava/liblava/tree/master/liblava/block) [![app](https://img.shields.io/badge/lava-app-brightgreen.svg)](https://github.com/liblava/liblava/tree/master/liblava/app) [![engine](https://img.shields.io/badge/lava-engine-brightgreen.svg)](https://git.io/liblava-engine)
+[![core](https://img.shields.io/badge/lava-core-blue.svg)](https://github.com/liblava/liblava/tree/master/liblava/core) [![util](https://img.shields.io/badge/lava-util-blue.svg)](https://github.com/liblava/liblava/tree/master/liblava/util) [![file](https://img.shields.io/badge/lava-file-blue.svg)](https://github.com/liblava/liblava/tree/master/liblava/file) [![base](https://img.shields.io/badge/lava-base-yellow.svg)](https://github.com/liblava/liblava/tree/master/liblava/base) [![resource](https://img.shields.io/badge/lava-resource-yellow.svg)](https://github.com/liblava/liblava/tree/master/liblava/resource) [![asset](https://img.shields.io/badge/lava-asset-yellow.svg)](https://github.com/liblava/liblava/tree/master/liblava/asset) [![frame](https://img.shields.io/badge/lava-frame-red.svg)](https://github.com/liblava/liblava/tree/master/liblava/frame) [![block](https://img.shields.io/badge/lava-block-red.svg)](https://github.com/liblava/liblava/tree/master/liblava/block) [![app](https://img.shields.io/badge/lava-app-brightgreen.svg)](https://github.com/liblava/liblava/tree/master/liblava/app) [![engine](https://img.shields.io/badge/lava-engine-brightgreen.svg)](https://git.io/liblava-engine)
 
 <br />
 
diff --git a/liblava/app/app.cpp b/liblava/app/app.cpp
index eaad5d5224be66e76400b3a3d6f6a21730a82c24..e81aa23593df5a29489efc3c6a79ea95aec6b93e 100644
--- a/liblava/app/app.cpp
+++ b/liblava/app/app.cpp
@@ -5,6 +5,7 @@
 #include <imgui.h>
 #include <liblava/app/app.hpp>
 #include <liblava/app/def.hpp>
+#include <liblava/asset/scope_image.hpp>
 #include <liblava/base/debug_utils.hpp>
 
 namespace lava {
diff --git a/liblava/asset.hpp b/liblava/asset.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..4de8dbe3c86914e021e9fc177a8bad77e80236bb
--- /dev/null
+++ b/liblava/asset.hpp
@@ -0,0 +1,9 @@
+// file      : liblava/asset.hpp
+// copyright : Copyright (c) 2018-present, Lava Block OÜ
+// license   : MIT; see accompanying LICENSE file
+
+#pragma once
+
+#include <liblava/asset/mesh_loader.hpp>
+#include <liblava/asset/scope_image.hpp>
+#include <liblava/asset/texture_loader.hpp>
diff --git a/liblava/asset/mesh_loader.cpp b/liblava/asset/mesh_loader.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..aeecfe0c808d2a72bd32310d6acf21d722af9e89
--- /dev/null
+++ b/liblava/asset/mesh_loader.cpp
@@ -0,0 +1,102 @@
+// file      : liblava/asset/mesh_loader.cpp
+// copyright : Copyright (c) 2018-present, Lava Block OÜ
+// license   : MIT; see accompanying LICENSE file
+
+#include <liblava/asset/mesh_loader.hpp>
+#include <liblava/file.hpp>
+
+#ifndef LIBLAVA_TINYOBJLOADER
+#    define LIBLAVA_TINYOBJLOADER 1
+#endif
+
+#if LIBLAVA_TINYOBJLOADER
+
+#    ifdef _WIN32
+#        pragma warning(push, 4)
+#    else
+#        pragma GCC diagnostic push
+#        pragma GCC diagnostic ignored "-Wmaybe-uninitialized"
+#    endif
+
+#    define TINYOBJLOADER_IMPLEMENTATION
+#    include <tiny_obj_loader.h>
+
+#    ifdef _WIN32
+#        pragma warning(pop)
+#    else
+#        pragma GCC diagnostic pop
+#    endif
+
+#endif
+
+lava::mesh::ptr lava::load_mesh(device_ptr device, name filename) {
+#if LIBLAVA_TINYOBJLOADER
+    if (extension(filename, "OBJ")) {
+        tinyobj::attrib_t attrib;
+        std::vector<tinyobj::shape_t> shapes;
+        std::vector<tinyobj::material_t> materials;
+        std::string err;
+        std::string warn;
+
+        string target_file = filename;
+
+        file_remover temp_file_remover;
+        {
+            file file(filename);
+            if (file.opened() && file.get_type() == file_type::fs) {
+                string temp_file;
+                temp_file = file_system::get_pref_dir();
+                temp_file += get_filename_from(target_file, true);
+
+                scope_data temp_data(file.get_size());
+                if (!temp_data.ptr)
+                    return nullptr;
+
+                if (file_error(file.read(temp_data.ptr)))
+                    return nullptr;
+
+                if (!write_file(str(temp_file), temp_data.ptr, temp_data.size))
+                    return nullptr;
+
+                target_file = temp_file;
+
+                temp_file_remover.filename = target_file;
+            }
+        }
+
+        if (tinyobj::LoadObj(&attrib, &shapes, &materials, &warn, &err, str(target_file))) {
+            auto mesh = make_mesh();
+
+            for (auto const& shape : shapes) {
+                for (auto const& index : shape.mesh.indices) {
+                    vertex vertex;
+
+                    vertex.position = v3(attrib.vertices[3 * index.vertex_index],
+                                         attrib.vertices[3 * index.vertex_index + 1],
+                                         attrib.vertices[3 * index.vertex_index + 2]);
+
+                    vertex.color = v4(1.f);
+
+                    if (!attrib.texcoords.empty())
+                        vertex.uv = v2(attrib.texcoords[2 * index.texcoord_index], 1.f - attrib.texcoords[2 * index.texcoord_index + 1]);
+
+                    vertex.normal = attrib.normals.empty() ? v3(0.f) : v3(attrib.normals[3 * index.normal_index], attrib.normals[3 * index.normal_index + 1], attrib.normals[3 * index.normal_index + 2]);
+
+                    mesh->get_vertices().push_back(vertex);
+                    mesh->get_indices().push_back(mesh->get_indices_count());
+                }
+            }
+
+            if (mesh->empty())
+                return nullptr;
+
+            if (!mesh->create(device))
+                return nullptr;
+
+            return mesh;
+        }
+    }
+#endif
+
+    return nullptr;
+}
diff --git a/liblava/asset/mesh_loader.hpp b/liblava/asset/mesh_loader.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..7364ea1724fbb3e9d9dee7a5a25d7168bab1a4fd
--- /dev/null
+++ b/liblava/asset/mesh_loader.hpp
@@ -0,0 +1,13 @@
+// file      : liblava/asset/mesh_loader.hpp
+// copyright : Copyright (c) 2018-present, Lava Block OÜ
+// license   : MIT; see accompanying LICENSE file
+
+#pragma once
+
+#include <liblava/resource/mesh.hpp>
+
+namespace lava {
+
+    mesh::ptr load_mesh(device_ptr device, name filename);
+
+} // namespace lava
diff --git a/liblava/asset/scope_image.cpp b/liblava/asset/scope_image.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..2d491b7f7627f3681b13cc089390f2b2bcf37e51
--- /dev/null
+++ b/liblava/asset/scope_image.cpp
@@ -0,0 +1,41 @@
+// file      : liblava/asset/scope_image.cpp
+// copyright : Copyright (c) 2018-present, Lava Block OÜ
+// license   : MIT; see accompanying LICENSE file
+
+#include <stb_image.h>
+#include <liblava/asset/scope_image.hpp>
+
+namespace lava {
+
+    scope_image::scope_image(string_ref filename)
+    : image_file(str(filename)), file_data(image_file.get_size(), false) {
+        if (image_file.opened()) {
+            if (!file_data.allocate())
+                return;
+
+            if (file_error(image_file.read(file_data.ptr)))
+                return;
+        }
+
+        i32 tex_width, tex_height, tex_channels = 0;
+
+        if (image_file.opened())
+            data = as_ptr(stbi_load_from_memory((stbi_uc const*) file_data.ptr, to_i32(file_data.size),
+                                                &tex_width, &tex_height, &tex_channels, STBI_rgb_alpha));
+        else
+            data = as_ptr(stbi_load(str(filename), &tex_width, &tex_height, &tex_channels, STBI_rgb_alpha));
+
+        if (!data)
+            return;
+
+        size = { tex_width, tex_height };
+        channels = tex_channels;
+        ready = true;
+    }
+
+    scope_image::~scope_image() {
+        if (data)
+            stbi_image_free(data);
+    }
+
+} // namespace lava
diff --git a/liblava/asset/scope_image.hpp b/liblava/asset/scope_image.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..178f0117a1b16e723b5048eef3f4d9a22a9ebc54
--- /dev/null
+++ b/liblava/asset/scope_image.hpp
@@ -0,0 +1,27 @@
+// file      : liblava/asset/scope_image.hpp
+// copyright : Copyright (c) 2018-present, Lava Block OÜ
+// license   : MIT; see accompanying LICENSE file
+
+#pragma once
+
+#include <liblava/core/math.hpp>
+#include <liblava/file/file.hpp>
+
+namespace lava {
+
+    struct scope_image {
+        explicit scope_image(string_ref filename);
+        ~scope_image();
+
+        bool ready = false;
+
+        data_ptr data = nullptr;
+        uv2 size = uv2(0, 0);
+        ui32 channels = 0;
+
+    private:
+        file image_file;
+        scope_data file_data;
+    };
+
+} // namespace lava
diff --git a/liblava/asset/texture_loader.cpp b/liblava/asset/texture_loader.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..c6bf757fed3d1d7419ea4c3fd7f62782929a7221
--- /dev/null
+++ b/liblava/asset/texture_loader.cpp
@@ -0,0 +1,240 @@
+// file      : liblava/asset/texture_loader.cpp
+// copyright : Copyright (c) 2018-present, Lava Block OÜ
+// license   : MIT; see accompanying LICENSE file
+
+#include <bitmap_image.hpp>
+#include <liblava/asset/texture_loader.hpp>
+#include <liblava/file.hpp>
+#include <selene/img/pixel/PixelTypeAliases.hpp>
+#include <selene/img_ops/ImageConversions.hpp>
+
+#ifdef _WIN32
+#    pragma warning(push, 4)
+#    pragma warning(disable : 4458)
+#    pragma warning(disable : 4100)
+#    pragma warning(disable : 5054)
+#    pragma warning(disable : 4244)
+#else
+#    pragma GCC diagnostic push
+#    pragma GCC diagnostic ignored "-Wignored-qualifiers"
+#    pragma GCC diagnostic ignored "-Wunused-variable"
+#    pragma GCC diagnostic ignored "-Wtype-limits"
+#    pragma GCC diagnostic ignored "-Wempty-body"
+#    pragma GCC diagnostic ignored "-Wunused-result"
+#endif
+
+#include <gli/gli.hpp>
+
+#ifdef _WIN32
+#    pragma warning(pop)
+#else
+#    pragma GCC diagnostic pop
+#endif
+
+#define STB_IMAGE_IMPLEMENTATION
+#include <stb_image.h>
+
+namespace lava {
+
+    texture::ptr create_gli_texture_2d(device_ptr device, file const& file, VkFormat format, scope_data const& temp_data) {
+        gli::texture2d tex(file.opened() ? gli::load(temp_data.ptr, temp_data.size)
+                                         : gli::load(file.get_path()));
+        assert(!tex.empty());
+        if (tex.empty())
+            return nullptr;
+
+        auto mip_levels = to_ui32(tex.levels());
+
+        texture::layer layer;
+
+        for (auto m = 0u; m < mip_levels; ++m) {
+            texture::mip_level level;
+            level.extent = { tex[m].extent().x, tex[m].extent().y };
+            level.size = to_ui32(tex[m].size());
+
+            layer.levels.push_back(level);
+        }
+
+        texture::layer::list layers;
+        layers.push_back(layer);
+
+        auto texture = make_texture();
+
+        uv2 size = { tex[0].extent().x, tex[0].extent().y };
+        if (!texture->create(device, size, format, layers, texture_type::tex_2d))
+            return nullptr;
+
+        if (!texture->upload(tex.data(), tex.size()))
+            return nullptr;
+
+        return texture;
+    }
+
+    template<typename T>
+    texture::layer::list create_layer_list(T const& tex, ui32 layer_count) {
+        texture::layer::list layers;
+
+        auto mip_levels = to_ui32(tex.levels());
+
+        for (auto i = 0u; i < layer_count; ++i) {
+            texture::layer layer;
+
+            for (auto m = 0u; m < mip_levels; ++m) {
+                texture::mip_level level;
+                level.extent = { tex[i][m].extent().x, tex[i][m].extent().y };
+                level.size = to_ui32(tex[i][m].size());
+
+                layer.levels.push_back(level);
+            }
+
+            layers.push_back(layer);
+        }
+
+        return layers;
+    }
+
+    texture::ptr create_gli_texture_array(device_ptr device, file const& file, VkFormat format, scope_data const& temp_data) {
+        gli::texture2d_array tex(file.opened() ? gli::load(temp_data.ptr, temp_data.size)
+                                               : gli::load(file.get_path()));
+        assert(!tex.empty());
+        if (tex.empty())
+            return nullptr;
+
+        auto layers = create_layer_list(tex, to_ui32(tex.layers()));
+
+        auto texture = make_texture();
+
+        uv2 size = { tex[0].extent().x, tex[0].extent().y };
+        if (!texture->create(device, size, format, layers, texture_type::array))
+            return nullptr;
+
+        if (!texture->upload(tex.data(), tex.size()))
+            return nullptr;
+
+        return texture;
+    }
+
+    texture::ptr create_gli_texture_cube_map(device_ptr device, file const& file, VkFormat format, scope_data const& temp_data) {
+        gli::texture_cube tex(file.opened() ? gli::load(temp_data.ptr, temp_data.size)
+                                            : gli::load(file.get_path()));
+        assert(!tex.empty());
+        if (tex.empty())
+            return nullptr;
+
+        auto layers = create_layer_list(tex, to_ui32(tex.faces()));
+
+        auto texture = make_texture();
+
+        uv2 size = { tex[0].extent().x, tex[0].extent().y };
+        if (!texture->create(device, size, format, layers, texture_type::cube_map))
+            return nullptr;
+
+        if (!texture->upload(tex.data(), tex.size()))
+            return nullptr;
+
+        return texture;
+    }
+
+    texture::ptr create_stbi_texture(device_ptr device, file const& file, scope_data const& temp_data) {
+        i32 tex_width = 0, tex_height = 0;
+        stbi_uc* data = nullptr;
+
+        if (file.opened())
+            data = stbi_load_from_memory((stbi_uc const*) temp_data.ptr, to_i32(temp_data.size),
+                                         &tex_width, &tex_height, nullptr, STBI_rgb_alpha);
+        else
+            data = stbi_load(str(file.get_path()), &tex_width, &tex_height, nullptr, STBI_rgb_alpha);
+
+        if (!data)
+            return nullptr;
+
+        auto texture = make_texture();
+
+        uv2 size = { tex_width, tex_height };
+        if (!texture->create(device, size, VK_FORMAT_R8G8B8A8_UNORM))
+            return nullptr;
+
+        const i32 tex_channels = 4;
+        auto uploadSize = tex_width * tex_height * tex_channels * sizeof(char);
+        auto result = texture->upload(data, uploadSize);
+
+        stbi_image_free(data);
+
+        if (!result)
+            return nullptr;
+
+        return texture;
+    }
+
+} // namespace lava
+
+lava::texture::ptr lava::load_texture(device_ptr device, file_format filename, texture_type type) {
+    auto supported = (filename.format == VK_FORMAT_R8G8B8A8_UNORM) || (device->get_features().textureCompressionBC && (filename.format == VK_FORMAT_BC3_UNORM_BLOCK)) || (device->get_features().textureCompressionASTC_LDR && (filename.format == VK_FORMAT_ASTC_8x8_UNORM_BLOCK)) || (device->get_features().textureCompressionETC2 && (filename.format == VK_FORMAT_ETC2_R8G8B8A8_UNORM_BLOCK));
+    if (!supported)
+        return nullptr;
+
+    auto use_gli = extension(str(filename.path), { "DDS", "KTX", "KMG" });
+    auto use_stbi = false;
+
+    if (!use_gli)
+        use_stbi = extension(str(filename.path), { "JPG", "PNG", "TGA", "BMP", "PSD", "GIF", "HDR", "PIC" });
+
+    if (!use_gli && !use_stbi)
+        return nullptr;
+
+    file file(str(filename.path));
+    scope_data temp_data(file.get_size(), false);
+
+    if (file.opened()) {
+        if (!temp_data.allocate())
+            return nullptr;
+
+        if (file_error(file.read(temp_data.ptr)))
+            return nullptr;
+    }
+
+    if (use_gli) {
+        texture::layer::list layers;
+
+        switch (type) {
+        case texture_type::tex_2d: {
+            return create_gli_texture_2d(device, file, filename.format, temp_data);
+        }
+
+        case texture_type::array: {
+            return create_gli_texture_array(device, file, filename.format, temp_data);
+        }
+
+        case texture_type::cube_map: {
+            return create_gli_texture_cube_map(device, file, filename.format, temp_data);
+        }
+        }
+    } else {
+        return create_stbi_texture(device, file, temp_data);
+    }
+
+    return nullptr;
+}
+
+lava::texture::ptr lava::create_default_texture(device_ptr device, uv2 size) {
+    auto result = make_texture();
+
+    if (!result->create(device, size, VK_FORMAT_R8G8B8A8_UNORM))
+        return nullptr;
+
+    bitmap_image image(size.x, size.y);
+    checkered_pattern(64, 64, 255, 255, 255, image);
+
+    image.bgr_to_rgb();
+
+    sln::TypedLayout typed_layout((sln::PixelLength) size.x, (sln::PixelLength) size.y, (sln::Stride)(image.width() * image.bytes_per_pixel()));
+
+    sln::ImageView<sln::PixelRGB_8u, sln::ImageModifiability::Mutable> img_rgb((uint8_t*) image.data(), typed_layout);
+
+    sln::Image<sln::PixelRGBA_8u> const img_rgba = sln::convert_image<sln::PixelFormat::RGBA>(img_rgb, std::uint8_t{ 192 });
+
+    if (!result->upload(img_rgba.data(), img_rgba.total_bytes()))
+        return nullptr;
+
+    return result;
+}
diff --git a/liblava/asset/texture_loader.hpp b/liblava/asset/texture_loader.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..b9e3b575688447e71e55933053173edd7370d5b6
--- /dev/null
+++ b/liblava/asset/texture_loader.hpp
@@ -0,0 +1,20 @@
+// file      : liblava/asset/texture_loader.hpp
+// copyright : Copyright (c) 2018-present, Lava Block OÜ
+// license   : MIT; see accompanying LICENSE file
+
+#pragma once
+
+#include <liblava/resource/texture.hpp>
+
+namespace lava {
+
+    texture::ptr load_texture(device_ptr device, file_format filename, texture_type type = texture_type::tex_2d);
+
+    inline texture::ptr load_texture(device_ptr device, string_ref filename,
+                                     VkFormat format = VK_FORMAT_R8G8B8A8_UNORM, texture_type type = texture_type::tex_2d) {
+        return load_texture(device, { filename, format }, type);
+    }
+
+    texture::ptr create_default_texture(device_ptr device, uv2 size = { 512, 512 });
+
+} // namespace lava
diff --git a/liblava/lava.hpp b/liblava/lava.hpp
index 779458a57ae0ca0d4481d98846f3a18b7010cb0c..70a86a1111751fc86c90e393aa60becf063bd08d 100644
--- a/liblava/lava.hpp
+++ b/liblava/lava.hpp
@@ -5,6 +5,7 @@
 #pragma once
 
 #include <liblava/app.hpp>
+#include <liblava/asset.hpp>
 #include <liblava/base.hpp>
 #include <liblava/block.hpp>
 #include <liblava/core.hpp>
diff --git a/liblava/resource/buffer.hpp b/liblava/resource/buffer.hpp
index e8310b509b6bb6c26ed58d636360c2365e381073..67e58e7015143d08ceb1787b5c8d8adba19aca78 100644
--- a/liblava/resource/buffer.hpp
+++ b/liblava/resource/buffer.hpp
@@ -5,7 +5,6 @@
 #pragma once
 
 #include <liblava/base/device.hpp>
-#include <liblava/base/memory.hpp>
 
 namespace lava {
 
diff --git a/liblava/resource/format.hpp b/liblava/resource/format.hpp
index 896f232de3b0ebddbf3cb1f008b5a5a92f46556e..f3e5c517c18e6444847f9c6e04dd317b0860ac20 100644
--- a/liblava/resource/format.hpp
+++ b/liblava/resource/format.hpp
@@ -5,7 +5,6 @@
 #pragma once
 
 #include <liblava/base/device.hpp>
-#include <liblava/core/data.hpp>
 
 namespace lava {
 
diff --git a/liblava/resource/image.hpp b/liblava/resource/image.hpp
index 1f16664e8adaaff96a73ea445b57b3e8719f3b06..c606c502662ddb34d1952a2678faf7d7092aace3 100644
--- a/liblava/resource/image.hpp
+++ b/liblava/resource/image.hpp
@@ -5,9 +5,6 @@
 #pragma once
 
 #include <liblava/base/device.hpp>
-#include <liblava/base/memory.hpp>
-#include <liblava/core/id.hpp>
-#include <liblava/core/math.hpp>
 
 namespace lava {
 
diff --git a/liblava/resource/mesh.cpp b/liblava/resource/mesh.cpp
index bb6793aaf855aa67ff8f0f59c45a27f64c633536..b094f53ff5339c0915a48701ab3b697914f7105d 100644
--- a/liblava/resource/mesh.cpp
+++ b/liblava/resource/mesh.cpp
@@ -2,33 +2,8 @@
 // copyright : Copyright (c) 2018-present, Lava Block OÜ
 // license   : MIT; see accompanying LICENSE file
 
-#include <liblava/file.hpp>
 #include <liblava/resource/mesh.hpp>
 
-#ifndef LIBLAVA_TINYOBJLOADER
-#    define LIBLAVA_TINYOBJLOADER 1
-#endif
-
-#if LIBLAVA_TINYOBJLOADER
-
-#    ifdef _WIN32
-#        pragma warning(push, 4)
-#    else
-#        pragma GCC diagnostic push
-#        pragma GCC diagnostic ignored "-Wmaybe-uninitialized"
-#    endif
-
-#    define TINYOBJLOADER_IMPLEMENTATION
-#    include <tiny_obj_loader.h>
-
-#    ifdef _WIN32
-#        pragma warning(pop)
-#    else
-#        pragma GCC diagnostic pop
-#    endif
-
-#endif
-
 namespace lava {
 
     void mesh::add_data(mesh_data const& value) {
@@ -101,78 +76,6 @@ namespace lava {
 
 } // namespace lava
 
-lava::mesh::ptr lava::load_mesh(device_ptr device, name filename) {
-#if LIBLAVA_TINYOBJLOADER
-    if (extension(filename, "OBJ")) {
-        tinyobj::attrib_t attrib;
-        std::vector<tinyobj::shape_t> shapes;
-        std::vector<tinyobj::material_t> materials;
-        std::string err;
-        std::string warn;
-
-        string target_file = filename;
-
-        file_remover temp_file_remover;
-        {
-            file file(filename);
-            if (file.opened() && file.get_type() == file_type::fs) {
-                string temp_file;
-                temp_file = file_system::get_pref_dir();
-                temp_file += get_filename_from(target_file, true);
-
-                scope_data temp_data(file.get_size());
-                if (!temp_data.ptr)
-                    return nullptr;
-
-                if (file_error(file.read(temp_data.ptr)))
-                    return nullptr;
-
-                if (!write_file(str(temp_file), temp_data.ptr, temp_data.size))
-                    return nullptr;
-
-                target_file = temp_file;
-
-                temp_file_remover.filename = target_file;
-            }
-        }
-
-        if (tinyobj::LoadObj(&attrib, &shapes, &materials, &warn, &err, str(target_file))) {
-            auto mesh = make_mesh();
-
-            for (auto const& shape : shapes) {
-                for (auto const& index : shape.mesh.indices) {
-                    vertex vertex;
-
-                    vertex.position = v3(attrib.vertices[3 * index.vertex_index],
-                                         attrib.vertices[3 * index.vertex_index + 1],
-                                         attrib.vertices[3 * index.vertex_index + 2]);
-
-                    vertex.color = v4(1.f);
-
-                    if (!attrib.texcoords.empty())
-                        vertex.uv = v2(attrib.texcoords[2 * index.texcoord_index], 1.f - attrib.texcoords[2 * index.texcoord_index + 1]);
-
-                    vertex.normal = attrib.normals.empty() ? v3(0.f) : v3(attrib.normals[3 * index.normal_index], attrib.normals[3 * index.normal_index + 1], attrib.normals[3 * index.normal_index + 2]);
-
-                    mesh->get_vertices().push_back(vertex);
-                    mesh->get_indices().push_back(mesh->get_indices_count());
-                }
-            }
-
-            if (mesh->empty())
-                return nullptr;
-
-            if (!mesh->create(device))
-                return nullptr;
-
-            return mesh;
-        }
-    }
-#endif
-
-    return nullptr;
-}
-
 lava::mesh::ptr lava::create_mesh(device_ptr device, mesh_type type) {
     switch (type) {
     case mesh_type::cube: {
diff --git a/liblava/resource/mesh.hpp b/liblava/resource/mesh.hpp
index c4b5b5c67b44bc5a7919dbfcfde87df7d3e765c0..a629cf801c92736119b6f8946938dafe9a805615 100644
--- a/liblava/resource/mesh.hpp
+++ b/liblava/resource/mesh.hpp
@@ -4,8 +4,6 @@
 
 #pragma once
 
-#include <liblava/core/data.hpp>
-#include <liblava/core/math.hpp>
 #include <liblava/resource/buffer.hpp>
 
 namespace lava {
diff --git a/liblava/resource/texture.cpp b/liblava/resource/texture.cpp
index 1cee8f079bc5817bf874af42ddc60f2d50f63389..5191179d2993cdea2942b0cc73f973cebb1b0da5 100644
--- a/liblava/resource/texture.cpp
+++ b/liblava/resource/texture.cpp
@@ -2,38 +2,8 @@
 // copyright : Copyright (c) 2018-present, Lava Block OÜ
 // license   : MIT; see accompanying LICENSE file
 
-#include <bitmap_image.hpp>
-#include <liblava/file/file_utils.hpp>
 #include <liblava/resource/format.hpp>
 #include <liblava/resource/texture.hpp>
-#include <selene/img/pixel/PixelTypeAliases.hpp>
-#include <selene/img/typed/ImageView.hpp>
-#include <selene/img_ops/ImageConversions.hpp>
-
-#ifdef _WIN32
-#    pragma warning(push, 4)
-#    pragma warning(disable : 4458)
-#    pragma warning(disable : 4100)
-#    pragma warning(disable : 5054)
-#else
-#    pragma GCC diagnostic push
-#    pragma GCC diagnostic ignored "-Wignored-qualifiers"
-#    pragma GCC diagnostic ignored "-Wunused-variable"
-#    pragma GCC diagnostic ignored "-Wtype-limits"
-#    pragma GCC diagnostic ignored "-Wempty-body"
-#    pragma GCC diagnostic ignored "-Wunused-result"
-#endif
-
-#include <gli/gli.hpp>
-
-#ifdef _WIN32
-#    pragma warning(pop)
-#else
-#    pragma GCC diagnostic pop
-#endif
-
-#define STB_IMAGE_IMPLEMENTATION
-#include <stb_image.h>
 
 namespace lava {
 
@@ -249,236 +219,4 @@ namespace lava {
         return true;
     }
 
-    scope_image::scope_image(string_ref filename)
-    : image_file(str(filename)), file_data(image_file.get_size(), false) {
-        if (image_file.opened()) {
-            if (!file_data.allocate())
-                return;
-
-            if (file_error(image_file.read(file_data.ptr)))
-                return;
-        }
-
-        i32 tex_width, tex_height, tex_channels = 0;
-
-        if (image_file.opened())
-            data = as_ptr(stbi_load_from_memory((stbi_uc const*) file_data.ptr, to_i32(file_data.size),
-                                                &tex_width, &tex_height, &tex_channels, STBI_rgb_alpha));
-        else
-            data = as_ptr(stbi_load(str(filename), &tex_width, &tex_height, &tex_channels, STBI_rgb_alpha));
-
-        if (!data)
-            return;
-
-        size = { tex_width, tex_height };
-        channels = tex_channels;
-        ready = true;
-    }
-
-    scope_image::~scope_image() {
-        if (data)
-            stbi_image_free(data);
-    }
-
-    texture::ptr create_gli_texture_2d(device_ptr device, file const& file, VkFormat format, scope_data const& temp_data) {
-        gli::texture2d tex(file.opened() ? gli::load(temp_data.ptr, temp_data.size)
-                                         : gli::load(file.get_path()));
-        assert(!tex.empty());
-        if (tex.empty())
-            return nullptr;
-
-        auto mip_levels = to_ui32(tex.levels());
-
-        texture::layer layer;
-
-        for (auto m = 0u; m < mip_levels; ++m) {
-            texture::mip_level level;
-            level.extent = { tex[m].extent().x, tex[m].extent().y };
-            level.size = to_ui32(tex[m].size());
-
-            layer.levels.push_back(level);
-        }
-
-        texture::layer::list layers;
-        layers.push_back(layer);
-
-        auto texture = make_texture();
-
-        uv2 size = { tex[0].extent().x, tex[0].extent().y };
-        if (!texture->create(device, size, format, layers, texture_type::tex_2d))
-            return nullptr;
-
-        if (!texture->upload(tex.data(), tex.size()))
-            return nullptr;
-
-        return texture;
-    }
-
-    template<typename T>
-    texture::layer::list create_layer_list(T const& tex, ui32 layer_count) {
-        texture::layer::list layers;
-
-        auto mip_levels = to_ui32(tex.levels());
-
-        for (auto i = 0u; i < layer_count; ++i) {
-            texture::layer layer;
-
-            for (auto m = 0u; m < mip_levels; ++m) {
-                texture::mip_level level;
-                level.extent = { tex[i][m].extent().x, tex[i][m].extent().y };
-                level.size = to_ui32(tex[i][m].size());
-
-                layer.levels.push_back(level);
-            }
-
-            layers.push_back(layer);
-        }
-
-        return layers;
-    }
-
-    texture::ptr create_gli_texture_array(device_ptr device, file const& file, VkFormat format, scope_data const& temp_data) {
-        gli::texture2d_array tex(file.opened() ? gli::load(temp_data.ptr, temp_data.size)
-                                               : gli::load(file.get_path()));
-        assert(!tex.empty());
-        if (tex.empty())
-            return nullptr;
-
-        auto layers = create_layer_list(tex, to_ui32(tex.layers()));
-
-        auto texture = make_texture();
-
-        uv2 size = { tex[0].extent().x, tex[0].extent().y };
-        if (!texture->create(device, size, format, layers, texture_type::array))
-            return nullptr;
-
-        if (!texture->upload(tex.data(), tex.size()))
-            return nullptr;
-
-        return texture;
-    }
-
-    texture::ptr create_gli_texture_cube_map(device_ptr device, file const& file, VkFormat format, scope_data const& temp_data) {
-        gli::texture_cube tex(file.opened() ? gli::load(temp_data.ptr, temp_data.size)
-                                            : gli::load(file.get_path()));
-        assert(!tex.empty());
-        if (tex.empty())
-            return nullptr;
-
-        auto layers = create_layer_list(tex, to_ui32(tex.faces()));
-
-        auto texture = make_texture();
-
-        uv2 size = { tex[0].extent().x, tex[0].extent().y };
-        if (!texture->create(device, size, format, layers, texture_type::cube_map))
-            return nullptr;
-
-        if (!texture->upload(tex.data(), tex.size()))
-            return nullptr;
-
-        return texture;
-    }
-
-    texture::ptr create_stbi_texture(device_ptr device, file const& file, scope_data const& temp_data) {
-        i32 tex_width = 0, tex_height = 0;
-        stbi_uc* data = nullptr;
-
-        if (file.opened())
-            data = stbi_load_from_memory((stbi_uc const*) temp_data.ptr, to_i32(temp_data.size),
-                                         &tex_width, &tex_height, nullptr, STBI_rgb_alpha);
-        else
-            data = stbi_load(str(file.get_path()), &tex_width, &tex_height, nullptr, STBI_rgb_alpha);
-
-        if (!data)
-            return nullptr;
-
-        auto texture = make_texture();
-
-        uv2 size = { tex_width, tex_height };
-        if (!texture->create(device, size, VK_FORMAT_R8G8B8A8_UNORM))
-            return nullptr;
-
-        const i32 tex_channels = 4;
-        auto uploadSize = tex_width * tex_height * tex_channels * sizeof(char);
-        auto result = texture->upload(data, uploadSize);
-
-        stbi_image_free(data);
-
-        if (!result)
-            return nullptr;
-
-        return texture;
-    }
-
 } // namespace lava
-
-lava::texture::ptr lava::load_texture(device_ptr device, file_format filename, texture_type type) {
-    auto supported = (filename.format == VK_FORMAT_R8G8B8A8_UNORM) || (device->get_features().textureCompressionBC && (filename.format == VK_FORMAT_BC3_UNORM_BLOCK)) || (device->get_features().textureCompressionASTC_LDR && (filename.format == VK_FORMAT_ASTC_8x8_UNORM_BLOCK)) || (device->get_features().textureCompressionETC2 && (filename.format == VK_FORMAT_ETC2_R8G8B8A8_UNORM_BLOCK));
-    if (!supported)
-        return nullptr;
-
-    auto use_gli = extension(str(filename.path), { "DDS", "KTX", "KMG" });
-    auto use_stbi = false;
-
-    if (!use_gli)
-        use_stbi = extension(str(filename.path), { "JPG", "PNG", "TGA", "BMP", "PSD", "GIF", "HDR", "PIC" });
-
-    if (!use_gli && !use_stbi)
-        return nullptr;
-
-    file file(str(filename.path));
-    scope_data temp_data(file.get_size(), false);
-
-    if (file.opened()) {
-        if (!temp_data.allocate())
-            return nullptr;
-
-        if (file_error(file.read(temp_data.ptr)))
-            return nullptr;
-    }
-
-    if (use_gli) {
-        texture::layer::list layers;
-
-        switch (type) {
-        case texture_type::tex_2d: {
-            return create_gli_texture_2d(device, file, filename.format, temp_data);
-        }
-
-        case texture_type::array: {
-            return create_gli_texture_array(device, file, filename.format, temp_data);
-        }
-
-        case texture_type::cube_map: {
-            return create_gli_texture_cube_map(device, file, filename.format, temp_data);
-        }
-        }
-    } else {
-        return create_stbi_texture(device, file, temp_data);
-    }
-
-    return nullptr;
-}
-
-lava::texture::ptr lava::create_default_texture(device_ptr device, uv2 size) {
-    auto result = make_texture();
-
-    if (!result->create(device, size, VK_FORMAT_R8G8B8A8_UNORM))
-        return nullptr;
-
-    bitmap_image image(size.x, size.y);
-    checkered_pattern(64, 64, 255, 255, 255, image);
-
-    image.bgr_to_rgb();
-
-    sln::TypedLayout typed_layout((sln::PixelLength) size.x, (sln::PixelLength) size.y, (sln::Stride)(image.width() * image.bytes_per_pixel()));
-
-    sln::ImageView<sln::PixelRGB_8u, sln::ImageModifiability::Mutable> img_rgb((uint8_t*) image.data(), typed_layout);
-
-    sln::Image<sln::PixelRGBA_8u> const img_rgba = sln::convert_image<sln::PixelFormat::RGBA>(img_rgb, std::uint8_t{ 192 });
-
-    if (!result->upload(img_rgba.data(), img_rgba.total_bytes()))
-        return nullptr;
-
-    return result;
-}
diff --git a/liblava/resource/texture.hpp b/liblava/resource/texture.hpp
index c654a7196f1515de6e2985c1bd7a2da4d6107e2b..6fa58a08d9ae051e01170ce5c03e87f76818e7e2 100644
--- a/liblava/resource/texture.hpp
+++ b/liblava/resource/texture.hpp
@@ -4,7 +4,6 @@
 
 #pragma once
 
-#include <liblava/file/file.hpp>
 #include <liblava/resource/buffer.hpp>
 #include <liblava/resource/image.hpp>
 
@@ -92,15 +91,6 @@ namespace lava {
         return std::make_shared<texture>();
     }
 
-    texture::ptr load_texture(device_ptr device, file_format filename, texture_type type = texture_type::tex_2d);
-
-    inline texture::ptr load_texture(device_ptr device, string_ref filename,
-                                     VkFormat format = VK_FORMAT_R8G8B8A8_UNORM, texture_type type = texture_type::tex_2d) {
-        return load_texture(device, { filename, format }, type);
-    }
-
-    texture::ptr create_default_texture(device_ptr device, uv2 size = { 512, 512 });
-
     struct staging {
         void add(texture::ptr texture) {
             todo.push_back(texture);
@@ -126,19 +116,4 @@ namespace lava {
 
     using texture_registry = id_registry<texture, file_format>;
 
-    struct scope_image {
-        explicit scope_image(string_ref filename);
-        ~scope_image();
-
-        bool ready = false;
-
-        data_ptr data = nullptr;
-        uv2 size = uv2(0, 0);
-        ui32 channels = 0;
-
-    private:
-        file image_file;
-        scope_data file_data;
-    };
-
 } // namespace lava