diff --git a/modules/asset_loader/include/vkcv/asset/asset_loader.hpp b/modules/asset_loader/include/vkcv/asset/asset_loader.hpp
index c44595786a5e01639f5fcd76ef21ea988f6713c1..72679e27f23c9482e8362ee5be3b18e74852804d 100644
--- a/modules/asset_loader/include/vkcv/asset/asset_loader.hpp
+++ b/modules/asset_loader/include/vkcv/asset/asset_loader.hpp
@@ -129,14 +129,6 @@ typedef struct {
 	std::vector<Material> materials;
 	std::vector<Texture> textures;
 	std::vector<Sampler> samplers;
-	// TODO Replace usage of the texture_hack everywhere with use of the
-	// textures, materials and sampler-arrays.
-	// FIXME Dirty hack to get one(!) texture for our cube demo
-	// hardcoded to always have RGBA channel layout
-	struct {
-		int w, h, ch;	// width, height and channels of image
-		uint8_t *img;	// raw bytes, free after use (deal with it)
-	} texture_hack;
 } Mesh;
 
 
diff --git a/modules/asset_loader/src/vkcv/asset/asset_loader.cpp b/modules/asset_loader/src/vkcv/asset/asset_loader.cpp
index e581f917b706296808dcb776a77781f2efce55e5..388d2a95abdcf24c9a089febd3e3cf46e73a2d29 100644
--- a/modules/asset_loader/src/vkcv/asset/asset_loader.cpp
+++ b/modules/asset_loader/src/vkcv/asset/asset_loader.cpp
@@ -182,11 +182,11 @@ int loadMesh(const std::string &path, Mesh &mesh) {
 		materials,
 		textures,
 		samplers,
-		0, 0, 0, NULL
 	};
 
 	// FIXME HACK HACK HACK HACK HACK HACK HACK HACK HACK HACK HACK HACK
 	// fail quietly if there is no texture
+	mesh.textures.reserve(1);
 	if (object.textures.size()) {
 		const std::string mime_type("image/jpeg");
 		const fx::gltf::Texture &tex = object.textures[0];
@@ -201,9 +201,30 @@ int loadMesh(const std::string &path, Mesh &mesh) {
 		size_t pos = path.find_last_of("/");
 		auto dir = path.substr(0, pos);
 		
-		mesh.texture_hack.img = stbi_load((dir + "/" + img.uri).c_str(),
-				&mesh.texture_hack.w, &mesh.texture_hack.h,
-				&mesh.texture_hack.ch, 4);
+		std::string img_uri = dir + "/" + img.uri;
+		int w, h, c;
+		// FIXME hardcoded to always have RGBA channel layout
+		uint8_t *data = stbi_load(img_uri.c_str(), &w, &h, &c, 4);
+		if (!data) {
+			std::cerr << "ERROR loading texture image data.\n";
+			return 0;
+		}
+		const size_t byteLen = w * h * c;
+
+		std::vector<uint8_t> imgdata;
+		imgdata.resize(byteLen);
+		if (!memcpy(imgdata.data(), data, byteLen)) {
+			std::cerr << "ERROR copying texture image data.\n";
+			free(data);
+			return 0;
+		}
+		free(data);
+
+		mesh.textures.push_back({
+			0, static_cast<uint8_t>(c),
+			static_cast<uint16_t>(w), static_cast<uint16_t>(h),
+			imgdata
+		});
 	}
 	// FIXME HACK HACK HACK HACK HACK HACK HACK HACK HACK HACK HACK HACK
 	return 1;
diff --git a/projects/first_mesh/src/main.cpp b/projects/first_mesh/src/main.cpp
index 306aa524262325b993ade46eaba4b8d989400c51..1225af1cb5bca794e751d1aa341396958ed9a6ca 100644
--- a/projects/first_mesh/src/main.cpp
+++ b/projects/first_mesh/src/main.cpp
@@ -125,8 +125,11 @@ int main(int argc, const char** argv) {
 		return EXIT_FAILURE;
 	}
 	
-	vkcv::Image texture = core.createImage(vk::Format::eR8G8B8A8Srgb, mesh.texture_hack.w, mesh.texture_hack.h);
-	texture.fill(mesh.texture_hack.img);
+	// FIXME There should be a test here to make sure there is at least 1
+	// texture in the mesh.
+	vkcv::asset::Texture &tex = mesh.textures[0];
+	vkcv::Image texture = core.createImage(vk::Format::eR8G8B8A8Srgb, tex.w, tex.h);
+	texture.fill(tex.data.data());
 
 	vkcv::SamplerHandle sampler = core.createSampler(
 		vkcv::SamplerFilterType::LINEAR,