diff --git a/src/core/render/primitives/mod.rs b/src/core/render/primitives/mod.rs
index 606c12d..5ab8266 100644
--- a/src/core/render/primitives/mod.rs
+++ b/src/core/render/primitives/mod.rs
@@ -1,4 +1,25 @@
+use std::{collections::BTreeMap, sync::Arc};
+
+use vulkano::{
+    Validated, VulkanError,
+    descriptor_set::{
+        DescriptorSet,
+        allocator::StandardDescriptorSetAllocator,
+        layout::{DescriptorSetLayout, DescriptorSetLayoutBinding},
+    },
+};
+
 pub mod camera;
 pub mod mvp;
 pub mod transform;
 pub mod vertex;
+
+pub trait AsBindableDescriptorSet<T> {
+    fn as_descriptor_set_layout_bindings() -> BTreeMap<u32, DescriptorSetLayoutBinding>;
+
+    fn as_descriptor_set(
+        descriptor_set_allocator: &Arc<StandardDescriptorSetAllocator>,
+        layout: &Arc<DescriptorSetLayout>,
+        data: &T,
+    ) -> Result<Arc<DescriptorSet>, Validated<VulkanError>>;
+}
diff --git a/src/core/render/primitives/mvp.rs b/src/core/render/primitives/mvp.rs
index 192c67b..d05307e 100644
--- a/src/core/render/primitives/mvp.rs
+++ b/src/core/render/primitives/mvp.rs
@@ -1,10 +1,19 @@
+use std::collections::BTreeMap;
 use std::sync::Arc;
 
-use vulkano::Validated;
 use vulkano::buffer::{
     AllocateBufferError, Buffer, BufferContents, BufferCreateInfo, BufferUsage, Subbuffer,
 };
+use vulkano::descriptor_set::allocator::StandardDescriptorSetAllocator;
+use vulkano::descriptor_set::layout::{
+    DescriptorSetLayout, DescriptorSetLayoutBinding, DescriptorType,
+};
+use vulkano::descriptor_set::{DescriptorSet, WriteDescriptorSet};
 use vulkano::memory::allocator::{AllocationCreateInfo, MemoryTypeFilter, StandardMemoryAllocator};
+use vulkano::shader::ShaderStages;
+use vulkano::{Validated, VulkanError};
+
+use crate::core::render::primitives::AsBindableDescriptorSet;
 
 #[derive(BufferContents, Clone, Copy)]
 #[repr(C)]
@@ -34,3 +43,28 @@ impl Mvp {
         )
     }
 }
+
+impl AsBindableDescriptorSet<Subbuffer<[Mvp]>> for Mvp {
+    fn as_descriptor_set_layout_bindings() -> BTreeMap<u32, DescriptorSetLayoutBinding> {
+        BTreeMap::<u32, DescriptorSetLayoutBinding>::from_iter([(
+            0,
+            DescriptorSetLayoutBinding {
+                stages: ShaderStages::VERTEX,
+                ..DescriptorSetLayoutBinding::descriptor_type(DescriptorType::UniformBuffer)
+            },
+        )])
+    }
+
+    fn as_descriptor_set(
+        descriptor_set_allocator: &Arc<StandardDescriptorSetAllocator>,
+        layout: &Arc<DescriptorSetLayout>,
+        data: &Subbuffer<[Mvp]>,
+    ) -> Result<Arc<DescriptorSet>, Validated<VulkanError>> {
+        DescriptorSet::new(
+            descriptor_set_allocator.clone(),
+            layout.clone(),
+            [WriteDescriptorSet::buffer(0, data.clone())],
+            [],
+        )
+    }
+}
diff --git a/src/core/render/texture.rs b/src/core/render/texture.rs
index 8b7f63c..db0bcc5 100644
--- a/src/core/render/texture.rs
+++ b/src/core/render/texture.rs
@@ -1,10 +1,15 @@
-use std::{path::Path, sync::Arc};
+use std::{collections::BTreeMap, error::Error, sync::Arc};
 
-use anyhow::Error;
-use image::{DynamicImage, EncodableLayout};
+use image::DynamicImage;
 use vulkano::{
+    Validated, VulkanError,
     buffer::{Buffer, BufferCreateInfo, BufferUsage},
     command_buffer::{AutoCommandBufferBuilder, CopyBufferToImageInfo, PrimaryAutoCommandBuffer},
+    descriptor_set::{
+        DescriptorSet, WriteDescriptorSet,
+        allocator::StandardDescriptorSetAllocator,
+        layout::{DescriptorSetLayout, DescriptorSetLayoutBinding, DescriptorType},
+    },
     device::Device,
     format::Format,
     image::{
@@ -13,8 +18,11 @@ use vulkano::{
         view::ImageView,
     },
     memory::allocator::{AllocationCreateInfo, MemoryTypeFilter, StandardMemoryAllocator},
+    shader::ShaderStages,
 };
 
+use crate::core::render::primitives::AsBindableDescriptorSet;
+
 pub struct Texture {
     texture: Arc<ImageView>,
     sampler: Arc<Sampler>,
@@ -30,7 +38,7 @@ impl Texture {
         memory_allocator: &Arc<StandardMemoryAllocator>,
         builder: &mut AutoCommandBufferBuilder<PrimaryAutoCommandBuffer>,
         path: &str,
-    ) -> Result<Self, Error> {
+    ) -> Result<Self, Box<dyn Error>> {
         let _span = tracing::info_span!("texture_load_from_file", path = path);
 
         let bytes = std::fs::read(path)?;
@@ -42,7 +50,7 @@ impl Texture {
         memory_allocator: &Arc<StandardMemoryAllocator>,
         builder: &mut AutoCommandBufferBuilder<PrimaryAutoCommandBuffer>,
         bytes: &[u8],
-    ) -> Result<Self, Error> {
+    ) -> Result<Self, Box<dyn Error>> {
         let image = image::load_from_memory(bytes)?;
         Self::from_dynamic_image(device, memory_allocator, builder, image)
     }
@@ -52,7 +60,7 @@ impl Texture {
         memory_allocator: &Arc<StandardMemoryAllocator>,
         builder: &mut AutoCommandBufferBuilder<PrimaryAutoCommandBuffer>,
         image: DynamicImage,
-    ) -> Result<Self, Error> {
+    ) -> Result<Self, Box<dyn Error>> {
         let _span = tracing::info_span!("texture_from_dynamic_image");
 
         let image_data = image.to_rgba8();
@@ -121,3 +129,40 @@ impl Texture {
         &self.sampler
     }
 }
+
+impl AsBindableDescriptorSet<Texture> for Texture {
+    fn as_descriptor_set_layout_bindings() -> BTreeMap<u32, DescriptorSetLayoutBinding> {
+        BTreeMap::<u32, DescriptorSetLayoutBinding>::from_iter([
+            (
+                0,
+                DescriptorSetLayoutBinding {
+                    stages: ShaderStages::FRAGMENT,
+                    ..DescriptorSetLayoutBinding::descriptor_type(DescriptorType::Sampler)
+                },
+            ),
+            (
+                1,
+                DescriptorSetLayoutBinding {
+                    stages: ShaderStages::FRAGMENT,
+                    ..DescriptorSetLayoutBinding::descriptor_type(DescriptorType::SampledImage)
+                },
+            ),
+        ])
+    }
+
+    fn as_descriptor_set(
+        descriptor_set_allocator: &Arc<StandardDescriptorSetAllocator>,
+        layout: &Arc<DescriptorSetLayout>,
+        data: &Texture,
+    ) -> Result<Arc<DescriptorSet>, Validated<VulkanError>> {
+        DescriptorSet::new(
+            descriptor_set_allocator.clone(),
+            layout.clone(),
+            [
+                WriteDescriptorSet::sampler(0, data.sampler.clone()),
+                WriteDescriptorSet::image_view(1, data.texture.clone()),
+            ],
+            [],
+        )
+    }
+}
diff --git a/src/game/assets/square.rs b/src/game/assets/square.rs
index 7219157..c4a33ad 100644
--- a/src/game/assets/square.rs
+++ b/src/game/assets/square.rs
@@ -31,7 +31,7 @@ use vulkano::{
 };
 
 use crate::core::render::{
-    primitives::{mvp::Mvp, transform::TransformRaw, vertex::Vertex3D},
+    primitives::{AsBindableDescriptorSet, mvp::Mvp, transform::TransformRaw, vertex::Vertex3D},
     texture::Texture,
 };
 
@@ -130,29 +130,8 @@ impl Square {
             PipelineShaderStageCreateInfo::new(fs),
         ];
 
-        let vertex_bindings = BTreeMap::<u32, DescriptorSetLayoutBinding>::from_iter([(
-            0,
-            DescriptorSetLayoutBinding {
-                stages: ShaderStages::VERTEX,
-                ..DescriptorSetLayoutBinding::descriptor_type(DescriptorType::UniformBuffer)
-            },
-        )]);
-        let fragment_bindings = BTreeMap::<u32, DescriptorSetLayoutBinding>::from_iter([
-            (
-                0,
-                DescriptorSetLayoutBinding {
-                    stages: ShaderStages::FRAGMENT,
-                    ..DescriptorSetLayoutBinding::descriptor_type(DescriptorType::Sampler)
-                },
-            ),
-            (
-                1,
-                DescriptorSetLayoutBinding {
-                    stages: ShaderStages::FRAGMENT,
-                    ..DescriptorSetLayoutBinding::descriptor_type(DescriptorType::SampledImage)
-                },
-            ),
-        ]);
+        let vertex_bindings = Mvp::as_descriptor_set_layout_bindings();
+        let texture_bindings = Texture::as_descriptor_set_layout_bindings();
 
         let vertex_descriptor_set_layout = DescriptorSetLayoutCreateInfo {
             bindings: vertex_bindings,
@@ -160,7 +139,7 @@ impl Square {
         };
 
         let fragment_descriptor_set_layout = DescriptorSetLayoutCreateInfo {
-            bindings: fragment_bindings,
+            bindings: texture_bindings,
             ..Default::default()
         };
 
@@ -220,22 +199,11 @@ impl Square {
     ) -> Result<(), Box<dyn Error>> {
         let layouts = self.pipeline.layout().set_layouts();
 
-        let uniform_descriptor_set = DescriptorSet::new(
-            descriptor_set_allocator.clone(),
-            layouts[0].clone(),
-            [WriteDescriptorSet::buffer(0, mvp_uniform.clone())],
-            [],
-        )?;
+        let uniform_descriptor_set =
+            Mvp::as_descriptor_set(descriptor_set_allocator, &layouts[0], mvp_uniform)?;
 
-        let texture_descriptor_set = DescriptorSet::new(
-            descriptor_set_allocator.clone(),
-            layouts[1].clone(),
-            [
-                WriteDescriptorSet::sampler(0, texture.get_sampler().clone()),
-                WriteDescriptorSet::image_view(1, texture.get_texture().clone()),
-            ],
-            [],
-        )?;
+        let texture_descriptor_set =
+            Texture::as_descriptor_set(descriptor_set_allocator, &layouts[1], texture)?;
 
         command_buffer.bind_pipeline_graphics(self.pipeline.clone())?;
         command_buffer.bind_descriptor_sets(