kitgit

tirbofish/dropbear · diff

e2c482b · Thribhu K

refactor: organised code. Unverified

diff --git a/Cargo.toml b/Cargo.toml
index e1f1105..c8e6ee3 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -7,15 +7,7 @@ package.readme = "README.md"
 
 resolver = "3"
 members = [
-    "crates/dropbear-engine",
-    "crates/dropbear-macro",
-    "crates/dropbear-shader",
-    "crates/dropbear-traits",
-    "crates/dropbear_future-queue",
-    "crates/eucalyptus-core",
-    "crates/eucalyptus-editor",
-    "crates/magna-carta",
-#    "crates/redback-runtime"
+    "crates/*"
 ]
 
 [workspace.dependencies]
diff --git a/crates/dropbear-engine/Cargo.toml b/crates/dropbear-engine/Cargo.toml
index 887b142..ae0863a 100644
--- a/crates/dropbear-engine/Cargo.toml
+++ b/crates/dropbear-engine/Cargo.toml
@@ -10,7 +10,6 @@ readme.workspace = true
 
 [dependencies]
 dropbear_future-queue = { path = "../dropbear_future-queue" }
-dropbear-shader = { path = "../dropbear-shader" }
 dropbear-traits = { path = "../dropbear-traits" }
 dropbear-macro = { path = "../dropbear-macro" }
 
diff --git a/crates/dropbear-engine/src/asset.rs b/crates/dropbear-engine/src/asset.rs
index 380be76..4cdd052 100644
--- a/crates/dropbear-engine/src/asset.rs
+++ b/crates/dropbear-engine/src/asset.rs
@@ -6,9 +6,10 @@ use std::sync::{
 use dashmap::DashMap;
 
 use crate::{
-    graphics::{SharedGraphicsContext, Texture},
+    graphics::{SharedGraphicsContext},
     model::{Material, Mesh, Model, ModelId},
     utils::ResourceReference,
+    texture::Texture,
 };
 
 /// Opaque identifier returned from the [`AssetRegistry`] for stored assets.
@@ -139,10 +140,9 @@ impl AssetRegistry {
             return Arc::clone(existing.value());
         }
 
-        let texture = Texture::from_rgba_buffer(graphics, &rgba, (1, 1));
+        let texture = Texture::from_bytes_verbose(&graphics.device, &graphics.queue, &rgba, Some((1, 1)), None, None, None);
         let texture = Arc::new(texture);
 
-        // Race-safe insert: if another thread beat us, reuse theirs.
         match self.solid_textures.entry(key) {
             dashmap::mapref::entry::Entry::Occupied(entry) => Arc::clone(entry.get()),
             dashmap::mapref::entry::Entry::Vacant(entry) => {
diff --git a/crates/dropbear-engine/src/buffer.rs b/crates/dropbear-engine/src/buffer.rs
index bfe6f75..6ad4874 100644
--- a/crates/dropbear-engine/src/buffer.rs
+++ b/crates/dropbear-engine/src/buffer.rs
@@ -2,6 +2,8 @@
 
 use std::marker::PhantomData;
 
+use wgpu::BufferUsages;
+
 #[repr(C)]
 #[derive(Copy, Clone, Debug, Default, bytemuck::Pod, bytemuck::Zeroable)]
 pub struct Vertex {
@@ -30,7 +32,7 @@ impl Vertex {
     }
 }
 
-#[derive(Clone)]
+#[derive(Debug, Clone)]
 pub struct ResizableBuffer<T> {
     buffer: wgpu::Buffer,
     capacity: usize,
@@ -39,6 +41,84 @@ pub struct ResizableBuffer<T> {
     _marker: PhantomData<T>,
 }
 
+#[derive(Debug, Clone)]
+pub struct UniformBuffer<T> {
+    buffer: wgpu::Buffer,
+    label: String,
+    _marker: PhantomData<T>,
+}
+
+impl<T: bytemuck::Pod> UniformBuffer<T> {
+    pub fn new(device: &wgpu::Device, label: &str) -> Self {
+        let size = (std::mem::size_of::<T>() as wgpu::BufferAddress).max(16);
+        let buffer = device.create_buffer(&wgpu::BufferDescriptor {
+            label: Some(label),
+            size,
+            usage: BufferUsages::UNIFORM | BufferUsages::COPY_DST,
+            mapped_at_creation: false,
+        });
+
+        Self {
+            buffer,
+            label: label.to_string(),
+            _marker: PhantomData,
+        }
+    }
+
+    pub fn write(&self, queue: &wgpu::Queue, value: &T) {
+        queue.write_buffer(&self.buffer, 0, bytemuck::bytes_of(value));
+    }
+
+    pub fn buffer(&self) -> &wgpu::Buffer {
+        &self.buffer
+    }
+
+    pub fn label(&self) -> &str {
+        &self.label
+    }
+}
+
+#[derive(Debug, Clone)]
+pub struct StorageBuffer<T> {
+    buffer: wgpu::Buffer,
+    label: String,
+    _marker: PhantomData<T>,
+}
+
+impl<T: bytemuck::Pod> StorageBuffer<T> {
+    /// Creates a storage buffer intended to be written by the CPU and read by the GPU.
+    ///
+    /// Note: whether it is bound as read-only is controlled by the bind group layout
+    /// (`BufferBindingType::Storage { read_only: true }`).
+    pub fn new(device: &wgpu::Device, label: &str) -> Self {
+        let size = (std::mem::size_of::<T>() as wgpu::BufferAddress).max(16);
+        let buffer = device.create_buffer(&wgpu::BufferDescriptor {
+            label: Some(label),
+            size,
+            usage: BufferUsages::STORAGE | BufferUsages::COPY_DST,
+            mapped_at_creation: false,
+        });
+
+        Self {
+            buffer,
+            label: label.to_string(),
+            _marker: PhantomData,
+        }
+    }
+
+    pub fn write(&self, queue: &wgpu::Queue, value: &T) {
+        queue.write_buffer(&self.buffer, 0, bytemuck::bytes_of(value));
+    }
+
+    pub fn buffer(&self) -> &wgpu::Buffer {
+        &self.buffer
+    }
+
+    pub fn label(&self) -> &str {
+        &self.label
+    }
+}
+
 impl<T: bytemuck::Pod> ResizableBuffer<T> {
     pub fn new(
         device: &wgpu::Device,
@@ -63,6 +143,13 @@ impl<T: bytemuck::Pod> ResizableBuffer<T> {
         }
     }
 
+    pub fn uniform(
+        device: &wgpu::Device,
+        label: &str,
+    ) -> Self {
+        Self::new(device, 1, BufferUsages::UNIFORM | BufferUsages::COPY_DST, label)
+    }
+
     pub fn write(&mut self, device: &wgpu::Device, queue: &wgpu::Queue, data: &[T]) {
         if data.is_empty() {
             return;
diff --git a/crates/dropbear-engine/src/camera.rs b/crates/dropbear-engine/src/camera.rs
index 078ec9a..325733d 100644
--- a/crates/dropbear-engine/src/camera.rs
+++ b/crates/dropbear-engine/src/camera.rs
@@ -5,11 +5,11 @@ use std::sync::Arc;
 use glam::{DMat4, DQuat, DVec3, Mat4};
 use serde::{Deserialize, Serialize};
 use wgpu::{
-    BindGroup, BindGroupDescriptor, BindGroupEntry, BindGroupLayout, BindGroupLayoutDescriptor,
-    BindGroupLayoutEntry, BindingType, Buffer, BufferBindingType, ShaderStages,
+    BindGroup, BindGroupDescriptor, BindGroupEntry, BindGroupLayoutDescriptor, BindGroupLayoutEntry,
+    BindingType, BufferBindingType, ShaderStages
 };
 
-use crate::graphics::SharedGraphicsContext;
+use crate::{buffer::UniformBuffer, graphics::SharedGraphicsContext};
 
 /// Matrix that converts OpenGL (from [`glam`]) to [`wgpu`] values
 #[rustfmt::skip]
@@ -45,7 +45,7 @@ impl Default for CameraSettings {
 }
 
 /// The basic values of a Camera.
-#[derive(Default, Debug, Clone)]
+#[derive(Debug, Clone)]
 pub struct Camera {
     /// The name of the camera
     pub label: String,
@@ -72,10 +72,9 @@ pub struct Camera {
 
     /// Uniform/interface for Rust and the GPU
     pub uniform: CameraUniform,
-    buffer: Option<Buffer>,
 
-    layout: Option<BindGroupLayout>,
-    bind_group: Option<BindGroup>,
+    buffer: UniformBuffer<CameraUniform>,
+    pub bind_group: BindGroup,
 
     /// View matrix
     pub view_mat: DMat4,
@@ -95,19 +94,58 @@ pub struct CameraBuilder {
 }
 
 impl Camera {
+    pub const CAMERA_BIND_GROUP_LAYOUT: wgpu::BindGroupLayoutDescriptor<'_> = 
+        BindGroupLayoutDescriptor {
+            entries: &[BindGroupLayoutEntry {
+                binding: 0,
+                visibility: ShaderStages::VERTEX.union(ShaderStages::FRAGMENT),
+                ty: BindingType::Buffer {
+                    ty: BufferBindingType::Uniform,
+                    has_dynamic_offset: false,
+                    min_binding_size: None,
+                },
+                count: None,
+            }],
+            label: Some("camera_bind_group_layout"),
+        };
+
     /// Creates a new camera
     pub fn new(
         graphics: Arc<SharedGraphicsContext>,
         builder: CameraBuilder,
         label: Option<&str>,
     ) -> Self {
-        let uniform = CameraUniform::new();
+        let mut uniform = CameraUniform::new();
 
         let dir = (builder.target - builder.eye).normalize();
         let pitch = dir.y.clamp(-1.0, 1.0).asin();
         let yaw = dir.z.atan2(dir.x);
 
-        let mut camera = Self {
+        let view = DMat4::look_at_lh(builder.eye, builder.target, builder.up);
+        let proj = DMat4::perspective_infinite_reverse_lh(
+            builder.settings.fov_y.to_radians(),
+            builder.aspect,
+            builder.znear,
+        );
+
+        let view_mat = view;
+        let proj_mat = proj;
+
+        let mvp = DMat4::from_cols_array_2d(&OPENGL_TO_WGPU_MATRIX) * proj * view;
+        uniform.view_proj = mvp.as_mat4().to_cols_array_2d();
+
+        let buffer = UniformBuffer::new(&graphics.device, "camera uniform");
+
+        let bind_group = graphics.device.create_bind_group(&BindGroupDescriptor {
+            layout: &graphics.layouts.camera_bind_group_layout,
+            entries: &[BindGroupEntry {
+                binding: 0,
+                resource: buffer.buffer().as_entire_binding(),
+            }],
+            label: Some("camera_bind_group"),
+        });
+
+        let camera = Self {
             eye: builder.eye,
             target: builder.target,
             up: builder.up,
@@ -115,9 +153,8 @@ impl Camera {
             znear: builder.znear,
             zfar: builder.zfar,
             uniform,
-            buffer: None,
-            layout: None,
-            bind_group: None,
+            buffer,
+            bind_group,
             yaw,
             pitch,
             settings: builder.settings,
@@ -126,12 +163,10 @@ impl Camera {
             } else {
                 String::from("Camera")
             },
-            ..Default::default()
+            view_mat,
+            proj_mat,
         };
-        camera.update_view_proj();
-        let buffer = graphics.create_uniform(camera.uniform, Some("Camera Uniform"));
-        camera.create_bind_group_layout(graphics.clone(), buffer.clone());
-        camera.buffer = Some(buffer);
+
         log::debug!("Created new camera{}", if let Some(l) = label { format!(" with the label {}", l) } else { String::new() } );
         camera
     }
@@ -144,7 +179,7 @@ impl Camera {
                 eye: DVec3::new(0.0, 1.0, 2.0),
                 target: DVec3::new(0.0, 0.0, 0.0),
                 up: DVec3::Y,
-                aspect: (graphics.screen_size.0 / graphics.screen_size.1).into(),
+                aspect: (graphics.window.inner_size().width / graphics.window.inner_size().height).into(),
                 znear: 0.1,
                 zfar: 100.0,
                 settings: CameraSettings::default(),
@@ -159,18 +194,6 @@ impl Camera {
         yaw * pitch
     }
 
-    pub fn uniform_buffer(&self) -> &Buffer {
-        self.buffer.as_ref().unwrap()
-    }
-
-    pub fn layout(&self) -> &BindGroupLayout {
-        self.layout.as_ref().unwrap()
-    }
-
-    pub fn bind_group(&self) -> &BindGroup {
-        self.bind_group.as_ref().unwrap()
-    }
-
     pub fn forward(&self) -> DVec3 {
         (self.target - self.eye).normalize()
     }
@@ -207,47 +230,9 @@ impl Camera {
         DMat4::from_cols_array_2d(&OPENGL_TO_WGPU_MATRIX) * proj * view
     }
 
-    pub fn create_bind_group_layout(
-        &mut self,
-        graphics: Arc<SharedGraphicsContext>,
-        camera_buffer: Buffer,
-    ) {
-        let camera_bind_group_layout =
-            graphics
-                .device
-                .create_bind_group_layout(&BindGroupLayoutDescriptor {
-                    entries: &[BindGroupLayoutEntry {
-                        binding: 0,
-                        visibility: ShaderStages::VERTEX | ShaderStages::FRAGMENT,
-                        ty: BindingType::Buffer {
-                            ty: BufferBindingType::Uniform,
-                            has_dynamic_offset: false,
-                            min_binding_size: None,
-                        },
-                        count: None,
-                    }],
-                    label: Some("camera_bind_group_layout"),
-                });
-
-        let camera_bind_group = graphics.device.create_bind_group(&BindGroupDescriptor {
-            layout: &camera_bind_group_layout,
-            entries: &[BindGroupEntry {
-                binding: 0,
-                resource: camera_buffer.as_entire_binding(),
-            }],
-            label: Some("camera_bind_group"),
-        });
-        self.layout = Some(camera_bind_group_layout);
-        self.bind_group = Some(camera_bind_group);
-    }
-
     pub fn update(&mut self, graphics: Arc<SharedGraphicsContext>) {
         self.update_view_proj();
-        graphics.queue.write_buffer(
-            self.buffer.as_ref().unwrap(),
-            0,
-            bytemuck::cast_slice(&[self.uniform]),
-        );
+        self.buffer.write(&graphics.queue, &self.uniform);
     }
 
     pub fn update_view_proj(&mut self) {
diff --git a/crates/dropbear-engine/src/entity.rs b/crates/dropbear-engine/src/entity.rs
index 31d12fe..3e49d4e 100644
--- a/crates/dropbear-engine/src/entity.rs
+++ b/crates/dropbear-engine/src/entity.rs
@@ -10,9 +10,10 @@ use std::{
 
 use crate::{
     asset::{ASSET_REGISTRY, AssetHandle, AssetKind, AssetRegistry},
-    graphics::{Instance, SharedGraphicsContext, Texture},
+    graphics::{Instance, SharedGraphicsContext},
     model::{LoadedModel, MODEL_CACHE, Model, ModelId},
     utils::ResourceReference,
+    texture::Texture,
 };
 use anyhow::anyhow;
 use dropbear_macro::SerializableComponent;
diff --git a/crates/dropbear-engine/src/graphics.rs b/crates/dropbear-engine/src/graphics.rs
index aa79b64..344f16e 100644
--- a/crates/dropbear-engine/src/graphics.rs
+++ b/crates/dropbear-engine/src/graphics.rs
@@ -1,4 +1,6 @@
 use crate::shader::Shader;
+use crate::texture::Texture;
+use crate::{BindGroupLayouts, texture};
 use crate::{
     State,
     egui_renderer::EguiRenderer,
@@ -7,20 +9,16 @@ use crate::{
 use dropbear_future_queue::FutureQueue;
 use egui::{Context, TextureId};
 use glam::{DMat4, DQuat, DVec3, Mat3};
-use image::GenericImageView;
 use parking_lot::Mutex;
-use std::{fs, path::PathBuf, sync::Arc, time::Instant};
-use crate::utils::TextureWrapMode;
+use std::sync::Arc;
 use wgpu::*;
-use wgpu::util::*;
 use winit::window::Window;
 
 pub const NO_TEXTURE: &[u8] = include_bytes!("../../../resources/textures/no-texture.png");
-pub const NO_MODEL: &[u8] = include_bytes!("../../../resources/models/error.glb");
 
-pub struct RenderContext<'a> {
-    pub shared: Arc<SharedGraphicsContext>,
-    pub frame: FrameGraphicsContext<'a>,
+pub struct FrameGraphicsContext<'a> {
+    pub view: TextureView,
+    pub encoder: &'a mut CommandEncoder,
 }
 
 pub struct SharedGraphicsContext {
@@ -29,43 +27,18 @@ pub struct SharedGraphicsContext {
     pub surface: Arc<Surface<'static>>,
     pub surface_format: TextureFormat,
     pub instance: Arc<wgpu::Instance>,
-    pub texture_bind_layout: Arc<BindGroupLayout>,
-    pub material_tint_bind_layout: Arc<BindGroupLayout>,
+    pub layouts: Arc<BindGroupLayouts>,
     pub window: Arc<Window>,
-    pub viewport_texture: Arc<Texture>,
+    pub viewport_texture: texture::Texture,
+    pub depth_texture: texture::Texture,
     pub egui_renderer: Arc<Mutex<EguiRenderer>>,
-    pub diffuse_sampler: Arc<Sampler>,
-    pub screen_size: (f32, f32),
     pub texture_id: Arc<TextureId>,
     pub future_queue: Arc<FutureQueue>,
 }
 
-pub struct FrameGraphicsContext<'a> {
-    pub encoder: &'a mut CommandEncoder,
-    pub view: &'a TextureView,
-    pub depth_texture: &'a Texture,
-    pub screen_size: (f32, f32),
-}
-
 impl SharedGraphicsContext {
-    pub fn get_egui_context(&self) -> Context {
-        self.egui_renderer.lock().context().clone()
-    }
-
-    pub fn create_uniform<T>(&self, uniform: T, label: Option<&str>) -> Buffer
-    where
-        T: bytemuck::Pod + bytemuck::Zeroable,
-    {
-        self.device.create_buffer_init(&BufferInitDescriptor {
-            label,
-            contents: bytemuck::cast_slice(&[uniform]),
-            usage: BufferUsages::UNIFORM | BufferUsages::COPY_DST,
-        })
-    }
-
-    pub fn create_model_uniform_bind_group_layout(&self) -> BindGroupLayout {
-        self.device
-            .create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
+    pub const MODEL_UNIFORM_BIND_GROUP_LAYOUT: wgpu::BindGroupLayoutDescriptor<'_> = 
+        wgpu::BindGroupLayoutDescriptor {
                 entries: &[wgpu::BindGroupLayoutEntry {
                     binding: 0,
                     visibility: wgpu::ShaderStages::VERTEX,
@@ -77,49 +50,30 @@ impl SharedGraphicsContext {
                     count: None,
                 }],
                 label: Some("model_uniform_bind_group_layout"),
-            })
+            };
+
+    pub fn get_egui_context(&self) -> Context {
+        self.egui_renderer.lock().context().clone()
     }
 }
 
-impl<'a> RenderContext<'a> {
-    pub fn from_state(
-        state: &'a mut State,
-        view: &'a TextureView,
-        encoder: &'a mut CommandEncoder,
+impl SharedGraphicsContext {
+    pub(crate) fn from_state(
+        state: &State,
     ) -> Self {
-        let screen_size = (state.config.width as f32, state.config.height as f32);
-        let diffuse_sampler = Arc::new(state.device.create_sampler(&wgpu::SamplerDescriptor {
-            address_mode_u: wgpu::AddressMode::ClampToEdge,
-            address_mode_v: wgpu::AddressMode::ClampToEdge,
-            address_mode_w: wgpu::AddressMode::ClampToEdge,
-            mag_filter: wgpu::FilterMode::Linear,
-            min_filter: wgpu::FilterMode::Nearest,
-            mipmap_filter: wgpu::FilterMode::Nearest,
-            ..Default::default()
-        }));
-        Self {
-            shared: Arc::new(SharedGraphicsContext {
-                future_queue: state.future_queue.clone(),
-                device: state.device.clone(),
-                queue: state.queue.clone(),
-                instance: state.instance.clone(),
-                texture_bind_layout: Arc::new(state.texture_bind_layout.clone()),
-                material_tint_bind_layout: Arc::new(state.material_tint_bind_layout.clone()),
-                window: state.window.clone(),
-                viewport_texture: Arc::new(state.viewport_texture.clone()),
-                egui_renderer: state.egui_renderer.clone(),
-                diffuse_sampler,
-                screen_size,
-                texture_id: state.texture_id.clone(),
-                surface: state.surface.clone(),
-                surface_format: state.surface_format,
-            }),
-            frame: FrameGraphicsContext {
-                encoder,
-                view,
-                depth_texture: &state.depth_texture,
-                screen_size,
-            },
+        SharedGraphicsContext {
+            future_queue: state.future_queue.clone(),
+            device: state.device.clone(),
+            queue: state.queue.clone(),
+            instance: state.instance.clone(),
+            layouts: state.layouts.clone(),
+            window: state.window.clone(),
+            viewport_texture: state.viewport_texture.clone(),
+            depth_texture: state.depth_texture.clone(),
+            egui_renderer: state.egui_renderer.clone(),
+            texture_id: state.texture_id.clone(),
+            surface: state.surface.clone(),
+            surface_format: state.surface_format,
         }
     }
 
@@ -130,8 +84,7 @@ impl<'a> RenderContext<'a> {
         label: Option<&str>,
     ) -> RenderPipeline {
         let render_pipeline_layout =
-            self.shared
-                .device
+            self.device
                 .create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
                     label: Some(label.unwrap_or("Render Pipeline Descriptor")),
                     bind_group_layouts: bind_group_layouts.as_slice(),
@@ -139,8 +92,7 @@ impl<'a> RenderContext<'a> {
                 });
 
         let render_pipeline =
-            self.shared
-                .device
+            self.device
                 .create_render_pipeline(&wgpu::RenderPipelineDescriptor {
                     label: Some(label.unwrap_or("Render Pipeline")),
                     layout: Some(&render_pipeline_layout),
@@ -164,8 +116,7 @@ impl<'a> RenderContext<'a> {
                         topology: wgpu::PrimitiveTopology::TriangleList,
                         strip_index_format: None,
                         front_face: wgpu::FrontFace::Ccw,
-                        // cull_mode: Some(wgpu::Face::Back), // todo: change for improved performance
-                        cull_mode: None,
+                        cull_mode: Some(wgpu::Face::Back),
                         polygon_mode: wgpu::PolygonMode::Fill,
                         unclipped_depth: false,
                         conservative: false,
@@ -188,510 +139,6 @@ impl<'a> RenderContext<'a> {
         log::debug!("Created new render pipeline");
         render_pipeline
     }
-
-    pub fn clear_colour(&mut self, color: Color) -> RenderPass<'static> {
-        self.frame
-            .encoder
-            .begin_render_pass(&wgpu::RenderPassDescriptor {
-                label: Some("Render Pass"),
-                color_attachments: &[Some(wgpu::RenderPassColorAttachment {
-                    view: self.frame.view,
-                    depth_slice: None,
-                    resolve_target: None,
-                    ops: wgpu::Operations {
-                        load: wgpu::LoadOp::Clear(color),
-                        store: wgpu::StoreOp::Store,
-                    },
-                })],
-                depth_stencil_attachment: Some(RenderPassDepthStencilAttachment {
-                    view: &self.frame.depth_texture.view,
-                    depth_ops: Some(Operations {
-                        load: LoadOp::Clear(0.0),
-                        store: wgpu::StoreOp::Store,
-                    }),
-                    stencil_ops: None,
-                }),
-                occlusion_query_set: None,
-                timestamp_writes: None,
-            })
-            .forget_lifetime()
-    }
-
-    pub fn continue_pass(&mut self) -> RenderPass<'static> {
-        self.frame
-            .encoder
-            .begin_render_pass(&wgpu::RenderPassDescriptor {
-                label: Some("Render Pass"),
-                color_attachments: &[Some(wgpu::RenderPassColorAttachment {
-                    view: self.frame.view,
-                    depth_slice: None,
-                    resolve_target: None,
-                    ops: wgpu::Operations {
-                        load: wgpu::LoadOp::Load,
-                        store: wgpu::StoreOp::Store,
-                    },
-                })],
-                depth_stencil_attachment: Some(RenderPassDepthStencilAttachment {
-                    view: &self.frame.depth_texture.view,
-                    depth_ops: Some(Operations {
-                        load: LoadOp::Load,
-                        store: wgpu::StoreOp::Store,
-                    }),
-                    stencil_ops: None,
-                }),
-                occlusion_query_set: None,
-                timestamp_writes: None,
-            })
-            .forget_lifetime()
-    }
-
-    pub fn begin_shadow_pass(
-        &mut self,
-        shadow_view: &wgpu::TextureView,
-        label: Option<&str>,
-    ) -> RenderPass<'static> {
-        self.frame
-            .encoder
-            .begin_render_pass(&wgpu::RenderPassDescriptor {
-                label: Some(label.unwrap_or("Shadow Pass")),
-                color_attachments: &[],
-                depth_stencil_attachment: Some(RenderPassDepthStencilAttachment {
-                    view: shadow_view,
-                    depth_ops: Some(Operations {
-                        load: LoadOp::Clear(1.0),
-                        store: wgpu::StoreOp::Store,
-                    }),
-                    stencil_ops: None,
-                }),
-                occlusion_query_set: None,
-                timestamp_writes: None,
-            })
-            .forget_lifetime()
-    }
-}
-
-#[derive(Clone)]
-/// Describes a texture, like an image of some sort. Can be a normal texture on a model or a viewport or depth texture.
-pub struct Texture {
-    pub texture: wgpu::Texture,
-    pub sampler: Sampler,
-    pub size: Extent3d,
-    pub view: TextureView,
-}
-
-impl Texture {
-    /// Describes the depth format for all Texture related functions in WGPU to use. Makes life easier
-    pub const DEPTH_FORMAT: TextureFormat = TextureFormat::Depth32Float;
-
-    fn align_up(value: u32, alignment: u32) -> u32 {
-        debug_assert!(alignment.is_power_of_two());
-        (value + alignment - 1) & !(alignment - 1)
-    }
-
-    fn write_rgba8_texture(
-        queue: &wgpu::Queue,
-        texture: &wgpu::Texture,
-        rgba_data: &[u8],
-        dimensions: (u32, u32),
-    ) {
-        let (width, height) = dimensions;
-        let texture_size = wgpu::Extent3d {
-            width,
-            height,
-            depth_or_array_layers: 1,
-        };
-
-        let unpadded_bytes_per_row = 4 * width;
-        let padded_bytes_per_row =
-            Self::align_up(unpadded_bytes_per_row, wgpu::COPY_BYTES_PER_ROW_ALIGNMENT);
-
-        debug_assert!(rgba_data.len() >= (unpadded_bytes_per_row * height) as usize);
-
-        if padded_bytes_per_row == unpadded_bytes_per_row {
-            queue.write_texture(
-                wgpu::TexelCopyTextureInfo {
-                    texture,
-                    mip_level: 0,
-                    origin: wgpu::Origin3d::ZERO,
-                    aspect: wgpu::TextureAspect::All,
-                },
-                rgba_data,
-                wgpu::TexelCopyBufferLayout {
-                    offset: 0,
-                    bytes_per_row: Some(unpadded_bytes_per_row),
-                    rows_per_image: Some(height),
-                },
-                texture_size,
-            );
-            return;
-        }
-
-        let mut padded = vec![0u8; (padded_bytes_per_row * height) as usize];
-        let src_stride = unpadded_bytes_per_row as usize;
-        let dst_stride = padded_bytes_per_row as usize;
-        for row in 0..height as usize {
-            let src_start = row * src_stride;
-            let dst_start = row * dst_stride;
-            padded[dst_start..dst_start + src_stride]
-                .copy_from_slice(&rgba_data[src_start..src_start + src_stride]);
-        }
-
-        queue.write_texture(
-            wgpu::TexelCopyTextureInfo {
-                texture,
-                mip_level: 0,
-                origin: wgpu::Origin3d::ZERO,
-                aspect: wgpu::TextureAspect::All,
-            },
-            &padded,
-            wgpu::TexelCopyBufferLayout {
-                offset: 0,
-                bytes_per_row: Some(padded_bytes_per_row),
-                rows_per_image: Some(height),
-            },
-            texture_size,
-        );
-    }
-
-    /// Creates a new Texture from the bytes of an image. This function is blocking, and takes roughly 4 seconds to
-    /// convert from the image to RGBA, which can cause issues. There are better options, such as doing it yourself.
-    ///
-    /// Once async is implemented, this will be a better use.
-    pub fn new(graphics: Arc<SharedGraphicsContext>, diffuse_bytes: &[u8]) -> Self {
-        let start = Instant::now();
-        let diffuse_image = image::load_from_memory(diffuse_bytes).unwrap();
-        log::trace!("Loading image to memory: {:?}", start.elapsed());
-
-        let start = Instant::now();
-        let diffuse_rgba = diffuse_image.to_rgba8();
-        log::trace!(
-            "Converting diffuse image to rgba8 took {:?}",
-            start.elapsed()
-        );
-
-        let dimensions = diffuse_image.dimensions();
-        let texture_size = wgpu::Extent3d {
-            width: dimensions.0,
-            height: dimensions.1,
-            depth_or_array_layers: 1,
-        };
-
-        let start = Instant::now();
-        let diffuse_texture = Self::create_mipmapped_diffuse_texture(&graphics.device, texture_size);
-        log::trace!("Creating new diffuse texture took {:?}", start.elapsed());
-
-        let start = Instant::now();
-        Self::write_rgba8_texture(
-            graphics.queue.as_ref(),
-            &diffuse_texture,
-            diffuse_rgba.as_raw(),
-            dimensions,
-        );
-        log::trace!(
-            "Writing texture to graphics queue took {:?}",
-            start.elapsed()
-        );
-
-        let start = Instant::now();
-        let diffuse_texture_view = diffuse_texture.create_view(&TextureViewDescriptor::default());
-        let diffuse_sampler = graphics.device.create_sampler(&wgpu::SamplerDescriptor {
-            address_mode_u: wgpu::AddressMode::ClampToEdge,
-            address_mode_v: wgpu::AddressMode::ClampToEdge,
-            address_mode_w: wgpu::AddressMode::ClampToEdge,
-            mag_filter: wgpu::FilterMode::Linear,
-            min_filter: wgpu::FilterMode::Nearest,
-            mipmap_filter: wgpu::FilterMode::Nearest,
-            ..Default::default()
-        });
-        log::trace!("Creating sampler took {:?}", start.elapsed());
-
-        log::trace!("Done creating texture");
-        Self {
-            texture: diffuse_texture,
-            sampler: diffuse_sampler,
-            size: texture_size,
-            view: diffuse_texture_view,
-        }
-    }
-
-    pub fn new_with_wrap_mode(
-        graphics: Arc<SharedGraphicsContext>,
-        diffuse_bytes: &[u8],
-        wrap_mode: TextureWrapMode,
-    ) -> Self {
-        let address_mode = match wrap_mode {
-            TextureWrapMode::Repeat => wgpu::AddressMode::Repeat,
-            TextureWrapMode::Clamp => wgpu::AddressMode::ClampToEdge,
-        };
-
-        Self::new_with_sampler(graphics, diffuse_bytes, address_mode)
-    }
-
-    /// Creates a new depth texture. This is an internal function.
-    // note: this should not be mipmapped
-    pub fn create_depth_texture(
-        config: &SurfaceConfiguration,
-        device: &Device,
-        label: Option<&str>,
-    ) -> Self {
-        let size = Extent3d {
-            width: config.width.max(1),
-            height: config.height.max(1),
-            depth_or_array_layers: 1,
-        };
-
-        let desc = TextureDescriptor {
-            label,
-            size,
-            mip_level_count: 1, // leave me alone
-            sample_count: 1,
-            dimension: wgpu::TextureDimension::D2,
-            format: Self::DEPTH_FORMAT,
-            usage: TextureUsages::RENDER_ATTACHMENT | TextureUsages::TEXTURE_BINDING,
-            view_formats: &[],
-        };
-        let texture = device.create_texture(&desc);
-
-        let view = texture.create_view(&wgpu::TextureViewDescriptor::default());
-        let sampler = device.create_sampler(&wgpu::SamplerDescriptor {
-            address_mode_u: wgpu::AddressMode::ClampToEdge,
-            address_mode_v: wgpu::AddressMode::ClampToEdge,
-            address_mode_w: wgpu::AddressMode::ClampToEdge,
-            mag_filter: wgpu::FilterMode::Linear,
-            min_filter: wgpu::FilterMode::Linear,
-            mipmap_filter: wgpu::FilterMode::Nearest,
-            compare: Some(wgpu::CompareFunction::LessEqual),
-            lod_min_clamp: 0.0,
-            lod_max_clamp: 100.0,
-            ..Default::default()
-        });
-
-        Self {
-            texture,
-            sampler,
-            view,
-            size,
-        }
-    }
-
-    /// Creates a viewport texture. This is an internal function.
-    // note: this should not be mipmapped
-    pub fn create_viewport_texture(
-        config: &SurfaceConfiguration,
-        device: &Device,
-        label: Option<&str>,
-    ) -> Self {
-        let size = Extent3d {
-            width: config.width.max(1),
-            height: config.height.max(1),
-            depth_or_array_layers: 1,
-        };
-
-        let desc = TextureDescriptor {
-            label,
-            size,
-            mip_level_count: 1, // leave me alone
-            sample_count: 1,
-            dimension: wgpu::TextureDimension::D2,
-            format: wgpu::TextureFormat::Rgba16Float,
-            usage: TextureUsages::RENDER_ATTACHMENT | TextureUsages::TEXTURE_BINDING,
-            view_formats: &[],
-        };
-
-        let texture = device.create_texture(&desc);
-        let view = texture.create_view(&wgpu::TextureViewDescriptor::default());
-        let sampler = device.create_sampler(&wgpu::SamplerDescriptor::default());
-
-        Self {
-            texture,
-            sampler,
-            view,
-            size,
-        }
-    }
-
-    /// Alternative to [`Texture::new()`], which uses an existing rgba data buffer compared to new which synchronously
-    /// converts the image to RGBA form.
-    pub(crate) fn from_rgba_buffer(
-        graphics: Arc<SharedGraphicsContext>,
-        rgba_data: &[u8],
-        dimensions: (u32, u32),
-    ) -> Texture {
-        let texture_size = wgpu::Extent3d {
-            width: dimensions.0,
-            height: dimensions.1,
-            depth_or_array_layers: 1,
-        };
-
-        let create_start = Instant::now();
-        let diffuse_texture = Self::create_mipmapped_diffuse_texture(&graphics.device, texture_size);
-        log::trace!(
-            "Creating new diffuse texture took {:?}",
-            create_start.elapsed()
-        );
-
-        let write_start = Instant::now();
-        Self::write_rgba8_texture(graphics.queue.as_ref(), &diffuse_texture, rgba_data, dimensions);
-        log::trace!(
-            "Writing texture to graphics queue took {:?}",
-            write_start.elapsed()
-        );
-
-        let sampler_start = Instant::now();
-        let diffuse_sampler = graphics.device.create_sampler(&wgpu::SamplerDescriptor {
-            address_mode_u: wgpu::AddressMode::ClampToEdge,
-            address_mode_v: wgpu::AddressMode::ClampToEdge,
-            address_mode_w: wgpu::AddressMode::ClampToEdge,
-            mag_filter: wgpu::FilterMode::Linear,
-            min_filter: wgpu::FilterMode::Nearest,
-            mipmap_filter: wgpu::FilterMode::Nearest,
-            ..Default::default()
-        });
-        log::trace!("Creating sampler took {:?}", sampler_start.elapsed());
-
-        let view = diffuse_texture.create_view(&wgpu::TextureViewDescriptor::default());
-
-        let bind_group_start = Instant::now();
-        log::trace!(
-            "Creating diffuse bind group took {:?}",
-            bind_group_start.elapsed()
-        );
-
-        log::trace!("Done creating texture");
-
-        Texture {
-            texture: diffuse_texture,
-            sampler: diffuse_sampler,
-            view,
-            size: texture_size,
-        }
-    }
-
-    fn create_mipmapped_diffuse_texture(device: &wgpu::Device, texture_size: wgpu::Extent3d) -> wgpu::Texture {
-        device.create_texture(&wgpu::TextureDescriptor {
-            label: Some("diffuse_texture"),
-            size: texture_size,
-            mip_level_count: 1,
-            sample_count: 1,
-            dimension: wgpu::TextureDimension::D2,
-            format: wgpu::TextureFormat::Rgba8UnormSrgb,
-            usage: TextureUsages::TEXTURE_BINDING | TextureUsages::COPY_DST | TextureUsages::RENDER_ATTACHMENT,
-            view_formats: &[],
-        })
-    }
-
-    /// Creates a new [`Texture`] with a specified sampler (wgpu) and already converted RGBA byte buffer.
-    pub fn new_with_sampler_with_rgba_buffer(
-        graphics: Arc<SharedGraphicsContext>,
-        rgba_data: &[u8],
-        dimensions: (u32, u32),
-        address_mode: wgpu::AddressMode,
-    ) -> Self {
-        let texture_size = wgpu::Extent3d {
-            width: dimensions.0,
-            height: dimensions.1,
-            depth_or_array_layers: 1,
-        };
-
-        let create_start = Instant::now();
-        let diffuse_texture = Self::create_mipmapped_diffuse_texture(&graphics.device, texture_size);
-        log::trace!(
-            "Creating new diffuse texture took {:?}",
-            create_start.elapsed()
-        );
-
-        let write_start = Instant::now();
-        Self::write_rgba8_texture(graphics.queue.as_ref(), &diffuse_texture, rgba_data, dimensions);
-        log::trace!(
-            "Writing texture to graphics queue took {:?}",
-            write_start.elapsed()
-        );
-
-        let sampler_start = Instant::now();
-        let diffuse_sampler = graphics.device.create_sampler(&wgpu::SamplerDescriptor {
-            address_mode_u: address_mode,
-            address_mode_v: address_mode,
-            address_mode_w: address_mode,
-            mag_filter: wgpu::FilterMode::Linear,
-            min_filter: wgpu::FilterMode::Nearest,
-            mipmap_filter: wgpu::FilterMode::Nearest,
-            ..Default::default()
-        });
-        log::trace!("Creating sampler took {:?}", sampler_start.elapsed());
-
-        let view = diffuse_texture.create_view(&wgpu::TextureViewDescriptor::default());
-
-        let bind_group_start = Instant::now();
-        log::trace!(
-            "Creating diffuse bind group took {:?}",
-            bind_group_start.elapsed()
-        );
-
-        log::trace!("Done creating texture");
-
-        Texture {
-            texture: diffuse_texture,
-            sampler: diffuse_sampler,
-            view,
-            size: texture_size,
-        }
-    }
-
-    /// Creates a new [`Texture`] with a specified sampler (wgpu).
-    ///
-    /// This function decodes the image to RGBA, which can take a long time. This function is not
-    /// recommended to be used until you have async working.
-    pub fn new_with_sampler(
-        graphics: Arc<SharedGraphicsContext>,
-        diffuse_bytes: &[u8],
-        address_mode: wgpu::AddressMode,
-    ) -> Self {
-        let diffuse_image = image::load_from_memory(diffuse_bytes).unwrap();
-        let diffuse_rgba = diffuse_image.to_rgba8();
-
-        let dimensions = diffuse_image.dimensions();
-        let texture_size = wgpu::Extent3d {
-            width: dimensions.0,
-            height: dimensions.1,
-            depth_or_array_layers: 1,
-        };
-
-        let diffuse_texture = Self::create_mipmapped_diffuse_texture(&graphics.device, texture_size);
-
-        Self::write_rgba8_texture(
-            graphics.queue.as_ref(),
-            &diffuse_texture,
-            diffuse_rgba.as_raw(),
-            dimensions,
-        );
-
-        let diffuse_texture_view = diffuse_texture.create_view(&TextureViewDescriptor::default());
-        let diffuse_sampler = graphics.device.create_sampler(&wgpu::SamplerDescriptor {
-            address_mode_u: address_mode,
-            address_mode_v: address_mode,
-            address_mode_w: address_mode,
-            mag_filter: wgpu::FilterMode::Linear,
-            min_filter: wgpu::FilterMode::Nearest,
-            mipmap_filter: wgpu::FilterMode::Nearest,
-            ..Default::default()
-        });
-
-        Self {
-            texture: diffuse_texture,
-            sampler: diffuse_sampler,
-            view: diffuse_texture_view,
-            size: texture_size,
-        }
-    }
-
-    /// A helper function that loads the texture from a path. Still returns the same [`Texture`].
-    pub async fn load_texture(
-        graphics: Arc<SharedGraphicsContext>,
-        path: &PathBuf,
-    ) -> anyhow::Result<Texture> {
-        let data = fs::read(path)?;
-        Ok(Self::new(graphics.clone(), &data))
-    }
 }
 
 #[derive(Default, Clone)]
@@ -699,8 +146,6 @@ pub struct Instance {
     pub position: DVec3,
     pub rotation: DQuat,
     pub scale: DVec3,
-
-    buffer: Option<Buffer>,
 }
 
 impl Instance {
@@ -709,7 +154,6 @@ impl Instance {
             position,
             rotation,
             scale,
-            buffer: None,
         }
     }
 
@@ -722,17 +166,12 @@ impl Instance {
         }
     }
 
-    pub fn buffer(&self) -> &Buffer {
-        self.buffer.as_ref().unwrap()
-    }
-
     pub fn from_matrix(mat: DMat4) -> Self {
         let (scale, rotation, position) = mat.to_scale_rotation_translation();
         Instance {
             position,
             rotation,
             scale,
-            buffer: None,
         }
     }
 }
diff --git a/crates/dropbear-engine/src/lib.rs b/crates/dropbear-engine/src/lib.rs
index bd463e4..9d4831b 100644
--- a/crates/dropbear-engine/src/lib.rs
+++ b/crates/dropbear-engine/src/lib.rs
@@ -8,7 +8,6 @@ pub mod entity;
 pub mod graphics;
 pub mod input;
 pub mod lighting;
-pub mod mipmap;
 pub mod model;
 pub mod panic;
 pub mod procedural;
@@ -16,12 +15,11 @@ pub mod resources;
 pub mod scene;
 pub mod shader;
 pub mod utils;
+pub mod texture;
 
 pub static WGPU_BACKEND: OnceLock<String> = OnceLock::new();
 pub const PHYSICS_STEP_RATE: u32 = 120;
 const MAX_PHYSICS_STEPS_PER_FRAME: usize = 4;
-/// Note: image size is 256x256
-pub const LOGO_AS_BYTES: &[u8] = include_bytes!("../../../resources/eucalyptus-editor.png");
 
 use app_dirs2::{AppDataType, AppInfo};
 use bytemuck::Contiguous;
@@ -51,7 +49,11 @@ use winit::{
     window::Window,
 };
 
-use crate::{egui_renderer::EguiRenderer, graphics::Texture};
+use crate::camera::Camera;
+use crate::graphics::{FrameGraphicsContext, SharedGraphicsContext};
+use crate::lighting::{Light, LightManager};
+use crate::texture::Texture;
+use crate::{egui_renderer::EguiRenderer};
 
 pub use dropbear_future_queue as future;
 pub use gilrs;
@@ -60,6 +62,15 @@ pub use winit;
 use winit::window::{WindowAttributes, WindowId};
 use crate::scene::Scene;
 
+pub struct BindGroupLayouts {
+    pub texture_bind_layout: BindGroupLayout,
+    pub material_tint_bind_layout: BindGroupLayout,
+    pub camera_bind_group_layout: BindGroupLayout,
+    pub light_bind_group_layout: BindGroupLayout,
+    pub light_array_bind_group_layout: BindGroupLayout,
+    pub light_cube_bind_group_layout: BindGroupLayout,
+}
+
 /// The backend information, such as the device, queue, config, surface, renderer, window and more.
 pub struct State {
     // keep top for drop order
@@ -72,10 +83,9 @@ pub struct State {
     pub queue: Arc<Queue>,
     pub config: SurfaceConfiguration,
     pub is_surface_configured: bool,
-    pub depth_texture: Texture,
-    pub texture_bind_layout: BindGroupLayout,
-    pub material_tint_bind_layout: BindGroupLayout,
+    pub layouts: Arc<BindGroupLayouts>,
     pub egui_renderer: Arc<Mutex<EguiRenderer>>,
+    pub depth_texture: Texture,
     pub viewport_texture: Texture,
     pub texture_id: Arc<TextureId>,
     pub future_queue: Arc<FutureQueue>,
@@ -85,18 +95,26 @@ pub struct State {
     pub scene_manager: scene::Manager,
 }
 
-/// Generates the dropbear engine logo in a form that [winit::window::Icon] can accept. 
-/// 
-/// Returns (the bytes, width, height) in resp order. 
-pub fn gen_logo() -> anyhow::Result<(Vec<u8>, u32, u32)> {
-    let image = image::load_from_memory(LOGO_AS_BYTES)?.into_rgba8();
-    let (width, height) = image.dimensions();
-    let rgba = image.into_raw();
-    Ok((rgba, width, height))
-
-}
-
 impl State {
+    /// As defined in `shader.wgsl` as 
+    /// ```
+    /// @group(3) @binding(0)
+    /// var<uniform> u_material: MaterialUniform;
+    /// ```
+    const MATERIAL_BIND_GROUP_LAYOUT: wgpu::BindGroupLayoutDescriptor<'_> = wgpu::BindGroupLayoutDescriptor {
+        entries: &[wgpu::BindGroupLayoutEntry {
+            binding: 0,
+            visibility: wgpu::ShaderStages::FRAGMENT,
+            ty: wgpu::BindingType::Buffer {
+                ty: wgpu::BufferBindingType::Uniform,
+                has_dynamic_offset: false,
+                min_binding_size: None,
+            },
+            count: None,
+        }],
+        label: Some("material bind group layout"),
+    };
+
     /// Asynchronously initialised the state and sets up the backend and surface for wgpu to render to.
     pub async fn new(window: Arc<Window>, instance: Arc<Instance>, future_queue: Arc<FutureQueue>) -> anyhow::Result<Self> {
         let title = window.title();
@@ -196,64 +214,15 @@ Hardware:
             surface.configure(&device, &config);
         }
 
-        let depth_texture = Texture::create_depth_texture(&config, &device, Some("depth texture"));
+        let depth_texture = Texture::depth_texture(&config, &device, Some("depth texture"));
         let viewport_texture =
-            Texture::create_viewport_texture(&config, &device, Some("viewport texture"));
+            Texture::viewport(&config, &device, Some("viewport texture"));
 
         let texture_bind_group_layout =
-            device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
-                entries: &[
-                    wgpu::BindGroupLayoutEntry {
-                        binding: 0,
-                        visibility: wgpu::ShaderStages::FRAGMENT,
-                        ty: wgpu::BindingType::Texture {
-                            multisampled: false,
-                            view_dimension: wgpu::TextureViewDimension::D2,
-                            sample_type: wgpu::TextureSampleType::Float { filterable: true },
-                        },
-                        count: None,
-                    },
-                    wgpu::BindGroupLayoutEntry {
-                        binding: 1,
-                        visibility: wgpu::ShaderStages::FRAGMENT,
-                        ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
-                        count: None,
-                    },
-                    // normal map
-                    wgpu::BindGroupLayoutEntry {
-                        binding: 2,
-                        visibility: wgpu::ShaderStages::FRAGMENT,
-                        ty: wgpu::BindingType::Texture {
-                            multisampled: false,
-                            sample_type: wgpu::TextureSampleType::Float { filterable: true },
-                            view_dimension: wgpu::TextureViewDimension::D2,
-                        },
-                        count: None,
-                    },
-                    wgpu::BindGroupLayoutEntry {
-                        binding: 3,
-                        visibility: wgpu::ShaderStages::FRAGMENT,
-                        ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
-                        count: None,
-                    },
-                ],
-                label: Some("texture_bind_group_layout"),
-            });
+            device.create_bind_group_layout(&texture::TEXTURE_BIND_GROUP_LAYOUT);
 
         let material_tint_bind_group_layout =
-            device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
-                entries: &[wgpu::BindGroupLayoutEntry {
-                    binding: 0,
-                    visibility: wgpu::ShaderStages::FRAGMENT,
-                    ty: wgpu::BindingType::Buffer {
-                        ty: wgpu::BufferBindingType::Uniform,
-                        has_dynamic_offset: false,
-                        min_binding_size: None,
-                    },
-                    count: None,
-                }],
-                label: Some("material_tint_bind_group_layout"),
-            });
+            device.create_bind_group_layout(&Self::MATERIAL_BIND_GROUP_LAYOUT);
 
         let mut egui_renderer = Arc::new(Mutex::new(EguiRenderer::new(
             &device,
@@ -269,6 +238,12 @@ Hardware:
             .renderer()
             .register_native_texture(&device, &viewport_texture.view, wgpu::FilterMode::Linear);
 
+        let camera_bind_group_layout = device.create_bind_group_layout(&Camera::CAMERA_BIND_GROUP_LAYOUT);
+        let light_bind_group_layout = device.create_bind_group_layout(&Light::LIGHT_BIND_GROUP_LAYOUT);
+        let light_array_bind_group_layout = device.create_bind_group_layout(&LightManager::LIGHT_ARRAY_BIND_GROUP_LAYOUT);
+        let light_cube_bind_group_layout = device
+            .create_bind_group_layout(&LightManager::LIGHT_CUBE_BIND_GROUP_LAYOUT);
+
         let result = Self {
             surface: Arc::new(surface),
             surface_format,
@@ -277,8 +252,6 @@ Hardware:
             config,
             is_surface_configured,
             depth_texture,
-            texture_bind_layout: texture_bind_group_layout,
-            material_tint_bind_layout: material_tint_bind_group_layout,
             window,
             egui_renderer,
             viewport_texture,
@@ -287,6 +260,14 @@ Hardware:
             instance,
             physics_accumulator: Duration::ZERO,
             scene_manager: scene::Manager::new(),
+            layouts: Arc::new(BindGroupLayouts {
+                texture_bind_layout: texture_bind_group_layout,
+                material_tint_bind_layout: material_tint_bind_group_layout,
+                camera_bind_group_layout,
+                light_bind_group_layout,
+                light_array_bind_group_layout,
+                light_cube_bind_group_layout,
+            }),
         };
 
         Ok(result)
@@ -302,9 +283,9 @@ Hardware:
         }
 
         self.depth_texture =
-            Texture::create_depth_texture(&self.config, &self.device, Some("depth texture"));
+            Texture::depth_texture(&self.config, &self.device, Some("depth texture"));
         self.viewport_texture =
-            Texture::create_viewport_texture(&self.config, &self.device, Some("viewport texture"));
+            Texture::viewport(&self.config, &self.device, Some("viewport texture"));
         self.egui_renderer
             .lock()
             .renderer()
@@ -322,6 +303,7 @@ Hardware:
         &mut self,
         previous_dt: f32,
         event_loop: &ActiveEventLoop,
+        graphics: Arc<SharedGraphicsContext>,
     ) -> anyhow::Result<Vec<scene::SceneCommand>> {
         if !self.is_surface_configured {
             return Ok(Vec::new());
@@ -371,7 +353,10 @@ Hardware:
                 label: Some("Render Encoder"),
             });
 
-        let viewport_view = { &self.viewport_texture.view.clone() };
+        let frame_ctx = FrameGraphicsContext {
+            view: view.clone(),
+            encoder: &mut encoder,
+        };
 
         self.egui_renderer.lock().begin_frame(&self.window);
 
@@ -382,11 +367,9 @@ Hardware:
         let mut physics_accumulator = self.physics_accumulator + frame_dt;
 
         let window_commands = {
-            let mut graphics = graphics::RenderContext::from_state(self, viewport_view, &mut encoder);
-
             let mut steps = 0usize;
             while physics_accumulator >= physics_dt && steps < MAX_PHYSICS_STEPS_PER_FRAME {
-                scene_manager.physics_update(physics_dt.as_secs_f32(), &mut graphics);
+                scene_manager.physics_update(physics_dt.as_secs_f32(), graphics.clone());
                 physics_accumulator -= physics_dt;
                 steps += 1;
             }
@@ -395,8 +378,8 @@ Hardware:
                 physics_accumulator = physics_accumulator.min(physics_dt);
             }
 
-            let commands = scene_manager.update(previous_dt, &mut graphics, event_loop);
-            scene_manager.render(&mut graphics);
+            let commands = scene_manager.update(previous_dt, graphics.clone(), event_loop);
+            scene_manager.render(graphics.clone(), frame_ctx);
             commands
         };
 
@@ -446,8 +429,7 @@ Hardware:
         drop(self.egui_renderer);
 
         drop(self.depth_texture);
-        drop(self.viewport_texture);
-        drop(self.texture_bind_layout);
+        drop(self.layouts);
 
         drop(self.surface);
 
@@ -732,7 +714,7 @@ pub struct App {
     instance: Arc<Instance>,
 
     // multi-window management
-    windows: HashMap<WindowId, State>,
+    windows: HashMap<WindowId, (State, Arc<SharedGraphicsContext>)>,
     root_window_id: Option<WindowId>,
     windows_to_create: Vec<WindowData>,
 }
@@ -787,7 +769,9 @@ impl App {
         let size = win_state.window.inner_size();
         win_state.resize(size.width, size.height);
 
-        self.windows.insert(window_id, win_state);
+        let graphics = Arc::new(graphics::SharedGraphicsContext::from_state(&win_state));
+
+        self.windows.insert(window_id, (win_state, graphics));
         Ok(window_id)
     }
 
@@ -800,7 +784,7 @@ impl App {
         log::info!("Exiting app!");
 
         let windows = std::mem::take(&mut self.windows);
-        for (_, state) in windows {
+        for (_, (state, _)) in windows {
             state.cleanup(event_loop);
         }
         self.root_window_id = None;
@@ -821,7 +805,7 @@ impl ApplicationHandler for App {
                 for window_data in windows_to_create {
                     match self.create_window(event_loop, window_data.attributes) {
                         Ok(window_id) => {
-                            if let Some(state) = self.windows.get_mut(&window_id) {
+                            if let Some((state, _)) = self.windows.get_mut(&window_id) {
                                 for (scene_name, scene) in window_data.scenes {
                                     state.scene_manager.add(&scene_name, scene.clone());
 
@@ -876,14 +860,14 @@ impl ApplicationHandler for App {
                 self.quit(event_loop, None);
             } else {
                 log::info!("Closing non-root window: {:?}", window_id);
-                if let Some(state) = self.windows.remove(&window_id) {
+                if let Some((state, _)) = self.windows.remove(&window_id) {
                     state.cleanup(event_loop);
                 }
             }
             return;
         }
 
-        let state = match self.windows.get_mut(&window_id) {
+        let (state, graphics) = match self.windows.get_mut(&window_id) {
             Some(canvas) => canvas,
             None => return,
         };
@@ -907,7 +891,7 @@ impl ApplicationHandler for App {
 
                 self.input_manager.update(&mut self.gilrs);
 
-                let render_result = state.render(self.delta_time, event_loop);
+                let render_result = state.render(self.delta_time, event_loop, graphics.clone());
 
                 let window_commands = render_result.unwrap_or_else(|e| {
                     log::error!("Render failed: {:?}", e);
@@ -933,7 +917,7 @@ impl ApplicationHandler for App {
                             log::info!("Scene requested new window creation");
                             match self.create_window(event_loop, window_data.attributes) {
                                 Ok(new_window_id) => {
-                                    if let Some(new_state) = self.windows.get_mut(&new_window_id) {
+                                    if let Some((new_state, _)) = self.windows.get_mut(&new_window_id) {
                                         for (scene_name, scene) in window_data.scenes {
                                             new_state.scene_manager.add(&scene_name, scene.clone());
 
@@ -983,7 +967,7 @@ impl ApplicationHandler for App {
                     }
                 }
 
-                for state in self.windows.values() {
+                for (state, _) in self.windows.values() {
                     state.window.request_redraw();
                 }
 
@@ -1034,7 +1018,7 @@ impl ApplicationHandler for App {
     }
 
     fn about_to_wait(&mut self, _event_loop: &ActiveEventLoop) {
-        for window_state in self.windows.values() {
+        for (window_state, _) in self.windows.values() {
             window_state.window.request_redraw();
         }
     }
diff --git a/crates/dropbear-engine/src/lighting.rs b/crates/dropbear-engine/src/lighting.rs
index 85a27d5..6131f67 100644
--- a/crates/dropbear-engine/src/lighting.rs
+++ b/crates/dropbear-engine/src/lighting.rs
@@ -1,9 +1,8 @@
 use crate::attenuation::{Attenuation, RANGE_50};
-use crate::buffer::ResizableBuffer;
+use crate::buffer::{ResizableBuffer, StorageBuffer, UniformBuffer};
 use crate::graphics::SharedGraphicsContext;
 use crate::shader::Shader;
 use crate::{
-    camera::Camera,
     entity::{EntityTransform, Transform},
     model::{self, Model, Vertex},
 };
@@ -12,7 +11,7 @@ use dropbear_traits::SerializableComponent;
 use glam::{DMat4, DQuat, DVec3};
 use std::fmt::{Display, Formatter};
 use std::sync::Arc;
-use wgpu::{BindGroup, BindGroupLayout, Buffer, BufferAddress, CompareFunction, DepthBiasState, RenderPipeline, StencilState, VertexBufferLayout, FilterMode};
+use wgpu::{BindGroup, Buffer, BufferAddress, CompareFunction, DepthBiasState, RenderPipeline, StencilState, VertexBufferLayout};
 
 pub const MAX_LIGHTS: usize = 8;
 
@@ -28,11 +27,6 @@ pub struct LightUniform {
     pub linear: f32,
     pub quadratic: f32,
     pub cutoff: f32,
-
-    pub shadow_index: i32,
-    pub _padding: [u32; 3],
-
-    pub(crate) proj: [[f32; 4]; 4],
 }
 
 fn dvec3_to_uniform_array(vec: DVec3) -> [f32; 4] {
@@ -68,9 +62,6 @@ impl Default for LightUniform {
             linear: 0.0,
             quadratic: 0.0,
             cutoff: f32::cos(12.5_f32.to_radians()),
-            shadow_index: -1,
-            _padding: [0; 3],
-            proj: glam::Mat4::IDENTITY.to_cols_array_2d(),
         }
     }
 }
@@ -251,13 +242,27 @@ pub struct Light {
     pub uniform: LightUniform,
     pub cube_model: Arc<Model>,
     pub label: String,
-    buffer: Option<Buffer>,
-    layout: Option<BindGroupLayout>,
-    bind_group: Option<BindGroup>,
+    pub buffer: UniformBuffer<LightUniform>,
+    pub bind_group: BindGroup,
     pub instance_buffer: ResizableBuffer<InstanceRaw>,
 }
 
 impl Light {
+    pub const LIGHT_BIND_GROUP_LAYOUT: wgpu::BindGroupLayoutDescriptor<'_> = 
+        wgpu::BindGroupLayoutDescriptor {
+            entries: &[wgpu::BindGroupLayoutEntry {
+                binding: 0,
+                visibility: wgpu::ShaderStages::VERTEX.union(wgpu::ShaderStages::FRAGMENT),
+                ty: wgpu::BindingType::Buffer {
+                    ty: wgpu::BufferBindingType::Uniform,
+                    has_dynamic_offset: false,
+                    min_binding_size: None,
+                },
+                count: None,
+            }],
+            label: Some("light bind group layout descriptor"),
+        };
+
     pub async fn new(
         graphics: Arc<SharedGraphicsContext>,
         light: LightComponent,
@@ -278,9 +283,6 @@ impl Light {
             linear: light.attenuation.linear,
             quadratic: light.attenuation.quadratic,
             cutoff: f32::cos(light.cutoff_angle.to_radians()),
-            shadow_index: -1,
-            _padding: Default::default(),
-            proj: Default::default(),
         };
 
         log::trace!("Created new light uniform");
@@ -297,31 +299,15 @@ impl Light {
 
         let label_str = label.unwrap_or("Light").to_string();
 
-        let buffer = graphics.create_uniform(uniform, label);
-
-        let layout = graphics
-            .device
-            .create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
-                entries: &[wgpu::BindGroupLayoutEntry {
-                    binding: 0,
-                    visibility: wgpu::ShaderStages::VERTEX | wgpu::ShaderStages::FRAGMENT,
-                    ty: wgpu::BindingType::Buffer {
-                        ty: wgpu::BufferBindingType::Uniform,
-                        has_dynamic_offset: false,
-                        min_binding_size: None,
-                    },
-                    count: None,
-                }],
-                label,
-            });
+        let buffer = UniformBuffer::new(&graphics.device, &label_str);
 
         let bind_group = graphics
             .device
             .create_bind_group(&wgpu::BindGroupDescriptor {
-                layout: &layout,
+                layout: &graphics.layouts.light_bind_group_layout,
                 entries: &[wgpu::BindGroupEntry {
                     binding: 0,
-                    resource: buffer.as_entire_binding(),
+                    resource: buffer.buffer().as_entire_binding(),
                 }],
                 label,
             });
@@ -347,9 +333,8 @@ impl Light {
             uniform,
             cube_model,
             label: label_str,
-            buffer: Some(buffer),
-            layout: Some(layout),
-            bind_group: Some(bind_group),
+            buffer,
+            bind_group,
             instance_buffer,
         }
     }
@@ -370,48 +355,7 @@ impl Light {
 
         self.uniform.cutoff = f32::cos(light.cutoff_angle.to_radians());
 
-        let safe_up = if direction.normalize_or_zero().dot(DVec3::Y).abs() > 0.99 {
-            DVec3::Z
-        } else {
-            DVec3::Y
-        };
-
-        let view = glam::DMat4::look_at_lh(
-            transform.position,
-            transform.position + direction,
-            safe_up,
-        );
-
-        let projection = match light.light_type {
-            LightType::Directional => {
-                let extent = 50.0;
-                glam::DMat4::orthographic_lh(
-                    -extent,
-                    extent,
-                    -extent,
-                    extent,
-                    light.depth.start as f64,
-                    light.depth.end as f64,
-                )
-            }
-            LightType::Spot => glam::DMat4::perspective_lh(
-                light.outer_cutoff_angle.to_radians() as f64 * 2.0,
-                1.0,
-                light.depth.start as f64,
-                light.depth.end as f64,
-            ),
-            // Point light shadows require cubemaps; not supported here.
-            LightType::Point => glam::DMat4::IDENTITY,
-        };
-
-        let light_vp = projection * view;
-        self.uniform.proj = light_vp.as_mat4().to_cols_array_2d();
-
-        if let Some(buffer) = &self.buffer {
-            graphics
-                .queue
-                .write_buffer(buffer, 0, bytemuck::cast_slice(&[self.uniform]));
-        }
+        self.buffer.write(&graphics.queue, &self.uniform);
     }
 
     pub fn uniform(&self) -> &LightUniform {
@@ -425,32 +369,13 @@ impl Light {
     pub fn label(&self) -> &str {
         &self.label
     }
-
-    pub fn bind_group(&self) -> &BindGroup {
-        self.bind_group.as_ref().unwrap()
-    }
-
-    pub fn layout(&self) -> &BindGroupLayout {
-        self.layout.as_ref().unwrap()
-    }
-
-    pub fn buffer(&self) -> &Buffer {
-        self.buffer.as_ref().unwrap()
-    }
 }
 
 #[derive(Clone)]
 pub struct LightManager {
     pub pipeline: Option<RenderPipeline>,
-    pub shadow_pipeline: Option<RenderPipeline>,
-    light_array_buffer: Option<Buffer>,
+    light_array_buffer: Option<StorageBuffer<LightArrayUniform>>,
     light_array_bind_group: Option<BindGroup>,
-    light_array_layout: Option<BindGroupLayout>,
-
-    pub shadow_texture: Option<wgpu::Texture>,
-    pub shadow_view: Option<wgpu::TextureView>,
-    pub shadow_sampler: Option<wgpu::Sampler>,
-    pub shadow_target_views: Vec<wgpu::TextureView>,
 }
 
 impl Default for LightManager {
@@ -460,216 +385,66 @@ impl Default for LightManager {
 }
 
 impl LightManager {
-    pub const SHADOW_FORMAT: wgpu::TextureFormat = wgpu::TextureFormat::Depth32Float;
-    pub const SHADOW_SIZE: u32 = 2048;
-
-    pub fn new() -> Self {
-        log::info!("Initialised lighting");
-        Self {
-            pipeline: None,
-            shadow_pipeline: None,
-            light_array_buffer: None,
-            light_array_bind_group: None,
-            light_array_layout: None,
-            shadow_texture: None,
-            shadow_view: None,
-            shadow_sampler: None,
-            shadow_target_views: vec![],
-        }
-    }
-
-    pub fn create_shadow_pipeline(
-        &mut self,
-        graphics: Arc<SharedGraphicsContext>,
-        shader_contents: &str,
-        label: Option<&str>,
-    ) {
-        let shader = Shader::new(graphics.clone(), shader_contents, label);
-
-        // Layout compatible with `Light::bind_group()` (single uniform buffer).
-        let per_light_layout = graphics
-            .device
-            .create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
-                entries: &[wgpu::BindGroupLayoutEntry {
+    pub const LIGHT_ARRAY_BIND_GROUP_LAYOUT: wgpu::BindGroupLayoutDescriptor<'_> = 
+        wgpu::BindGroupLayoutDescriptor {
+            entries: &[
+                // light data
+                wgpu::BindGroupLayoutEntry {
                     binding: 0,
-                    visibility: wgpu::ShaderStages::VERTEX | wgpu::ShaderStages::FRAGMENT,
+                    visibility: wgpu::ShaderStages::VERTEX.union(wgpu::ShaderStages::FRAGMENT),
                     ty: wgpu::BindingType::Buffer {
-                        ty: wgpu::BufferBindingType::Uniform,
+                        ty: wgpu::BufferBindingType::Storage { read_only: true },
                         has_dynamic_offset: false,
                         min_binding_size: None,
                     },
                     count: None,
-                }],
-                label: Some("Shadow Per-Light Layout"),
-            });
-
-        let pipeline_layout = graphics
-            .device
-            .create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
-                label: Some(label.unwrap_or("Shadow Pipeline Layout")),
-                bind_group_layouts: &[&per_light_layout],
-                push_constant_ranges: &[],
-            });
-
-        let pipeline = graphics
-            .device
-            .create_render_pipeline(&wgpu::RenderPipelineDescriptor {
-                label: Some(label.unwrap_or("Shadow Pipeline")),
-                layout: Some(&pipeline_layout),
-                vertex: wgpu::VertexState {
-                    module: &shader.module,
-                    entry_point: Some("vs_main"),
-                    buffers: &[model::ModelVertex::desc(), InstanceRaw::desc()],
-                    compilation_options: Default::default(),
                 },
-                fragment: None,
-                primitive: wgpu::PrimitiveState {
-                    topology: wgpu::PrimitiveTopology::TriangleList,
-                    strip_index_format: None,
-                    front_face: wgpu::FrontFace::Ccw,
-                    cull_mode: Some(wgpu::Face::Front),
-                    polygon_mode: wgpu::PolygonMode::Fill,
-                    unclipped_depth: false,
-                    conservative: false,
-                },
-                depth_stencil: Some(wgpu::DepthStencilState {
-                    format: Self::SHADOW_FORMAT,
-                    depth_write_enabled: true,
-                    depth_compare: wgpu::CompareFunction::LessEqual,
-                    stencil: wgpu::StencilState::default(),
-                    bias: wgpu::DepthBiasState {
-                        constant: 2,
-                        slope_scale: 2.0,
-                        clamp: 0.0,
-                    },
-                }),
-                multisample: wgpu::MultisampleState {
-                    count: 1,
-                    mask: !0,
-                    alpha_to_coverage_enabled: false,
+            ],
+            label: Some("Light Array Layout"),
+        };
+    pub const LIGHT_CUBE_BIND_GROUP_LAYOUT: wgpu::BindGroupLayoutDescriptor<'_> = 
+        wgpu::BindGroupLayoutDescriptor {
+            entries: &[wgpu::BindGroupLayoutEntry {
+                binding: 0,
+                visibility: wgpu::ShaderStages::VERTEX.union(wgpu::ShaderStages::FRAGMENT),
+                ty: wgpu::BindingType::Buffer {
+                    ty: wgpu::BufferBindingType::Uniform,
+                    has_dynamic_offset: false,
+                    min_binding_size: None,
                 },
-                multiview: None,
-                cache: None,
-            });
-
-        self.shadow_pipeline = Some(pipeline);
-        log::debug!("Created shadow render pipeline");
+                count: None,
+            }],
+            label: Some("Per-Light Layout"),
+        };
+    
+    pub fn new() -> Self {
+        log::info!("Initialised lighting");
+        Self {
+            pipeline: None,
+            light_array_buffer: None,
+            light_array_bind_group: None,
+        }
     }
 
     pub fn create_light_array_resources(&mut self, graphics: Arc<SharedGraphicsContext>) {
-        let shadow_texture = graphics.device.create_texture(&wgpu::TextureDescriptor {
-            label: Some("Shadow Map Array"),
-            size: wgpu::Extent3d {
-                width: Self::SHADOW_SIZE,
-                height: Self::SHADOW_SIZE,
-                depth_or_array_layers: MAX_LIGHTS as u32,
-            },
-            mip_level_count: 1,
-            sample_count: 1,
-            dimension: wgpu::TextureDimension::D2,
-            format: Self::SHADOW_FORMAT,
-            usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::TEXTURE_BINDING,
-            view_formats: &[],
-        });
-
-        let shadow_view = shadow_texture.create_view(&wgpu::TextureViewDescriptor {
-            label: Some("Shadow Array View"),
-            dimension: Some(wgpu::TextureViewDimension::D2Array),
-            ..Default::default()
-        });
-
-        let shadow_sampler = graphics.device.create_sampler(&wgpu::SamplerDescriptor {
-            label: Some("Shadow Sampler"),
-            address_mode_u: wgpu::AddressMode::ClampToEdge,
-            address_mode_v: wgpu::AddressMode::ClampToEdge,
-            address_mode_w: wgpu::AddressMode::ClampToEdge,
-            mag_filter: wgpu::FilterMode::Linear,
-            min_filter: wgpu::FilterMode::Linear,
-            mipmap_filter: FilterMode::Nearest,
-            compare: Some(wgpu::CompareFunction::LessEqual),
-            ..Default::default()
-        });
-
-        self.shadow_target_views = (0..MAX_LIGHTS)
-            .map(|i| {
-                shadow_texture.create_view(&wgpu::TextureViewDescriptor {
-                    label: Some(&format!("Shadow Layer {}", i)),
-                    dimension: Some(wgpu::TextureViewDimension::D2),
-                    base_array_layer: i as u32,
-                    array_layer_count: Some(1),
-                    ..Default::default()
-                })
-            })
-            .collect();
-
-        let layout = graphics
-            .device
-            .create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
-                entries: &[
-                    // light data
-                    wgpu::BindGroupLayoutEntry {
-                        binding: 0,
-                        visibility: wgpu::ShaderStages::VERTEX | wgpu::ShaderStages::FRAGMENT,
-                        ty: wgpu::BindingType::Buffer {
-                            ty: wgpu::BufferBindingType::Uniform,
-                            has_dynamic_offset: false,
-                            min_binding_size: None,
-                        },
-                        count: None,
-                    },
-                    // shadow texture array
-                    wgpu::BindGroupLayoutEntry {
-                        binding: 1,
-                        visibility: wgpu::ShaderStages::FRAGMENT,
-                        ty: wgpu::BindingType::Texture {
-                            multisampled: false,
-                            sample_type: wgpu::TextureSampleType::Depth,
-                            view_dimension: wgpu::TextureViewDimension::D2Array,
-                        },
-                        count: None,
-                    },
-                    // shadow sampler
-                    wgpu::BindGroupLayoutEntry {
-                        binding: 2,
-                        visibility: wgpu::ShaderStages::FRAGMENT,
-                        ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Comparison),
-                        count: None,
-                    },
-                ],
-                label: Some("Light Array Layout"),
-            });
-
-        let buffer = graphics.create_uniform(LightArrayUniform::default(), Some("Light Array"));
+        let buffer = StorageBuffer::new(&graphics.device, "Light Array");
 
         let bind_group = graphics
             .device
             .create_bind_group(&wgpu::BindGroupDescriptor {
-                layout: &layout,
+                layout: &graphics.layouts.light_array_bind_group_layout,
                 entries: &[
                     wgpu::BindGroupEntry {
                         binding: 0,
-                        resource: buffer.as_entire_binding(),
-                    },
-                    wgpu::BindGroupEntry {
-                        binding: 1,
-                        resource: wgpu::BindingResource::TextureView(&shadow_view),
-                    },
-                    wgpu::BindGroupEntry {
-                        binding: 2,
-                        resource: wgpu::BindingResource::Sampler(&shadow_sampler),
+                        resource: buffer.buffer().as_entire_binding(),
                     },
                 ],
                 label: Some("Light Array Bind Group"),
             });
 
-        self.light_array_layout = Some(layout);
         self.light_array_buffer = Some(buffer);
         self.light_array_bind_group = Some(bind_group);
 
-        self.shadow_texture = Some(shadow_texture);
-        self.shadow_view = Some(shadow_view);
-        self.shadow_sampler = Some(shadow_sampler);
-
         log::debug!("Created light array resources");
     }
 
@@ -677,8 +452,6 @@ impl LightManager {
         let mut light_array = LightArrayUniform::default();
         let mut light_index = 0;
 
-        let mut shadow_map_index = 0;
-
         for (light_component, s_trans, e_trans, light) in world
             .query::<(&LightComponent, Option<&Transform>, Option<&EntityTransform>, &mut Light)>()
             .iter()
@@ -695,23 +468,9 @@ impl LightManager {
             light.instance_buffer.write(&graphics.device, &graphics.queue, &[instance.to_raw()]);
 
             if light_component.enabled && light_index < MAX_LIGHTS {
-                let mut uniform = *light.uniform();
-
-                if light_component.cast_shadows
-                    && light_component.light_type != LightType::Point
-                    && shadow_map_index < MAX_LIGHTS
-                {
-                    uniform.shadow_index = shadow_map_index as i32;
-                    shadow_map_index += 1;
-                } else {
-                    uniform.shadow_index = -1;
-                }
-
-                // Keep per-light uniform in sync (used by shadow pass and light cube rendering).
-                light.uniform.shadow_index = uniform.shadow_index;
-                graphics
-                    .queue
-                    .write_buffer(light.buffer(), 0, bytemuck::cast_slice(&[light.uniform]));
+                let uniform = *light.uniform();
+
+                light.buffer.write(&graphics.queue, &uniform);
 
                 light_array.lights[light_index] = uniform;
                 light_index += 1;
@@ -719,20 +478,13 @@ impl LightManager {
         }
 
         light_array.light_count = light_index as u32;
-
-        if let Some(buffer) = &self.light_array_buffer {
-            graphics
-                .queue
-                .write_buffer(buffer, 0, bytemuck::cast_slice(&[light_array]));
+        if let Some(buf) = &self.light_array_buffer {
+            buf.write(&graphics.queue, &light_array);
         }
 
         log_once::debug_once!("LightUniform size = {}", size_of::<LightUniform>());
     }
 
-    pub fn layout(&self) -> &BindGroupLayout {
-        self.light_array_layout.as_ref().unwrap()
-    }
-
     pub fn bind_group(&self) -> &BindGroup {
         self.light_array_bind_group.as_ref().unwrap()
     }
@@ -741,33 +493,16 @@ impl LightManager {
         &mut self,
         graphics: Arc<SharedGraphicsContext>,
         shader_contents: &str,
-        camera: &Camera,
         label: Option<&str>,
     ) {
         use crate::shader::Shader;
 
         let shader = Shader::new(graphics.clone(), shader_contents, label);
 
-        let per_light_layout = graphics
-            .device
-            .create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
-                entries: &[wgpu::BindGroupLayoutEntry {
-                    binding: 0,
-                    visibility: wgpu::ShaderStages::VERTEX | wgpu::ShaderStages::FRAGMENT,
-                    ty: wgpu::BindingType::Buffer {
-                        ty: wgpu::BufferBindingType::Uniform,
-                        has_dynamic_offset: false,
-                        min_binding_size: None,
-                    },
-                    count: None,
-                }],
-                label: Some("Per-Light Layout"),
-            });
-
+        // the light cube rendering
         let pipeline = Self::create_render_pipeline_for_lighting(
             graphics,
             &shader,
-            vec![camera.layout(), &per_light_layout],
             label,
         );
 
@@ -775,10 +510,10 @@ impl LightManager {
         log::debug!("Created ECS light render pipeline");
     }
 
+    // light cube rendering
     fn create_render_pipeline_for_lighting(
         graphics: Arc<SharedGraphicsContext>,
         shader: &Shader,
-        bind_group_layouts: Vec<&BindGroupLayout>,
         label: Option<&str>,
     ) -> RenderPipeline {
         let render_pipeline_layout =
@@ -786,7 +521,7 @@ impl LightManager {
                 .device
                 .create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
                     label: Some(label.unwrap_or("Light Render Pipeline Descriptor")),
-                    bind_group_layouts: bind_group_layouts.as_slice(),
+                    bind_group_layouts: &[&graphics.layouts.camera_bind_group_layout, &graphics.layouts.light_cube_bind_group_layout],
                     push_constant_ranges: &[],
                 });
 
diff --git a/crates/dropbear-engine/src/mipmap.rs b/crates/dropbear-engine/src/mipmap.rs
deleted file mode 100644
index 848541c..0000000
--- a/crates/dropbear-engine/src/mipmap.rs
+++ /dev/null
@@ -1,156 +0,0 @@
-use std::sync::Arc;
-
-use wgpu::TextureFormat;
-
-use crate::{graphics::SharedGraphicsContext, shader::Shader};
-
-pub struct MipMapGenerator {
-    pipeline: wgpu::RenderPipeline,
-    bind_group_layout: wgpu::BindGroupLayout,
-    sampler: wgpu::Sampler,
-}
-
-impl MipMapGenerator {
-    pub fn new(graphics: Arc<SharedGraphicsContext>) -> Self {
-        let shader = Shader::new(graphics.clone(), crate::shader::shader_wesl::MIPMAP_SHADER, Some("mipmap shader")); 
-
-        let bind_group_layout = graphics.device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
-            label: Some("mipmap_bind_group_layout"),
-            entries: &[
-                wgpu::BindGroupLayoutEntry {
-                    binding: 0,
-                    visibility: wgpu::ShaderStages::FRAGMENT,
-                    ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering), 
-                    count: None, 
-                },
-                wgpu::BindGroupLayoutEntry {
-                    binding: 1,
-                    visibility: wgpu::ShaderStages::FRAGMENT,
-                    ty: wgpu::BindingType::Texture {
-                        multisampled: false,
-                        view_dimension: wgpu::TextureViewDimension::D2,
-                        sample_type: wgpu::TextureSampleType::Float { filterable: true },
-                    },
-                    count: None,
-                },
-            ],
-        });
-
-        let pipeline_layout = graphics.device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
-            label: Some("mipmap_pipeline_layout"),
-            bind_group_layouts: &[&bind_group_layout],
-            push_constant_ranges: &[],
-        });
-
-        let pipeline = graphics.device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
-            label: Some("mipmap_pipeline"),
-            layout: Some(&pipeline_layout),
-            cache: None,
-            vertex: wgpu::VertexState {
-                module: &shader.module,
-                entry_point: Some("vs_main"),
-                buffers: &[],
-                compilation_options: Default::default(),
-            },
-            fragment: Some(wgpu::FragmentState {
-                module: &shader.module,
-                entry_point: Some("fs_main"),
-                targets: &[Some(wgpu::ColorTargetState {
-                    format: TextureFormat::Rgba16Float,
-                    blend: None,
-                    write_mask: wgpu::ColorWrites::ALL,
-                })],
-                compilation_options: Default::default(),
-            }),
-            primitive: wgpu::PrimitiveState {
-                topology: wgpu::PrimitiveTopology::TriangleStrip,
-                strip_index_format: Some(wgpu::IndexFormat::Uint32),
-                ..Default::default()
-            },
-            depth_stencil: None,
-            multisample: wgpu::MultisampleState::default(),
-            multiview: None,
-        });
-
-        let sampler = graphics.device.create_sampler(&wgpu::SamplerDescriptor {
-            label: Some("mipmap_sampler"),
-            address_mode_u: wgpu::AddressMode::ClampToEdge,
-            address_mode_v: wgpu::AddressMode::ClampToEdge,
-            address_mode_w: wgpu::AddressMode::ClampToEdge,
-            mag_filter: wgpu::FilterMode::Linear,
-            min_filter: wgpu::FilterMode::Linear,
-            mipmap_filter: wgpu::FilterMode::Nearest,
-            ..Default::default()
-        });
-
-        Self {
-            pipeline,
-            bind_group_layout,
-            sampler,
-        }
-    }
-
-    /// generates the mipmaps. yeah!
-    pub fn generate(
-        &self,
-        device: &wgpu::Device,
-        encoder: &mut wgpu::CommandEncoder,
-        texture: &wgpu::Texture,
-    ) {
-        let mip_count = texture.mip_level_count();
-        if mip_count < 2 { return; }
-
-        let mut src_view = texture.create_view(&wgpu::TextureViewDescriptor {
-            base_mip_level: 0,
-            mip_level_count: Some(1),
-            ..Default::default()
-        });
-
-        for i in 1..mip_count {
-            let dst_view = texture.create_view(&wgpu::TextureViewDescriptor {
-                base_mip_level: i,
-                mip_level_count: Some(1),
-                ..Default::default()
-            });
-
-            let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
-                label: Some("mipmap_bind_group"),
-                layout: &self.bind_group_layout,
-                entries: &[
-                    wgpu::BindGroupEntry {
-                        binding: 0,
-                        resource: wgpu::BindingResource::Sampler(&self.sampler),
-                    },
-                    wgpu::BindGroupEntry {
-                        binding: 1,
-                        resource: wgpu::BindingResource::TextureView(&src_view),
-                    },
-                ],
-            });
-
-            let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
-                label: Some("mipmap_pass"),
-                color_attachments: &[Some(wgpu::RenderPassColorAttachment {
-                    view: &dst_view,
-                    resolve_target: None,
-                    ops: wgpu::Operations {
-                        load: wgpu::LoadOp::Clear(wgpu::Color::BLACK),
-                        store: wgpu::StoreOp::Store,
-                    },
-                    depth_slice: None,
-                })],
-                depth_stencil_attachment: None,
-                timestamp_writes: None,
-                occlusion_query_set: None,
-            });
-
-            pass.set_pipeline(&self.pipeline);
-            pass.set_bind_group(0, &bind_group, &[]);
-            pass.draw(0..4, 0..1);
-            
-            drop(pass);
-
-            src_view = dst_view;
-        }
-    }
-}
\ No newline at end of file
diff --git a/crates/dropbear-engine/src/model.rs b/crates/dropbear-engine/src/model.rs
index f30b6a1..6966208 100644
--- a/crates/dropbear-engine/src/model.rs
+++ b/crates/dropbear-engine/src/model.rs
@@ -1,8 +1,10 @@
 use crate::asset::AssetRegistry;
+use crate::buffer::UniformBuffer;
 use crate::{
     asset::{ASSET_REGISTRY, AssetHandle},
-    graphics::{SharedGraphicsContext, Texture},
-    utils::{ResourceReference, TextureWrapMode},
+    graphics::{SharedGraphicsContext},
+    utils::{ResourceReference},
+    texture::{Texture, TextureWrapMode}
 };
 use image::GenericImageView;
 use parking_lot::Mutex;
@@ -182,7 +184,7 @@ pub struct Material {
     pub bind_group: wgpu::BindGroup,
     pub tint: [f32; 4],
     pub uv_tiling: [f32; 2],
-    pub tint_buffer: wgpu::Buffer,
+    pub tint_buffer: UniformBuffer<MaterialUniform>,
     pub tint_bind_group: wgpu::BindGroup,
     pub texture_tag: Option<String>,
     pub wrap_mode: TextureWrapMode,
@@ -214,14 +216,16 @@ impl Material {
             uv_tiling,
             _pad: [0.0, 0.0],
         };
-        let tint_buffer = graphics.create_uniform(uniform, Some("material_tint_uniform"));
+        
+        let tint_buffer = UniformBuffer::new(&graphics.device, "material_tint_uniform");
+        tint_buffer.write(&graphics.queue, &uniform);
         let tint_bind_group = graphics.device.create_bind_group(&wgpu::BindGroupDescriptor {
-            layout: &graphics.material_tint_bind_layout,
+            layout: &graphics.layouts.material_tint_bind_layout,
             entries: &[wgpu::BindGroupEntry {
                 binding: 0,
-                resource: tint_buffer.as_entire_binding(),
+                resource: tint_buffer.buffer().as_entire_binding(),
             }],
-            label: Some("material_tint_bind_group"),
+            label: Some("material tint bind group"),
         });
 
         let bind_group = Self::create_bind_group(&graphics, &diffuse_texture, &normal_texture, &name);
@@ -247,7 +251,7 @@ impl Material {
         name: &str,
     ) -> BindGroup {
         graphics.device.create_bind_group(&wgpu::BindGroupDescriptor {
-            layout: &graphics.texture_bind_layout,
+            layout: &graphics.layouts.texture_bind_layout,
             entries: &[
                 wgpu::BindGroupEntry {
                     binding: 0,
@@ -277,9 +281,8 @@ impl Material {
             uv_tiling: self.uv_tiling,
             _pad: [0.0, 0.0],
         };
-        graphics
-            .queue
-            .write_buffer(&self.tint_buffer, 0, bytemuck::bytes_of(&uniform));
+
+        self.tint_buffer.write(&graphics.queue, &uniform);
     }
 
     pub fn set_uv_tiling(&mut self, graphics: &SharedGraphicsContext, tiling: [f32; 2]) {
@@ -289,9 +292,7 @@ impl Material {
             uv_tiling: tiling,
             _pad: [0.0, 0.0],
         };
-        graphics
-            .queue
-            .write_buffer(&self.tint_buffer, 0, bytemuck::bytes_of(&uniform));
+        self.tint_buffer.write(&graphics.queue, &uniform);
     }
 }
 
@@ -593,13 +594,13 @@ impl Model {
             let start = Instant::now();
 
             let diffuse_texture = if let Some((rgba_data, dimensions)) = processed_diffuse {
-                Texture::from_rgba_buffer(graphics.clone(), &rgba_data, dimensions)
+                Texture::from_bytes_verbose(&graphics.device, &graphics.queue, &rgba_data, Some(dimensions), None, None, None)
             } else {
                 (*grey_texture).clone()
             };
 
             let normal_texture = if let Some((rgba_data, dimensions)) = processed_normal {
-                Texture::from_rgba_buffer(graphics.clone(), &rgba_data, dimensions)
+                Texture::from_bytes_verbose(&graphics.device, &graphics.queue, &rgba_data, Some(dimensions), None, None, None)
             } else {
                 (*flat_normal_texture).clone()
             };
diff --git a/crates/dropbear-engine/src/procedural/plane.rs b/crates/dropbear-engine/src/procedural/plane.rs
deleted file mode 100644
index 6539c3e..0000000
--- a/crates/dropbear-engine/src/procedural/plane.rs
+++ /dev/null
@@ -1,186 +0,0 @@
-//! A straight plane (and some components). That's it.
-//!
-//! Inspiration taken from `https://github.com/tirbofish/RedLight/blob/main/src/RedLight/Entities/Plane.cs`,
-//! my old game engine made in C sharp, where this is the plane "algorithm".
-
-use crate::asset::{ASSET_REGISTRY, AssetRegistry};
-use crate::entity::MeshRenderer;
-use crate::graphics::{SharedGraphicsContext, Texture};
-use crate::model::{LoadedModel, MODEL_CACHE, Material, Mesh, Model, ModelId, ModelVertex};
-use crate::utils::{ResourceReference, ResourceReferenceType};
-use parking_lot::Mutex;
-use std::collections::HashMap;
-use std::hash::{DefaultHasher, Hash, Hasher};
-use std::sync::{Arc, LazyLock};
-use wgpu::{AddressMode, util::DeviceExt};
-
-/// Creates a plane wrapped in a [`MeshRenderer`].
-pub struct PlaneBuilder {
-    width: f32,
-    height: f32,
-    tiles_x: u32,
-    tiles_z: u32,
-}
-
-impl Default for PlaneBuilder {
-    fn default() -> Self {
-        Self::new()
-    }
-}
-
-impl PlaneBuilder {
-    pub fn new() -> Self {
-        Self {
-            width: 10.0,
-            height: 10.0,
-            tiles_x: 0,
-            tiles_z: 0,
-        }
-    }
-
-    pub fn with_size(mut self, width: f32, height: f32) -> Self {
-        self.width = width;
-        self.height = height;
-        self
-    }
-
-    pub fn with_tiles(mut self, tiles_x: u32, tiles_z: u32) -> Self {
-        self.tiles_x = tiles_x;
-        self.tiles_z = tiles_z;
-        self
-    }
-
-    pub async fn build(
-        self,
-        graphics: Arc<SharedGraphicsContext>,
-        texture_bytes: &[u8],
-        label: Option<&str>,
-    ) -> anyhow::Result<MeshRenderer> {
-        self.build_raw(
-            graphics,
-            texture_bytes,
-            label,
-            &ASSET_REGISTRY,
-            LazyLock::force(&MODEL_CACHE),
-        )
-        .await
-    }
-
-    pub async fn build_raw(
-        mut self,
-        graphics: Arc<SharedGraphicsContext>,
-        texture_bytes: &[u8],
-        label: Option<&str>,
-        registry: &AssetRegistry,
-        cache: &Mutex<HashMap<String, Arc<Model>>>,
-    ) -> anyhow::Result<MeshRenderer> {
-        let label = if let Some(label) = label {
-            label.to_string()
-        } else {
-            format!(
-                "{}*{}_tx{}xtz{}_plane",
-                self.width, self.height, self.tiles_x, self.tiles_z
-            )
-        };
-        let mut hasher = DefaultHasher::new();
-        if self.tiles_x == 0 && self.tiles_z == 0 {
-            self.tiles_x = self.width as u32;
-            self.tiles_z = self.height as u32;
-        }
-        let mut vertices = Vec::new();
-        let mut indices = Vec::new();
-
-        for z in 0..=1 {
-            for x in 0..=1 {
-                let position = [
-                    (x as f32 - 0.5) * self.width,
-                    0.0,
-                    (z as f32 - 0.5) * self.height,
-                ];
-                let normal = [0.0, 1.0, 0.0];
-                let tex_coords = [
-                    x as f32 * self.tiles_x as f32,
-                    z as f32 * self.tiles_z as f32,
-                ];
-                let _ = position.iter().map(|v| (*v as i32).hash(&mut hasher));
-                let _ = normal.iter().map(|v| (*v as i32).hash(&mut hasher));
-                let _ = tex_coords.iter().map(|v| (*v as i32).hash(&mut hasher));
-
-                vertices.push(ModelVertex {
-                    position,
-                    tex_coords,
-                    normal,
-                    tangent: [1.0, 0.0, 0.0],
-                    bitangent: [0.0, 0.0, 1.0],
-                });
-            }
-        }
-
-        indices.extend_from_slice(&[0, 2, 1, 1, 2, 3]);
-        indices.hash(&mut hasher);
-
-        let hash = hasher.finish();
-
-        if let Some(cached_model) = {
-            let cache_guard = cache.lock();
-            cache_guard.get(&label).cloned()
-        } {
-            log::debug!("Model loaded from cache: {:?}", label);
-            let handle = LoadedModel::new_raw(registry, cached_model);
-            return Ok(MeshRenderer::from_handle(handle));
-        }
-
-        let vertex_buffer = graphics
-            .device
-            .create_buffer_init(&wgpu::util::BufferInitDescriptor {
-                label: Some(&format!("{:?} Vertex Buffer", label.clone())),
-                contents: bytemuck::cast_slice(&vertices),
-                usage: wgpu::BufferUsages::VERTEX,
-            });
-        let index_buffer = graphics
-            .device
-            .create_buffer_init(&wgpu::util::BufferInitDescriptor {
-                label: Some(&format!("{:?} Index Buffer", label)),
-                contents: bytemuck::cast_slice(&indices),
-                usage: wgpu::BufferUsages::INDEX,
-            });
-
-        let mesh = Mesh {
-            name: "plane".to_string(),
-            vertex_buffer,
-            index_buffer,
-            num_elements: indices.len() as u32,
-            material: 0,
-        };
-
-        let diffuse_texture =
-            Texture::new_with_sampler(graphics.clone(), texture_bytes, AddressMode::Repeat);
-        let normal_texture = (*registry
-            .solid_texture_rgba8(graphics.clone(), [128, 128, 255, 255]))
-            .clone();
-        let material = Material::new_with_tint(
-            graphics.clone(),
-            "plane_material",
-            diffuse_texture,
-            normal_texture,
-            [1.0, 1.0, 1.0, 1.0],
-            Some("plane_material".to_string()),
-        );
-
-        let model = Arc::new(Model {
-            label: label.clone(),
-            path: ResourceReference::from_reference(ResourceReferenceType::Plane),
-            meshes: vec![mesh],
-            materials: vec![material],
-            id: ModelId(hash),
-        });
-
-        {
-            let mut cache_guard = cache.lock();
-            cache_guard.insert(label.clone(), Arc::clone(&model));
-        }
-
-        let handle = LoadedModel::new_raw(registry, model);
-        Ok(MeshRenderer::from_handle(handle))
-    }
-}
diff --git a/crates/dropbear-engine/src/scene.rs b/crates/dropbear-engine/src/scene.rs
index 141f08c..a1a1082 100644
--- a/crates/dropbear-engine/src/scene.rs
+++ b/crates/dropbear-engine/src/scene.rs
@@ -5,15 +5,15 @@
 use winit::event_loop::ActiveEventLoop;
 use winit::window::WindowId;
 
-use crate::{input, WindowData};
+use crate::{WindowData, graphics::{FrameGraphicsContext, SharedGraphicsContext}, input};
 use parking_lot::RwLock;
-use std::{collections::HashMap, rc::Rc};
+use std::{collections::HashMap, rc::Rc, sync::Arc};
 
 pub trait Scene {
-    fn load(&mut self, graphics: &mut crate::graphics::RenderContext);
-    fn physics_update(&mut self, dt: f32, graphics: &mut crate::graphics::RenderContext);
-    fn update(&mut self, dt: f32, graphics: &mut crate::graphics::RenderContext);
-    fn render(&mut self, graphics: &mut crate::graphics::RenderContext);
+    fn load(&mut self, graphics: Arc<SharedGraphicsContext>);
+    fn physics_update(&mut self, dt: f32, graphics: Arc<SharedGraphicsContext>);
+    fn update(&mut self, dt: f32, graphics: Arc<SharedGraphicsContext>);
+    fn render<'a>(&mut self, graphics: Arc<SharedGraphicsContext>, frame_ctx: FrameGraphicsContext<'a>);
     fn exit(&mut self, event_loop: &ActiveEventLoop);
     /// By far a mess of a trait however it works.
     ///
@@ -22,7 +22,6 @@ pub trait Scene {
     fn run_command(&mut self) -> SceneCommand {
         SceneCommand::None
     }
-    fn clear_ui(&mut self) {}
 }
 
 #[derive(Clone)]
@@ -68,7 +67,6 @@ impl Manager {
         }
     }
 
-    /// Switches the scene from the current one to another.
     pub fn switch(&mut self, name: &str) {
         if self.scenes.contains_key(name) {
             self.next_scene = Some(name.to_string());
@@ -90,7 +88,7 @@ impl Manager {
     pub fn update<'a>(
         &mut self,
         dt: f32,
-        graphics: &mut crate::graphics::RenderContext<'a>,
+        graphics: Arc<SharedGraphicsContext>,
         event_loop: &ActiveEventLoop,
     ) -> Vec<SceneCommand> {
         // transition scene
@@ -104,7 +102,7 @@ impl Manager {
             }
             if let Some(scene) = self.scenes.get_mut(&next_scene_name) {
                 {
-                    scene.write().load(graphics);
+                    scene.write().load(graphics.clone());
                 }
             }
             self.current_scene = Some(next_scene_name);
@@ -115,7 +113,7 @@ impl Manager {
             && let Some(scene) = self.scenes.get_mut(scene_name)
         {
             {
-                scene.write().update(dt, graphics);
+                scene.write().update(dt, graphics.clone());
             }
             let command = scene.write().run_command();
             match command {
@@ -125,7 +123,7 @@ impl Manager {
                             // reload the scene
                             if let Some(scene) = self.scenes.get_mut(current) {
                                 scene.write().exit(event_loop);
-                                scene.write().load(graphics);
+                                scene.write().load(graphics.clone());
 
                                 log::debug!("Reloaded scene: {}", current);
                             }
@@ -150,23 +148,23 @@ impl Manager {
         Vec::new()
     }
 
-    pub fn physics_update<'a>(
+    pub fn physics_update(
         &mut self,
         dt: f32,
-        graphics: &mut crate::graphics::RenderContext<'a>,
+        graphics: Arc<SharedGraphicsContext>,
     ) {
         if let Some(scene_name) = &self.current_scene
             && let Some(scene) = self.scenes.get_mut(scene_name)
         {
-            scene.write().physics_update(dt, graphics)
+            scene.write().physics_update(dt, graphics.clone())
         }
     }
 
-    pub fn render<'a>(&mut self, graphics: &mut crate::graphics::RenderContext<'a>) {
+    pub fn render<'a>(&mut self, graphics: Arc<SharedGraphicsContext>, frame_ctx: FrameGraphicsContext<'a>) {
         if let Some(scene_name) = &self.current_scene
             && let Some(scene) = self.scenes.get_mut(scene_name)
         {
-            scene.write().render(graphics)
+            scene.write().render(graphics.clone(), frame_ctx)
         }
     }
 
diff --git a/crates/dropbear-engine/src/shader.rs b/crates/dropbear-engine/src/shader.rs
index 4ae8e61..17636a1 100644
--- a/crates/dropbear-engine/src/shader.rs
+++ b/crates/dropbear-engine/src/shader.rs
@@ -1,16 +1,24 @@
 //! Deals with shaders, including WESL shaders
+
+use std::ops::Deref;
 use crate::graphics::SharedGraphicsContext;
 use std::sync::Arc;
 use wgpu::ShaderModule;
 
-pub use dropbear_shader as shader_wesl;
-
 /// A nice little struct that stored basic information about a WGPU shader.
 pub struct Shader {
     pub label: String,
     pub module: ShaderModule,
 }
 
+impl Deref for Shader {
+    type Target = ShaderModule;
+
+    fn deref(&self) -> &Self::Target {
+        &self.module
+    }
+}
+
 impl Shader {
     /// Creates a new [`ShaderModule`] from its file contents.
     pub fn new(
diff --git a/crates/dropbear-engine/src/texture.rs b/crates/dropbear-engine/src/texture.rs
new file mode 100644
index 0000000..964d8ce
--- /dev/null
+++ b/crates/dropbear-engine/src/texture.rs
@@ -0,0 +1,346 @@
+use std::{fs, path::PathBuf, sync::Arc};
+
+use image::{EncodableLayout, GenericImageView};
+use serde::{Deserialize, Serialize};
+
+use crate::graphics::SharedGraphicsContext;
+
+
+/// As defined in `shader.wgsl` as 
+/// ```
+/// @group(0) @binding(0)
+/// var t_diffuse: texture_2d<f32>;
+/// @group(0) @binding(1)
+/// var s_diffuse: sampler;
+/// @group(0) @binding(2)
+/// var t_normal: texture_2d<f32>;
+/// @group(0) @binding(3)
+/// var s_normal: sampler;
+/// ```
+pub const TEXTURE_BIND_GROUP_LAYOUT: wgpu::BindGroupLayoutDescriptor<'_> = 
+    wgpu::BindGroupLayoutDescriptor {
+        entries: &[
+            // t_diffuse
+            wgpu::BindGroupLayoutEntry {
+                binding: 0,
+                visibility: wgpu::ShaderStages::FRAGMENT,
+                ty: wgpu::BindingType::Texture {
+                    multisampled: false,
+                    view_dimension: wgpu::TextureViewDimension::D2,
+                    sample_type: wgpu::TextureSampleType::Float { filterable: true },
+                },
+                count: None,
+            },
+            // s_diffuse
+            wgpu::BindGroupLayoutEntry {
+                binding: 1,
+                visibility: wgpu::ShaderStages::FRAGMENT,
+                ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
+                count: None,
+            },
+            // t_normal
+            wgpu::BindGroupLayoutEntry {
+                binding: 2,
+                visibility: wgpu::ShaderStages::FRAGMENT,
+                ty: wgpu::BindingType::Texture {
+                    multisampled: false,
+                    sample_type: wgpu::TextureSampleType::Float { filterable: true },
+                    view_dimension: wgpu::TextureViewDimension::D2,
+                },
+                count: None,
+            },
+            // s_normal
+            wgpu::BindGroupLayoutEntry {
+                binding: 3,
+                visibility: wgpu::ShaderStages::FRAGMENT,
+                ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
+                count: None,
+            },
+        ],
+        label: Some("texture bind group layout"),
+    };
+
+#[derive(Clone)]
+/// Describes a texture, like an image of some sort. Can be a normal texture on a model or a viewport or depth texture.
+pub struct Texture {
+    pub texture: wgpu::Texture,
+    pub sampler: wgpu::Sampler,
+    pub size: wgpu::Extent3d,
+    pub view: wgpu::TextureView,
+}
+
+impl Texture {
+    /// Describes the depth format for all Texture related functions in WGPU to use. Makes life easier
+    pub const DEPTH_FORMAT: wgpu::TextureFormat = wgpu::TextureFormat::Depth32Float;
+
+    /// Creates a new depth texture. This is an internal function.
+    pub fn depth_texture(
+        config: &wgpu::SurfaceConfiguration,
+        device: &wgpu::Device,
+        label: Option<&str>,
+    ) -> Self {
+        let size = wgpu::Extent3d {
+            width: config.width.max(1),
+            height: config.height.max(1),
+            depth_or_array_layers: 1,
+        };
+
+        let desc = wgpu::TextureDescriptor {
+            label,
+            size,
+            mip_level_count: 1, // leave me alone
+            sample_count: 1,
+            dimension: wgpu::TextureDimension::D2,
+            format: Self::DEPTH_FORMAT,
+            usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::TEXTURE_BINDING,
+            view_formats: &[],
+        };
+        let texture = device.create_texture(&desc);
+
+        let sampler = device.create_sampler(&wgpu::SamplerDescriptor {
+            address_mode_u: wgpu::AddressMode::ClampToEdge,
+            address_mode_v: wgpu::AddressMode::ClampToEdge,
+            address_mode_w: wgpu::AddressMode::ClampToEdge,
+            mag_filter: wgpu::FilterMode::Linear,
+            min_filter: wgpu::FilterMode::Linear,
+            mipmap_filter: wgpu::FilterMode::Nearest,
+            compare: Some(wgpu::CompareFunction::LessEqual),
+            lod_min_clamp: 0.0,
+            lod_max_clamp: 100.0,
+            ..Default::default()
+        });
+        let view = texture.create_view(&wgpu::TextureViewDescriptor::default());
+
+        Self {
+            texture,
+            sampler,
+            size,
+            view
+        }
+    }
+
+    /// Creates a viewport texture.
+    ///  
+    /// This is an internal function. 
+    pub fn viewport(
+        config: &wgpu::SurfaceConfiguration,
+        device: &wgpu::Device,
+        label: Option<&str>,
+    ) -> Self {
+        let size = wgpu::Extent3d {
+            width: config.width.max(1),
+            height: config.height.max(1),
+            depth_or_array_layers: 1,
+        };
+
+        let desc = wgpu::TextureDescriptor {
+            label,
+            size,
+            mip_level_count: 1, // leave me alone
+            sample_count: 1,
+            dimension: wgpu::TextureDimension::D2,
+            format: wgpu::TextureFormat::Rgba16Float,
+            usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::TEXTURE_BINDING,
+            view_formats: &[],
+        };
+
+        let texture = device.create_texture(&desc);
+        let sampler = device.create_sampler(&wgpu::SamplerDescriptor::default());
+        let view = texture.create_view(&wgpu::TextureViewDescriptor::default());
+
+        Self {
+            texture,
+            sampler,
+            size,
+            view,
+        }
+    }
+
+    /// Loads the texture from a file. 
+    pub async fn from_file(
+        graphics: Arc<SharedGraphicsContext>,
+        path: &PathBuf,
+    ) -> anyhow::Result<Self> {
+        let data = fs::read(path)?;
+        Ok(Self::from_bytes(graphics.clone(), &data))
+    }
+
+    /// Loads the texture from bytes.
+    /// 
+    /// If you want more customisability in the texture being generated, you can use [Self::from_bytes_verbose]
+    pub fn from_bytes(graphics: Arc<SharedGraphicsContext>, bytes: &[u8]) -> Self {
+        Self::from_bytes_verbose(
+            &graphics.device, 
+            &graphics.queue, 
+            bytes, 
+            None, 
+            None, 
+            None,
+            None
+        )
+    }
+
+    /// Loads the texture from bytes, with options for more arguments. 
+    /// 
+    /// Requires more arguments. For a simpler usage, you should use [Self::from_bytes]
+    pub fn from_bytes_verbose(
+        device: &wgpu::Device,
+        queue: &wgpu::Queue,
+        bytes: &[u8],
+        dimensions: Option<(u32, u32)>,
+        texture_descriptor: Option<wgpu::TextureDescriptor>,
+        view_descriptor: Option<wgpu::TextureViewDescriptor>,
+        sampler: Option<wgpu::SamplerDescriptor>,
+    ) -> Self {
+        let diffuse_image = image::load_from_memory(bytes).unwrap();
+        let diffuse_rgba = diffuse_image.to_rgba8();
+
+        let dimensions = dimensions.unwrap_or_else(|| diffuse_image.dimensions());
+        let size = wgpu::Extent3d {
+            width: dimensions.0,
+            height: dimensions.1,
+            depth_or_array_layers: 1,
+        };
+
+        let desc = texture_descriptor.unwrap_or_else(|| 
+            wgpu::TextureDescriptor {
+                label: Some("diffuse_texture"),
+                size: size,
+                mip_level_count: 1,
+                sample_count: 1,
+                dimension: wgpu::TextureDimension::D2,
+                format: wgpu::TextureFormat::Rgba8UnormSrgb,
+                usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST | wgpu::TextureUsages::RENDER_ATTACHMENT,
+                view_formats: &[],
+            }
+        );
+
+        let texture = device.create_texture(&desc);
+
+        let unpadded_bytes_per_row = 4 * size.width;
+        debug_assert!(unpadded_bytes_per_row.is_power_of_two());
+        let padded_bytes_per_row = (unpadded_bytes_per_row + wgpu::COPY_BYTES_PER_ROW_ALIGNMENT - 1) & !(wgpu::COPY_BYTES_PER_ROW_ALIGNMENT - 1);
+        debug_assert!(diffuse_rgba.len() >= (unpadded_bytes_per_row * size.height) as usize);
+
+        if padded_bytes_per_row == unpadded_bytes_per_row {
+            queue.write_texture(
+                wgpu::TexelCopyTextureInfo {
+                    texture: &texture,
+                    mip_level: 0,
+                    origin: wgpu::Origin3d::ZERO,
+                    aspect: wgpu::TextureAspect::All,
+                },
+                &diffuse_rgba,
+                wgpu::TexelCopyBufferLayout {
+                    offset: 0,
+                    bytes_per_row: Some(unpadded_bytes_per_row),
+                    rows_per_image: Some(size.height),
+                },
+                size,
+            );
+        }
+
+        let mut padded = vec![0u8; (padded_bytes_per_row * size.height) as usize];
+        let src_stride = unpadded_bytes_per_row as usize;
+        let dst_stride = padded_bytes_per_row as usize;
+        for row in 0..size.height as usize {
+            let src_start = row * src_stride;
+            let dst_start = row * dst_stride;
+            padded[dst_start..dst_start + src_stride]
+                .copy_from_slice(&diffuse_rgba.as_bytes()[src_start..src_start + src_stride]);
+        }
+
+        queue.write_texture(
+            wgpu::TexelCopyTextureInfo {
+                texture: &texture,
+                mip_level: 0,
+                origin: wgpu::Origin3d::ZERO,
+                aspect: wgpu::TextureAspect::All,
+            },
+            &padded,
+            wgpu::TexelCopyBufferLayout {
+                offset: 0,
+                bytes_per_row: Some(padded_bytes_per_row),
+                rows_per_image: Some(size.height),
+            },
+            size,
+        );
+
+        let sampler_desc = if let Some(sampler) = sampler {
+            sampler
+        } else {
+            wgpu::SamplerDescriptor {
+                address_mode_u: wgpu::AddressMode::ClampToEdge,
+                address_mode_v: wgpu::AddressMode::ClampToEdge,
+                address_mode_w: wgpu::AddressMode::ClampToEdge,
+                mag_filter: wgpu::FilterMode::Linear,
+                min_filter: wgpu::FilterMode::Nearest,
+                mipmap_filter: wgpu::FilterMode::Nearest,
+                ..Default::default()
+            }
+        };
+
+        let sampler = device.create_sampler(&sampler_desc);
+
+        let view = texture.create_view(&view_descriptor.unwrap_or_default());
+
+        Self {
+            texture,
+            sampler,
+            size,
+            view,
+        }
+    }
+
+    pub fn sampler_from_wrap(wrap: TextureWrapMode) -> wgpu::SamplerDescriptor<'static> {
+        wgpu::SamplerDescriptor {
+            address_mode_u: wrap.into(),
+            address_mode_v: wrap.into(),
+            address_mode_w: wrap.into(),
+            mag_filter: wgpu::FilterMode::Linear,
+            min_filter: wgpu::FilterMode::Nearest,
+            mipmap_filter: wgpu::FilterMode::Nearest,
+            ..Default::default()
+        }
+    }
+}
+
+#[derive(
+    Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize
+)]
+pub enum TextureWrapMode {
+    Repeat,
+    Clamp,
+}
+
+impl Default for TextureWrapMode {
+    fn default() -> Self {
+        Self::Repeat
+    }
+}
+
+impl Into<wgpu::AddressMode> for TextureWrapMode {
+    fn into(self) -> wgpu::AddressMode {
+        match self {
+            TextureWrapMode::Repeat => wgpu::AddressMode::Repeat,
+            TextureWrapMode::Clamp => wgpu::AddressMode::ClampToEdge,
+        }
+    }
+}
+
+pub struct DropbearEngineLogo;
+
+impl DropbearEngineLogo {
+    /// Note: image size is 256x256
+    pub const DROPBEAR_ENGINE_LOGO: &[u8] = include_bytes!("../../../resources/eucalyptus-editor.png");
+
+    /// Generates the dropbear engine logo in a form that [winit::window::Icon] can accept. 
+    /// 
+    /// Returns (the bytes, width, height) in resp order. 
+    pub fn generate() -> anyhow::Result<(Vec<u8>, u32, u32)> {
+        let image = image::load_from_memory(Self::DROPBEAR_ENGINE_LOGO)?.into_rgba8();
+        let (width, height) = image.dimensions();
+        let rgba = image.into_raw();
+        Ok((rgba, width, height))
+    }
+}
\ No newline at end of file
diff --git a/crates/dropbear-engine/src/utils.rs b/crates/dropbear-engine/src/utils.rs
index 1bcc759..68187d9 100644
--- a/crates/dropbear-engine/src/utils.rs
+++ b/crates/dropbear-engine/src/utils.rs
@@ -115,20 +115,6 @@ pub enum ResourceReferenceType {
     Cuboid { size_bits: [u32; 3] },
 }
 
-#[derive(
-    Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize
-)]
-pub enum TextureWrapMode {
-    Repeat,
-    Clamp,
-}
-
-impl Default for TextureWrapMode {
-    fn default() -> Self {
-        Self::Repeat
-    }
-}
-
 impl Default for ResourceReferenceType {
     fn default() -> Self {
         Self::None
diff --git a/crates/dropbear-shader/Cargo.toml b/crates/dropbear-shader/Cargo.toml
deleted file mode 100644
index b0b862b..0000000
--- a/crates/dropbear-shader/Cargo.toml
+++ /dev/null
@@ -1,13 +0,0 @@
-[package]
-name = "dropbear-shader"
-version.workspace = true
-edition.workspace = true
-license.workspace = true
-repository.workspace = true
-readme.workspace = true
-
-[dependencies]
-wesl = { workspace = true }
-
-[build-dependencies]
-wesl = { workspace = true, features = ["package"] }
diff --git a/crates/dropbear-shader/README.md b/crates/dropbear-shader/README.md
deleted file mode 100644
index cdafabf..0000000
--- a/crates/dropbear-shader/README.md
+++ /dev/null
@@ -1,10 +0,0 @@
-# dropbear-shader
-
-This crate is used for shaders created with the WESL language, such as rendering models and generating
-mipmaps. It also provides templates for users to create their own shaders for their own 
-projects with the WESL language. 
-
-## What is WESL?
-
-WESL (or WebGPU Extension Shader Language) is a shader language that is fully compatible with WGPU shaders, and
-adds extension features such as import statements and rust-like #[cfg] functions. 
\ No newline at end of file
diff --git a/crates/dropbear-shader/build.rs b/crates/dropbear-shader/build.rs
deleted file mode 100644
index cf4545c..0000000
--- a/crates/dropbear-shader/build.rs
+++ /dev/null
@@ -1,48 +0,0 @@
-use std::fs;
-use std::path::Path;
-use std::env;
-
-fn main() {
-    let shader_dir = Path::new("src/shaders");
-
-    if shader_dir.exists() {
-        println!("cargo:rerun-if-changed={}", shader_dir.display());
-
-        if let Ok(entries) = fs::read_dir(shader_dir) {
-            for entry in entries.flatten() {
-                println!("cargo:rerun-if-changed={}", entry.path().display());
-            }
-        }
-    }
-
-    wesl::PkgBuilder::new("dropbear")
-        .scan_root("src/shaders")
-        .expect("failed to scan for dropbear wesl shaders")
-        .validate()
-        .map_err(|e| eprintln!("{e}"))
-        .expect("validation error")
-        .build_artifact()
-        .expect("failed to build artifact");
-
-    wesl::Wesl::new("src/shaders")
-        .build_artifact(&"package::light".parse().unwrap(), "dropbear_light");
-    wesl::Wesl::new("src/shaders")
-        .build_artifact(&"package::shader".parse().unwrap(), "dropbear_shader");
-    wesl::Wesl::new("src/shaders")
-        .build_artifact(&"package::shadow".parse().unwrap(), "dropbear_shadow");
-    wesl::Wesl::new("src/shaders")
-        .build_artifact(&"package::outline".parse().unwrap(), "dropbear_outline");
-    wesl::Wesl::new("src/shaders")
-        .build_artifact(&"package::collider".parse().unwrap(), "dropbear_collider");
-    wesl::Wesl::new("src/shaders")
-        .build_artifact(&"package::mipmap".parse().unwrap(), "dropbear_mipmap");
-
-    let out_dir = env::var("OUT_DIR").unwrap();
-    let dropbear_path = Path::new(&out_dir).join("dropbear.rs");
-
-    if let Ok(content) = fs::read_to_string(&dropbear_path) {
-        let fixed_content = format!("#[allow(dead_code)]\n\n{}", content);
-        fs::write(&dropbear_path, fixed_content)
-            .expect("failed to write fixed dropbear.rs");
-    }
-}
\ No newline at end of file
diff --git a/crates/dropbear-shader/src/lib.rs b/crates/dropbear-shader/src/lib.rs
deleted file mode 100644
index 24bc056..0000000
--- a/crates/dropbear-shader/src/lib.rs
+++ /dev/null
@@ -1,10 +0,0 @@
-use wesl::include_wesl;
-
-wesl::wesl_pkg!(dropbear);
-
-pub const LIGHT_SHADER: &str = include_wesl!("dropbear_light");
-pub const SHADER_SHADER: &str = include_wesl!("dropbear_shader");
-pub const SHADOW_SHADER: &str = include_wesl!("dropbear_shadow");
-pub const OUTLINE_SHADER: &str = include_wesl!("dropbear_outline");
-pub const COLLIDER_SHADER: &str = include_wesl!("dropbear_collider");
-pub const MIPMAP_SHADER: &str = include_wesl!("dropbear_mipmap");
\ No newline at end of file
diff --git a/crates/dropbear-shader/src/shaders/collider.wgsl b/crates/dropbear-shader/src/shaders/collider.wgsl
deleted file mode 100644
index b899ac9..0000000
--- a/crates/dropbear-shader/src/shaders/collider.wgsl
+++ /dev/null
@@ -1,45 +0,0 @@
-struct CameraUniform {
-    view_pos: vec4<f32>,
-    view_proj: mat4x4<f32>,
-};
-
-@group(0) @binding(0)
-var <uniform> camera: CameraUniform;
-
-struct VertexInput {
-    @location(0) position: vec3<f32>,
-    @location(1) model_0: vec4<f32>,
-    @location(2) model_1: vec4<f32>,
-    @location(3) model_2: vec4<f32>,
-    @location(4) model_3: vec4<f32>,
-    @location(5) color: vec4<f32>,
-}
-
-struct VertexOutput {
-    @builtin(position) clip_position: vec4<f32>,
-    @location(0) color: vec4<f32>,
-}
-
-@vertex
-fn vs_main(input: VertexInput) -> VertexOutput {
-    var output: VertexOutput;
-
-    let model = mat4x4<f32>(
-        input.model_0,
-        input.model_1,
-        input.model_2,
-        input.model_3,
-    );
-
-    let world_position = model * vec4<f32>(input.position, 1.0);
-    output.clip_position = camera.view_proj * world_position;
-
-    output.color = input.color;
-
-    return output;
-}
-
-@fragment
-fn fs_main(input: VertexOutput) -> @location(0) vec4<f32> {
-    return input.color;
-}
\ No newline at end of file
diff --git a/crates/dropbear-shader/src/shaders/input.wesl b/crates/dropbear-shader/src/shaders/input.wesl
deleted file mode 100644
index 9f038af..0000000
--- a/crates/dropbear-shader/src/shaders/input.wesl
+++ /dev/null
@@ -1,27 +0,0 @@
-const MAX_LIGHTS: u32 = 8;
-
-struct CameraUniform {
-    view_pos: vec4<f32>,
-    view_proj: mat4x4<f32>,
-};
-
-struct OutlineUniform {
-    outline_width: f32,
-    outline_color: vec4<f32>,
-};
-
-struct Light {
-    position: vec4<f32>,
-    direction: vec4<f32>, // x, y, z, outer_cutoff_angle
-    color: vec4<f32>, // r, g, b, light_type (0, 1, 2)
-    constant: f32,
-    lin: f32,
-    quadratic: f32,
-    cutoff: f32,
-}
-
-struct LightArray {
-    _lights: array<Light, config::MAX_LIGHTS>,
-    light_count: u32,
-    ambient_strength: f32,
-}
\ No newline at end of file
diff --git a/crates/dropbear-shader/src/shaders/light.wgsl b/crates/dropbear-shader/src/shaders/light.wgsl
deleted file mode 100644
index f703d11..0000000
--- a/crates/dropbear-shader/src/shaders/light.wgsl
+++ /dev/null
@@ -1,64 +0,0 @@
-// Shader for rendering the white cube for lighting (or diff depending on colour)
-
-struct Light {
-    position: vec4<f32>,
-    direction: vec4<f32>, // x, y, z, outer_cutoff_angle
-    color: vec4<f32>, // r, g, b, light_type (0, 1, 2)
-    constant: f32,
-    lin: f32,
-    quadratic: f32,
-    cutoff: f32,
-    shadow_index: i32,
-    proj: mat4x4<f32>,
-}
-
-struct CameraUniform {
-    view_pos: vec4<f32>,
-    view_proj: mat4x4<f32>,
-};
-
-@group(0) @binding(0)
-var<uniform> camera: CameraUniform;
-
-@group(1) @binding(0)
-var<uniform> light: Light;
-
-struct InstanceInput {
-    @location(5) model_matrix_0: vec4<f32>,
-    @location(6) model_matrix_1: vec4<f32>,
-    @location(7) model_matrix_2: vec4<f32>,
-    @location(8) model_matrix_3: vec4<f32>,
-}
-
-struct VertexInput {
-    @location(0) position: vec3<f32>,
-};
-
-struct VertexOutput {
-    @builtin(position) clip_position: vec4<f32>,
-    @location(0) color: vec3<f32>,
-};
-
-@vertex
-fn vs_main(
-    model: VertexInput,
-    instance: InstanceInput,
-) -> VertexOutput {
-    let model_matrix = mat4x4<f32>(
-        instance.model_matrix_0,
-        instance.model_matrix_1,
-        instance.model_matrix_2,
-        instance.model_matrix_3,
-    );
-    var out: VertexOutput;
-    out.clip_position = camera.view_proj * model_matrix * vec4<f32>(model.position, 1.0);
-    out.color = light.color.xyz;
-    return out;
-}
-
-// Fragment shader
-
-@fragment
-fn fs_main(in: VertexOutput) -> @location(0) vec4<f32> {
-    return vec4<f32>(in.color, 1.0);
-}
\ No newline at end of file
diff --git a/crates/dropbear-shader/src/shaders/mipmap.wgsl b/crates/dropbear-shader/src/shaders/mipmap.wgsl
deleted file mode 100644
index f156e95..0000000
--- a/crates/dropbear-shader/src/shaders/mipmap.wgsl
+++ /dev/null
@@ -1,28 +0,0 @@
-/// shader to generate mipmaps
-///
-/// source: https://shi-yan.github.io/webgpuunleashed/Basics/mipmapping_and_anisotropic_filtering.html
-
-var<private> pos : array<vec2<f32>, 4> = array<vec2<f32>, 4>(
-    vec2<f32>(-1.0, 1.0), vec2<f32>(1.0, 1.0),
-    vec2<f32>(-1.0, -1.0), vec2<f32>(1.0, -1.0));
-
-struct VertexOutput {
-    @builtin(position) position : vec4<f32>,
-    @location(0) texCoord : vec2<f32>,
-};
-
-@vertex
-fn vs_main(@builtin(vertex_index) vertexIndex : u32) -> VertexOutput {
-    var output : VertexOutput;
-    output.texCoord = pos[vertexIndex] * vec2<f32>(0.5, -0.5) + vec2<f32>(0.5);
-    output.position = vec4<f32>(pos[vertexIndex], 0.0, 1.0);
-    return output;
-}
-
-@group(0) @binding(0) var imgSampler : sampler;
-@group(0) @binding(1) var img : texture_2d<f32>;
-
-@fragment
-fn fs_main(@location(0) texCoord : vec2<f32>) -> @location(0) vec4<f32> {
-    return textureSample(img, imgSampler, texCoord);
-}
\ No newline at end of file
diff --git a/crates/dropbear-shader/src/shaders/outline.wesl b/crates/dropbear-shader/src/shaders/outline.wesl
deleted file mode 100644
index 9f004cf..0000000
--- a/crates/dropbear-shader/src/shaders/outline.wesl
+++ /dev/null
@@ -1,65 +0,0 @@
-// outline.wgsl
-import package::input::CameraUniform;
-import package::input::OutlineUniform;
-
-@group(1) @binding(0)
-var<uniform> camera: CameraUniform;
-
-@group(0) @binding(0)
-var<uniform> outline: OutlineUniform;
-
-struct InstanceInput {
-    @location(5) model_matrix_0: vec4<f32>,
-    @location(6) model_matrix_1: vec4<f32>,
-    @location(7) model_matrix_2: vec4<f32>,
-    @location(8) model_matrix_3: vec4<f32>,
-
-    @location(9) normal_matrix_0: vec3<f32>,
-    @location(10) normal_matrix_1: vec3<f32>,
-    @location(11) normal_matrix_2: vec3<f32>,
-}
-
-struct VertexInput {
-    @location(0) position: vec3<f32>,
-    @location(1) tex_coords: vec2<f32>, // unused, but must match vertex layout
-    @location(2) normal: vec3<f32>,
-}
-
-struct VertexOutput {
-    @builtin(position) clip_position: vec4<f32>,
-}
-
-@vertex
-fn vs_main(
-    model: VertexInput,
-    instance: InstanceInput,
-) -> VertexOutput {
-    let model_matrix = mat4x4<f32>(
-        instance.model_matrix_0,
-        instance.model_matrix_1,
-        instance.model_matrix_2,
-        instance.model_matrix_3,
-    );
-
-    let normal_matrix = mat3x3<f32>(
-        instance.normal_matrix_0,
-        instance.normal_matrix_1,
-        instance.normal_matrix_2,
-    );
-
-    // Transform normal to world space
-    let world_normal = normalize(normal_matrix * model.normal);
-
-    // Extrude position in WORLD SPACE
-    let world_position = (model_matrix * vec4<f32>(model.position, 1.0)).xyz;
-    let expanded_world_position = world_position + world_normal * outline.outline_width;
-
-    var out: VertexOutput;
-    out.clip_position = camera.view_proj * vec4<f32>(expanded_world_position, 1.0);
-    return out;
-}
-
-@fragment
-fn fs_main() -> @location(0) vec4<f32> {
-    return outline.outline_color; // Use the uniform color!
-}
\ No newline at end of file
diff --git a/crates/dropbear-shader/src/shaders/shader.wgsl b/crates/dropbear-shader/src/shaders/shader.wgsl
deleted file mode 100644
index cf50aca..0000000
--- a/crates/dropbear-shader/src/shaders/shader.wgsl
+++ /dev/null
@@ -1,285 +0,0 @@
-// Main shader for standard objects.
-
-const MAX_LIGHTS: u32 = 8;
-
-struct CameraUniform {
-    view_pos: vec4<f32>,
-    view_proj: mat4x4<f32>,
-};
-
-struct Light {
-    position: vec4<f32>,
-    direction: vec4<f32>, // x, y, z, outer_cutoff_angle
-    color: vec4<f32>, // r, g, b, light_type (0, 1, 2)
-    constant: f32,
-    lin: f32,
-    quadratic: f32,
-    cutoff: f32,
-    shadow_index: i32,
-    proj: mat4x4<f32>,
-}
-
-struct LightArray {
-    _lights: array<Light, MAX_LIGHTS>,
-    light_count: u32,
-    ambient_strength: f32,
-}
-
-struct MaterialUniform {
-    // for stuff like tinting
-    colour: vec4<f32>,
-
-    // scales incoming UVs before sampling
-    uv_tiling: vec2<f32>,
-    _pad: vec2<f32>,
-}
-
-@group(0) @binding(0)
-var t_diffuse: texture_2d<f32>;
-@group(0) @binding(1)
-var s_diffuse: sampler;
-@group(0) @binding(2)
-var t_normal: texture_2d<f32>;
-@group(0) @binding(3)
-var s_normal: sampler;
-
-@group(3) @binding(0)
-var<uniform> u_material: MaterialUniform;
-
-@group(1) @binding(0)
-var<uniform> camera: CameraUniform;
-
-@group(2) @binding(0)
-var<uniform> light_array: LightArray;
-@group(2) @binding(1)
-var t_shadow: texture_depth_2d_array;
-@group(2) @binding(2)
-var s_shadow: sampler_comparison;
-
-struct InstanceInput {
-    @location(5) model_matrix_0: vec4<f32>,
-    @location(6) model_matrix_1: vec4<f32>,
-    @location(7) model_matrix_2: vec4<f32>,
-    @location(8) model_matrix_3: vec4<f32>,
-
-    @location(9) normal_matrix_0: vec3<f32>,
-    @location(10) normal_matrix_1: vec3<f32>,
-    @location(11) normal_matrix_2: vec3<f32>,
-};
-
-struct VertexInput {
-    @location(0) position: vec3<f32>,
-    @location(1) tex_coords: vec2<f32>,
-    @location(2) normal: vec3<f32>,
-    @location(3) tangent: vec3<f32>,
-    @location(4) bitangent: vec3<f32>,
-};
-
-struct VertexOutput {
-    @builtin(position) clip_position: vec4<f32>,
-    @location(0) tex_coords: vec2<f32>,
-    @location(1) world_normal: vec3<f32>,
-    @location(2) world_position: vec3<f32>,
-    @location(3) world_tangent: vec3<f32>,
-    @location(4) world_bitangent: vec3<f32>,
-};
-
-@vertex
-fn vs_main(
-    model: VertexInput,
-    instance: InstanceInput,
-) -> VertexOutput {
-    let model_matrix = mat4x4<f32>(
-        instance.model_matrix_0,
-        instance.model_matrix_1,
-        instance.model_matrix_2,
-        instance.model_matrix_3,
-    );
-    let normal_matrix = mat3x3<f32>(
-        instance.normal_matrix_0,
-        instance.normal_matrix_1,
-        instance.normal_matrix_2,
-    );
-
-    let world_normal = normalize(normal_matrix * model.normal);
-    let world_tangent = normalize(normal_matrix * model.tangent);
-    let world_bitangent = normalize(normal_matrix * model.bitangent);
-    let world_position = model_matrix * vec4<f32>(model.position, 1.0);
-
-    var out: VertexOutput;
-    out.clip_position = camera.view_proj * world_position;
-    out.tex_coords = model.tex_coords;
-    out.world_normal = world_normal;
-    out.world_position = world_position.xyz;
-    out.world_tangent = world_tangent;
-    out.world_bitangent = world_bitangent;
-    return out;
-}
-
-fn calculate_shadow(light: Light, world_pos: vec3<f32>, normal: vec3<f32>, light_dir: vec3<f32>) -> f32 {
-    // If index is -1, light casts no shadow
-    if (light.shadow_index < 0) {
-        return 1.0;
-    }
-
-    let light_space_pos = light.proj * vec4<f32>(world_pos, 1.0);
-
-    let proj_coords = light_space_pos.xyz / light_space_pos.w;
-
-    let flip_correction = vec2<f32>(0.5, -0.5);
-    let uv = proj_coords.xy * flip_correction + vec2<f32>(0.5, 0.5);
-
-    if (uv.x < 0.0 || uv.x > 1.0 || uv.y < 0.0 || uv.y > 1.0 || proj_coords.z < 0.0 || proj_coords.z > 1.0) {
-        return 1.0;
-    }
-
-    let current_depth = proj_coords.z - 0.005;
-
-    return textureSampleCompareLevel(
-        t_shadow,
-        s_shadow,
-        uv,
-        light.shadow_index,
-        current_depth
-    );
-}
-
-fn directional_light(
-    light: Light,
-    world_normal: vec3<f32>,
-    view_dir: vec3<f32>,
-    tex_color: vec3<f32>,
-    world_pos: vec3<f32>
-) -> vec3<f32> {
-    let light_dir = normalize(-light.direction.xyz);
-
-    let ambient = light.color.xyz * light_array.ambient_strength * tex_color;
-
-    let diff = max(dot(world_normal, light_dir), 0.0);
-    let diffuse = light.color.xyz * diff * tex_color;
-
-    let reflect_dir = reflect(-light_dir, world_normal);
-    let spec = pow(max(dot(view_dir, reflect_dir), 0.0), 32.0);
-    let specular = light.color.xyz * spec * tex_color;
-
-    let shadow = calculate_shadow(light, world_pos, world_normal, light_dir);
-
-    return ambient + (shadow * (diffuse + specular));
-}
-
-fn point_light(
-    light: Light,
-    world_pos: vec3<f32>,
-    world_normal: vec3<f32>,
-    view_dir: vec3<f32>,
-    tex_color: vec3<f32>
-) -> vec3<f32> {
-    let light_dir = normalize(light.position.xyz - world_pos);
-
-    let distance = length(light.position.xyz - world_pos);
-    let attenuation = 1.0 / (light.constant + (light.lin * distance) + (light.quadratic * (distance * distance)));
-
-    let ambient = light.color.xyz * light_array.ambient_strength * tex_color;
-
-    let diff = max(dot(world_normal, light_dir), 0.0);
-    let diffuse = light.color.xyz * diff * tex_color;
-
-    let reflect_dir = reflect(-light_dir, world_normal);
-    let spec = pow(max(dot(view_dir, reflect_dir), 0.0), 32.0);
-    let specular = light.color.xyz * spec * tex_color;
-
-    let shadow = calculate_shadow(light, world_pos, world_normal, light_dir);
-
-    return (ambient + (shadow * (diffuse + specular))) * attenuation;
-}
-
-fn spot_light(
-    light: Light,
-    world_pos: vec3<f32>,
-    world_normal: vec3<f32>,
-    view_dir: vec3<f32>,
-    tex_color: vec3<f32>
-) -> vec3<f32> {
-    let light_dir = normalize(light.position.xyz - world_pos);
-    let theta = dot(light_dir, normalize(-light.direction.xyz));
-    let outer_cutoff = light.direction.w;
-
-    let epsilon = light.cutoff - outer_cutoff;
-    let intensity = clamp((theta - outer_cutoff) / epsilon, 0.0, 1.0);
-
-    let distance = length(light.position.xyz - world_pos);
-    let attenuation = 1.0 / (light.constant + (light.lin * distance) + (light.quadratic * (distance * distance)));
-
-    let ambient = light.color.xyz * light_array.ambient_strength * tex_color;
-
-    let diff = max(dot(world_normal, light_dir), 0.0);
-    let diffuse = light.color.xyz * diff * tex_color * intensity;
-
-    let reflect_dir = reflect(-light_dir, world_normal);
-    let spec = pow(max(dot(view_dir, reflect_dir), 0.0), 32.0);
-    let specular = light.color.xyz * spec * tex_color * intensity;
-
-    let shadow = calculate_shadow(light, world_pos, world_normal, light_dir);
-
-    return (ambient + (shadow * (diffuse + specular))) * attenuation;
-}
-
-fn apply_normal_map(
-    world_normal_in: vec3<f32>,
-    world_tangent_in: vec3<f32>,
-    world_bitangent_in: vec3<f32>,
-    normal_sample_rgb: vec3<f32>,
-) -> vec3<f32> {
-    let normal_ts = normalize(normal_sample_rgb * 2.0 - vec3<f32>(1.0));
-
-    let n = normalize(world_normal_in);
-    var t = normalize(world_tangent_in);
-    t = normalize(t - n * dot(n, t));
-
-    let b_in = normalize(world_bitangent_in);
-    let handedness = select(-1.0, 1.0, dot(cross(n, t), b_in) >= 0.0);
-    let b = cross(n, t) * handedness;
-
-    let tbn = mat3x3<f32>(t, b, n);
-    return normalize(tbn * normal_ts);
-}
-
-@fragment
-fn fs_main(in: VertexOutput) -> @location(0) vec4<f32> {
-    let uv = in.tex_coords * u_material.uv_tiling;
-    var tex_color = textureSample(t_diffuse, s_diffuse, uv);
-    var object_normal = textureSample(t_normal, s_normal, uv);
-
-    let base_colour = tex_color * u_material.colour;
-
-    if (base_colour.a < 0.1) {
-        discard;
-    }
-
-    let view_dir = normalize(camera.view_pos.xyz - in.world_position);
-
-    let world_normal = apply_normal_map(
-        in.world_normal,
-        in.world_tangent,
-        in.world_bitangent,
-        object_normal.xyz,
-    );
-
-    var final_color = vec3<f32>(0.0);
-
-    for (var i: u32 = 0u; i < light_array.light_count; i = i + 1u) {
-        let light = light_array._lights[i];
-
-        let light_type = i32(light.color.w + 0.1);
-
-        if (light_type == 0) {
-            final_color += directional_light(light, world_normal, view_dir, base_colour.xyz, in.world_position);
-        } else if (light_type == 1) {
-            final_color += point_light(light, in.world_position, world_normal, view_dir, base_colour.xyz);
-        } else if (light_type == 2) {
-            final_color += spot_light(light, in.world_position, world_normal, view_dir, base_colour.xyz);
-        }
-    }
-
-    return vec4<f32>(final_color, base_colour.a);
-}
\ No newline at end of file
diff --git a/crates/dropbear-shader/src/shaders/shadow.wgsl b/crates/dropbear-shader/src/shaders/shadow.wgsl
deleted file mode 100644
index 1be0fea..0000000
--- a/crates/dropbear-shader/src/shaders/shadow.wgsl
+++ /dev/null
@@ -1,45 +0,0 @@
-// Depth-only shader for shadow mapping into a depth texture array.
-
-struct Light {
-    position: vec4<f32>,
-    direction: vec4<f32>, // x, y, z, outer_cutoff_angle
-    color: vec4<f32>, // r, g, b, light_type
-    constant: f32,
-    lin: f32,
-    quadratic: f32,
-    cutoff: f32,
-    shadow_index: i32,
-    proj: mat4x4<f32>,
-}
-
-@group(0) @binding(0)
-var<uniform> light: Light;
-
-struct InstanceInput {
-    @location(5) model_matrix_0: vec4<f32>,
-    @location(6) model_matrix_1: vec4<f32>,
-    @location(7) model_matrix_2: vec4<f32>,
-    @location(8) model_matrix_3: vec4<f32>,
-}
-
-struct VertexInput {
-    @location(0) position: vec3<f32>,
-};
-
-struct VertexOutput {
-    @builtin(position) clip_position: vec4<f32>,
-};
-
-@vertex
-fn vs_main(model: VertexInput, instance: InstanceInput) -> VertexOutput {
-    let model_matrix = mat4x4<f32>(
-        instance.model_matrix_0,
-        instance.model_matrix_1,
-        instance.model_matrix_2,
-        instance.model_matrix_3,
-    );
-
-    var out: VertexOutput;
-    out.clip_position = light.proj * model_matrix * vec4<f32>(model.position, 1.0);
-    return out;
-}
diff --git a/crates/eucalyptus-core/Cargo.toml b/crates/eucalyptus-core/Cargo.toml
index b45ebbb..47772d6 100644
--- a/crates/eucalyptus-core/Cargo.toml
+++ b/crates/eucalyptus-core/Cargo.toml
@@ -7,7 +7,7 @@ repository.workspace = true
 readme = "README.md"
 
 [lib]
-crate-type = ["rlib", "dylib"]
+crate-type = ["rlib", "cdylib"]
 
 [dependencies]
 dropbear-traits = { path = "../dropbear-traits" }
diff --git a/crates/eucalyptus-core/src/command.rs b/crates/eucalyptus-core/src/command.rs
index 1667fae..9fa58af 100644
--- a/crates/eucalyptus-core/src/command.rs
+++ b/crates/eucalyptus-core/src/command.rs
@@ -1,9 +1,9 @@
 //! One way command buffers between the scripting module and the editor/runtime.
 use crossbeam_channel::{Receiver, Sender, unbounded};
-use dropbear_engine::graphics::RenderContext;
+use dropbear_engine::graphics::SharedGraphicsContext;
 use once_cell::sync::Lazy;
 use parking_lot::RwLock;
-use std::sync::{OnceLock};
+use std::sync::{Arc, OnceLock};
 use crate::scene::loading::SceneLoadHandle;
 
 pub static COMMAND_BUFFER: Lazy<(Box<Sender<CommandBuffer>>, Receiver<CommandBuffer>)> =
@@ -48,5 +48,5 @@ pub enum WindowCommand {
 
 /// Command buffer that is used for oneway communication between Kotlin to Rust.  
 pub trait CommandBufferPoller {
-    fn poll(&mut self, graphics: &mut RenderContext);
+    fn poll(&mut self, graphics: Arc<SharedGraphicsContext>);
 }
\ No newline at end of file
diff --git a/crates/eucalyptus-core/src/physics/collider/shader.rs b/crates/eucalyptus-core/src/physics/collider/shader.rs
index 152183b..2728314 100644
--- a/crates/eucalyptus-core/src/physics/collider/shader.rs
+++ b/crates/eucalyptus-core/src/physics/collider/shader.rs
@@ -9,6 +9,8 @@ use dropbear_engine::shader::Shader;
 use dropbear_engine::wgpu::*;
 use glam::Mat4;
 
+use crate::physics::collider::{ColliderShape, WireframeGeometry};
+
 pub struct ColliderWireframePipeline {
     pub pipeline: RenderPipeline,
 }
@@ -16,18 +18,17 @@ pub struct ColliderWireframePipeline {
 impl ColliderWireframePipeline {
     pub fn new(
         graphics: Arc<SharedGraphicsContext>,
-        camera_bind_group_layout: &BindGroupLayout,
     ) -> Self {
         let shader = Shader::new(
             graphics.clone(),
-            dropbear_engine::shader::shader_wesl::COLLIDER_SHADER,
+            include_str!("../../../../../resources/shaders/collider.wgsl"),
             Some("collider wireframe shader"),
         );
 
         let pipeline_layout = graphics.device.create_pipeline_layout(&PipelineLayoutDescriptor {
             label: Some("collider wireframe pipeline layout descriptor"),
             bind_group_layouts: &[
-                camera_bind_group_layout, // @group(0)
+                &graphics.layouts.camera_bind_group_layout, // @group(0)
             ],
             push_constant_ranges: &[],
         });
@@ -166,4 +167,27 @@ impl ColliderInstanceRaw {
             attributes: &ATTRIBS,
         }
     }
+}
+
+pub fn create_wireframe_geometry(
+    graphics: Arc<SharedGraphicsContext>,
+    shape: &ColliderShape,
+) -> WireframeGeometry {
+    match shape {
+        ColliderShape::Box { half_extents } => {
+            WireframeGeometry::box_wireframe(graphics, *half_extents)
+        }
+        ColliderShape::Sphere { radius } => {
+            WireframeGeometry::sphere_wireframe(graphics, *radius, 16, 16)
+        }
+        ColliderShape::Capsule { half_height, radius } => {
+            WireframeGeometry::capsule_wireframe(graphics, *half_height, *radius, 16)
+        }
+        ColliderShape::Cylinder { half_height, radius } => {
+            WireframeGeometry::cylinder_wireframe(graphics, *half_height, *radius, 16)
+        }
+        ColliderShape::Cone { half_height, radius } => {
+            WireframeGeometry::cone_wireframe(graphics, *half_height, *radius, 16)
+        }
+    }
 }
\ No newline at end of file
diff --git a/crates/eucalyptus-core/src/scene.rs b/crates/eucalyptus-core/src/scene.rs
index 3699478..5a825c7 100644
--- a/crates/eucalyptus-core/src/scene.rs
+++ b/crates/eucalyptus-core/src/scene.rs
@@ -10,7 +10,7 @@ use crate::utils::ResolveReference;
 use dropbear_engine::asset::ASSET_REGISTRY;
 use dropbear_engine::camera::{Camera, CameraBuilder};
 use dropbear_engine::entity::{EntityTransform, MeshRenderer, Transform};
-use dropbear_engine::graphics::Texture;
+use dropbear_engine::texture::Texture;
 use dropbear_engine::graphics::SharedGraphicsContext;
 use dropbear_engine::lighting::{Light as EngineLight, LightComponent};
 use dropbear_engine::model::{LoadedModel, Material, Model, ModelId};
@@ -266,10 +266,14 @@ impl SceneConfig {
                             if let Some(reference) = &custom.diffuse_texture {
                                 if let Ok(path) = reference.resolve() {
                                     if let Ok(bytes) = std::fs::read(&path) {
-                                        let diffuse = Texture::new_with_wrap_mode(
-                                            graphics.clone(),
-                                            &bytes,
-                                            custom.wrap_mode,
+                                        let diffuse = Texture::from_bytes_verbose(
+                                            &graphics.device,
+                                            &graphics.queue,
+                                            bytes.as_slice(),
+                                            None,
+                                            None,
+                                            None,
+                                            Some(Texture::sampler_from_wrap(custom.wrap_mode)),
                                         );
                                         let flat_normal = (*ASSET_REGISTRY
                                             .solid_texture_rgba8(
diff --git a/crates/eucalyptus-core/src/states.rs b/crates/eucalyptus-core/src/states.rs
index 802da45..7dbd854 100644
--- a/crates/eucalyptus-core/src/states.rs
+++ b/crates/eucalyptus-core/src/states.rs
@@ -9,7 +9,8 @@ use crate::traits::SerializableComponent;
 use dropbear_engine::camera::Camera;
 use dropbear_engine::entity::{MaterialOverride, MeshRenderer, Transform};
 use dropbear_engine::lighting::LightComponent;
-use dropbear_engine::utils::{ResourceReference, TextureWrapMode};
+use dropbear_engine::texture::TextureWrapMode;
+use dropbear_engine::utils::{ResourceReference};
 use dropbear_macro::SerializableComponent;
 use once_cell::sync::Lazy;
 use parking_lot::RwLock;
diff --git a/crates/eucalyptus-editor/Cargo.toml b/crates/eucalyptus-editor/Cargo.toml
index d96e566..a8570c7 100644
--- a/crates/eucalyptus-editor/Cargo.toml
+++ b/crates/eucalyptus-editor/Cargo.toml
@@ -10,6 +10,7 @@ readme = "README.md"
 [dependencies]
 eucalyptus-core = { path = "../eucalyptus-core", features = ["editor"] }
 magna-carta = { path = "../magna-carta" }
+redback-runtime = { path = "../redback-runtime" }
 
 anyhow.workspace = true
 app_dirs2.workspace = true
diff --git a/crates/eucalyptus-editor/src/about.rs b/crates/eucalyptus-editor/src/about.rs
index 622ae46..e228d1d 100644
--- a/crates/eucalyptus-editor/src/about.rs
+++ b/crates/eucalyptus-editor/src/about.rs
@@ -1,5 +1,6 @@
 //! Used for displaying the Help->About window in the editor.
 
+use dropbear_engine::graphics::FrameGraphicsContext;
 use egui::{CentralPanel};
 use gilrs::{Button, GamepadId};
 use winit::dpi::PhysicalPosition;
@@ -7,7 +8,6 @@ use winit::event::MouseButton;
 use winit::event_loop::ActiveEventLoop;
 use winit::keyboard::KeyCode;
 use winit::window::{WindowId};
-use dropbear_engine::graphics::RenderContext;
 use dropbear_engine::input::{Controller, Keyboard, Mouse};
 use dropbear_engine::scene::{Scene, SceneCommand};
 use eucalyptus_core::input::InputState;
@@ -29,14 +29,14 @@ impl AboutWindow {
 }
 
 impl Scene for AboutWindow {
-    fn load(&mut self, graphics: &mut RenderContext) {
-        self.window = Some(graphics.shared.window.id());
+    fn load(&mut self, graphics: std::sync::Arc<dropbear_engine::graphics::SharedGraphicsContext>) {
+        self.window = Some(graphics.window.id());
     }
 
-    fn physics_update(&mut self, _dt: f32, _graphics: &mut RenderContext) {}
+    fn physics_update(&mut self, _dt: f32, _graphics: std::sync::Arc<dropbear_engine::graphics::SharedGraphicsContext>) {}
 
-    fn update(&mut self, _dt: f32, graphics: &mut RenderContext) {
-        CentralPanel::default().show(&graphics.shared.get_egui_context(), |ui| {
+    fn update(&mut self, _dt: f32, graphics: std::sync::Arc<dropbear_engine::graphics::SharedGraphicsContext>) {
+        CentralPanel::default().show(&graphics.get_egui_context(), |ui| {
             ui.centered_and_justified(|ui| {
                 ui.add_space(8.0);
 
@@ -73,10 +73,10 @@ impl Scene for AboutWindow {
             });
         });
 
-        self.window = Some(graphics.shared.window.id());
+        self.window = Some(graphics.window.id());
     }
 
-    fn render(&mut self, _graphics: &mut RenderContext) {
+    fn render<'a>(&mut self, _graphics: std::sync::Arc<dropbear_engine::graphics::SharedGraphicsContext>, _frame_ctx: FrameGraphicsContext<'a>) {
     }
 
     fn exit(&mut self, _event_loop: &ActiveEventLoop) {}
diff --git a/crates/eucalyptus-editor/src/debug/window.rs b/crates/eucalyptus-editor/src/debug/window.rs
index 34e6c03..52ebd1a 100644
--- a/crates/eucalyptus-editor/src/debug/window.rs
+++ b/crates/eucalyptus-editor/src/debug/window.rs
@@ -9,7 +9,7 @@ use winit::event::MouseButton;
 use winit::event_loop::ActiveEventLoop;
 use winit::keyboard::KeyCode;
 use winit::window::{WindowId};
-use dropbear_engine::graphics::RenderContext;
+
 use dropbear_engine::input::{Controller, Keyboard, Mouse};
 use dropbear_engine::scene::{Scene, SceneCommand};
 use eucalyptus_core::input::InputState;
@@ -31,21 +31,21 @@ impl DebugWindow {
 }
 
 impl Scene for DebugWindow {
-    fn load(&mut self, graphics: &mut RenderContext) {
-        self.window = Some(graphics.shared.window.id());
+    fn load(&mut self, graphics: std::sync::Arc<dropbear_engine::graphics::SharedGraphicsContext>) {
+        self.window = Some(graphics.window.id());
     }
 
-    fn physics_update(&mut self, _dt: f32, _graphics: &mut RenderContext) {}
+    fn physics_update(&mut self, _dt: f32, _graphics: std::sync::Arc<dropbear_engine::graphics::SharedGraphicsContext>) {}
 
-    fn update(&mut self, _dt: f32, graphics: &mut RenderContext) {
-        CentralPanel::default().show(&graphics.shared.get_egui_context(), |ui| {
+    fn update(&mut self, _dt: f32, graphics: std::sync::Arc<dropbear_engine::graphics::SharedGraphicsContext>) {
+        CentralPanel::default().show(&graphics.get_egui_context(), |ui| {
             ui.label("Hello Debug Window!");
         });
 
-        self.window = Some(graphics.shared.window.id());
+        self.window = Some(graphics.window.id());
     }
 
-    fn render(&mut self, _graphics: &mut RenderContext) {
+    fn render<'a>(&mut self, _graphics: std::sync::Arc<dropbear_engine::graphics::SharedGraphicsContext>, _frame_ctx: dropbear_engine::graphics::FrameGraphicsContext<'a>) {
     }
 
     fn exit(&mut self, _event_loop: &ActiveEventLoop) {}
diff --git a/crates/eucalyptus-editor/src/editor/component.rs b/crates/eucalyptus-editor/src/editor/component.rs
index 6c1b153..cdc18c6 100644
--- a/crates/eucalyptus-editor/src/editor/component.rs
+++ b/crates/eucalyptus-editor/src/editor/component.rs
@@ -5,6 +5,7 @@ use dropbear_engine::asset::{AssetHandle, ASSET_REGISTRY};
 use dropbear_engine::attenuation::ATTENUATION_PRESETS;
 use dropbear_engine::entity::{EntityTransform, MeshRenderer, Transform};
 use dropbear_engine::lighting::LightType;
+use dropbear_engine::texture::TextureWrapMode;
 use dropbear_engine::utils::ResourceReferenceType;
 use egui::{CollapsingHeader, ComboBox, DragValue, Grid, RichText, TextEdit, Ui, UiBuilder};
 use eucalyptus_core::camera::CameraType;
@@ -1257,21 +1258,21 @@ impl InspectableComponent for MeshRenderer {
                             material_name
                         ))
                         .selected_text(match wrap_mode {
-                            dropbear_engine::utils::TextureWrapMode::Repeat => "Repeat",
-                            dropbear_engine::utils::TextureWrapMode::Clamp => "Clamp",
+                            TextureWrapMode::Repeat => "Repeat",
+                            TextureWrapMode::Clamp => "Clamp",
                         })
                         .show_ui(ui, |ui| {
                             wrap_changed |= ui
                                 .selectable_value(
                                 &mut wrap_mode,
-                                dropbear_engine::utils::TextureWrapMode::Repeat,
+                                TextureWrapMode::Repeat,
                                 "Repeat",
                                 )
                                 .changed();
                             wrap_changed |= ui
                                 .selectable_value(
                                 &mut wrap_mode,
-                                dropbear_engine::utils::TextureWrapMode::Clamp,
+                                TextureWrapMode::Clamp,
                                 "Clamp",
                                 )
                                 .changed();
@@ -1285,7 +1286,7 @@ impl InspectableComponent for MeshRenderer {
                             );
                         }
 
-                        if matches!(wrap_mode, dropbear_engine::utils::TextureWrapMode::Repeat) {
+                        if matches!(wrap_mode, TextureWrapMode::Repeat) {
                             ui.label("Repeat");
                             let mut tiling_changed = ui
                                 .add(DragValue::new(&mut uv_tiling[0]).speed(0.05).range(0.01..=10_000.0))
diff --git a/crates/eucalyptus-editor/src/editor/mod.rs b/crates/eucalyptus-editor/src/editor/mod.rs
index 912206c..091f639 100644
--- a/crates/eucalyptus-editor/src/editor/mod.rs
+++ b/crates/eucalyptus-editor/src/editor/mod.rs
@@ -9,16 +9,15 @@ pub(crate) use crate::editor::dock::*;
 
 use crate::build::build;
 use crate::debug;
-use crate::graphics::OutlineShader;
 use crate::plugin::PluginRegistry;
 use crate::stats::NerdStats;
 use crossbeam_channel::{unbounded, Receiver, Sender};
 use dropbear_engine::buffer::ResizableBuffer;
 use dropbear_engine::entity::EntityTransform;
 use dropbear_engine::graphics::InstanceRaw;
-use dropbear_engine::mipmap::MipMapGenerator;
 use dropbear_engine::shader::Shader;
-use dropbear_engine::{camera::Camera, entity::{MeshRenderer, Transform}, future::FutureHandle, graphics::{RenderContext, SharedGraphicsContext}, lighting::LightManager, model::{ModelId, MODEL_CACHE}, scene::SceneCommand, DropbearWindowBuilder, WindowData};
+use dropbear_engine::texture::TextureWrapMode;
+use dropbear_engine::{camera::Camera, entity::{MeshRenderer, Transform}, future::FutureHandle, graphics::{SharedGraphicsContext}, lighting::LightManager, model::{ModelId, MODEL_CACHE}, scene::SceneCommand, DropbearWindowBuilder, WindowData};
 use egui::{self, Context};
 use egui_dock::{DockArea, DockState, NodeIndex, Style};
 use eucalyptus_core::{register_components, APP_INFO};
@@ -58,7 +57,7 @@ use wgpu::{Color, Extent3d, RenderPipeline};
 use winit::window::{CursorGrabMode, WindowAttributes};
 use winit::{keyboard::KeyCode, window::Window};
 use winit::dpi::PhysicalSize;
-use eucalyptus_core::physics::collider::{ColliderShape, ColliderShapeKey, WireframeGeometry};
+use eucalyptus_core::physics::collider::{ColliderShapeKey, WireframeGeometry};
 use eucalyptus_core::physics::collider::shader::ColliderInstanceRaw;
 use eucalyptus_core::physics::collider::shader::ColliderWireframePipeline;
 use eucalyptus_core::properties::CustomProperties;
@@ -74,15 +73,12 @@ pub struct Editor {
     pub size: Extent3d,
     pub render_pipeline: Option<RenderPipeline>,
     pub instance_buffer_cache: HashMap<ModelId, ResizableBuffer<InstanceRaw>>,
-    pub outline_pipeline: Option<OutlineShader>,
     pub collider_wireframe_pipeline: Option<ColliderWireframePipeline>,
     pub collider_wireframe_geometry_cache: HashMap<ColliderShapeKey, WireframeGeometry>,
     pub collider_instance_buffer: Option<ResizableBuffer<ColliderInstanceRaw>>,
     pub light_manager: LightManager,
     pub color: Color,
 
-    pub mipmap_generator: Option<MipMapGenerator>,
-
     pub active_camera: Arc<Mutex<Option<Entity>>>,
 
     pub is_viewport_focused: bool,
@@ -231,7 +227,6 @@ impl Editor {
             show_build_error_window: false,
             plugin_registry,
             dock_state_shared: None,
-            outline_pipeline: None,
             open_new_scene_window: false,
             new_scene_name: String::new(),
             current_scene_name: None,
@@ -243,7 +238,6 @@ impl Editor {
             play_mode_pid: None,
             play_mode_exit_rx: None,
             collider_wireframe_pipeline: None,
-            mipmap_generator: None,
             instance_buffer_cache: HashMap::new(),
             collider_wireframe_geometry_cache: HashMap::new(),
             collider_instance_buffer: None,
@@ -607,9 +601,9 @@ impl Editor {
         Ok(())
     }
 
-    fn cleanup_scene_resources(&mut self, graphics: &mut RenderContext) {
+    fn cleanup_scene_resources(&mut self, graphics: std::sync::Arc<dropbear_engine::graphics::SharedGraphicsContext>) {
         if let Some(handle) = self.world_load_handle.take() {
-            graphics.shared.future_queue.cancel(&handle);
+            graphics.future_queue.cancel(&handle);
         }
 
         self.light_spawn_queue.clear();
@@ -623,7 +617,6 @@ impl Editor {
         self.active_camera.lock().take();
 
         self.render_pipeline = None;
-        self.outline_pipeline = None;
         self.texture_id = None;
         self.light_manager = LightManager::new();
 
@@ -633,8 +626,8 @@ impl Editor {
         }
     }
 
-    fn start_async_scene_load(&mut self, mut scene: SceneConfig, graphics: &mut RenderContext) {
-        self.cleanup_scene_resources(graphics);
+    fn start_async_scene_load(&mut self, mut scene: SceneConfig, graphics: std::sync::Arc<dropbear_engine::graphics::SharedGraphicsContext>) {
+        self.cleanup_scene_resources(graphics.clone());
 
         let (progress_sender, progress_receiver) =
             unbounded::<WorldLoadingStatus>();
@@ -647,12 +640,12 @@ impl Editor {
         self.is_world_loaded = IsWorldLoadedYet::new();
         self.is_world_loaded.mark_scene_loaded();
 
-        let graphics_shared = graphics.shared.clone();
+        let graphics_shared = graphics.clone();
         let active_camera = self.active_camera.clone();
         let scene_name = scene.scene_name.clone();
         let component_registry_clone = self.component_registry.clone();
 
-        let handle = graphics.shared.future_queue.push(async move {
+        let handle = graphics.future_queue.push(async move {
             let mut temp_world = World::new();
 
             let load_result = scene
@@ -1308,69 +1301,38 @@ impl Editor {
     /// Loads all the wgpu resources such as renderer.
     ///
     /// **Note**: To be ran AFTER [`Editor::load_project_config`]
-    pub fn load_wgpu_nerdy_stuff<'a>(&mut self, graphics: &mut RenderContext<'a>) {
-        // log::debug!("Contents of viewport shader: \n{:#?}", dropbear_engine::shader::shader_wesl::SHADER_SHADER);
+    pub fn load_wgpu_nerdy_stuff<'a>(&mut self, graphics: std::sync::Arc<dropbear_engine::graphics::SharedGraphicsContext>) {
         let shader = Shader::new(
-            graphics.shared.clone(),
-            dropbear_engine::shader::shader_wesl::SHADER_SHADER,
-            Some("viewport_shader"),
+            graphics.clone(),
+            include_str!("../../../../resources/shaders/shader.wgsl"),
+            Some("viewport shader"),
         );
 
         self.light_manager
-            .create_light_array_resources(graphics.shared.clone());
-
-        if let Some(active_camera) = *self.active_camera.lock() {
-            if let Ok((camera, _)) = self
-                .world
-                .query_one::<(&Camera, &CameraComponent)>(active_camera)
-                .get()
-            {
-                let pipeline = graphics.create_render_pipline(
-                    &shader,
-                    vec![
-                        &graphics.shared.texture_bind_layout.clone(),
-                        camera.layout(),
-                        self.light_manager.layout(),
-                        &graphics.shared.material_tint_bind_layout.clone(),
-                    ],
-                    None,
-                );
-                self.render_pipeline = Some(pipeline);
-
-                // log::debug!("Contents of light shader: \n{:#?}", dropbear_engine::shader::shader_wesl::LIGHT_SHADER);
-                self.light_manager.create_render_pipeline(
-                    graphics.shared.clone(),
-                    dropbear_engine::shader::shader_wesl::LIGHT_SHADER,
-                    camera,
-                    Some("Light Pipeline"),
-                );
-
-                self.light_manager.create_shadow_pipeline(
-                    graphics.shared.clone(),
-                    dropbear_engine::shader::shader_wesl::SHADOW_SHADER,
-                    Some("Shadow Pipeline"),
-                );
-
-                // log::debug!("Contents of outline shader: \n{:#?}", dropbear_engine::shader::shader_wesl::OUTLINE_SHADER);
-                let outline_shader =
-                    OutlineShader::init(graphics.shared.clone(), camera.layout());
-                self.outline_pipeline = Some(outline_shader);
+            .create_light_array_resources(graphics.clone());
+
+        let pipeline = graphics.create_render_pipline(
+            &shader,
+            vec![
+                &graphics.layouts.texture_bind_layout,
+                &graphics.layouts.camera_bind_group_layout,
+                &graphics.layouts.light_array_bind_group_layout,
+                &graphics.layouts.material_tint_bind_layout,
+            ],
+            None,
+        );
+        self.render_pipeline = Some(pipeline);
 
-                let collider_pipeline = ColliderWireframePipeline::new(graphics.shared.clone(), camera.layout());
-                self.collider_wireframe_pipeline = Some(collider_pipeline);
+        self.light_manager.create_render_pipeline(
+            graphics.clone(),
+            include_str!("../../../../resources/shaders/light.wgsl"),
+            Some("Light Pipeline"),
+        );
 
-                self.mipmap_generator = Some(MipMapGenerator::new(graphics.shared.clone()))
-            } else {
-                log_once::warn_once!(
-                    "Unable to query Camera and CameraComponent for active camera: {:?}",
-                    active_camera
-                );
-            }
-        } else {
-            log_once::warn_once!("No active camera found");
-        }
+        let collider_pipeline = ColliderWireframePipeline::new(graphics.clone());
+        self.collider_wireframe_pipeline = Some(collider_pipeline);
 
-        self.window = Some(graphics.shared.window.clone());
+        self.window = Some(graphics.window.clone());
         self.is_world_loaded.mark_rendering_loaded();
     }
 
@@ -1548,10 +1510,10 @@ pub enum Signal {
     UpdateProceduralCuboid(hecs::Entity, [f32; 3]),
 
     /// Applies a diffuse texture to a material by loading from a URI/path.
-    SetMaterialTexture(hecs::Entity, String, String, dropbear_engine::utils::TextureWrapMode),
+    SetMaterialTexture(hecs::Entity, String, String, TextureWrapMode),
 
     /// Changes the sampler wrap mode for a material.
-    SetMaterialWrapMode(hecs::Entity, String, dropbear_engine::utils::TextureWrapMode),
+    SetMaterialWrapMode(hecs::Entity, String, TextureWrapMode),
 
     /// Sets UV tiling (repeat counts) for a material.
     SetMaterialUvTiling(hecs::Entity, String, [f32; 2]),
diff --git a/crates/eucalyptus-editor/src/editor/scene.rs b/crates/eucalyptus-editor/src/editor/scene.rs
index ff26b5c..36678c4 100644
--- a/crates/eucalyptus-editor/src/editor/scene.rs
+++ b/crates/eucalyptus-editor/src/editor/scene.rs
@@ -1,21 +1,21 @@
 use crossbeam_channel::unbounded;
 use dropbear_engine::buffer::ResizableBuffer;
 use glam::DMat4;
+use wgpu::util::DeviceExt;
 use std::collections::HashMap;
 use super::*;
 use crate::signal::SignalController;
 use crate::spawn::PendingSpawnController;
 use dropbear_engine::asset::{PointerKind, ASSET_REGISTRY};
-use dropbear_engine::graphics::{InstanceRaw, RenderContext};
+use dropbear_engine::graphics::{FrameGraphicsContext, InstanceRaw};
 use dropbear_engine::model::MODEL_CACHE;
 use dropbear_engine::{
     entity::{EntityTransform, MeshRenderer, Transform},
     lighting::{Light, LightComponent},
-    model::{DrawLight, DrawModel, DrawShadow},
+    model::{DrawLight, DrawModel},
     scene::{Scene, SceneCommand},
 };
 use eucalyptus_core::hierarchy::EntityTransformExt;
-use eucalyptus_core::logging;
 use eucalyptus_core::states::{Label, WorldLoadingStatus};
 use log;
 use parking_lot::Mutex;
@@ -23,11 +23,11 @@ use wgpu::Color;
 use winit::{event_loop::ActiveEventLoop, keyboard::KeyCode};
 use eucalyptus_core::physics::collider::ColliderGroup;
 use eucalyptus_core::physics::collider::ColliderShapeKey;
-use eucalyptus_core::physics::collider::shader::ColliderInstanceRaw;
+use eucalyptus_core::physics::collider::shader::{ColliderInstanceRaw, create_wireframe_geometry};
 use eucalyptus_core::properties::CustomProperties;
 
 impl Scene for Editor {
-    fn load(&mut self, graphics: &mut RenderContext) {
+    fn load(&mut self, graphics: std::sync::Arc<dropbear_engine::graphics::SharedGraphicsContext>) {
         self.current_scene_name = {
             let last_opened = {
                 let project = PROJECT.read();
@@ -49,7 +49,7 @@ impl Scene for Editor {
         self.progress_tx = Some(rx);
         self.world_receiver = Some(rx2);
 
-        let graphics_shared = graphics.shared.clone();
+        let graphics_shared = graphics.clone();
         let active_camera_clone = self.active_camera.clone();
         let project_path_clone = self.project_path.clone();
 
@@ -58,7 +58,7 @@ impl Scene for Editor {
 
         let component_registry = self.component_registry.clone();
 
-        let handle = graphics.shared.future_queue.push(async move {
+        let handle = graphics.future_queue.push(async move {
             let mut temp_world = World::new();
             if let Err(e) = Self::load_project_config(
                 graphics_shared,
@@ -81,13 +81,13 @@ impl Scene for Editor {
 
         self.dock_state_shared = Some(dock_state_shared);
 
-        self.window = Some(graphics.shared.window.clone());
+        self.window = Some(graphics.window.clone());
         self.is_world_loaded.mark_scene_loaded();
     }
 
-    fn physics_update(&mut self, _dt: f32, _graphics: &mut RenderContext) {}
+    fn physics_update(&mut self, _dt: f32, _graphics: std::sync::Arc<dropbear_engine::graphics::SharedGraphicsContext>) {}
 
-    fn update(&mut self, dt: f32, graphics: &mut RenderContext) {
+    fn update(&mut self, dt: f32, graphics: std::sync::Arc<dropbear_engine::graphics::SharedGraphicsContext>) {
         if let Some(rx) = &self.play_mode_exit_rx {
             if rx.try_recv().is_ok() {
                 log::info!("Play mode process has exited, returning to editing mode");
@@ -99,12 +99,11 @@ impl Scene for Editor {
         }
 
         if let Some(request) = self.pending_scene_load.take() {
-            self.start_async_scene_load(request.scene, graphics);
+            self.start_async_scene_load(request.scene, graphics.clone());
         }
 
-
         if let Some(mut receiver) = self.world_receiver.take() {
-            self.show_project_loading_window(&graphics.shared.get_egui_context());
+            self.show_project_loading_window(&graphics.get_egui_context());
             if let Ok(loaded_world) = receiver.try_recv() {
                 self.world = Box::new(loaded_world);
                 self.is_world_loaded.mark_project_loaded();
@@ -136,8 +135,8 @@ impl Scene for Editor {
         }
 
         match self.check_up(
-            graphics.shared.clone(),
-            graphics.shared.future_queue.clone(),
+            graphics.clone(),
+            graphics.future_queue.clone(),
         ) {
             Ok(_) => {}
             Err(e) => {
@@ -166,7 +165,7 @@ impl Scene for Editor {
                 )
             };
             
-            graphics.shared.window.set_title(&title);
+            graphics.window.set_title(&title);
         }
 
         {
@@ -183,7 +182,6 @@ impl Scene for Editor {
             let mut completed = Vec::new();
             for (i, handle) in self.light_spawn_queue.iter().enumerate() {
                 if let Some(l) = graphics
-                    .shared
                     .future_queue
                     .exchange_owned_as::<Light>(handle)
                 {
@@ -246,7 +244,7 @@ impl Scene for Editor {
             }
         }
 
-        let _ = self.run_signal(graphics.shared.clone());
+        let _ = self.run_signal(graphics.clone());
         
 
         if let Some(e) = self.previously_selected_entity
@@ -261,7 +259,7 @@ impl Scene for Editor {
             entity.is_selected = true
         }
 
-        let current_size = graphics.shared.viewport_texture.size;
+        let current_size = graphics.viewport_texture.size;
         self.size = current_size;
 
         let new_aspect = current_size.width as f64 / current_size.height as f64;
@@ -285,7 +283,7 @@ impl Scene for Editor {
                 .iter()
             {
                 component.update(camera);
-                camera.update(graphics.shared.clone());
+                camera.update(graphics.clone());
             }
         }
 
@@ -330,7 +328,7 @@ impl Scene for Editor {
                         continue;
                     };
 
-                    light.update(graphics.shared.as_ref(), light_component, &transform);
+                    light.update(graphics.as_ref(), light_component, &transform);
                 }
             }
         }
@@ -339,7 +337,7 @@ impl Scene for Editor {
             let sim_world = self.world.as_ref();
 
             self.light_manager
-                .update(graphics.shared.clone(), sim_world);
+                .update(graphics.clone(), sim_world);
 
             self.nerd_stats.write().record_stats(dt, sim_world.len() as u32);
         }
@@ -349,312 +347,320 @@ impl Scene for Editor {
         self.input_state.mouse_delta = None;
     }
 
-    fn render(&mut self, graphics: &mut RenderContext) {
-        // cornflower blue
-        let color = Color {
+    fn render<'a>(&mut self, graphics: Arc<SharedGraphicsContext>, frame_ctx: FrameGraphicsContext<'a>) {
+        self.show_ui(&graphics.get_egui_context());
+        eucalyptus_core::logging::render(&graphics.get_egui_context());
+
+        let cam = {
+            let c = self.active_camera.lock();
+            *c
+        };
+
+        let Some(active_camera) = cam else {
+            return;
+        };
+        log_once::debug_once!("Active camera found: {:?}", active_camera);
+
+        let q = self.world.query_one::<&Camera>(active_camera).get().ok().cloned();
+
+        let Some(camera) = q else {
+            return;
+        };
+        log_once::debug_once!("Camera ready");
+        log_once::debug_once!("Camera currently being viewed: {}", camera.label);
+
+        let Some(pipeline) = &self.render_pipeline else {
+            log_once::warn_once!("Render pipeline not ready");
+            return;
+        };
+        log_once::debug_once!("Pipeline ready");
+
+        let clear_color = Color {
             r: 100.0 / 255.0,
             g: 149.0 / 255.0,
             b: 237.0 / 255.0,
             a: 1.0,
         };
 
-        self.color = color;
-        self.size = graphics.shared.viewport_texture.size;
-        self.texture_id = Some(*graphics.shared.texture_id.clone());
-        {
-            self.show_ui(&graphics.shared.get_egui_context());
+        let lights = {
+            let mut lights = Vec::new();
+            let mut query = self.world.query::<(&Light, &LightComponent)>();
+            for (light, comp) in query.iter() {
+                lights.push((light.clone(), comp.clone()));
+            }
+            lights
+        };
+
+        let renderers = {
+            let mut renderers = Vec::new();
+            let mut query = self.world.query::<&MeshRenderer>();
+            for renderer in query.iter() {
+                renderers.push(renderer.clone());
+            }
+            renderers
+        };
+
+        let mut model_batches: HashMap<ModelId, Vec<InstanceRaw>> = HashMap::new();
+        for renderer in &renderers {
+            model_batches
+                .entry(renderer.model_id())
+                .or_default()
+                .push(renderer.instance.to_raw());
         }
 
-        self.window = Some(graphics.shared.window.clone());
-        logging::render(&graphics.shared.get_egui_context());
-        if let Some(pipeline) = &self.render_pipeline {
-            log_once::debug_once!("Found render pipeline");
-            let (active_camera, world): (Option<Entity>, &World) =
-                (*self.active_camera.lock(), self.world.as_ref());
+        let mut prepared_models = Vec::new();
+        for (model_id, instances) in model_batches {
+            let model_opt = {
+                let cache = MODEL_CACHE.lock();
+                cache.values().find(|model| model.id == model_id).cloned()
+            };
 
-            if let Some(active_camera) = active_camera {
-                let cam = {
-                    world.query_one::<&Camera>(active_camera).get().ok().cloned()
-                };
+            let Some(model) = model_opt else {
+                log_once::error_once!("Missing model {:?} in cache", model_id);
+                continue;
+            };
 
-                if let Some(camera) = cam {
-                    let lights = {
-                        let mut lights = Vec::new();
-                        let mut light_query = world.query::<(&Light, &LightComponent)>();
-                        for (light, comp) in light_query.iter() {
-                            lights.push((light.clone(), comp.clone()));
-                        }
-                        lights
-                    };
+            let instance_buffer = graphics.device.create_buffer_init(
+                &wgpu::util::BufferInitDescriptor {
+                    label: Some("Runtime Instance Buffer"),
+                    contents: bytemuck::cast_slice(&instances),
+                    usage: wgpu::BufferUsages::VERTEX,
+                },
+            );
 
-                    let entities = {
-                        let mut entities = Vec::new();
-                        let mut entity_query = world.query::<&MeshRenderer>();
-                        for renderer in entity_query.iter() {
-                            entities.push(renderer.clone());
-                        }
-                        entities
-                    };
+            prepared_models.push((model, instance_buffer, instances.len() as u32));
+        }
 
-                    // mipmap gen
-                    // LOL THIS DOESNT WORK FUCK WHY CANT YOU BE LIKE OPENGL AND MAKE MIPMAPPING SIMPLER FML
-                    if let Some(mipmap) = &self.mipmap_generator {
-                        let graphics = graphics.shared.clone();
-                        let mut encoder = graphics.device.create_command_encoder(&wgpu::CommandEncoderDescriptor {
-                            label: Some("mip map generator command encoder descriptor"),
-                        });
+        {
+            let mut query = self.world.query::<(
+                &mut LightComponent,
+                Option<&dropbear_engine::entity::Transform>,
+                Option<&dropbear_engine::entity::EntityTransform>,
+                &mut Light,
+            )>();
+
+            for (light_component, transform_opt, entity_transform_opt, light) in query.iter() {
+                let transform = if let Some(entity_transform) = entity_transform_opt {
+                    entity_transform.sync()
+                } else if let Some(transform) = transform_opt {
+                    *transform
+                } else {
+                    continue;
+                };
 
-                        for i in ASSET_REGISTRY.iter_material() {
-                            mipmap.generate(&graphics.device, &mut encoder, &i.diffuse_texture.texture);
-                        }
+                light.update(graphics.as_ref(), light_component, &transform);
+            }
+        }
 
-                        graphics.queue.submit(Some(encoder.finish()));
-                    }
+        self.light_manager.update(graphics.clone(), &self.world);
 
-                    let mut model_batches: HashMap<ModelId, Vec<InstanceRaw>> = HashMap::new();
-                    for renderer in &entities {
-                        let model_ptr = renderer.model_id();
-                        let instance_raw = renderer.instance.to_raw();
-                        model_batches
-                            .entry(model_ptr)
-                            .or_default()
-                            .push(instance_raw);
+        {
+            let mut render_pass = frame_ctx.encoder
+                .begin_render_pass(&wgpu::RenderPassDescriptor {
+                    label: Some("light cube render pass"),
+                    color_attachments: &[Some(wgpu::RenderPassColorAttachment {
+                        view: &frame_ctx.view,
+                        depth_slice: None,
+                        resolve_target: None,
+                        ops: wgpu::Operations {
+                            load: wgpu::LoadOp::Clear(clear_color),
+                            store: wgpu::StoreOp::Store,
+                        },
+                    })],
+                    depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment {
+                        view: &graphics.depth_texture.view,
+                        depth_ops: Some(wgpu::Operations {
+                            load: wgpu::LoadOp::Clear(0.0),
+                            store: wgpu::StoreOp::Store,
+                        }),
+                        stencil_ops: None,
+                    }),
+                    occlusion_query_set: None,
+                    timestamp_writes: None,
+                });
+            if let Some(light_pipeline) = &self.light_manager.pipeline {
+                render_pass.set_pipeline(light_pipeline);
+                for (light, component) in &lights {
+                    render_pass.set_vertex_buffer(1, light.instance_buffer.buffer().slice(..));
+                    if component.visible {
+                        render_pass.draw_light_model(
+                            &light.cube_model,
+                            &camera.bind_group,
+                            &light.bind_group
+                        );
                     }
+                }
+            }
+        }
 
-                    let model_batches: Vec<(ModelId, Vec<InstanceRaw>)> =
-                        model_batches.into_iter().collect();
-
-                    let mut prepared_models = Vec::new();
-                    for (model_ptr, instances) in &model_batches {
-                        let model_opt = {
-                            let cache = MODEL_CACHE.lock();
-                            cache.values().find(|m| m.id == *model_ptr).cloned()
-                        };
-
-                        let Some(model) = model_opt else {
-                            log_once::error_once!("No such MODEL as {:?}", model_ptr);
-                            continue;
-                        };
-
-                        let buffer_handler = self
-                            .instance_buffer_cache
-                            .entry(*model_ptr)
-                            .or_insert_with(|| {
-                                ResizableBuffer::new(
-                                    &graphics.shared.device,
-                                    instances.len().max(10),
-                                    wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
-                                    "Batched Instance Buffer",
-                                )
-                            });
+        for (model, instance_buffer, instance_count) in prepared_models {
+            let mut render_pass = frame_ctx.encoder
+                .begin_render_pass(&wgpu::RenderPassDescriptor {
+                    label: Some("model render pass"),
+                    color_attachments: &[Some(wgpu::RenderPassColorAttachment {
+                        view: &frame_ctx.view,
+                        depth_slice: None,
+                        resolve_target: None,
+                        ops: wgpu::Operations {
+                            load: wgpu::LoadOp::Load,
+                            store: wgpu::StoreOp::Store,
+                        },
+                    })],
+                    depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment {
+                        view: &graphics.depth_texture.view,
+                        depth_ops: Some(wgpu::Operations {
+                            load: wgpu::LoadOp::Load,
+                            store: wgpu::StoreOp::Store,
+                        }),
+                        stencil_ops: None,
+                    }),
+                    occlusion_query_set: None,
+                    timestamp_writes: None,
+                });
+            render_pass.set_pipeline(pipeline);
+            render_pass.set_vertex_buffer(1, instance_buffer.slice(..));
+            render_pass.draw_model_instanced(
+                &model,
+                0..instance_count,
+                &camera.bind_group,
+                self.light_manager.bind_group(),
+            );
+        }
 
-                        buffer_handler.write(
-                            &graphics.shared.device,
-                            &graphics.shared.queue,
-                            instances,
-                        );
+        {
+            let show_hitboxes = self
+                .current_scene_name
+                .as_ref()
+                .and_then(|scene_name| {
+                    let scenes = SCENES.read();
+                    scenes
+                        .iter()
+                        .find(|scene| &scene.scene_name == scene_name)
+                        .map(|scene| scene.settings.show_hitboxes)
+                })
+                .unwrap_or(false);
+
+            if show_hitboxes {
+                if let Some(collider_pipeline) = &self.collider_wireframe_pipeline {
+                    let mut render_pass = frame_ctx.encoder
+                        .begin_render_pass(&wgpu::RenderPassDescriptor {
+                            label: Some("model render pass"),
+                            color_attachments: &[Some(wgpu::RenderPassColorAttachment {
+                                view: &frame_ctx.view,
+                                depth_slice: None,
+                                resolve_target: None,
+                                ops: wgpu::Operations {
+                                    load: wgpu::LoadOp::Load,
+                                    store: wgpu::StoreOp::Store,
+                                },
+                            })],
+                            depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment {
+                                view: &graphics.depth_texture.view,
+                                depth_ops: Some(wgpu::Operations {
+                                    load: wgpu::LoadOp::Load,
+                                    store: wgpu::StoreOp::Store,
+                                }),
+                                stencil_ops: None,
+                            }),
+                            occlusion_query_set: None,
+                            timestamp_writes: None,
+                        });
+                    
+                    render_pass.set_pipeline(&collider_pipeline.pipeline);
+                    render_pass.set_bind_group(0, &camera.bind_group, &[]);
 
-                        prepared_models.push((
-                            model,
-                            buffer_handler.buffer().clone(),
-                            instances.len() as u32,
-                        ));
-                    }
+                    let mut instances_by_shape: HashMap<ColliderShapeKey, Vec<ColliderInstanceRaw>> =
+                        HashMap::new();
 
-                    if let Some(shadow_pipeline) = &self.light_manager.shadow_pipeline {
-                        for (light, component) in &lights {
-                            if !component.enabled || !component.cast_shadows {
-                                continue;
-                            }
+                    let mut q = self.world.query::<(&EntityTransform, &ColliderGroup)>();
+                    for (entity_transform, group) in q.iter() {
+                        for collider in &group.colliders {
+                            let world_tf = entity_transform.sync();
 
-                            let shadow_index = light.uniform().shadow_index;
-                            if shadow_index < 0 {
-                                continue;
-                            }
+                            let entity_matrix = DMat4::from_rotation_translation(
+                                world_tf.rotation,
+                                world_tf.position,
+                            )
+                            .as_mat4();
 
-                            let shadow_index = shadow_index as usize;
-                            if shadow_index >= self.light_manager.shadow_target_views.len() {
-                                continue;
-                            }
-
-                            let shadow_view =
-                                &self.light_manager.shadow_target_views[shadow_index];
-                            let mut shadow_pass = graphics
-                                .begin_shadow_pass(shadow_view, Some("Shadow Pass"));
-                            shadow_pass.set_pipeline(shadow_pipeline);
-
-                            for (model, instance_buffer, instance_count) in &prepared_models {
-                                shadow_pass.set_vertex_buffer(1, instance_buffer.slice(..));
-                                shadow_pass.draw_shadow_model_instanced(
-                                    model,
-                                    0..*instance_count,
-                                    light.bind_group(),
-                                );
-                            }
-                        }
-                    }
+                            let offset_transform = Transform::new()
+                                .with_offset(collider.translation, collider.rotation);
+                            let offset_matrix = offset_transform.matrix().as_mat4();
+
+                            let final_matrix = entity_matrix * offset_matrix;
+
+                            let color = [0.0, 1.0, 0.0, 1.0];
+                            let instance = ColliderInstanceRaw::from_matrix(final_matrix, color);
 
-                    { // light cube rendering
-                        let mut render_pass = graphics.clear_colour(color);
-                        if let Some(light_pipeline) = &self.light_manager.pipeline {
-                            render_pass.set_pipeline(light_pipeline);
-                            for (light, _component) in &lights {
-                                render_pass.set_vertex_buffer(
-                                    1,
-                                    light.instance_buffer.buffer().slice(..),
-                                );
-                                if _component.visible {
-                                    render_pass.draw_light_model(
-                                        &light.cube_model,
-                                        camera.bind_group(),
-                                        light.bind_group(),
-                                    );
-                                }
-                            }
+                            let key = ColliderShapeKey::from(&collider.shape);
+                            instances_by_shape.entry(key).or_default().push(instance);
+
+                            self.collider_wireframe_geometry_cache.entry(key).or_insert_with(|| {
+                                create_wireframe_geometry(
+                                    graphics.clone(),
+                                    &collider.shape,
+                                )
+                            });
                         }
                     }
 
-                    { // standard model rendering
-                        for (model, instance_buffer, instance_count) in prepared_models {
-                            let mut render_pass = graphics.continue_pass();
-                            render_pass.set_pipeline(pipeline);
-                            render_pass.set_vertex_buffer(1, instance_buffer.slice(..));
-
-                            render_pass.draw_model_instanced(
-                                &model,
-                                0..instance_count,
-                                camera.bind_group(),
-                                self.light_manager.bind_group(),
-                            );
+                    if !instances_by_shape.is_empty() {
+                        let total_instances: usize =
+                            instances_by_shape.values().map(|v| v.len()).sum();
+                        let mut all_instances = Vec::with_capacity(total_instances);
+                        let mut draws: Vec<(ColliderShapeKey, usize, usize)> = Vec::new();
+
+                        for (key, instances) in instances_by_shape {
+                            let start = all_instances.len();
+                            all_instances.extend_from_slice(&instances);
+                            let count = instances.len();
+                            draws.push((key, start, count));
                         }
-                    }
 
-                    {
-                        let show_hitboxes = self
-                            .current_scene_name
-                            .as_ref()
-                            .and_then(|scene_name| {
-                                let scenes = SCENES.read();
-                                scenes
-                                    .iter()
-                                    .find(|scene| &scene.scene_name == scene_name)
-                                    .map(|scene| scene.settings.show_hitboxes)
-                            })
-                            .unwrap_or(false);
-
-                        if show_hitboxes {
-                            if let Some(collider_pipeline) = &self.collider_wireframe_pipeline {
-                            let mut render_pass = graphics.continue_pass();
-                            render_pass.set_pipeline(&collider_pipeline.pipeline);
-                            render_pass.set_bind_group(0, camera.bind_group(), &[]);
-
-                                let mut instances_by_shape: HashMap<ColliderShapeKey, Vec<ColliderInstanceRaw>> =
-                                    HashMap::new();
-
-                                let mut q = self.world.query::<(&EntityTransform, &ColliderGroup)>();
-                                for (entity_transform, group) in q.iter() {
-                                    for collider in &group.colliders {
-                                        let world_tf = entity_transform.sync();
-
-                                        let entity_matrix = DMat4::from_rotation_translation(
-                                            world_tf.rotation,
-                                            world_tf.position,
-                                        )
-                                        .as_mat4();
-
-                                        let offset_transform = Transform::new()
-                                            .with_offset(collider.translation, collider.rotation);
-                                        let offset_matrix = offset_transform.matrix().as_mat4();
-
-                                        let final_matrix = entity_matrix * offset_matrix;
-
-                                        let color = [0.0, 1.0, 0.0, 1.0];
-                                        let instance = ColliderInstanceRaw::from_matrix(final_matrix, color);
-
-                                        let key = ColliderShapeKey::from(&collider.shape);
-                                        instances_by_shape.entry(key).or_default().push(instance);
-
-                                        self.collider_wireframe_geometry_cache
-                                            .entry(key)
-                                            .or_insert_with(|| {
-                                                Editor::create_wireframe_geometry(
-                                                    graphics.shared.clone(),
-                                                    &collider.shape,
-                                                )
-                                            });
-                                    }
-                                }
-
-                                if !instances_by_shape.is_empty() {
-                                    let total_instances: usize =
-                                        instances_by_shape.values().map(|v| v.len()).sum();
-                                    let mut all_instances = Vec::with_capacity(total_instances);
-                                    let mut draws: Vec<(ColliderShapeKey, usize, usize)> = Vec::new();
-
-                                    for (key, instances) in instances_by_shape {
-                                        let start = all_instances.len();
-                                        all_instances.extend_from_slice(&instances);
-                                        let count = instances.len();
-                                        draws.push((key, start, count));
-                                    }
-
-                                    let instance_buffer = self.collider_instance_buffer.get_or_insert_with(|| {
-                                        ResizableBuffer::new(
-                                            &graphics.shared.device,
-                                            all_instances.len().max(10),
-                                            wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
-                                            "Collider Instance Buffer",
-                                        )
-                                    });
-                                    instance_buffer.write(
-                                        &graphics.shared.device,
-                                        &graphics.shared.queue,
-                                        &all_instances,
-                                    );
-
-                                    for (key, start, count) in draws {
-                                        let Some(geometry) = self.collider_wireframe_geometry_cache.get(&key) else {
-                                            continue;
-                                        };
-
-                                        let start_bytes =
-                                            (start * std::mem::size_of::<ColliderInstanceRaw>()) as wgpu::BufferAddress;
-                                        let end_bytes =
-                                            ((start + count) * std::mem::size_of::<ColliderInstanceRaw>()) as wgpu::BufferAddress;
-
-                                        render_pass.set_vertex_buffer(
-                                            1,
-                                            instance_buffer.buffer().slice(start_bytes..end_bytes),
-                                        );
-                                        render_pass.set_vertex_buffer(
-                                            0,
-                                            geometry.vertex_buffer.slice(..),
-                                        );
-                                        render_pass.set_index_buffer(
-                                            geometry.index_buffer.slice(..),
-                                            wgpu::IndexFormat::Uint16,
-                                        );
-                                        render_pass.draw_indexed(
-                                            0..geometry.index_count,
-                                            0,
-                                            0..count as u32,
-                                        );
-                                    }
-                                }
-                            }
+                        let instance_buffer = self.collider_instance_buffer.get_or_insert_with(|| {
+                            ResizableBuffer::new(
+                                &graphics.device,
+                                all_instances.len().max(10),
+                                wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
+                                "Collider Instance Buffer",
+                            )
+                        });
+                        instance_buffer.write(
+                            &graphics.device,
+                            &graphics.queue,
+                            &all_instances,
+                        );
+
+                        for (key, start, count) in draws {
+                            let Some(geometry) = self.collider_wireframe_geometry_cache.get(&key) else {
+                                continue;
+                            };
+
+                            let start_bytes =
+                                (start * std::mem::size_of::<ColliderInstanceRaw>()) as wgpu::BufferAddress;
+                            let end_bytes =
+                                ((start + count) * std::mem::size_of::<ColliderInstanceRaw>()) as wgpu::BufferAddress;
+
+                            render_pass.set_vertex_buffer(
+                                1,
+                                instance_buffer.buffer().slice(start_bytes..end_bytes),
+                            );
+                            render_pass.set_vertex_buffer(
+                                0,
+                                geometry.vertex_buffer.slice(..),
+                            );
+                            render_pass.set_index_buffer(
+                                geometry.index_buffer.slice(..),
+                                wgpu::IndexFormat::Uint16,
+                            );
+                            render_pass.draw_indexed(
+                                0..geometry.index_count,
+                                0,
+                                0..count as u32,
+                            );
                         }
                     }
-                } else {
-                    log_once::error_once!("Camera returned None");
                 }
-            } else {
-                log_once::error_once!("No active camera found");
-            }
-        } else {
-            if self.is_world_loaded.is_fully_loaded() {
-                log_once::warn_once!("No render pipeline exists");
-            } else {
-                log_once::debug_once!("No render pipeline exists, but world not loaded yet");
             }
         }
     }
@@ -664,29 +670,4 @@ impl Scene for Editor {
     fn run_command(&mut self) -> SceneCommand {
         std::mem::replace(&mut self.scene_command, SceneCommand::None)
     }
-}
-
-impl Editor {
-    pub fn create_wireframe_geometry(
-        graphics: Arc<SharedGraphicsContext>,
-        shape: &ColliderShape,
-    ) -> WireframeGeometry {
-        match shape {
-            ColliderShape::Box { half_extents } => {
-                WireframeGeometry::box_wireframe(graphics, *half_extents)
-            }
-            ColliderShape::Sphere { radius } => {
-                WireframeGeometry::sphere_wireframe(graphics, *radius, 16, 16)
-            }
-            ColliderShape::Capsule { half_height, radius } => {
-                WireframeGeometry::capsule_wireframe(graphics, *half_height, *radius, 16)
-            }
-            ColliderShape::Cylinder { half_height, radius } => {
-                WireframeGeometry::cylinder_wireframe(graphics, *half_height, *radius, 16)
-            }
-            ColliderShape::Cone { half_height, radius } => {
-                WireframeGeometry::cone_wireframe(graphics, *half_height, *radius, 16)
-            }
-        }
-    }
 }
\ No newline at end of file
diff --git a/crates/eucalyptus-editor/src/editor/settings/editor.rs b/crates/eucalyptus-editor/src/editor/settings/editor.rs
index b618605..051a931 100644
--- a/crates/eucalyptus-editor/src/editor/settings/editor.rs
+++ b/crates/eucalyptus-editor/src/editor/settings/editor.rs
@@ -13,7 +13,6 @@ use winit::event::MouseButton;
 use winit::event_loop::ActiveEventLoop;
 use winit::keyboard::KeyCode;
 use winit::window::{WindowId};
-use dropbear_engine::graphics::RenderContext;
 use dropbear_engine::input::{Controller, Keyboard, Mouse};
 use dropbear_engine::scene::{Scene, SceneCommand};
 use eucalyptus_core::input::InputState;
@@ -125,14 +124,14 @@ impl EditorSettingsWindow {
 }
 
 impl Scene for EditorSettingsWindow {
-    fn load(&mut self, graphics: &mut RenderContext) {
-        self.window = Some(graphics.shared.window.id());
+    fn load(&mut self, graphics: std::sync::Arc<dropbear_engine::graphics::SharedGraphicsContext>) {
+        self.window = Some(graphics.window.id());
     }
 
-    fn physics_update(&mut self, _dt: f32, _graphics: &mut RenderContext) {}
+    fn physics_update(&mut self, _dt: f32, _graphics: std::sync::Arc<dropbear_engine::graphics::SharedGraphicsContext>) {}
 
-    fn update(&mut self, _dt: f32, graphics: &mut RenderContext) {
-        CentralPanel::default().show(&graphics.shared.get_egui_context(), |ui| {
+    fn update(&mut self, _dt: f32, graphics: std::sync::Arc<dropbear_engine::graphics::SharedGraphicsContext>) {
+        CentralPanel::default().show(&graphics.get_egui_context(), |ui| {
             let mut editor = EDITOR_SETTINGS.write();
 
             egui::SidePanel::left("editor_settings_tree_panel")
@@ -211,10 +210,10 @@ impl Scene for EditorSettingsWindow {
             });
         });
 
-        self.window = Some(graphics.shared.window.id());
+        self.window = Some(graphics.window.id());
     }
 
-    fn render(&mut self, _graphics: &mut RenderContext) {
+    fn render<'a>(&mut self, _graphics: std::sync::Arc<dropbear_engine::graphics::SharedGraphicsContext>, _frame_ctx: dropbear_engine::graphics::FrameGraphicsContext<'a>) {
     }
 
     fn exit(&mut self, _event_loop: &ActiveEventLoop) {
diff --git a/crates/eucalyptus-editor/src/editor/settings/project.rs b/crates/eucalyptus-editor/src/editor/settings/project.rs
index 55d040b..15593bd 100644
--- a/crates/eucalyptus-editor/src/editor/settings/project.rs
+++ b/crates/eucalyptus-editor/src/editor/settings/project.rs
@@ -9,7 +9,6 @@ use winit::event::MouseButton;
 use winit::event_loop::ActiveEventLoop;
 use winit::keyboard::KeyCode;
 use winit::window::{WindowId};
-use dropbear_engine::graphics::RenderContext;
 use dropbear_engine::input::{Controller, Keyboard, Mouse};
 use dropbear_engine::scene::{Scene, SceneCommand};
 use eucalyptus_core::input::InputState;
@@ -45,14 +44,14 @@ impl ProjectSettingsWindow {
 }
 
 impl Scene for ProjectSettingsWindow {
-    fn load(&mut self, graphics: &mut RenderContext) {
-        self.window = Some(graphics.shared.window.id());
+    fn load(&mut self, graphics: std::sync::Arc<dropbear_engine::graphics::SharedGraphicsContext>) {
+        self.window = Some(graphics.window.id());
     }
 
-    fn physics_update(&mut self, _dt: f32, _graphics: &mut RenderContext) {}
+    fn physics_update(&mut self, _dt: f32, _graphics: std::sync::Arc<dropbear_engine::graphics::SharedGraphicsContext>) {}
 
-    fn update(&mut self, _dt: f32, graphics: &mut RenderContext) {
-        CentralPanel::default().show(&graphics.shared.get_egui_context(), |ui| {
+    fn update(&mut self, _dt: f32, graphics: std::sync::Arc<dropbear_engine::graphics::SharedGraphicsContext>) {
+        CentralPanel::default().show(&graphics.get_egui_context(), |ui| {
             let mut project = PROJECT.write();
 
             egui::SidePanel::left("project_settings_tree_panel")
@@ -181,10 +180,10 @@ impl Scene for ProjectSettingsWindow {
             });
         });
 
-        self.window = Some(graphics.shared.window.id());
+        self.window = Some(graphics.window.id());
     }
 
-    fn render(&mut self, _graphics: &mut RenderContext) {
+    fn render<'a>(&mut self, _graphics: std::sync::Arc<dropbear_engine::graphics::SharedGraphicsContext>, _frame_ctx: dropbear_engine::graphics::FrameGraphicsContext<'a>) {
     }
 
     fn exit(&mut self, _event_loop: &ActiveEventLoop) {
diff --git a/crates/eucalyptus-editor/src/graphics.rs b/crates/eucalyptus-editor/src/graphics.rs
deleted file mode 100644
index c52b6e9..0000000
--- a/crates/eucalyptus-editor/src/graphics.rs
+++ /dev/null
@@ -1,144 +0,0 @@
-//! Extension module for the dropbear graphics that are editor specific.
-
-use dropbear_engine::colour::Colour;
-use dropbear_engine::graphics::{InstanceRaw, SharedGraphicsContext, Texture};
-use dropbear_engine::model::{ModelVertex, Vertex};
-use dropbear_engine::shader::Shader;
-use std::sync::Arc;
-
-#[repr(C)]
-#[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
-pub struct OutlineUniform {
-    pub outline_width: f32,
-    pub outline_color: [f32; 4],
-    pub _padding: [f32; 3],
-}
-
-impl Default for OutlineUniform {
-    fn default() -> Self {
-        Self {
-            outline_width: 0.02,
-            outline_color: Colour::ORANGE.to_raw_vec4(),
-            _padding: [0.0, 0.0, 0.0],
-        }
-    }
-}
-
-pub struct OutlineShader {
-    pub pipeline: wgpu::RenderPipeline,
-    pub bind_group_layout: wgpu::BindGroupLayout,
-    pub bind_group: wgpu::BindGroup,
-    pub uniform_buffer: wgpu::Buffer,
-}
-
-impl OutlineShader {
-    pub fn init(
-        graphics: Arc<SharedGraphicsContext>,
-        camera_layout: &wgpu::BindGroupLayout,
-    ) -> Self {
-        let shader = Shader::new(
-            graphics.clone(),
-            dropbear_engine::shader::shader_wesl::OUTLINE_SHADER,
-            Some("outline_shader"),
-        );
-        log::trace!("Created outline shader");
-
-        let bind_group_layout =
-            graphics
-                .device
-                .create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
-                    label: Some("Outline Bind Group Layout"),
-                    entries: &[wgpu::BindGroupLayoutEntry {
-                        binding: 0,
-                        visibility: wgpu::ShaderStages::VERTEX | wgpu::ShaderStages::FRAGMENT,
-                        ty: wgpu::BindingType::Buffer {
-                            ty: wgpu::BufferBindingType::Uniform,
-                            has_dynamic_offset: false,
-                            min_binding_size: None,
-                        },
-                        count: None,
-                    }],
-                });
-        log::trace!("Created outline bind group layout");
-
-        let uniform = OutlineUniform::default();
-        let uniform_buffer = graphics.create_uniform(uniform, Some("Outline Uniform"));
-
-        let bind_group = graphics
-            .device
-            .create_bind_group(&wgpu::BindGroupDescriptor {
-                label: Some("Outline Bind Group"),
-                layout: &bind_group_layout,
-                entries: &[wgpu::BindGroupEntry {
-                    binding: 0,
-                    resource: uniform_buffer.as_entire_binding(),
-                }],
-            });
-        log::trace!("Created outline bind group");
-
-        let pipeline_layout =
-            graphics
-                .device
-                .create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
-                    label: Some("Outline Pipeline Layout"),
-                    bind_group_layouts: &[&bind_group_layout, &camera_layout],
-                    push_constant_ranges: &[],
-                });
-        log::trace!("Created outline pipeline layout");
-
-        let pipeline = graphics
-            .device
-            .create_render_pipeline(&wgpu::RenderPipelineDescriptor {
-                label: Some("Outline Pipeline"),
-                layout: Some(&pipeline_layout),
-                vertex: wgpu::VertexState {
-                    module: &shader.module,
-                    entry_point: Some("vs_main"),
-                    buffers: &[ModelVertex::desc(), InstanceRaw::desc()],
-                    compilation_options: wgpu::PipelineCompilationOptions::default(),
-                },
-                fragment: Some(wgpu::FragmentState {
-                    module: &shader.module,
-                    entry_point: Some("fs_main"),
-                    targets: &[Some(wgpu::ColorTargetState {
-                        format: wgpu::TextureFormat::Rgba16Float,
-                        blend: Some(wgpu::BlendState::REPLACE),
-                        write_mask: wgpu::ColorWrites::ALL,
-                    })],
-                    compilation_options: wgpu::PipelineCompilationOptions::default(),
-                }),
-                primitive: wgpu::PrimitiveState {
-                    topology: wgpu::PrimitiveTopology::TriangleList,
-                    strip_index_format: None,
-                    front_face: wgpu::FrontFace::Ccw,
-                    cull_mode: Some(wgpu::Face::Front),
-                    polygon_mode: wgpu::PolygonMode::Fill,
-                    unclipped_depth: false,
-                    conservative: false,
-                },
-                depth_stencil: Some(wgpu::DepthStencilState {
-                    format: Texture::DEPTH_FORMAT,
-                    depth_write_enabled: true,
-                    depth_compare: wgpu::CompareFunction::Greater,
-                    stencil: wgpu::StencilState::default(),
-                    bias: wgpu::DepthBiasState::default(),
-                }),
-                multisample: wgpu::MultisampleState {
-                    count: 1,
-                    mask: !0,
-                    alpha_to_coverage_enabled: false,
-                },
-                multiview: None,
-                cache: None,
-            });
-        log::trace!("Created outline pipeline");
-
-        log::debug!("Created outline render pipeline");
-        Self {
-            pipeline,
-            bind_group_layout,
-            bind_group,
-            uniform_buffer,
-        }
-    }
-}
diff --git a/crates/eucalyptus-editor/src/lib.rs b/crates/eucalyptus-editor/src/lib.rs
index 354f15e..6a9fa86 100644
--- a/crates/eucalyptus-editor/src/lib.rs
+++ b/crates/eucalyptus-editor/src/lib.rs
@@ -2,14 +2,13 @@ pub mod build;
 pub mod camera;
 pub mod debug;
 pub mod editor;
-pub mod graphics;
 pub mod menu;
 pub mod plugin;
 pub mod signal;
 pub mod spawn;
 pub mod stats;
 pub mod utils;
-pub mod runtime;
 pub mod about;
 pub mod process;
 pub mod physics;
+pub use redback_runtime as runtime;
\ No newline at end of file
diff --git a/crates/eucalyptus-editor/src/main.rs b/crates/eucalyptus-editor/src/main.rs
index f7de1fd..1d023c1 100644
--- a/crates/eucalyptus-editor/src/main.rs
+++ b/crates/eucalyptus-editor/src/main.rs
@@ -4,6 +4,7 @@
 use anyhow::{bail, Context};
 use clap::{Arg, Command};
 use dropbear_engine::future::FutureQueue;
+use dropbear_engine::texture::DropbearEngineLogo;
 use eucalyptus_editor::{build, editor, menu};
 use parking_lot::RwLock;
 use std::sync::Arc;
@@ -269,7 +270,7 @@ async fn main() -> anyhow::Result<()> {
                     panic!("Unable to initialise Eucalyptus Editor: {}", e)
                 })));
 
-            let img = dropbear_engine::gen_logo()?;
+            let img = DropbearEngineLogo::generate()?;
 
             let window_icon = Icon::from_rgba(img.0, img.1, img.2)
                 .inspect_err(|e| log::warn!("Unable to set logo: {}", e))
diff --git a/crates/eucalyptus-editor/src/menu.rs b/crates/eucalyptus-editor/src/menu.rs
index 7ea30e7..77cbb9f 100644
--- a/crates/eucalyptus-editor/src/menu.rs
+++ b/crates/eucalyptus-editor/src/menu.rs
@@ -1,5 +1,5 @@
 use anyhow::{Context, anyhow};
-use dropbear_engine::{future::{FutureHandle, FutureQueue}, graphics::RenderContext, input::{Controller, Keyboard, Mouse}, scene::{Scene, SceneCommand}, DropbearWindowBuilder};
+use dropbear_engine::{DropbearWindowBuilder, future::{FutureHandle, FutureQueue}, graphics::FrameGraphicsContext, input::{Controller, Keyboard, Mouse}, scene::{Scene, SceneCommand}};
 use egui::{self, FontId, Frame, RichText};
 use egui_toast::{ToastOptions, Toasts};
 use eucalyptus_core::config::ProjectConfig;
@@ -234,19 +234,18 @@ impl MainMenu {
 }
 
 impl Scene for MainMenu {
-    fn load(&mut self, _graphics: &mut RenderContext) {
+    fn load(&mut self, _graphics: std::sync::Arc<dropbear_engine::graphics::SharedGraphicsContext>) {
         log::info!("Loaded main menu scene");
     }
 
-    fn physics_update(&mut self, _dt: f32, _graphics: &mut RenderContext) {}
+    fn physics_update(&mut self, _dt: f32, _graphics: std::sync::Arc<dropbear_engine::graphics::SharedGraphicsContext>) {}
 
-    fn update(&mut self, _dt: f32, _graphics: &mut RenderContext) {}
+    fn update(&mut self, _dt: f32, _graphics: std::sync::Arc<dropbear_engine::graphics::SharedGraphicsContext>) {}
 
-    fn render(&mut self, graphics: &mut RenderContext) {
+    fn render<'a>(&mut self, graphics: std::sync::Arc<dropbear_engine::graphics::SharedGraphicsContext>, _frame_ctx: FrameGraphicsContext<'a>) {
         #[allow(clippy::collapsible_if)]
         if let Some(handle) = self.project_creation_handle.as_ref() {
             if let Some(result) = graphics
-                .shared
                 .future_queue
                 .exchange_owned_as::<anyhow::Result<()>>(handle)
             {
@@ -283,10 +282,10 @@ impl Scene for MainMenu {
         }
 
         let screen_size: (f32, f32) = (
-            graphics.shared.window.inner_size().width as f32 - 100.0,
-            graphics.shared.window.inner_size().height as f32 - 100.0,
+            graphics.window.inner_size().width as f32 - 100.0,
+            graphics.window.inner_size().height as f32 - 100.0,
         );
-        let egui_ctx = graphics.shared.get_egui_context();
+        let egui_ctx = graphics.get_egui_context();
         let mut local_open_project = false;
         let mut local_select_project = false;
 
@@ -437,7 +436,7 @@ impl Scene for MainMenu {
                         .clicked()
                     {
                         log::info!("Creating new project at {:?}", self.project_path);
-                        self.start_project_creation(graphics.shared.future_queue.clone());
+                        self.start_project_creation(graphics.future_queue.clone());
                     }
                 });
             });
diff --git a/crates/eucalyptus-editor/src/runtime/command.rs b/crates/eucalyptus-editor/src/runtime/command.rs
deleted file mode 100644
index 074bf5c..0000000
--- a/crates/eucalyptus-editor/src/runtime/command.rs
+++ /dev/null
@@ -1,60 +0,0 @@
-use dropbear_engine::{graphics::RenderContext, scene::SceneCommand};
-use eucalyptus_core::command::{CommandBufferPoller, COMMAND_BUFFER, CommandBuffer, WindowCommand};
-use winit::window::CursorGrabMode;
-use crate::runtime::PlayMode;
-use eucalyptus_core::scene::loading::IsSceneLoaded;
-
-impl CommandBufferPoller for PlayMode {
-    fn poll(&mut self, graphics: &mut RenderContext) {
-        while let Ok(cmd) = COMMAND_BUFFER.1.try_recv() {
-            log::trace!("Received GRAPHICS_COMMAND update: {:?}", cmd);
-            match cmd {
-                CommandBuffer::WindowCommand(w_cmd) => match w_cmd {
-                    WindowCommand::WindowGrab(lock) => {
-                        if lock {
-                            let window = &graphics.shared.window;
-                            window.set_cursor_visible(false);
-                            if let Err(e) = window.set_cursor_grab(CursorGrabMode::Locked).or_else(|_| {
-                                log_once::warn_once!("Using cursor grab fallback: CursorGrabMode::Confined");
-                                window.set_cursor_grab(CursorGrabMode::Confined)
-                            }) {
-                                log_once::error_once!("Unable to grab mouse: {}", e);
-                            }
-                        } else if let Err(e) = graphics.shared.window.set_cursor_grab(CursorGrabMode::None) {
-                            log_once::warn_once!("Failed to release cursor: {:?}", e);
-                        } else {
-                            log_once::info_once!("Released cursor");
-                        }
-                    }
-                    WindowCommand::HideCursor(should_hide) => {
-                        if should_hide {
-                            graphics.shared.window.set_cursor_visible(false);
-                        } else {
-                            graphics.shared.window.set_cursor_visible(true);
-                        }
-                    }
-                },
-                CommandBuffer::Quit => {
-                    self.scene_command = SceneCommand::CloseWindow(graphics.shared.window.id());
-                },
-                CommandBuffer::SwitchSceneImmediate(scene_name) => {
-                    log::debug!("Immediate scene switch requested: {}", scene_name);
-                    let scene_to_load = IsSceneLoaded::new(scene_name);
-                    self.request_immediate_scene_load(graphics, scene_to_load);
-                }
-                CommandBuffer::LoadSceneAsync(handle) => {
-                    log::debug!("Load scene async requested");
-                    let scene_to_load = IsSceneLoaded::new_with_id(handle.scene_name, handle.id);
-                    self.request_async_scene_load(graphics, scene_to_load);
-                }
-                CommandBuffer::SwitchToAsync(handle) => {
-                    if let Some(ref progress) = self.scene_progress {
-                        if progress.requested_scene == handle.scene_name && progress.is_everything_loaded() {
-                            self.switch_to(progress.clone(), graphics);
-                        }
-                    }
-                }
-            }
-        }
-    }
-}
\ No newline at end of file
diff --git a/crates/eucalyptus-editor/src/runtime/input.rs b/crates/eucalyptus-editor/src/runtime/input.rs
deleted file mode 100644
index 77566c2..0000000
--- a/crates/eucalyptus-editor/src/runtime/input.rs
+++ /dev/null
@@ -1,78 +0,0 @@
-use gilrs::{Button, GamepadId};
-use winit::dpi::PhysicalPosition;
-use winit::event::MouseButton;
-use winit::event_loop::ActiveEventLoop;
-use winit::keyboard::KeyCode;
-use dropbear_engine::input::{Controller, Keyboard, Mouse};
-use crate::runtime::PlayMode;
-
-impl Keyboard for PlayMode {
-    fn key_down(&mut self, key: KeyCode, _event_loop: &ActiveEventLoop) {
-        self.input_state.pressed_keys.insert(key);
-    }
-
-    fn key_up(&mut self, key: KeyCode, _event_loop: &ActiveEventLoop) {
-        self.input_state.pressed_keys.remove(&key);
-    }
-}
-
-impl Mouse for PlayMode {
-    fn mouse_move(&mut self, position: PhysicalPosition<f64>, delta: Option<(f64, f64)>) {
-        let delta = if delta.is_none() {
-            if let Some(last_pos) = self.input_state.last_mouse_pos {
-                Some((position.x - last_pos.0, position.y - last_pos.1))
-            } else {
-                None
-            }
-        } else {
-            delta
-        };
-
-        self.input_state.mouse_delta = delta;
-        self.input_state.mouse_pos = (position.x, position.y);
-        self.input_state.last_mouse_pos = Some(<(f64, f64)>::from(position));
-    }
-
-    fn mouse_down(&mut self, button: MouseButton) {
-        self.input_state.mouse_button.insert(button);
-    }
-
-    fn mouse_up(&mut self, button: MouseButton) {
-        self.input_state.mouse_button.remove(&button);
-    }
-}
-
-impl Controller for PlayMode {
-    fn button_down(&mut self, button: Button, id: GamepadId) {
-        self.input_state
-            .pressed_buttons
-            .entry(id)
-            .or_default()
-            .insert(button);
-    }
-
-    fn button_up(&mut self, button: Button, id: GamepadId) {
-        if let Some(buttons) = self.input_state.pressed_buttons.get_mut(&id) {
-            buttons.remove(&button);
-        }
-    }
-
-    fn left_stick_changed(&mut self, x: f32, y: f32, id: GamepadId) {
-        self.input_state.left_stick_position.insert(id, (x, y));
-    }
-
-    fn right_stick_changed(&mut self, x: f32, y: f32, id: GamepadId) {
-        self.input_state.right_stick_position.insert(id, (x, y));
-    }
-
-    fn on_connect(&mut self, id: GamepadId) {
-        self.input_state.connected_gamepads.insert(id);
-    }
-
-    fn on_disconnect(&mut self, id: GamepadId) {
-        self.input_state.connected_gamepads.remove(&id);
-        self.input_state.pressed_buttons.remove(&id);
-        self.input_state.left_stick_position.remove(&id);
-        self.input_state.right_stick_position.remove(&id);
-    }
-}
\ No newline at end of file
diff --git a/crates/eucalyptus-editor/src/runtime/mod.rs b/crates/eucalyptus-editor/src/runtime/mod.rs
deleted file mode 100644
index 1b55c90..0000000
--- a/crates/eucalyptus-editor/src/runtime/mod.rs
+++ /dev/null
@@ -1,482 +0,0 @@
-//! Allows you to a launch play mode as another window.
-
-use std::sync::Arc;
-use crossbeam_channel::{unbounded, Receiver};
-use dropbear_engine::buffer::ResizableBuffer;
-use eucalyptus_core::physics::collider::shader::ColliderWireframePipeline;
-use eucalyptus_core::physics::collider::shader::ColliderInstanceRaw;
-use eucalyptus_core::physics::collider::{ColliderShapeKey, WireframeGeometry};
-use futures::executor;
-use hecs::{Entity, World};
-use wgpu::{RenderPipeline, SurfaceConfiguration};
-use dropbear_engine::camera::Camera;
-use dropbear_engine::future::FutureHandle;
-use dropbear_engine::graphics::RenderContext;
-use dropbear_engine::lighting::LightManager;
-use dropbear_engine::scene::SceneCommand;
-use dropbear_engine::shader::Shader;
-use eucalyptus_core::camera::CameraComponent;
-use eucalyptus_core::input::InputState;
-use eucalyptus_core::scripting::{ScriptManager, ScriptTarget};
-use eucalyptus_core::states::{WorldLoadingStatus, SCENES, Script, PROJECT};
-use eucalyptus_core::scene::loading::SCENE_LOADER;
-use eucalyptus_core::traits::registry::ComponentRegistry;
-use eucalyptus_core::ptr::{CommandBufferPtr, InputStatePtr, PhysicsStatePtr, WorldPtr};
-use eucalyptus_core::command::COMMAND_BUFFER;
-use eucalyptus_core::scene::loading::IsSceneLoaded;
-use std::collections::HashMap;
-use std::path::PathBuf;
-use winit::window::Fullscreen;
-use eucalyptus_core::physics::PhysicsState;
-use eucalyptus_core::rapier3d::prelude::*;
-use eucalyptus_core::register_components;
-
-mod scene;
-mod input;
-mod command;
-
-fn find_jvm_library_path() -> PathBuf {
-    let proj = PROJECT.read();
-    let project_path = if !proj.project_path.is_dir() {
-        proj.project_path
-            .parent()
-            .expect("Unable to locate parent of project")
-            .to_path_buf()
-    } else {
-        proj.project_path.clone()
-    }
-    .join("../../../../build/libs");
-
-    let mut latest_jar: Option<(PathBuf, std::time::SystemTime)> = None;
-
-    for entry in std::fs::read_dir(&project_path).expect("Unable to read directory") {
-        let entry = entry.expect("Unable to get directory entry");
-        let path = entry.path();
-
-        if let Some(filename) = path.file_name().and_then(|n| n.to_str()) {
-            if filename.ends_with("-all.jar") {
-                let metadata = entry.metadata().expect("Unable to get file metadata");
-                let modified = metadata.modified().expect("Unable to get file modified time");
-
-                match latest_jar {
-                    None => latest_jar = Some((path.clone(), modified)),
-                    Some((_, latest_time)) if modified > latest_time => {
-                        latest_jar = Some((path.clone(), modified));
-                    }
-                    _ => {}
-                }
-            }
-        }
-    }
-
-    latest_jar
-        .map(|(path, _)| path)
-        .expect("No suitable candidate for a JVM targeted play mode session available")
-}
-
-pub struct PlayMode {
-    scene_command: SceneCommand,
-    input_state: InputState,
-    script_manager: ScriptManager,
-    world: Box<World>,
-    component_registry: Arc<ComponentRegistry>,
-    active_camera: Option<Entity>,
-    
-    // rendering
-    render_pipeline: Option<RenderPipeline>,
-    light_manager: LightManager,
-
-    display_settings: DisplaySettings,
-
-    initial_scene: Option<String>,
-    current_scene: Option<String>,
-    world_loading_progress: Option<Receiver<WorldLoadingStatus>>,
-    world_receiver: Option<tokio::sync::oneshot::Receiver<World>>,
-    physics_receiver: Option<tokio::sync::oneshot::Receiver<PhysicsState>>,
-    scene_loading_handle: Option<FutureHandle>,
-    scene_progress: Option<IsSceneLoaded>,
-    pending_world: Option<Box<World>>,
-    pending_camera: Option<Entity>,
-    pending_physics_state: Option<Box<PhysicsState>>,
-    pub(crate) scripts_ready: bool,
-    has_initial_resize_done: bool,
-
-    // physics
-    physics_pipeline: PhysicsPipeline,
-    physics_state: Box<PhysicsState>,
-    collision_event_receiver: Option<std::sync::mpsc::Receiver<CollisionEvent>>,
-    collision_force_event_receiver: Option<std::sync::mpsc::Receiver<ContactForceEvent>>,
-    event_collector: ChannelEventCollector,
-
-    collider_wireframe_pipeline: Option<ColliderWireframePipeline>,
-    collider_wireframe_geometry_cache: HashMap<ColliderShapeKey, WireframeGeometry>,
-    collider_instance_buffer: Option<ResizableBuffer<ColliderInstanceRaw>>,
-}
-
-impl PlayMode {
-    pub fn new(initial_scene: Option<String>) -> anyhow::Result<Self> {
-
-        let mut component_registry = ComponentRegistry::new();
-
-        register_components(&mut component_registry);
-
-        let (collision_event_sender, ce_r) = std::sync::mpsc::channel::<CollisionEvent>();
-        let (contact_force_event_sender, cfe_r) = std::sync::mpsc::channel::<ContactForceEvent>();
-
-        let event_collector = ChannelEventCollector::new(collision_event_sender, contact_force_event_sender);
-
-        let result = Self {
-            scene_command: SceneCommand::None,
-            input_state: InputState::new(),
-            script_manager: ScriptManager::new()?,
-            world: Box::new(World::new()),
-            initial_scene,
-            current_scene: None,
-            world_loading_progress: None,
-            world_receiver: None,
-            component_registry: Arc::new(component_registry),
-            scene_loading_handle: None,
-            scene_progress: None,
-            pending_world: None,
-            pending_camera: None,
-            active_camera: None,
-            render_pipeline: None,
-            light_manager: Default::default(),
-            scripts_ready: false,
-            has_initial_resize_done: false,
-            display_settings: DisplaySettings {
-                window_mode: WindowMode::Windowed,
-                maintain_aspect_ratio: true,
-                vsync: true,
-            },
-            physics_pipeline: Default::default(),
-            physics_state: Box::new(PhysicsState::new()),
-            pending_physics_state: Default::default(),
-            physics_receiver: Default::default(),
-            collider_wireframe_pipeline: None,
-            collider_wireframe_geometry_cache: HashMap::new(),
-            collider_instance_buffer: None,
-            collision_event_receiver: Some(ce_r),
-            collision_force_event_receiver: Some(cfe_r),
-            event_collector,
-        };
-
-        log::debug!("Created new play mode instance");
-
-        Ok(result)
-    }
-
-    pub fn load_wgpu_nerdy_stuff<'a>(&mut self, graphics: &mut RenderContext<'a>) {
-        let shader = Shader::new(
-            graphics.shared.clone(),
-            dropbear_engine::shader::shader_wesl::SHADER_SHADER,
-            Some("viewport_shader"),
-        );
-
-        self.light_manager
-            .create_light_array_resources(graphics.shared.clone());
-
-        let Some(active_camera) = self.active_camera else {
-            log_once::warn_once!("No active camera found");
-            return;
-        };
-
-        if let Ok((camera, _component)) = self
-            .world
-            .query_one::<(&Camera, &CameraComponent)>(active_camera)
-            .get()
-        {
-            let pipeline = graphics.create_render_pipline(
-                &shader,
-                vec![
-                    &graphics.shared.texture_bind_layout.clone(),
-                    camera.layout(),
-                    self.light_manager.layout(),
-                    &graphics.shared.material_tint_bind_layout.clone(),
-                ],
-                None,
-            );
-            self.render_pipeline = Some(pipeline);
-
-            self.light_manager.create_render_pipeline(
-                graphics.shared.clone(),
-                dropbear_engine::shader::shader_wesl::LIGHT_SHADER,
-                camera,
-                Some("Light Pipeline"),
-            );
-
-            self.light_manager.create_shadow_pipeline(
-                graphics.shared.clone(),
-                dropbear_engine::shader::shader_wesl::SHADOW_SHADER,
-                Some("Shadow Pipeline"),
-            );
-
-            let collider_pipeline = ColliderWireframePipeline::new(graphics.shared.clone(), camera.layout());
-            self.collider_wireframe_pipeline = Some(collider_pipeline);
-        } else {
-            log_once::warn_once!(
-                    "Unable to query camera, component for active camera: {:?}",
-                    active_camera
-                );
-        }
-    }
-
-    fn reload_scripts_for_current_world(&mut self) {
-        let mut entity_tag_map: HashMap<String, Vec<Entity>> = HashMap::new();
-        for (entity_id, script) in self.world.query::<(Entity, &Script)>().iter() {
-            for tag in &script.tags {
-                entity_tag_map.entry(tag.clone()).or_default().push(entity_id);
-            }
-        }
-
-        let target = ScriptTarget::JVM {
-            library_path: find_jvm_library_path(),
-        };
-
-        self.scripts_ready = false;
-
-        if let Err(e) = self
-            .script_manager
-            .init_script(None, entity_tag_map.clone(), target.clone())
-        {
-            panic!("Failed to initialise scripts: {}", e);
-        } else {
-            log::debug!("Initialised scripts successfully!");
-        }
-
-        let world_ptr = self.world.as_mut() as WorldPtr;
-        let input_ptr = &mut self.input_state as InputStatePtr;
-        let graphics_ptr = COMMAND_BUFFER.0.as_ref() as CommandBufferPtr;
-        let physics_ptr = self.physics_state.as_mut() as PhysicsStatePtr;
-        
-        if let Err(e) = self
-            .script_manager
-            .load_script(world_ptr, input_ptr, graphics_ptr, physics_ptr)
-        {
-            panic!("Failed to load scripts: {}", e);
-        } else {
-            log::debug!("Loaded scripts successfully!");
-        }
-
-        self.scripts_ready = true;
-        log::debug!("Scripts reloaded successfully!");
-    }
-
-    /// Requests an asynchronous scene load, returning immediately and loading the scene in the background.
-    pub fn request_async_scene_load(&mut self, graphics: &RenderContext, requested_scene: IsSceneLoaded) {
-        log::debug!("Requested async scene load: {}", requested_scene.requested_scene);
-        let scene_name = requested_scene.requested_scene.clone();
-        self.scene_progress = Some(requested_scene);
-
-        let (tx, rx) = unbounded::<WorldLoadingStatus>();
-        let (world_tx, world_rx) = tokio::sync::oneshot::channel::<World>();
-        let (physics_tx, physics_rx) = tokio::sync::oneshot::channel::<PhysicsState>();
-
-        self.world_loading_progress = Some(rx);
-        self.world_receiver = Some(world_rx);
-        self.physics_receiver = Some(physics_rx);
-
-        if let Some(ref progress) = self.scene_progress {
-            if let Some(id) = progress.id {
-                let mut loader = SCENE_LOADER.lock();
-                if let Some(entry) = loader.get_entry_mut(id) {
-                    if entry.status.is_none() {
-                        entry.status = self.world_loading_progress.as_ref().cloned();
-                    }
-                }
-            }
-        }
-
-        let mut scene_to_load = {
-            let scenes = SCENES.read();
-            let scene = scenes.iter().find(|s| s.scene_name == scene_name).unwrap().clone();
-            scene
-        };
-
-        let graphics_cloned = graphics.shared.clone();
-        let component_registry = self.component_registry.clone();
-
-        let handle = graphics.shared.future_queue.push(async move {
-            let mut temp_world = World::new();
-            let load_status = scene_to_load.load_into_world(
-                &mut temp_world,
-                graphics_cloned,
-                Some(&component_registry),
-                Some(tx),
-                true,
-            ).await;
-            match load_status {
-                Ok(v) => {
-                    if world_tx.send(temp_world).is_err() {
-                        log::warn!("Unable to send world: Receiver has been deallocated. This usually means a new scene load was requested before this one finished.");
-                    };
-
-                    if physics_tx.send(scene_to_load.physics_state.clone()).is_err() {
-                        log::warn!("Unable to send physics state: Receiver has been deallocated");
-                    }
-
-                    v
-                }
-                Err(e) => {panic!("Failed to load scene [{}]: {}", scene_to_load.scene_name, e);}
-            }
-        });
-
-        log::debug!("Created future handle for scene loading: {:?}", handle);
-
-        self.scene_loading_handle = Some(handle);
-        if let Some(ref mut progress) = self.scene_progress {
-            progress.scene_handle_requested = true;
-        }
-    }
-
-    /// Requests an immediate scene load, blocking the current thread until the scene is fully loaded.
-    pub fn request_immediate_scene_load(&mut self, graphics: &mut RenderContext, requested_scene: IsSceneLoaded) {
-        let scene_name = requested_scene.requested_scene.clone();
-        log::debug!("Immediate scene load requested: {}", scene_name);
-
-        self.world = Box::new(World::new());
-        self.physics_state = Box::new(PhysicsState::new());
-        self.physics_receiver = None;
-        self.active_camera = None;
-        self.render_pipeline = None;
-        self.current_scene = None;
-        self.world_loading_progress = None;
-        self.world_receiver = None;
-        self.scene_loading_handle = None;
-        self.scene_progress = None;
-
-        let mut scene_to_load = {
-            let scenes = SCENES.read();
-            scenes.iter()
-                .find(|s| s.scene_name == scene_name)
-                .cloned()
-                .expect(&format!("Scene '{}' not found", scene_name))
-        };
-
-        let graphics_cloned = graphics.shared.clone();
-        let component_registry = self.component_registry.clone();
-
-        let (tx, _rx) = unbounded::<WorldLoadingStatus>();
-
-        let (loaded_world, camera_entity, physics_state) = executor::block_on(async move {
-            let mut temp_world = World::new();
-            let camera = scene_to_load.load_into_world(
-                &mut temp_world,
-                graphics_cloned,
-                Some(&component_registry),
-                Some(tx),
-                true,
-            ).await;
-
-            match camera {
-                Ok(cam) => (temp_world, cam, scene_to_load.physics_state),
-                Err(e) => panic!("Failed to immediately load scene [{}]: {}", scene_to_load.scene_name, e),
-            }
-        });
-
-        self.world = Box::new(loaded_world);
-        self.physics_state = Box::new(physics_state);
-        self.active_camera = Some(camera_entity);
-        self.current_scene = Some(scene_name.clone());
-
-        let mut progress = requested_scene;
-        progress.scene_handle_requested = true;
-        progress.world_loaded = true;
-        progress.camera_received = true;
-        self.scene_progress = Some(progress);
-
-        self.load_wgpu_nerdy_stuff(graphics);
-
-        self.reload_scripts_for_current_world();
-
-        log::debug!("Scene '{}' loaded", scene_name);
-    }
-
-    /// Switches to a new scene, clearing the current world and preparing to load the new scene.
-    pub fn switch_to(&mut self, scene_progress: IsSceneLoaded, graphics: &mut RenderContext) {
-        log::debug!("Switching to new scene requested: {}", scene_progress.requested_scene);
-
-        if scene_progress.is_everything_loaded() {
-            if let Some(new_world) = self.pending_world.take() {
-                self.world = new_world;
-            }
-            if let Some(physics_state) = self.pending_physics_state.take() {
-                self.physics_state = physics_state;
-            }
-            self.has_initial_resize_done = false;
-            if let Some(new_camera) = self.pending_camera.take() {
-                self.active_camera = Some(new_camera);
-            }
-
-            self.load_wgpu_nerdy_stuff(graphics);
-            self.reload_scripts_for_current_world();
-
-            self.current_scene = Some(scene_progress.requested_scene.clone());
-        }
-    }
-}
-
-pub struct DisplaySettings {
-    pub window_mode: WindowMode,
-    pub maintain_aspect_ratio: bool,
-    pub vsync: bool,
-}
-
-impl DisplaySettings {
-    pub fn update(&mut self, graphics: &RenderContext) {
-        let window = graphics.shared.window.clone();
-
-        let is_maximized = window.is_maximized();
-        let is_fullscreen = window.fullscreen().is_some();
-
-        self.window_mode = if is_fullscreen {
-            WindowMode::BorderlessFullscreen
-        } else if is_maximized {
-            WindowMode::Maximized
-        } else {
-            WindowMode::Windowed
-        };
-
-        match self.window_mode {
-            WindowMode::Windowed => {
-                window.set_fullscreen(None);
-                window.set_maximized(false);
-            }
-            WindowMode::Maximized => {
-                window.set_fullscreen(None);
-                window.set_maximized(true);
-            }
-            WindowMode::Fullscreen | WindowMode::BorderlessFullscreen => {
-                let monitor = window.current_monitor();
-                window.set_fullscreen(Some(Fullscreen::Borderless(monitor)));
-                window.set_maximized(false);
-            }
-        }
-
-        if self.vsync {
-            let config = SurfaceConfiguration {
-                usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
-                format: graphics.shared.surface_format,
-                width: graphics.frame.screen_size.0 as u32,
-                height: graphics.frame.screen_size.1 as u32,
-                present_mode: if self.vsync {
-                    wgpu::PresentMode::Fifo
-                } else {
-                    wgpu::PresentMode::Immediate
-                },
-                alpha_mode: wgpu::CompositeAlphaMode::Auto,
-                view_formats: vec![],
-                desired_maximum_frame_latency: 2,
-            };
-
-            graphics.shared.surface.configure(&graphics.shared.device, &config);
-        }
-    }
-}
-
-pub enum WindowMode {
-    Windowed,
-    Maximized,
-    Fullscreen,
-    BorderlessFullscreen,
-}
\ No newline at end of file
diff --git a/crates/eucalyptus-editor/src/runtime/scene.rs b/crates/eucalyptus-editor/src/runtime/scene.rs
deleted file mode 100644
index 915e0dd..0000000
--- a/crates/eucalyptus-editor/src/runtime/scene.rs
+++ /dev/null
@@ -1,766 +0,0 @@
-use std::collections::HashMap;
-use egui::{CentralPanel, MenuBar, TopBottomPanel};
-use eucalyptus_core::physics::collider::ColliderGroup;
-use eucalyptus_core::physics::collider::ColliderShapeKey;
-use eucalyptus_core::physics::collider::shader::ColliderInstanceRaw;
-use glam::{DMat4, DQuat, DVec3, Quat};
-use hecs::Entity;
-use wgpu::Color;
-use wgpu::util::DeviceExt;
-use winit::event_loop::ActiveEventLoop;
-use dropbear_engine::camera::Camera;
-use dropbear_engine::buffer::ResizableBuffer;
-use dropbear_engine::entity::{EntityTransform, MeshRenderer, Transform};
-use dropbear_engine::graphics::{InstanceRaw, RenderContext};
-use dropbear_engine::lighting::{Light, LightComponent};
-use dropbear_engine::model::{DrawLight, DrawModel, DrawShadow, ModelId, MODEL_CACHE};
-use dropbear_engine::scene::{Scene, SceneCommand};
-use eucalyptus_core::camera::CameraComponent;
-use eucalyptus_core::command::CommandBufferPoller;
-use eucalyptus_core::hierarchy::{EntityTransformExt, Parent};
-use eucalyptus_core::physics::kcc::KCC;
-use eucalyptus_core::rapier3d::prelude::QueryFilter;
-use eucalyptus_core::rapier3d::geometry::SharedShape;
-use eucalyptus_core::states::{Label, PROJECT};
-use eucalyptus_core::states::SCENES;
-use eucalyptus_core::scene::loading::{IsSceneLoaded, SceneLoadResult, SCENE_LOADER};
-use crate::editor::Editor;
-use crate::runtime::{PlayMode, WindowMode};
-
-impl Scene for PlayMode {
-    fn load(&mut self, graphics: &mut RenderContext) {
-        if self.current_scene.is_none() {
-            let initial_scene = if let Some(s) = &self.initial_scene {
-                s.clone()
-            } else {
-                let proj = PROJECT.read();
-                proj.runtime_settings.initial_scene.clone().expect("No initial scene set in project settings")
-            };
-
-            log::debug!("Loading initial scene: {}", initial_scene);
-
-            let first_time = IsSceneLoaded::new_first_time(initial_scene);
-
-            self.request_async_scene_load(graphics, first_time);
-        }
-    }
-
-    fn physics_update(&mut self, dt: f32, _graphics: &mut RenderContext) {
-        if self.scripts_ready {
-            let _ = self.script_manager.physics_update_script(self.world.as_mut(), dt as f64);
-        }
-
-        for kcc in self.world.query::<&mut KCC>().iter() {
-            kcc.collisions.clear();
-        }
-
-        for (e, l, _) in self.world.query::<(Entity, &Label, &KCC)>().iter() {
-            log_once::debug_once!("This entity [{:?}, label = {}] has the KCC (KinematicCharacterController) component attached", e, l);
-        }
-
-        if !self.physics_state.collision_events_to_deal_with.is_empty() {
-            let entities_with_collisions: Vec<Entity> = self
-                .physics_state
-                .collision_events_to_deal_with
-                .keys()
-                .copied()
-                .collect();
-
-            for entity in entities_with_collisions {
-                let Some(collisions) = self.physics_state.collision_events_to_deal_with.remove(&entity) else {
-                    continue;
-                };
-                if collisions.is_empty() {
-                    continue;
-                }
-
-                if let Ok(mut kcc) = self.world.get::<&mut KCC>(entity) {
-                    kcc.collisions = collisions.clone();
-                }
-
-                let (label, kcc_controller) = match self.world.query_one::<(&Label, &KCC)>(entity).get() {
-                    Ok(v) => {
-                        (v.0.clone(), v.1.clone())
-                    }
-                    Err(e) => {
-                        log_once::warn_once!("Unable to query {:?}: {}", entity, e);
-                        continue;
-                    }
-                };
-
-                let Some(rigid_body_handle) = self.physics_state.bodies_entity_map.get(&label).copied() else {
-                    continue;
-                };
-
-                let Some((_, collider_handle)) = self
-                    .physics_state
-                    .colliders_entity_map
-                    .get(&label)
-                    .and_then(|handles| handles.first())
-                    .copied()
-                else {
-                    continue;
-                };
-
-                let (character_shape, character_mass): (SharedShape, f32) = {
-                    let Some(collider) = self.physics_state.colliders.get(collider_handle) else {
-                        continue;
-                    };
-
-                    (collider.shared_shape().clone(), collider.mass())
-                };
-
-                let character_mass = if character_mass > 0.0 { character_mass } else { 1.0 };
-
-                let filter = QueryFilter::default().exclude_rigid_body(rigid_body_handle);
-                let dispatcher = self.physics_state.narrow_phase.query_dispatcher();
-
-                let broad_phase = &mut self.physics_state.broad_phase;
-                let bodies = &mut self.physics_state.bodies;
-                let colliders = &mut self.physics_state.colliders;
-
-                let mut query_pipeline_mut = broad_phase.as_query_pipeline_mut(
-                    dispatcher,
-                    bodies,
-                    colliders,
-                    filter,
-                );
-
-                kcc_controller.controller.solve_character_collision_impulses(
-                    dt,
-                    &mut query_pipeline_mut,
-                    character_shape.as_ref(),
-                    character_mass,
-                    &collisions,
-                );
-            }
-        }
-        
-        let mut entity_label_map = HashMap::new();
-        for (entity, label) in self.world.query::<(Entity, &Label)>().iter() {
-            entity_label_map.insert(entity, label.clone());
-        }
-        
-        self.physics_state.step(entity_label_map, &mut self.physics_pipeline, &(), &self.event_collector);
-
-        if self.scripts_ready {
-            if let (Some(ce_r), Some(cfe_r)) = (&self.collision_event_receiver, &self.collision_force_event_receiver) {
-                // both are not crucial, so no need to panic
-                while let Ok(event) = ce_r.try_recv() {
-                    log_once::debug_once!("Collision event received");
-                    if let Some(evt) = eucalyptus_core::types::CollisionEvent::from_rapier3d(&self.physics_state, event) {
-                        if let Err(err) = self.script_manager.collision_event_script(self.world.as_mut(), &evt) {
-                            log::error!("Script collision event error: {}", err);
-                        }
-                    }
-                }
-
-                while let Ok(event) = cfe_r.try_recv() {
-                    log_once::debug_once!("Contact force event received");
-                    if let Some(evt) = eucalyptus_core::types::ContactForceEvent::from_rapier3d(&self.physics_state, event) {
-                        if let Err(err) = self.script_manager.contact_force_event_script(self.world.as_mut(), &evt) {
-                            log::error!("Script contact force event error: {}", err);
-                        }
-                    }
-                }
-            }
-        }
-
-        let mut sync_updates = Vec::new();
-
-        for (entity, label, _) in self.world.query::<(Entity, &Label, &EntityTransform)>().iter() {
-            if let Some(handle) = self.physics_state.bodies_entity_map.get(label) {
-                if let Some(body) = self.physics_state.bodies.get(*handle) {
-                    let p = body.translation();
-                    let r = body.rotation();
-
-                    sync_updates.push((
-                        entity,
-                        DVec3::new(p.x as f64, p.y as f64, p.z as f64),
-                        Quat::from(r.clone()).as_dquat()
-                    ));
-                }
-            }
-        }
-
-        for (entity, new_world_pos, new_world_rot) in sync_updates {
-
-            let parent_world = if let Ok(parent_comp) = self.world.get::<&Parent>(entity) {
-                let parent_entity = parent_comp.parent();
-                if let Ok(p_transform) = self.world.get::<&EntityTransform>(parent_entity) {
-                    Some(p_transform.propagate(&self.world, parent_entity))
-                } else {
-                    None
-                }
-            } else {
-                None
-            };
-
-            if let Ok(mut entity_transform) = self.world.get::<&mut EntityTransform>(entity) {
-                if let Some(p_world) = parent_world {
-                    let inv_p_rot = p_world.rotation.inverse();
-
-                    let relative_pos = new_world_pos - p_world.position;
-                    let new_local_pos = (inv_p_rot * relative_pos) / p_world.scale;
-                    let new_local_rot = inv_p_rot * new_world_rot;
-
-                    let base = entity_transform.world_mut();
-                    base.position = new_local_pos;
-                    base.rotation = new_local_rot;
-
-                    let offset = entity_transform.local_mut();
-                    offset.position = DVec3::ZERO;
-                    offset.rotation = DQuat::IDENTITY;
-                } else {
-                    let base = entity_transform.world_mut();
-                    base.position = new_world_pos;
-                    base.rotation = new_world_rot;
-
-                    let offset = entity_transform.local_mut();
-                    offset.position = DVec3::ZERO;
-                    offset.rotation = DQuat::IDENTITY;
-                }
-            }
-        }
-    }
-
-    fn update(&mut self, dt: f32, graphics: &mut RenderContext) {
-        graphics.shared.future_queue.poll();
-        self.poll(graphics);
-
-        {
-            if let Some(fps) = PROJECT.read().runtime_settings.target_fps.get() {
-                log_once::debug_once!("setting new fps for play mode session: {}", fps);
-                if matches!(self.scene_command, SceneCommand::None) {
-                    self.scene_command = SceneCommand::SetFPS(*fps);
-                }
-            }
-        }
-
-        if let Some(ref progress) = self.scene_progress {
-            if !progress.scene_handle_requested && self.world_receiver.is_none() && self.scene_loading_handle.is_none() {
-                log::debug!("Starting async load for scene: {}", progress.requested_scene);
-                let scene_to_load = IsSceneLoaded::new(progress.requested_scene.clone());
-                self.request_async_scene_load(graphics, scene_to_load);
-            }
-        }
-
-        if let Some(mut receiver) = self.world_receiver.take() {
-            if let Ok(loaded_world) = receiver.try_recv() {
-                self.pending_world = Some(Box::new(loaded_world));
-                log::debug!("World received");
-                if let Some(ref mut progress) = self.scene_progress {
-                    progress.world_loaded = true;
-
-                    if progress.camera_received {
-                        if let Some(id) = progress.id {
-                            let mut loader = SCENE_LOADER.lock();
-                            if let Some(entry) = loader.get_entry_mut(id) {
-                                entry.result = SceneLoadResult::Success;
-                            }
-                        }
-                    }
-                }
-            } else {
-                self.world_receiver = Some(receiver);
-            }
-        }
-
-        if let Some(mut receiver) = self.physics_receiver.take() {
-            if let Ok(loaded_physics) = receiver.try_recv() {
-                self.pending_physics_state = Some(Box::new(loaded_physics));
-                log::debug!("PhysicsState received");
-            } else {
-                self.physics_receiver = Some(receiver);
-            }
-        }
-
-        if let Some(handle) = self.scene_loading_handle.take() {
-            if let Some(cam) = graphics.shared.future_queue.exchange_owned_as::<Entity>(&handle) {
-                self.pending_camera = Some(cam);
-                log::debug!("Camera entity received: {:?}", cam);
-                if let Some(ref mut progress) = self.scene_progress {
-                    progress.camera_received = true;
-
-                    if progress.world_loaded {
-                        if let Some(id) = progress.id {
-                            let mut loader = SCENE_LOADER.lock();
-                            if let Some(entry) = loader.get_entry_mut(id) {
-                                entry.result = SceneLoadResult::Success;
-                            }
-                        }
-                    }
-                }
-            } else {
-                self.scene_loading_handle = Some(handle)
-            }
-        }
-
-        if let Some(ref progress) = self.scene_progress {
-            if progress.is_everything_loaded() {
-                if self.current_scene.as_ref() != Some(&progress.requested_scene) {
-                    self.switch_to(progress.clone(), graphics);
-                }
-            }
-        }
-
-        if self.scripts_ready {
-            if let Err(e) = self.script_manager.update_script(self.world.as_mut(), dt as f64) {
-                panic!("Script update error: {}", e);
-            }
-        }
-
-        {
-            let mut query = self.world.query::<(&mut MeshRenderer, &Transform)>();
-            for (renderer, transform) in query.iter() {
-                renderer.update(transform);
-            }
-        }
-
-        {
-            let mut updates = Vec::new();
-            for (entity, transform) in self.world.query::<(Entity, &EntityTransform)>().iter() {
-                let final_transform = transform.propagate(&self.world, entity);
-                updates.push((entity, final_transform));
-            }
-
-            for (entity, final_transform) in updates {
-                if let Ok(mut renderer) = self.world.get::<&mut MeshRenderer>(entity) {
-                    renderer.update(&final_transform);
-                }
-            }
-        }
-
-        {
-            let mut light_query = self
-                .world
-                .query::<(&mut LightComponent, Option<&Transform>, Option<&EntityTransform>, &mut Light)>();
-
-            for (light_comp, transform_opt, entity_transform_opt, light) in light_query.iter() {
-                let transform = if let Some(entity_transform) = entity_transform_opt {
-                    entity_transform.sync()
-                } else if let Some(transform) = transform_opt {
-                    *transform
-                } else {
-                    continue;
-                };
-
-                light.update(graphics.shared.as_ref(), light_comp, &transform);
-            }
-        }
-
-        {
-            for (camera, component) in self
-                .world
-                .query::<(&mut Camera, &mut CameraComponent)>()
-                .iter()
-            {
-                component.update(camera);
-                camera.update(graphics.shared.clone());
-            }
-        }
-
-        self.light_manager
-            .update(graphics.shared.clone(), &self.world);
-
-        TopBottomPanel::top("menu_bar").show(&graphics.shared.get_egui_context(), |ui| {
-            MenuBar::new().ui(ui, |ui| {
-                ui.menu_button("Window", |ui| {
-                    ui.menu_button("Window Mode", |ui| {
-                        let is_windowed = matches!(self.display_settings.window_mode, WindowMode::Windowed);
-                        if ui.selectable_label(is_windowed, "Windowed").clicked() {
-                            self.display_settings.window_mode = WindowMode::Windowed;
-                            ui.close();
-                        }
-
-                        let is_maximized = matches!(self.display_settings.window_mode, WindowMode::Maximized);
-                        if ui.selectable_label(is_maximized, "Maximized").clicked() {
-                            self.display_settings.window_mode = WindowMode::Maximized;
-                            ui.close();
-                        }
-
-                        let is_fullscreen = matches!(self.display_settings.window_mode, WindowMode::Fullscreen);
-                        if ui.selectable_label(is_fullscreen, "Fullscreen").clicked() {
-                            self.display_settings.window_mode = WindowMode::Fullscreen;
-                            ui.close();
-                        }
-
-                        let is_borderless = matches!(self.display_settings.window_mode, WindowMode::BorderlessFullscreen);
-                        if ui.selectable_label(is_borderless, "Borderless Fullscreen").clicked() {
-                            self.display_settings.window_mode = WindowMode::BorderlessFullscreen;
-                            ui.close();
-                        }
-                    });
-
-                    ui.separator();
-
-                    ui.checkbox(&mut self.display_settings.maintain_aspect_ratio, "Maintain aspect ratio");
-                    ui.checkbox(&mut self.display_settings.vsync, "VSync").clicked();
-                });
-
-                ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| {
-                    ui.group(|ui| {
-                        ui.add_enabled_ui(true, |ui| {
-                            if ui.button("⏹").clicked() {
-                                log::debug!("Menu button Stop button pressed");
-                                self.scene_command = SceneCommand::CloseWindow(graphics.shared.window.id());
-                            }
-                        });
-
-                        ui.add_enabled_ui(false, |ui| {
-                            if ui.button("▶").clicked() {
-                                log::debug!("how tf can you press this???");
-                            }
-                        });
-                    });
-                });
-            });
-        });
-
-        CentralPanel::default().show(&graphics.shared.get_egui_context(), |ui| {
-            if let Some(p) = &self.scene_progress {
-                if !p.is_everything_loaded() && p.is_first_scene {
-                    // todo: change from label to "splashscreen"
-                    ui.centered_and_justified(|ui| {
-                        ui.label("Loading scene...");
-                    });
-                    return;
-                }
-            }
-
-            let texture_id = *graphics.shared.texture_id;
-
-            let available_size = ui.available_size();
-            let available_rect = ui.available_rect_before_wrap();
-
-            if let Some(active_camera) = self.active_camera {
-                if let Ok(cam) = self.world.query_one_mut::<&mut Camera>(active_camera) {
-                    if !self.has_initial_resize_done {
-                        cam.aspect = (available_size.x / available_size.y) as f64;
-
-                        self.has_initial_resize_done = true;
-                    }
-
-                    if !self.display_settings.maintain_aspect_ratio {
-                        cam.aspect = (available_size.x / available_size.y) as f64;
-                    }
-                    cam.update_view_proj();
-                    cam.update(graphics.shared.clone());
-
-                    let (display_width, display_height) = if self.display_settings.maintain_aspect_ratio {
-                        let width = available_size.x;
-                        let height = width / cam.aspect as f32;
-                        (width, height)
-                    } else {
-                        (available_size.x, available_size.y)
-                    };
-
-                    let center_x = available_rect.center().x;
-                    let center_y = available_rect.center().y;
-
-                    let image_rect = egui::Rect::from_center_size(
-                        egui::pos2(center_x, center_y),
-                        egui::vec2(display_width, display_height),
-                    );
-
-                    ui.allocate_exact_size(available_size, egui::Sense::hover());
-
-                    ui.scope_builder(egui::UiBuilder::new().max_rect(image_rect), |ui| {
-                        ui.add(egui::Image::new(egui::load::SizedTexture {
-                            id: texture_id,
-                            size: egui::vec2(display_width, display_height),
-                        }));
-                    });
-                } else {
-                    log::warn!("No such camera exists in the world");
-                }
-            } else {
-                log::warn!("No active camera available");
-            }
-        });
-
-        self.input_state.mouse_delta = None;
-    }
-
-    fn render(&mut self, graphics: &mut RenderContext) {
-        let Some(active_camera) = self.active_camera else {
-            return;
-        };
-        log_once::debug_once!("Active camera found: {:?}", active_camera);
-
-        let q = self.world.query_one::<&Camera>(active_camera).get().ok().cloned();
-
-        let Some(camera) = q else {
-            return;
-        };
-        log_once::debug_once!("Camera ready");
-        log_once::debug_once!("Camera currently being viewed: {}", camera.label);
-
-        let Some(pipeline) = &self.render_pipeline else {
-            log_once::warn_once!("Render pipeline not ready");
-            return;
-        };
-        log_once::debug_once!("Pipeline ready");
-
-        let clear_color = Color {
-            r: 100.0 / 255.0,
-            g: 149.0 / 255.0,
-            b: 237.0 / 255.0,
-            a: 1.0,
-        };
-
-        let lights = {
-            let mut lights = Vec::new();
-            let mut query = self.world.query::<(&Light, &LightComponent)>();
-            for (light, comp) in query.iter() {
-                lights.push((light.clone(), comp.clone()));
-            }
-            lights
-        };
-
-        let renderers = {
-            let mut renderers = Vec::new();
-            let mut query = self.world.query::<&MeshRenderer>();
-            for renderer in query.iter() {
-                renderers.push(renderer.clone());
-            }
-            renderers
-        };
-
-        let mut model_batches: HashMap<ModelId, Vec<InstanceRaw>> = HashMap::new();
-        for renderer in &renderers {
-            model_batches
-                .entry(renderer.model_id())
-                .or_default()
-                .push(renderer.instance.to_raw());
-        }
-
-        let mut prepared_models = Vec::new();
-        for (model_id, instances) in model_batches {
-            let model_opt = {
-                let cache = MODEL_CACHE.lock();
-                cache.values().find(|model| model.id == model_id).cloned()
-            };
-
-            let Some(model) = model_opt else {
-                log_once::error_once!("Missing model {:?} in cache", model_id);
-                continue;
-            };
-
-            let instance_buffer = graphics.shared.device.create_buffer_init(
-                &wgpu::util::BufferInitDescriptor {
-                    label: Some("Runtime Instance Buffer"),
-                    contents: bytemuck::cast_slice(&instances),
-                    usage: wgpu::BufferUsages::VERTEX,
-                },
-            );
-
-            prepared_models.push((model, instance_buffer, instances.len() as u32));
-        }
-
-        {
-            let mut query = self.world.query::<(
-                &mut LightComponent,
-                Option<&dropbear_engine::entity::Transform>,
-                Option<&dropbear_engine::entity::EntityTransform>,
-                &mut Light,
-            )>();
-
-            for (light_component, transform_opt, entity_transform_opt, light) in query.iter() {
-                let transform = if let Some(entity_transform) = entity_transform_opt {
-                    entity_transform.sync()
-                } else if let Some(transform) = transform_opt {
-                    *transform
-                } else {
-                    continue;
-                };
-
-                light.update(graphics.shared.as_ref(), light_component, &transform);
-            }
-        }
-
-        self.light_manager.update(graphics.shared.clone(), &self.world);
-
-        if let Some(shadow_pipeline) = &self.light_manager.shadow_pipeline {
-            for (light, component) in &lights {
-                if !component.enabled || !component.cast_shadows {
-                    continue;
-                }
-
-                let shadow_index = light.uniform().shadow_index;
-                if shadow_index < 0 {
-                    continue;
-                }
-
-                let shadow_index = shadow_index as usize;
-                if shadow_index >= self.light_manager.shadow_target_views.len() {
-                    continue;
-                }
-
-                let shadow_view = &self.light_manager.shadow_target_views[shadow_index];
-                let mut shadow_pass =
-                    graphics.begin_shadow_pass(shadow_view, Some("Shadow Pass"));
-                shadow_pass.set_pipeline(shadow_pipeline);
-
-                for (model, instance_buffer, instance_count) in &prepared_models {
-                    shadow_pass.set_vertex_buffer(1, instance_buffer.slice(..));
-                    shadow_pass.draw_shadow_model_instanced(
-                        model,
-                        0..*instance_count,
-                        light.bind_group(),
-                    );
-                }
-            }
-        }
-
-        {
-            let mut render_pass = graphics.clear_colour(clear_color);
-            if let Some(light_pipeline) = &self.light_manager.pipeline {
-                render_pass.set_pipeline(light_pipeline);
-                for (light, component) in &lights {
-                    render_pass.set_vertex_buffer(1, light.instance_buffer.buffer().slice(..));
-                    if component.visible {
-                        render_pass.draw_light_model(
-                            &light.cube_model,
-                            camera.bind_group(),
-                            light.bind_group(),
-                        );
-                    }
-                }
-            }
-        }
-
-        for (model, instance_buffer, instance_count) in prepared_models {
-            let mut render_pass = graphics.continue_pass();
-            render_pass.set_pipeline(pipeline);
-            render_pass.set_vertex_buffer(1, instance_buffer.slice(..));
-            render_pass.draw_model_instanced(
-                &model,
-                0..instance_count,
-                camera.bind_group(),
-                self.light_manager.bind_group(),
-            );
-        }
-
-        {
-            let show_hitboxes = self
-                .current_scene
-                .as_ref()
-                .and_then(|scene_name| {
-                    let scenes = SCENES.read();
-                    scenes
-                        .iter()
-                        .find(|scene| &scene.scene_name == scene_name)
-                        .map(|scene| scene.settings.show_hitboxes)
-                })
-                .unwrap_or(false);
-
-            if show_hitboxes {
-                if let Some(collider_pipeline) = &self.collider_wireframe_pipeline {
-                let mut render_pass = graphics.continue_pass();
-                render_pass.set_pipeline(&collider_pipeline.pipeline);
-                render_pass.set_bind_group(0, camera.bind_group(), &[]);
-
-                    let mut instances_by_shape: HashMap<ColliderShapeKey, Vec<ColliderInstanceRaw>> =
-                        HashMap::new();
-
-                    let mut q = self.world.query::<(&EntityTransform, &ColliderGroup)>();
-                    for (entity_transform, group) in q.iter() {
-                        for collider in &group.colliders {
-                            let world_tf = entity_transform.sync();
-
-                            let entity_matrix = DMat4::from_rotation_translation(
-                                world_tf.rotation,
-                                world_tf.position,
-                            )
-                            .as_mat4();
-
-                            let offset_transform = Transform::new()
-                                .with_offset(collider.translation, collider.rotation);
-                            let offset_matrix = offset_transform.matrix().as_mat4();
-
-                            let final_matrix = entity_matrix * offset_matrix;
-
-                            let color = [0.0, 1.0, 0.0, 1.0];
-                            let instance = ColliderInstanceRaw::from_matrix(final_matrix, color);
-
-                            let key = ColliderShapeKey::from(&collider.shape);
-                            instances_by_shape.entry(key).or_default().push(instance);
-
-                            self.collider_wireframe_geometry_cache.entry(key).or_insert_with(|| {
-                                Editor::create_wireframe_geometry(
-                                    graphics.shared.clone(),
-                                    &collider.shape,
-                                )
-                            });
-                        }
-                    }
-
-                    if !instances_by_shape.is_empty() {
-                        let total_instances: usize =
-                            instances_by_shape.values().map(|v| v.len()).sum();
-                        let mut all_instances = Vec::with_capacity(total_instances);
-                        let mut draws: Vec<(ColliderShapeKey, usize, usize)> = Vec::new();
-
-                        for (key, instances) in instances_by_shape {
-                            let start = all_instances.len();
-                            all_instances.extend_from_slice(&instances);
-                            let count = instances.len();
-                            draws.push((key, start, count));
-                        }
-
-                        let instance_buffer = self.collider_instance_buffer.get_or_insert_with(|| {
-                            ResizableBuffer::new(
-                                &graphics.shared.device,
-                                all_instances.len().max(10),
-                                wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
-                                "Collider Instance Buffer",
-                            )
-                        });
-                        instance_buffer.write(
-                            &graphics.shared.device,
-                            &graphics.shared.queue,
-                            &all_instances,
-                        );
-
-                        for (key, start, count) in draws {
-                            let Some(geometry) = self.collider_wireframe_geometry_cache.get(&key) else {
-                                continue;
-                            };
-
-                            let start_bytes =
-                                (start * std::mem::size_of::<ColliderInstanceRaw>()) as wgpu::BufferAddress;
-                            let end_bytes =
-                                ((start + count) * std::mem::size_of::<ColliderInstanceRaw>()) as wgpu::BufferAddress;
-
-                            render_pass.set_vertex_buffer(
-                                1,
-                                instance_buffer.buffer().slice(start_bytes..end_bytes),
-                            );
-                            render_pass.set_vertex_buffer(
-                                0,
-                                geometry.vertex_buffer.slice(..),
-                            );
-                            render_pass.set_index_buffer(
-                                geometry.index_buffer.slice(..),
-                                wgpu::IndexFormat::Uint16,
-                            );
-                            render_pass.draw_indexed(
-                                0..geometry.index_count,
-                                0,
-                                0..count as u32,
-                            );
-                        }
-                    }
-                }
-            }
-        }
-    }
-
-    fn exit(&mut self, _event_loop: &ActiveEventLoop) {}
-
-    fn run_command(&mut self) -> SceneCommand {
-        std::mem::replace(&mut self.scene_command, SceneCommand::None)
-    }
-}
-
diff --git a/crates/eucalyptus-editor/src/signal.rs b/crates/eucalyptus-editor/src/signal.rs
index 6ca4b56..8556ba7 100644
--- a/crates/eucalyptus-editor/src/signal.rs
+++ b/crates/eucalyptus-editor/src/signal.rs
@@ -1,12 +1,12 @@
 use crate::editor::{Editor, EditorState, Signal};
 use dropbear_engine::camera::Camera;
 use dropbear_engine::entity::{MeshRenderer, Transform};
-use dropbear_engine::graphics::{SharedGraphicsContext, Texture};
+use dropbear_engine::graphics::{SharedGraphicsContext};
 use dropbear_engine::lighting::{Light as EngineLight, LightComponent};
 use dropbear_engine::model::{LoadedModel, Material, Model, ModelId, MODEL_CACHE};
 use dropbear_engine::procedural::ProcedurallyGeneratedObject;
+use dropbear_engine::texture::{Texture, TextureWrapMode};
 use dropbear_engine::utils::{relative_path_from_euca, EUCA_SCHEME, ResourceReference, ResourceReferenceType};
-use dropbear_engine::utils::TextureWrapMode;
 use egui::Align2;
 use eucalyptus_core::camera::{CameraComponent, CameraType};
 use eucalyptus_core::scripting::{build_jvm, BuildStatus};
@@ -800,10 +800,14 @@ impl SignalController for Editor {
                                         let path = resolve_editor_path(&uri);
 
                                         if let Ok(bytes) = std::fs::read(&path) {
-                                            let diffuse = Texture::new_with_wrap_mode(
-                                                graphics.clone(),
-                                                &bytes,
-                                                wrap_mode,
+                                            let diffuse = Texture::from_bytes_verbose(
+                                                &graphics.device, 
+                                                &graphics.queue, 
+                                                &bytes, 
+                                                None, 
+                                                None,
+                                                None,
+                                                Some(Texture::sampler_from_wrap(wrap_mode))
                                             );
                                             let flat_normal = (*dropbear_engine::asset::ASSET_REGISTRY
                                                 .solid_texture_rgba8(
@@ -852,7 +856,15 @@ impl SignalController for Editor {
                     }
                 };
 
-                let diffuse = Texture::new_with_wrap_mode(graphics.clone(), &bytes, *wrap_mode);
+                let diffuse = Texture::from_bytes_verbose(
+                    &graphics.device, 
+                    &graphics.queue, 
+                    &bytes, 
+                    None, 
+                    None,
+                    None,
+                    Some(Texture::sampler_from_wrap(wrap_mode.clone()))
+                );
                 let flat_normal = (*dropbear_engine::asset::ASSET_REGISTRY
                     .solid_texture_rgba8(graphics.clone(), [128, 128, 255, 255]))
                     .clone();
@@ -899,10 +911,14 @@ impl SignalController for Editor {
                             let path = resolve_editor_path(&uri);
 
                             if let Ok(bytes) = std::fs::read(&path) {
-                                let diffuse = Texture::new_with_wrap_mode(
-                                    graphics.clone(),
-                                    &bytes,
-                                    *wrap_mode,
+                                let diffuse = Texture::from_bytes_verbose(
+                                    &graphics.device, 
+                                    &graphics.queue, 
+                                    &bytes, 
+                                    None, 
+                                    None,
+                                    None,
+                                    Some(Texture::sampler_from_wrap(wrap_mode.clone()))
                                 );
                                 material.diffuse_texture = diffuse;
                                 material.bind_group = Material::create_bind_group(
diff --git a/crates/eucalyptus-editor/src/spawn.rs b/crates/eucalyptus-editor/src/spawn.rs
index 51e3216..b5572c3 100644
--- a/crates/eucalyptus-editor/src/spawn.rs
+++ b/crates/eucalyptus-editor/src/spawn.rs
@@ -3,9 +3,10 @@ use dropbear_engine::asset::ASSET_REGISTRY;
 use dropbear_engine::camera::Camera;
 use dropbear_engine::entity::{EntityTransform, MeshRenderer, Transform};
 use dropbear_engine::future::FutureQueue;
-use dropbear_engine::graphics::{SharedGraphicsContext, Texture};
+use dropbear_engine::graphics::{SharedGraphicsContext};
 use dropbear_engine::lighting::{Light, LightComponent};
 use dropbear_engine::model::{LoadedModel, Material, Model, ModelId};
+use dropbear_engine::texture::Texture;
 use dropbear_engine::utils::{ResourceReference, ResourceReferenceType};
 use eucalyptus_core::camera::CameraComponent;
 use eucalyptus_core::scene::SceneEntity;
@@ -392,10 +393,14 @@ async fn load_renderer_from_serialized(
                     if let Ok(path) = reference.resolve() {
                         match std::fs::read(&path) {
                             Ok(bytes) => {
-                                let diffuse = Texture::new_with_wrap_mode(
-                                    graphics.clone(),
-                                    &bytes,
-                                    custom.wrap_mode,
+                                let diffuse = Texture::from_bytes_verbose(
+                                    &graphics.device, 
+                                    &graphics.queue, 
+                                    &bytes, 
+                                    None, 
+                                    None,
+                                    None,
+                                    Some(Texture::sampler_from_wrap(custom.wrap_mode))
                                 );
                                 let flat_normal = (*ASSET_REGISTRY
                                     .solid_texture_rgba8(graphics.clone(), [128, 128, 255, 255]))
diff --git a/crates/eucalyptus-editor/src/stats.rs b/crates/eucalyptus-editor/src/stats.rs
index 46b08bb..2106c99 100644
--- a/crates/eucalyptus-editor/src/stats.rs
+++ b/crates/eucalyptus-editor/src/stats.rs
@@ -1,11 +1,12 @@
 use std::{collections::VecDeque, time::Instant};
 
 use dropbear_engine::WGPU_BACKEND;
+use dropbear_engine::graphics::FrameGraphicsContext;
 use egui::{Color32, Context, RichText, Ui};
 use egui_plot::{Legend, Line, Plot, PlotPoints};
 use dropbear_engine::scene::Scene;
 use dropbear_engine::input::{Keyboard, Mouse, Controller};
-use dropbear_engine::graphics::RenderContext;
+
 use winit::event_loop::ActiveEventLoop;
 use winit::keyboard::KeyCode;
 use winit::event::MouseButton;
@@ -330,13 +331,13 @@ impl NerdStats {
 }
 
 impl Scene for NerdStats {
-    fn load(&mut self, _graphics: &mut RenderContext) {}
-    fn physics_update(&mut self, _dt: f32, _graphics: &mut RenderContext) {}
-    fn update(&mut self, dt: f32, _graphics: &mut RenderContext) {
+    fn load(&mut self, _graphics: std::sync::Arc<dropbear_engine::graphics::SharedGraphicsContext>) {}
+    fn physics_update(&mut self, _dt: f32, _graphics: std::sync::Arc<dropbear_engine::graphics::SharedGraphicsContext>) {}
+    fn update(&mut self, dt: f32, _graphics: std::sync::Arc<dropbear_engine::graphics::SharedGraphicsContext>) {
         self.record_stats(dt, self.entity_count);
     }
-    fn render(&mut self, graphics: &mut RenderContext) {
-        self.show_window(&graphics.shared.get_egui_context());
+    fn render<'a>(&mut self, graphics: std::sync::Arc<dropbear_engine::graphics::SharedGraphicsContext>, _frame_ctx: FrameGraphicsContext<'a>) {
+        self.show_window(&graphics.get_egui_context());
     }
     fn exit(&mut self, _event_loop: &ActiveEventLoop) {}
 }
diff --git a/crates/redback-runtime/Cargo.toml b/crates/redback-runtime/Cargo.toml
index 55c1cb3..83de71d 100644
--- a/crates/redback-runtime/Cargo.toml
+++ b/crates/redback-runtime/Cargo.toml
@@ -7,8 +7,8 @@ repository.workspace = true
 readme = "README.md"
 
 [dependencies]
-dropbear-engine = { path = "../crates/dropbear-engine" }
-eucalyptus-core = { path = "../crates/eucalyptus-core", features = ["runtime"], default-features = false }
+dropbear-engine = { path = "../dropbear-engine" }
+eucalyptus-core = { path = "../eucalyptus-core", features = ["runtime"], default-features = false }
 
 anyhow.workspace = true
 log.workspace = true
@@ -28,13 +28,10 @@ serde.workspace = true
 crossbeam-channel.workspace = true
 gilrs.workspace = true
 futures.workspace = true
+glam.workspace = true
+wgpu.workspace = true
+egui.workspace = true
 
 [features]
-default = ["eucalyptus-core/runtime"]
-
-# enable this to allow for JVM support
-jvm = ["eucalyptus-core/jvm"]
-
-# enable this to enable JVM support and allow for debugging through sockets.
-# WARN: ENSURE THIS IS NOT ENABLED ON DEBUG
-jvm_debug = ["eucalyptus-core/jvm_debug"]
\ No newline at end of file
+debug = []
+jvm = []
\ No newline at end of file
diff --git a/crates/redback-runtime/src/command.rs b/crates/redback-runtime/src/command.rs
index 052c658..4d69173 100644
--- a/crates/redback-runtime/src/command.rs
+++ b/crates/redback-runtime/src/command.rs
@@ -1,25 +1,29 @@
-use dropbear_engine::graphics::RenderContext;
+use std::sync::Arc;
+
+use dropbear_engine::{scene::SceneCommand};
 use eucalyptus_core::command::{CommandBufferPoller, COMMAND_BUFFER, CommandBuffer, WindowCommand};
 use winit::window::CursorGrabMode;
-use crate::scene::IsSceneLoaded;
+use crate::PlayMode;
+use eucalyptus_core::scene::loading::IsSceneLoaded;
+use dropbear_engine::graphics::SharedGraphicsContext;
 
-impl CommandBufferPoller for Runtime {
-    fn poll(&mut self, graphics: &mut RenderContext) {
+impl CommandBufferPoller for PlayMode {
+    fn poll(&mut self, graphics: Arc<SharedGraphicsContext>) {
         while let Ok(cmd) = COMMAND_BUFFER.1.try_recv() {
             log::trace!("Received GRAPHICS_COMMAND update: {:?}", cmd);
             match cmd {
                 CommandBuffer::WindowCommand(w_cmd) => match w_cmd {
                     WindowCommand::WindowGrab(lock) => {
                         if lock {
-                            if let Err(e) = graphics.shared.window
-                                .set_cursor_grab(CursorGrabMode::Confined)
-                                .or_else(|_| graphics.shared.window.set_cursor_grab(CursorGrabMode::Locked))
-                            {
-                                log_once::warn_once!("Failed to grab cursor: {:?}", e);
-                            } else {
-                                log_once::info_once!("Grabbed cursor");
+                            let window = &graphics.window;
+                            window.set_cursor_visible(false);
+                            if let Err(e) = window.set_cursor_grab(CursorGrabMode::Locked).or_else(|_| {
+                                log_once::warn_once!("Using cursor grab fallback: CursorGrabMode::Confined");
+                                window.set_cursor_grab(CursorGrabMode::Confined)
+                            }) {
+                                log_once::error_once!("Unable to grab mouse: {}", e);
                             }
-                        } else if let Err(e) = graphics.shared.window.set_cursor_grab(CursorGrabMode::None) {
+                        } else if let Err(e) = graphics.clone().window.set_cursor_grab(CursorGrabMode::None) {
                             log_once::warn_once!("Failed to release cursor: {:?}", e);
                         } else {
                             log_once::info_once!("Released cursor");
@@ -27,29 +31,29 @@ impl CommandBufferPoller for Runtime {
                     }
                     WindowCommand::HideCursor(should_hide) => {
                         if should_hide {
-                            graphics.shared.window.set_cursor_visible(false);
+                            graphics.window.set_cursor_visible(false);
                         } else {
-                            graphics.shared.window.set_cursor_visible(true);
+                            graphics.window.set_cursor_visible(true);
                         }
                     }
                 },
                 CommandBuffer::Quit => {
-                    self.scene_command = dropbear_engine::scene::SceneCommand::Quit;
+                    self.scene_command = SceneCommand::CloseWindow(graphics.window.id());
                 },
                 CommandBuffer::SwitchSceneImmediate(scene_name) => {
                     log::debug!("Immediate scene switch requested: {}", scene_name);
                     let scene_to_load = IsSceneLoaded::new(scene_name);
-                    self.request_immediate_scene_load(graphics, scene_to_load);
+                    self.request_immediate_scene_load(graphics.clone(), scene_to_load);
                 }
                 CommandBuffer::LoadSceneAsync(handle) => {
                     log::debug!("Load scene async requested");
                     let scene_to_load = IsSceneLoaded::new_with_id(handle.scene_name, handle.id);
-                    self.request_async_scene_load(graphics, scene_to_load);
+                    self.request_async_scene_load(graphics.clone(), scene_to_load);
                 }
                 CommandBuffer::SwitchToAsync(handle) => {
                     if let Some(ref progress) = self.scene_progress {
                         if progress.requested_scene == handle.scene_name && progress.is_everything_loaded() {
-                            self.switch_to(progress.clone(), graphics);
+                            self.switch_to(progress.clone(), graphics.clone());
                         }
                     }
                 }
diff --git a/crates/redback-runtime/src/debug.rs b/crates/redback-runtime/src/debug.rs
deleted file mode 100644
index 37659da..0000000
--- a/crates/redback-runtime/src/debug.rs
+++ /dev/null
@@ -1,81 +0,0 @@
-use std::any::TypeId;
-use dropbear_engine::camera::Camera;
-use dropbear_engine::entity::{MeshRenderer, Transform};
-use dropbear_engine::lighting::{Light, LightComponent};
-use eucalyptus_core::camera::CameraComponent;
-use eucalyptus_core::states::{Label, CustomProperties, Script};
-
-impl Runtime {
-    #[allow(dead_code)]
-    pub fn display_all_entities(&self) {
-        log::debug!("====================");
-        log::info!("world total items: {}", self.world.len());
-        log::info!("typeid of Label: {:?}", TypeId::of::<Label>());
-        log::info!("typeid of MeshRenderer: {:?}", TypeId::of::<MeshRenderer>());
-        log::info!("typeid of Transform: {:?}", TypeId::of::<Transform>());
-        log::info!(
-                    "typeid of ModelProperties: {:?}",
-                    TypeId::of::<CustomProperties>()
-                );
-        log::info!("typeid of Camera: {:?}", TypeId::of::<Camera>());
-        log::info!(
-                    "typeid of CameraComponent: {:?}",
-                    TypeId::of::<CameraComponent>()
-                );
-        log::info!("typeid of Script: {:?}", TypeId::of::<Script>());
-        log::info!("typeid of Light: {:?}", TypeId::of::<Light>());
-        log::info!(
-                    "typeid of LightComponent: {:?}",
-                    TypeId::of::<LightComponent>()
-                );
-        for i in self.world.iter() {
-            log::info!("entity id: {:?}", i.entity().id());
-            log::info!("entity bytes: {:?}", i.entity().to_bits().get());
-            log::info!(
-                        "components [{}]: ",
-                        i.component_types().collect::<Vec<_>>().len()
-                    );
-            let mut comp_builder = String::new();
-            for j in i.component_types() {
-                comp_builder.push_str(format!("{:?} ", j).as_str());
-                if TypeId::of::<Label>() == j {
-                    log::info!(" |- Label");
-                }
-
-                if TypeId::of::<MeshRenderer>() == j {
-                    log::info!(" |- MeshRenderer");
-                }
-
-                if TypeId::of::<Transform>() == j {
-                    log::info!(" |- Transform");
-                }
-
-                if TypeId::of::<CustomProperties>() == j {
-                    log::info!(" |- ModelProperties");
-                }
-
-                if TypeId::of::<Camera>() == j {
-                    log::info!(" |- Camera");
-                }
-
-                if TypeId::of::<CameraComponent>() == j {
-                    log::info!(" |- CameraComponent");
-                }
-
-                if TypeId::of::<Script>() == j {
-                    log::info!(" |- Script");
-                }
-
-                if TypeId::of::<Light>() == j {
-                    log::info!(" |- Light");
-                }
-
-                if TypeId::of::<LightComponent>() == j {
-                    log::info!(" |- LightComponent");
-                }
-                log::info!("----------")
-            }
-            log::info!("components (typeid) [{}]: ", comp_builder);
-        }
-    }
-}
\ No newline at end of file
diff --git a/crates/redback-runtime/src/input.rs b/crates/redback-runtime/src/input.rs
index 44839b1..d127717 100644
--- a/crates/redback-runtime/src/input.rs
+++ b/crates/redback-runtime/src/input.rs
@@ -4,9 +4,9 @@ use winit::event::MouseButton;
 use winit::event_loop::ActiveEventLoop;
 use winit::keyboard::KeyCode;
 use dropbear_engine::input::{Controller, Keyboard, Mouse};
-use crate::Runtime;
+use crate::PlayMode;
 
-impl Keyboard for Runtime {
+impl Keyboard for PlayMode {
     fn key_down(&mut self, key: KeyCode, _event_loop: &ActiveEventLoop) {
         self.input_state.pressed_keys.insert(key);
     }
@@ -16,7 +16,7 @@ impl Keyboard for Runtime {
     }
 }
 
-impl Mouse for Runtime {
+impl Mouse for PlayMode {
     fn mouse_move(&mut self, position: PhysicalPosition<f64>, delta: Option<(f64, f64)>) {
         let delta = if delta.is_none() {
             if let Some(last_pos) = self.input_state.last_mouse_pos {
@@ -42,7 +42,7 @@ impl Mouse for Runtime {
     }
 }
 
-impl Controller for Runtime {
+impl Controller for PlayMode {
     fn button_down(&mut self, button: Button, id: GamepadId) {
         self.input_state
             .pressed_buttons
diff --git a/crates/redback-runtime/src/lib.rs b/crates/redback-runtime/src/lib.rs
index 13faa4d..d904a9f 100644
--- a/crates/redback-runtime/src/lib.rs
+++ b/crates/redback-runtime/src/lib.rs
@@ -2,61 +2,124 @@
 
 use std::sync::Arc;
 use crossbeam_channel::{unbounded, Receiver};
+use dropbear_engine::buffer::ResizableBuffer;
+use eucalyptus_core::physics::collider::shader::ColliderWireframePipeline;
+use eucalyptus_core::physics::collider::shader::ColliderInstanceRaw;
+use eucalyptus_core::physics::collider::{ColliderShapeKey, WireframeGeometry};
 use futures::executor;
 use hecs::{Entity, World};
-use wgpu::RenderPipeline;
-use dropbear_engine::camera::Camera;
+use wgpu::{RenderPipeline};
 use dropbear_engine::future::FutureHandle;
-use dropbear_engine::graphics::RenderContext;
+use dropbear_engine::graphics::SharedGraphicsContext;
 use dropbear_engine::lighting::LightManager;
 use dropbear_engine::scene::SceneCommand;
 use dropbear_engine::shader::Shader;
-use eucalyptus_core::camera::CameraComponent;
 use eucalyptus_core::input::InputState;
 use eucalyptus_core::scripting::{ScriptManager, ScriptTarget};
 use eucalyptus_core::states::{WorldLoadingStatus, SCENES, Script, PROJECT};
-use eucalyptus_core::scene::loading::{IsSceneLoaded, SCENE_LOADER};
+use eucalyptus_core::scene::loading::SCENE_LOADER;
 use eucalyptus_core::traits::registry::ComponentRegistry;
-use eucalyptus_core::ptr::{CommandBufferPtr, InputStatePtr, WorldPtr};
+use eucalyptus_core::ptr::{CommandBufferPtr, InputStatePtr, PhysicsStatePtr, WorldPtr};
 use eucalyptus_core::command::COMMAND_BUFFER;
+use eucalyptus_core::scene::loading::IsSceneLoaded;
 use std::collections::HashMap;
 use std::path::PathBuf;
-use dropbear_engine::wgpu::RenderPipeline;
+use eucalyptus_core::physics::PhysicsState;
+use eucalyptus_core::rapier3d::prelude::*;
+use eucalyptus_core::register_components;
 
-mod input;
 mod scene;
-mod debug;
-mod utils;
+mod input;
 mod command;
 
-pub struct Runtime {
+fn find_jvm_library_path() -> PathBuf {
+    let proj = PROJECT.read();
+    let project_path = if !proj.project_path.is_dir() {
+        proj.project_path
+            .parent()
+            .expect("Unable to locate parent of project")
+            .to_path_buf()
+    } else {
+        proj.project_path.clone()
+    }
+    .join("../../../../build/libs");
+
+    let mut latest_jar: Option<(PathBuf, std::time::SystemTime)> = None;
+
+    for entry in std::fs::read_dir(&project_path).expect("Unable to read directory") {
+        let entry = entry.expect("Unable to get directory entry");
+        let path = entry.path();
+
+        if let Some(filename) = path.file_name().and_then(|n| n.to_str()) {
+            if filename.ends_with("-all.jar") {
+                let metadata = entry.metadata().expect("Unable to get file metadata");
+                let modified = metadata.modified().expect("Unable to get file modified time");
+
+                match latest_jar {
+                    None => latest_jar = Some((path.clone(), modified)),
+                    Some((_, latest_time)) if modified > latest_time => {
+                        latest_jar = Some((path.clone(), modified));
+                    }
+                    _ => {}
+                }
+            }
+        }
+    }
+
+    latest_jar
+        .map(|(path, _)| path)
+        .expect("No suitable candidate for a JVM targeted play mode session available")
+}
+
+pub struct PlayMode {
     scene_command: SceneCommand,
     input_state: InputState,
     script_manager: ScriptManager,
     world: Box<World>,
     component_registry: Arc<ComponentRegistry>,
     active_camera: Option<Entity>,
-
+    
     // rendering
     render_pipeline: Option<RenderPipeline>,
     light_manager: LightManager,
 
-    display_settings: DisplaySettings,
-
     initial_scene: Option<String>,
     current_scene: Option<String>,
     world_loading_progress: Option<Receiver<WorldLoadingStatus>>,
     world_receiver: Option<tokio::sync::oneshot::Receiver<World>>,
+    physics_receiver: Option<tokio::sync::oneshot::Receiver<PhysicsState>>,
     scene_loading_handle: Option<FutureHandle>,
     scene_progress: Option<IsSceneLoaded>,
     pending_world: Option<Box<World>>,
     pending_camera: Option<Entity>,
+    pending_physics_state: Option<Box<PhysicsState>>,
     pub(crate) scripts_ready: bool,
     has_initial_resize_done: bool,
+
+    // physics
+    physics_pipeline: PhysicsPipeline,
+    physics_state: Box<PhysicsState>,
+    collision_event_receiver: Option<std::sync::mpsc::Receiver<CollisionEvent>>,
+    collision_force_event_receiver: Option<std::sync::mpsc::Receiver<ContactForceEvent>>,
+    event_collector: ChannelEventCollector,
+
+    collider_wireframe_pipeline: Option<ColliderWireframePipeline>,
+    collider_wireframe_geometry_cache: HashMap<ColliderShapeKey, WireframeGeometry>,
+    collider_instance_buffer: Option<ResizableBuffer<ColliderInstanceRaw>>,
 }
 
-impl Runtime {
+impl PlayMode {
     pub fn new(initial_scene: Option<String>) -> anyhow::Result<Self> {
+
+        let mut component_registry = ComponentRegistry::new();
+
+        register_components(&mut component_registry);
+
+        let (collision_event_sender, ce_r) = std::sync::mpsc::channel::<CollisionEvent>();
+        let (contact_force_event_sender, cfe_r) = std::sync::mpsc::channel::<ContactForceEvent>();
+
+        let event_collector = ChannelEventCollector::new(collision_event_sender, contact_force_event_sender);
+
         let result = Self {
             scene_command: SceneCommand::None,
             input_state: InputState::new(),
@@ -66,7 +129,7 @@ impl Runtime {
             current_scene: None,
             world_loading_progress: None,
             world_receiver: None,
-            component_registry: Arc::new(Default::default()),
+            component_registry: Arc::new(component_registry),
             scene_loading_handle: None,
             scene_progress: None,
             pending_world: None,
@@ -76,11 +139,16 @@ impl Runtime {
             light_manager: Default::default(),
             scripts_ready: false,
             has_initial_resize_done: false,
-            display_settings: DisplaySettings {
-                window_mode: WindowMode::Windowed,
-                maintain_aspect_ratio: true,
-                vsync: true,
-            },
+            physics_pipeline: Default::default(),
+            physics_state: Box::new(PhysicsState::new()),
+            pending_physics_state: Default::default(),
+            physics_receiver: Default::default(),
+            collider_wireframe_pipeline: None,
+            collider_wireframe_geometry_cache: HashMap::new(),
+            collider_instance_buffer: None,
+            collision_event_receiver: Some(ce_r),
+            collision_force_event_receiver: Some(cfe_r),
+            event_collector,
         };
 
         log::debug!("Created new play mode instance");
@@ -88,66 +156,41 @@ impl Runtime {
         Ok(result)
     }
 
-    pub fn load_wgpu_nerdy_stuff<'a>(&mut self, graphics: &mut RenderContext<'a>) {
+    pub fn load_wgpu_nerdy_stuff<'a>(&mut self, graphics: Arc<SharedGraphicsContext>) {
         let shader = Shader::new(
-            graphics.shared.clone(),
-            dropbear_engine::shader::shader_wesl::SHADER_SHADER,
-            Some("viewport_shader"),
+            graphics.clone(),
+            include_str!("../../../resources/shaders/shader.wgsl"),
+            Some("viewport shader"),
         );
 
         self.light_manager
-            .create_light_array_resources(graphics.shared.clone());
-
-        if let Some(active_camera) = self.active_camera {
-            if let Ok(mut q) = self
-                .world
-                .query_one::<(&Camera, &CameraComponent)>(active_camera)
-            {
-                if let Some((camera, _component)) = q.get() {
-                    let pipeline = graphics.create_render_pipline(
-                        &shader,
-                        vec![
-                            &graphics.shared.texture_bind_layout.clone(),
-                            camera.layout(),
-                            self.light_manager.layout(),
-                            &graphics.shared.material_tint_bind_layout.clone(),
-                        ],
-                        None,
-                    );
-                    self.render_pipeline = Some(pipeline);
-
-                    self.light_manager.create_render_pipeline(
-                        graphics.shared.clone(),
-                        dropbear_engine::shader::shader_wesl::LIGHT_SHADER,
-                        camera,
-                        Some("Light Pipeline"),
-                    );
-
-                    self.light_manager.create_shadow_pipeline(
-                        graphics.shared.clone(),
-                        dropbear_engine::shader::shader_wesl::SHADOW_SHADER,
-                        Some("Shadow Pipeline"),
-                    );
-                } else {
-                    log_once::warn_once!(
-                        "Unable to fetch the query result of camera: {:?}",
-                        active_camera
-                    )
-                }
-            } else {
-                log_once::warn_once!(
-                    "Unable to query camera, component for active camera: {:?}",
-                    active_camera
-                );
-            }
-        } else {
-            log_once::warn_once!("No active camera found");
-        }
+            .create_light_array_resources(graphics.clone());
+
+        let pipeline = graphics.create_render_pipline(
+            &shader,
+            vec![
+                &graphics.layouts.texture_bind_layout,
+                &graphics.layouts.camera_bind_group_layout,
+                &graphics.layouts.light_array_bind_group_layout,
+                &graphics.layouts.material_tint_bind_layout,
+            ],
+            None,
+        );
+        self.render_pipeline = Some(pipeline);
+
+        self.light_manager.create_render_pipeline(
+            graphics.clone(),
+            include_str!("../../../resources/shaders/light.wgsl"),
+            Some("Light Pipeline"),
+        );
+
+        let collider_pipeline = ColliderWireframePipeline::new(graphics.clone());
+        self.collider_wireframe_pipeline = Some(collider_pipeline);
     }
 
     fn reload_scripts_for_current_world(&mut self) {
         let mut entity_tag_map: HashMap<String, Vec<Entity>> = HashMap::new();
-        for (entity_id, script) in self.world.query::<&Script>().iter() {
+        for (entity_id, script) in self.world.query::<(Entity, &Script)>().iter() {
             for tag in &script.tags {
                 entity_tag_map.entry(tag.clone()).or_default().push(entity_id);
             }
@@ -163,38 +206,42 @@ impl Runtime {
             .script_manager
             .init_script(None, entity_tag_map.clone(), target.clone())
         {
-            log::error!("Failed to initialise scripts: {}", e);
-            return;
+            panic!("Failed to initialise scripts: {}", e);
+        } else {
+            log::debug!("Initialised scripts successfully!");
         }
 
         let world_ptr = self.world.as_mut() as WorldPtr;
         let input_ptr = &mut self.input_state as InputStatePtr;
         let graphics_ptr = COMMAND_BUFFER.0.as_ref() as CommandBufferPtr;
-
+        let physics_ptr = self.physics_state.as_mut() as PhysicsStatePtr;
+        
         if let Err(e) = self
             .script_manager
-            .load_script(world_ptr, input_ptr, graphics_ptr)
+            .load_script(world_ptr, input_ptr, graphics_ptr, physics_ptr)
         {
-            log::error!("Failed to load scripts: {}", e);
-            return;
+            panic!("Failed to load scripts: {}", e);
+        } else {
+            log::debug!("Loaded scripts successfully!");
         }
 
         self.scripts_ready = true;
-        log::debug!("Scripts initialised successfully");
+        log::debug!("Scripts reloaded successfully!");
     }
 
     /// Requests an asynchronous scene load, returning immediately and loading the scene in the background.
-    pub fn request_async_scene_load(&mut self, graphics: &RenderContext, requested_scene: IsSceneLoaded) {
+    pub fn request_async_scene_load(&mut self, graphics: Arc<SharedGraphicsContext>, requested_scene: IsSceneLoaded) {
         log::debug!("Requested async scene load: {}", requested_scene.requested_scene);
         let scene_name = requested_scene.requested_scene.clone();
         self.scene_progress = Some(requested_scene);
 
         let (tx, rx) = unbounded::<WorldLoadingStatus>();
         let (world_tx, world_rx) = tokio::sync::oneshot::channel::<World>();
+        let (physics_tx, physics_rx) = tokio::sync::oneshot::channel::<PhysicsState>();
 
         self.world_loading_progress = Some(rx);
         self.world_receiver = Some(world_rx);
-
+        self.physics_receiver = Some(physics_rx);
 
         if let Some(ref progress) = self.scene_progress {
             if let Some(id) = progress.id {
@@ -207,16 +254,16 @@ impl Runtime {
             }
         }
 
-        let scene_to_load = {
+        let mut scene_to_load = {
             let scenes = SCENES.read();
             let scene = scenes.iter().find(|s| s.scene_name == scene_name).unwrap().clone();
             scene
         };
 
-        let graphics_cloned = graphics.shared.clone();
+        let graphics_cloned = graphics.clone();
         let component_registry = self.component_registry.clone();
 
-        let handle = graphics.shared.future_queue.push(async move {
+        let handle = graphics.future_queue.push(async move {
             let mut temp_world = World::new();
             let load_status = scene_to_load.load_into_world(
                 &mut temp_world,
@@ -230,6 +277,11 @@ impl Runtime {
                     if world_tx.send(temp_world).is_err() {
                         log::warn!("Unable to send world: Receiver has been deallocated. This usually means a new scene load was requested before this one finished.");
                     };
+
+                    if physics_tx.send(scene_to_load.physics_state.clone()).is_err() {
+                        log::warn!("Unable to send physics state: Receiver has been deallocated");
+                    }
+
                     v
                 }
                 Err(e) => {panic!("Failed to load scene [{}]: {}", scene_to_load.scene_name, e);}
@@ -245,11 +297,13 @@ impl Runtime {
     }
 
     /// Requests an immediate scene load, blocking the current thread until the scene is fully loaded.
-    pub fn request_immediate_scene_load(&mut self, graphics: &mut RenderContext, requested_scene: IsSceneLoaded) {
+    pub fn request_immediate_scene_load(&mut self, graphics: Arc<SharedGraphicsContext>, requested_scene: IsSceneLoaded) {
         let scene_name = requested_scene.requested_scene.clone();
         log::debug!("Immediate scene load requested: {}", scene_name);
 
         self.world = Box::new(World::new());
+        self.physics_state = Box::new(PhysicsState::new());
+        self.physics_receiver = None;
         self.active_camera = None;
         self.render_pipeline = None;
         self.current_scene = None;
@@ -258,7 +312,7 @@ impl Runtime {
         self.scene_loading_handle = None;
         self.scene_progress = None;
 
-        let scene_to_load = {
+        let mut scene_to_load = {
             let scenes = SCENES.read();
             scenes.iter()
                 .find(|s| s.scene_name == scene_name)
@@ -266,12 +320,12 @@ impl Runtime {
                 .expect(&format!("Scene '{}' not found", scene_name))
         };
 
-        let graphics_cloned = graphics.shared.clone();
+        let graphics_cloned = graphics.clone();
         let component_registry = self.component_registry.clone();
 
         let (tx, _rx) = unbounded::<WorldLoadingStatus>();
 
-        let (loaded_world, camera_entity) = executor::block_on(async move {
+        let (loaded_world, camera_entity, physics_state) = executor::block_on(async move {
             let mut temp_world = World::new();
             let camera = scene_to_load.load_into_world(
                 &mut temp_world,
@@ -282,12 +336,13 @@ impl Runtime {
             ).await;
 
             match camera {
-                Ok(cam) => (temp_world, cam),
+                Ok(cam) => (temp_world, cam, scene_to_load.physics_state),
                 Err(e) => panic!("Failed to immediately load scene [{}]: {}", scene_to_load.scene_name, e),
             }
         });
 
         self.world = Box::new(loaded_world);
+        self.physics_state = Box::new(physics_state);
         self.active_camera = Some(camera_entity);
         self.current_scene = Some(scene_name.clone());
 
@@ -305,13 +360,17 @@ impl Runtime {
     }
 
     /// Switches to a new scene, clearing the current world and preparing to load the new scene.
-    pub fn switch_to(&mut self, scene_progress: IsSceneLoaded, graphics: &mut RenderContext) {
+    pub fn switch_to(&mut self, scene_progress: IsSceneLoaded, graphics: Arc<SharedGraphicsContext>) {
         log::debug!("Switching to new scene requested: {}", scene_progress.requested_scene);
 
         if scene_progress.is_everything_loaded() {
             if let Some(new_world) = self.pending_world.take() {
                 self.world = new_world;
             }
+            if let Some(physics_state) = self.pending_physics_state.take() {
+                self.physics_state = physics_state;
+            }
+            self.has_initial_resize_done = false;
             if let Some(new_camera) = self.pending_camera.take() {
                 self.active_camera = Some(new_camera);
             }
@@ -322,17 +381,4 @@ impl Runtime {
             self.current_scene = Some(scene_progress.requested_scene.clone());
         }
     }
-}
-
-pub struct DisplaySettings {
-    pub window_mode: WindowMode,
-    pub maintain_aspect_ratio: bool,
-    pub vsync: bool,
-}
-
-pub enum WindowMode {
-    Windowed,
-    Maximized,
-    Fullscreen,
-    BorderlessFullscreen,
 }
\ No newline at end of file
diff --git a/crates/redback-runtime/src/main.rs b/crates/redback-runtime/src/main.rs
deleted file mode 100644
index b4dc9e9..0000000
--- a/crates/redback-runtime/src/main.rs
+++ /dev/null
@@ -1,203 +0,0 @@
-use crate::scene::RuntimeScene;
-use app_dirs2::AppInfo;
-use dropbear_engine::future::FutureQueue;
-use eucalyptus_core::runtime::RuntimeProjectConfig;
-use parking_lot::RwLock;
-use std::env::current_exe;
-use std::fs;
-use std::rc::Rc;
-use std::sync::Arc;
-use ron::ser::PrettyConfig;
-use winit::window::WindowAttributes;
-use dropbear_engine::{DropbearAppBuilder, DropbearWindowBuilder};
-use serde::{Deserialize, Serialize};
-
-#[tokio::main]
-async fn main() {
-    #[cfg(not(target_os = "android"))]
-    {
-        use chrono::offset::Local;
-        use colored::Colorize;
-        use env_logger::Builder;
-        use log::LevelFilter;
-        use parking_lot::Mutex;
-        use std::fs::OpenOptions;
-
-        let log_dir =
-            app_dirs2::app_root(app_dirs2::AppDataType::UserData, &eucalyptus_core::APP_INFO)
-                .expect("Failed to get app data directory")
-                .join("logs");
-        fs::create_dir_all(&log_dir).expect("Failed to create log dir");
-
-        let datetime_str = Local::now().format("%Y-%m-%d_%H-%M-%S");
-        let log_filename = format!("{}.{}.log", "redback-runtime", datetime_str);
-        let log_path = log_dir.join(log_filename);
-
-        let file = OpenOptions::new()
-            .create(true)
-            .append(true)
-            .open(&log_path)
-            .expect("Failed to open log file");
-        let file = Mutex::new(file);
-
-        Builder::new()
-            .format(move |buf, record| {
-                use std::io::Write;
-
-                let ts = Local::now().format("%Y-%m-%dT%H:%M:%S");
-
-                let colored_level = match record.level() {
-                    log::Level::Error => record.level().to_string().red().bold(),
-                    log::Level::Warn => record.level().to_string().yellow().bold(),
-                    log::Level::Info => record.level().to_string().green().bold(),
-                    log::Level::Debug => record.level().to_string().blue().bold(),
-                    log::Level::Trace => record.level().to_string().cyan().bold(),
-                };
-
-                let colored_timestamp = ts.to_string().bright_black();
-
-                let file_info = format!(
-                    "{}:{}",
-                    record.file().unwrap_or("unknown"),
-                    record.line().unwrap_or(0)
-                )
-                .bright_black();
-
-                let console_line = format!(
-                    "{} {} [{}] - {}\n",
-                    file_info,
-                    colored_timestamp,
-                    colored_level,
-                    record.args()
-                );
-
-                let file_line = format!(
-                    "{}:{} {} [{}] - {}\n",
-                    record.file().unwrap_or("unknown"),
-                    record.line().unwrap_or(0),
-                    ts,
-                    record.level(),
-                    record.args()
-                );
-
-                write!(buf, "{}", console_line).unwrap();
-
-                let mut fh = file.lock();
-                let _ = fh.write_all(file_line.as_bytes());
-
-                Ok(())
-            })
-            .filter(Some("dropbear_engine"), LevelFilter::Trace)
-            .filter(
-                Some("redback-runtime".replace('-', "_").as_str()),
-                LevelFilter::Debug,
-            )
-            .filter(Some("eucalyptus_core"), LevelFilter::Debug)
-            .filter(Some("dropbear_traits"), LevelFilter::Debug)
-            .init();
-        log::info!("Initialised logger");
-    }
-
-    dropbear_engine::panic::set_hook();
-    log::debug!("Set panic hook");
-
-    let window_config_file = current_exe().unwrap()
-        .parent()
-        .ok_or(anyhow::anyhow!(
-            "Unable to get parent of current executable"
-        )).unwrap()
-        .join("config.eucfg");
-    log::debug!("Fetched window config file path: {}", window_config_file.display());
-
-    log::debug!("Reading from window config file");
-    let value = fs::read(&window_config_file);
-
-    let config = match value {
-        Ok(val) => {
-            log::debug!("Config file exists, reading contents");
-            let config = ron::de::from_bytes::<ConfigFile>(val.as_slice()).unwrap();
-            log::debug!("File converted to ConfigFile");
-            config
-        }
-        Err(e) => {
-            log::warn!("Unable to read config: {}", e);
-            log::warn!("Creating new config file to overwrite old one");
-            let cfg = ConfigFile {
-                jvm_args: None,
-                max_fps: 60,
-                target_resolution: WindowModes::Windowed(1920, 1080),
-            };
-            let vec = ron::ser::to_string_pretty(&cfg, PrettyConfig::default()).unwrap();
-            if let Err(e) = fs::write(&window_config_file, vec) {
-                log::warn!("Unable to write, still running game: {}", e);
-            }
-            cfg
-        }
-    };
-
-    let path = current_exe().unwrap()
-        .parent()
-        .ok_or(anyhow::anyhow!(
-                "Unable to locate parent folder for current executable"
-            )).unwrap()
-        .join("data.eupak");
-    log::debug!("scene config (potential) file path: {}", path.display());
-
-    let scene_config = fs::read(&path).unwrap();
-    log::debug!("Located scene config file: [{}] ({} bytes)", path.display(), scene_config.len());
-
-    let (scene_config, _): (RuntimeProjectConfig, usize) =
-        bincode::decode_from_slice(scene_config.as_slice(), bincode::config::standard()).unwrap();
-    log::debug!("Converted scene config file to RuntimeProjectConfig");
-
-    let runtime_scene = Rc::new(RwLock::new(RuntimeScene::new(scene_config.clone(), config.clone()).unwrap()));
-    let future_queue = Arc::new(FutureQueue::new());
-
-    let authors = scene_config.authors.developer.clone();
-    let project_name = scene_config.project_name.clone();
-
-    let name = Box::leak(project_name.into_boxed_str());
-    let author = Box::leak(authors.into_boxed_str());
-
-    log::debug!("Loading {} by {}", name, author);
-
-    let attributes = WindowAttributes::default();
-
-    match config.target_resolution {
-        WindowModes::Windowed(_, _) => {}
-        WindowModes::Maximised => {}
-        WindowModes::Fullscreen => {}
-    }
-
-    let window = DropbearWindowBuilder::new()
-        .with_attributes(attributes)
-        .add_scene_with_input(runtime_scene, "runtime_scene")
-        .set_initial_scene("runtime_scene")
-        .build();
-
-    log::debug!("Running dropbear app");
-
-    DropbearAppBuilder::new()
-        .add_window(window)
-        .max_fps(config.max_fps)
-        .app_data(AppInfo {
-            name,
-            author,
-        })
-        .with_future_queue(future_queue)
-        .run().await.unwrap();
-}
-
-#[derive(Debug, Clone, Deserialize, Serialize, bincode::Encode, bincode::Decode)]
-pub struct ConfigFile {
-    pub jvm_args: Option<String>,
-    pub max_fps: u32,
-    pub target_resolution: WindowModes
-}
-
-#[derive(Debug, Clone, Deserialize, Serialize, bincode::Encode, bincode::Decode)]
-pub enum WindowModes {
-    Windowed(u32, u32),
-    Maximised,
-    Fullscreen,
-}
diff --git a/crates/redback-runtime/src/scene.rs b/crates/redback-runtime/src/scene.rs
index 5cfad19..6cdb995 100644
--- a/crates/redback-runtime/src/scene.rs
+++ b/crates/redback-runtime/src/scene.rs
@@ -1,26 +1,36 @@
+use std::sync::Arc;
+
 use std::collections::HashMap;
-use hecs::{Entity};
+use eucalyptus_core::egui::{CentralPanel, MenuBar, TopBottomPanel};
+use eucalyptus_core::physics::collider::ColliderGroup;
+use eucalyptus_core::physics::collider::ColliderShapeKey;
+use eucalyptus_core::physics::collider::shader::ColliderInstanceRaw;
+use glam::{DMat4, DQuat, DVec3, Quat};
+use hecs::Entity;
 use wgpu::Color;
 use wgpu::util::DeviceExt;
 use winit::event_loop::ActiveEventLoop;
 use dropbear_engine::camera::Camera;
+use dropbear_engine::buffer::ResizableBuffer;
 use dropbear_engine::entity::{EntityTransform, MeshRenderer, Transform};
-use dropbear_engine::graphics::{InstanceRaw, RenderContext};
+use dropbear_engine::graphics::{InstanceRaw, SharedGraphicsContext, FrameGraphicsContext};
 use dropbear_engine::lighting::{Light, LightComponent};
-use dropbear_engine::model::{DrawLight, DrawModel, DrawShadow, ModelId, MODEL_CACHE};
+use dropbear_engine::model::{DrawLight, DrawModel, ModelId, MODEL_CACHE};
 use dropbear_engine::scene::{Scene, SceneCommand};
-use dropbear_engine::wgpu;
 use eucalyptus_core::camera::CameraComponent;
 use eucalyptus_core::command::CommandBufferPoller;
-use eucalyptus_core::egui;
-use eucalyptus_core::egui::{CentralPanel, MenuBar, TopBottomPanel};
-use eucalyptus_core::hierarchy::EntityTransformExt;
-use eucalyptus_core::states::PROJECT;
-use eucalyptus_core::scene::loading::{SCENE_LOADER, SceneLoadResult, IsSceneLoaded};
-use crate::Runtime;
-
-impl Scene for Runtime {
-    fn load(&mut self, graphics: &mut RenderContext) {
+use eucalyptus_core::hierarchy::{EntityTransformExt, Parent};
+use eucalyptus_core::physics::kcc::KCC;
+use eucalyptus_core::rapier3d::prelude::QueryFilter;
+use eucalyptus_core::rapier3d::geometry::SharedShape;
+use eucalyptus_core::states::{Label, PROJECT};
+use eucalyptus_core::states::SCENES;
+use eucalyptus_core::scene::loading::{IsSceneLoaded, SceneLoadResult, SCENE_LOADER};
+use crate::{PlayMode};
+use eucalyptus_core::physics::collider::shader::create_wireframe_geometry;
+
+impl Scene for PlayMode {
+    fn load(&mut self, graphics: Arc<SharedGraphicsContext>) {
         if self.current_scene.is_none() {
             let initial_scene = if let Some(s) = &self.initial_scene {
                 s.clone()
@@ -37,15 +47,203 @@ impl Scene for Runtime {
         }
     }
 
-    fn update(&mut self, dt: f32, graphics: &mut RenderContext) {
-        graphics.shared.future_queue.poll();
-        self.poll(graphics);
+    fn physics_update(&mut self, dt: f32, _graphics: Arc<SharedGraphicsContext>) {
+        if self.scripts_ready {
+            let _ = self.script_manager.physics_update_script(self.world.as_mut(), dt as f64);
+        }
+
+        for kcc in self.world.query::<&mut KCC>().iter() {
+            kcc.collisions.clear();
+        }
+
+        for (e, l, _) in self.world.query::<(Entity, &Label, &KCC)>().iter() {
+            log_once::debug_once!("This entity [{:?}, label = {}] has the KCC (KinematicCharacterController) component attached", e, l);
+        }
+
+        if !self.physics_state.collision_events_to_deal_with.is_empty() {
+            let entities_with_collisions: Vec<Entity> = self
+                .physics_state
+                .collision_events_to_deal_with
+                .keys()
+                .copied()
+                .collect();
+
+            for entity in entities_with_collisions {
+                let Some(collisions) = self.physics_state.collision_events_to_deal_with.remove(&entity) else {
+                    continue;
+                };
+                if collisions.is_empty() {
+                    continue;
+                }
+
+                if let Ok(mut kcc) = self.world.get::<&mut KCC>(entity) {
+                    kcc.collisions = collisions.clone();
+                }
+
+                let (label, kcc_controller) = match self.world.query_one::<(&Label, &KCC)>(entity).get() {
+                    Ok(v) => {
+                        (v.0.clone(), v.1.clone())
+                    }
+                    Err(e) => {
+                        log_once::warn_once!("Unable to query {:?}: {}", entity, e);
+                        continue;
+                    }
+                };
+
+                let Some(rigid_body_handle) = self.physics_state.bodies_entity_map.get(&label).copied() else {
+                    continue;
+                };
+
+                let Some((_, collider_handle)) = self
+                    .physics_state
+                    .colliders_entity_map
+                    .get(&label)
+                    .and_then(|handles| handles.first())
+                    .copied()
+                else {
+                    continue;
+                };
+
+                let (character_shape, character_mass): (SharedShape, f32) = {
+                    let Some(collider) = self.physics_state.colliders.get(collider_handle) else {
+                        continue;
+                    };
+
+                    (collider.shared_shape().clone(), collider.mass())
+                };
+
+                let character_mass = if character_mass > 0.0 { character_mass } else { 1.0 };
+
+                let filter = QueryFilter::default().exclude_rigid_body(rigid_body_handle);
+                let dispatcher = self.physics_state.narrow_phase.query_dispatcher();
+
+                let broad_phase = &mut self.physics_state.broad_phase;
+                let bodies = &mut self.physics_state.bodies;
+                let colliders = &mut self.physics_state.colliders;
+
+                let mut query_pipeline_mut = broad_phase.as_query_pipeline_mut(
+                    dispatcher,
+                    bodies,
+                    colliders,
+                    filter,
+                );
+
+                kcc_controller.controller.solve_character_collision_impulses(
+                    dt,
+                    &mut query_pipeline_mut,
+                    character_shape.as_ref(),
+                    character_mass,
+                    &collisions,
+                );
+            }
+        }
+        
+        let mut entity_label_map = HashMap::new();
+        for (entity, label) in self.world.query::<(Entity, &Label)>().iter() {
+            entity_label_map.insert(entity, label.clone());
+        }
+        
+        self.physics_state.step(entity_label_map, &mut self.physics_pipeline, &(), &self.event_collector);
+
+        if self.scripts_ready {
+            if let (Some(ce_r), Some(cfe_r)) = (&self.collision_event_receiver, &self.collision_force_event_receiver) {
+                // both are not crucial, so no need to panic
+                while let Ok(event) = ce_r.try_recv() {
+                    log_once::debug_once!("Collision event received");
+                    if let Some(evt) = eucalyptus_core::types::CollisionEvent::from_rapier3d(&self.physics_state, event) {
+                        if let Err(err) = self.script_manager.collision_event_script(self.world.as_mut(), &evt) {
+                            log::error!("Script collision event error: {}", err);
+                        }
+                    }
+                }
+
+                while let Ok(event) = cfe_r.try_recv() {
+                    log_once::debug_once!("Contact force event received");
+                    if let Some(evt) = eucalyptus_core::types::ContactForceEvent::from_rapier3d(&self.physics_state, event) {
+                        if let Err(err) = self.script_manager.contact_force_event_script(self.world.as_mut(), &evt) {
+                            log::error!("Script contact force event error: {}", err);
+                        }
+                    }
+                }
+            }
+        }
+
+        let mut sync_updates = Vec::new();
+
+        for (entity, label, _) in self.world.query::<(Entity, &Label, &EntityTransform)>().iter() {
+            if let Some(handle) = self.physics_state.bodies_entity_map.get(label) {
+                if let Some(body) = self.physics_state.bodies.get(*handle) {
+                    let p = body.translation();
+                    let r = body.rotation();
+
+                    sync_updates.push((
+                        entity,
+                        DVec3::new(p.x as f64, p.y as f64, p.z as f64),
+                        Quat::from(r.clone()).as_dquat()
+                    ));
+                }
+            }
+        }
+
+        for (entity, new_world_pos, new_world_rot) in sync_updates {
+
+            let parent_world = if let Ok(parent_comp) = self.world.get::<&Parent>(entity) {
+                let parent_entity = parent_comp.parent();
+                if let Ok(p_transform) = self.world.get::<&EntityTransform>(parent_entity) {
+                    Some(p_transform.propagate(&self.world, parent_entity))
+                } else {
+                    None
+                }
+            } else {
+                None
+            };
+
+            if let Ok(mut entity_transform) = self.world.get::<&mut EntityTransform>(entity) {
+                if let Some(p_world) = parent_world {
+                    let inv_p_rot = p_world.rotation.inverse();
+
+                    let relative_pos = new_world_pos - p_world.position;
+                    let new_local_pos = (inv_p_rot * relative_pos) / p_world.scale;
+                    let new_local_rot = inv_p_rot * new_world_rot;
+
+                    let base = entity_transform.world_mut();
+                    base.position = new_local_pos;
+                    base.rotation = new_local_rot;
+
+                    let offset = entity_transform.local_mut();
+                    offset.position = DVec3::ZERO;
+                    offset.rotation = DQuat::IDENTITY;
+                } else {
+                    let base = entity_transform.world_mut();
+                    base.position = new_world_pos;
+                    base.rotation = new_world_rot;
+
+                    let offset = entity_transform.local_mut();
+                    offset.position = DVec3::ZERO;
+                    offset.rotation = DQuat::IDENTITY;
+                }
+            }
+        }
+    }
+
+    fn update(&mut self, dt: f32, graphics: Arc<SharedGraphicsContext>) {
+        graphics.future_queue.poll();
+        self.poll(graphics.clone());
+
+        {
+            if let Some(fps) = PROJECT.read().runtime_settings.target_fps.get() {
+                log_once::debug_once!("setting new fps for play mode session: {}", fps);
+                if matches!(self.scene_command, SceneCommand::None) {
+                    self.scene_command = SceneCommand::SetFPS(*fps);
+                }
+            }
+        }
 
         if let Some(ref progress) = self.scene_progress {
             if !progress.scene_handle_requested && self.world_receiver.is_none() && self.scene_loading_handle.is_none() {
                 log::debug!("Starting async load for scene: {}", progress.requested_scene);
                 let scene_to_load = IsSceneLoaded::new(progress.requested_scene.clone());
-                self.request_async_scene_load(graphics, scene_to_load);
+                self.request_async_scene_load(graphics.clone(), scene_to_load);
             }
         }
 
@@ -70,8 +268,17 @@ impl Scene for Runtime {
             }
         }
 
+        if let Some(mut receiver) = self.physics_receiver.take() {
+            if let Ok(loaded_physics) = receiver.try_recv() {
+                self.pending_physics_state = Some(Box::new(loaded_physics));
+                log::debug!("PhysicsState received");
+            } else {
+                self.physics_receiver = Some(receiver);
+            }
+        }
+
         if let Some(handle) = self.scene_loading_handle.take() {
-            if let Some(cam) = graphics.shared.future_queue.exchange_owned_as::<Entity>(&handle) {
+            if let Some(cam) = graphics.future_queue.exchange_owned_as::<Entity>(&handle) {
                 self.pending_camera = Some(cam);
                 log::debug!("Camera entity received: {:?}", cam);
                 if let Some(ref mut progress) = self.scene_progress {
@@ -94,27 +301,27 @@ impl Scene for Runtime {
         if let Some(ref progress) = self.scene_progress {
             if progress.is_everything_loaded() {
                 if self.current_scene.as_ref() != Some(&progress.requested_scene) {
-                    self.switch_to(progress.clone(), graphics);
+                    self.switch_to(progress.clone(), graphics.clone());
                 }
             }
         }
 
         if self.scripts_ready {
-            if let Err(e) = self.script_manager.update_script(self.world.as_mut(), dt) {
+            if let Err(e) = self.script_manager.update_script(self.world.as_mut(), dt as f64) {
                 panic!("Script update error: {}", e);
             }
         }
 
         {
             let mut query = self.world.query::<(&mut MeshRenderer, &Transform)>();
-            for (_entity, (renderer, transform)) in query.iter() {
+            for (renderer, transform) in query.iter() {
                 renderer.update(transform);
             }
         }
 
         {
             let mut updates = Vec::new();
-            for (entity, transform) in self.world.query::<&EntityTransform>().iter() {
+            for (entity, transform) in self.world.query::<(Entity, &EntityTransform)>().iter() {
                 let final_transform = transform.propagate(&self.world, entity);
                 updates.push((entity, final_transform));
             }
@@ -131,7 +338,7 @@ impl Scene for Runtime {
                 .world
                 .query::<(&mut LightComponent, Option<&Transform>, Option<&EntityTransform>, &mut Light)>();
 
-            for (_, (light_comp, transform_opt, entity_transform_opt, light)) in light_query.iter() {
+            for (light_comp, transform_opt, entity_transform_opt, light) in light_query.iter() {
                 let transform = if let Some(entity_transform) = entity_transform_opt {
                     entity_transform.sync()
                 } else if let Some(transform) = transform_opt {
@@ -140,25 +347,46 @@ impl Scene for Runtime {
                     continue;
                 };
 
-                light.update(graphics.shared.as_ref(), light_comp, &transform);
+                light.update(graphics.as_ref(), light_comp, &transform);
             }
         }
 
         {
-            for (_entity_id, (camera, component)) in self
+            for (camera, component) in self
                 .world
                 .query::<(&mut Camera, &mut CameraComponent)>()
                 .iter()
             {
                 component.update(camera);
-                camera.update(graphics.shared.clone());
+                camera.update(graphics.clone());
             }
         }
 
         self.light_manager
-            .update(graphics.shared.clone(), &self.world);
+            .update(graphics.clone(), &self.world);
+
+        TopBottomPanel::top("menu_bar").show(&graphics.get_egui_context(), |ui| {
+            MenuBar::new().ui(ui, |ui| {
+                ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| {
+                    ui.group(|ui| {
+                        ui.add_enabled_ui(true, |ui| {
+                            if ui.button("⏹").clicked() {
+                                log::debug!("Menu button Stop button pressed");
+                                self.scene_command = SceneCommand::CloseWindow(graphics.window.id());
+                            }
+                        });
 
-        CentralPanel::default().show(&graphics.shared.get_egui_context(), |ui| {
+                        ui.add_enabled_ui(false, |ui| {
+                            if ui.button("▶").clicked() {
+                                log::debug!("how tf can you press this???");
+                            }
+                        });
+                    });
+                });
+            });
+        });
+
+        CentralPanel::default().show(&graphics.get_egui_context(), |ui| {
             if let Some(p) = &self.scene_progress {
                 if !p.is_everything_loaded() && p.is_first_scene {
                     // todo: change from label to "splashscreen"
@@ -169,7 +397,7 @@ impl Scene for Runtime {
                 }
             }
 
-            let texture_id = *graphics.shared.texture_id;
+            let texture_id = *graphics.texture_id;
 
             let available_size = ui.available_size();
             let available_rect = ui.available_rect_before_wrap();
@@ -182,19 +410,20 @@ impl Scene for Runtime {
                         self.has_initial_resize_done = true;
                     }
 
-                    if !self.display_settings.maintain_aspect_ratio {
-                        cam.aspect = (available_size.x / available_size.y) as f64;
-                    }
+                    // if !self.display_settings.maintain_aspect_ratio {
+                    //     cam.aspect = (available_size.x / available_size.y) as f64;
+                    // }
                     cam.update_view_proj();
-                    cam.update(graphics.shared.clone());
-
-                    let (display_width, display_height) = if self.display_settings.maintain_aspect_ratio {
-                        let width = available_size.x;
-                        let height = width / cam.aspect as f32;
-                        (width, height)
-                    } else {
-                        (available_size.x, available_size.y)
-                    };
+                    cam.update(graphics.clone());
+
+                    let (display_width, display_height) = (available_size.x, available_size.y);
+                    // if self.display_settings.maintain_aspect_ratio {
+                    //     let width = available_size.x;
+                    //     let height = width / cam.aspect as f32;
+                    //     (width, height)
+                    // } else {
+                        
+                    // };
 
                     let center_x = available_rect.center().x;
                     let center_y = available_rect.center().y;
@@ -223,25 +452,13 @@ impl Scene for Runtime {
         self.input_state.mouse_delta = None;
     }
 
-    fn physics_update(&mut self, _dt: f32, _graphics: &mut RenderContext) {
-        if self.scripts_ready {
-            if let Err(e) = self.script_manager.physics_update_script(self.world.as_mut(), _dt) {
-                panic!("Script physics update error: {}", e);
-            }
-        }
-    }
-
-    fn render(&mut self, graphics: &mut RenderContext) {
+    fn render<'a>(&mut self, graphics: Arc<SharedGraphicsContext>, frame_ctx: FrameGraphicsContext<'a>) {
         let Some(active_camera) = self.active_camera else {
             return;
         };
         log_once::debug_once!("Active camera found: {:?}", active_camera);
 
-        let q = if let Ok(mut query) = self.world.query_one::<&Camera>(active_camera) {
-            query.get().cloned()
-        } else {
-            None
-        };
+        let q = self.world.query_one::<&Camera>(active_camera).get().ok().cloned();
 
         let Some(camera) = q else {
             return;
@@ -265,36 +482,27 @@ impl Scene for Runtime {
         let lights = {
             let mut lights = Vec::new();
             let mut query = self.world.query::<(&Light, &LightComponent)>();
-            for (_, (light, comp)) in query.iter() {
+            for (light, comp) in query.iter() {
                 lights.push((light.clone(), comp.clone()));
             }
             lights
         };
 
-        let mut model_batches: HashMap<ModelId, Vec<InstanceRaw>> = HashMap::new();
-        {
-            let mut query = self.world.query::<(
-                &mut MeshRenderer,
-                Option<&dropbear_engine::entity::Transform>,
-                Option<&dropbear_engine::entity::EntityTransform>,
-            )>();
-
-            for (_, (renderer, transform_opt, entity_transform_opt)) in query.iter() {
-                let transform = if let Some(entity_transform) = entity_transform_opt {
-                    entity_transform.sync()
-                } else if let Some(transform) = transform_opt {
-                    *transform
-                } else {
-                    continue;
-                };
-
-                renderer.update(&transform);
-
-                model_batches
-                    .entry(renderer.model_id())
-                    .or_default()
-                    .push(renderer.instance.to_raw());
+        let renderers = {
+            let mut renderers = Vec::new();
+            let mut query = self.world.query::<&MeshRenderer>();
+            for renderer in query.iter() {
+                renderers.push(renderer.clone());
             }
+            renderers
+        };
+
+        let mut model_batches: HashMap<ModelId, Vec<InstanceRaw>> = HashMap::new();
+        for renderer in &renderers {
+            model_batches
+                .entry(renderer.model_id())
+                .or_default()
+                .push(renderer.instance.to_raw());
         }
 
         let mut prepared_models = Vec::new();
@@ -309,7 +517,7 @@ impl Scene for Runtime {
                 continue;
             };
 
-            let instance_buffer = graphics.shared.device.create_buffer_init(
+            let instance_buffer = graphics.device.create_buffer_init(
                 &wgpu::util::BufferInitDescriptor {
                     label: Some("Runtime Instance Buffer"),
                     contents: bytemuck::cast_slice(&instances),
@@ -328,7 +536,7 @@ impl Scene for Runtime {
                 &mut Light,
             )>();
 
-            for (_, (light_component, transform_opt, entity_transform_opt, light)) in query.iter() {
+            for (light_component, transform_opt, entity_transform_opt, light) in query.iter() {
                 let transform = if let Some(entity_transform) = entity_transform_opt {
                     entity_transform.sync()
                 } else if let Some(transform) = transform_opt {
@@ -337,74 +545,221 @@ impl Scene for Runtime {
                     continue;
                 };
 
-                light.update(graphics.shared.as_ref(), light_component, &transform);
+                light.update(graphics.as_ref(), light_component, &transform);
             }
         }
 
-        self.light_manager.update(graphics.shared.clone(), &self.world);
-
-        if let Some(shadow_pipeline) = &self.light_manager.shadow_pipeline {
-            for (light, component) in &lights {
-                if !component.enabled || !component.cast_shadows {
-                    continue;
-                }
-
-                let shadow_index = light.uniform().shadow_index;
-                if shadow_index < 0 {
-                    continue;
-                }
-
-                let shadow_index = shadow_index as usize;
-                if shadow_index >= self.light_manager.shadow_target_views.len() {
-                    continue;
-                }
-
-                let shadow_view = &self.light_manager.shadow_target_views[shadow_index];
-                let mut shadow_pass =
-                    graphics.begin_shadow_pass(shadow_view, Some("Shadow Pass"));
-                shadow_pass.set_pipeline(shadow_pipeline);
-
-                for (model, instance_buffer, instance_count) in &prepared_models {
-                    shadow_pass.set_vertex_buffer(1, instance_buffer.slice(..));
-                    shadow_pass.draw_shadow_model_instanced(
-                        model,
-                        0..*instance_count,
-                        light.bind_group(),
-                    );
-                }
-            }
-        }
+        self.light_manager.update(graphics.clone(), &self.world);
 
         {
-            let mut render_pass = graphics.clear_colour(clear_color);
+            let mut render_pass = frame_ctx.encoder
+                .begin_render_pass(&wgpu::RenderPassDescriptor {
+                    label: Some("light cube render pass"),
+                    color_attachments: &[Some(wgpu::RenderPassColorAttachment {
+                        view: &frame_ctx.view,
+                        depth_slice: None,
+                        resolve_target: None,
+                        ops: wgpu::Operations {
+                            load: wgpu::LoadOp::Clear(clear_color),
+                            store: wgpu::StoreOp::Store,
+                        },
+                    })],
+                    depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment {
+                        view: &graphics.depth_texture.view,
+                        depth_ops: Some(wgpu::Operations {
+                            load: wgpu::LoadOp::Clear(0.0),
+                            store: wgpu::StoreOp::Store,
+                        }),
+                        stencil_ops: None,
+                    }),
+                    occlusion_query_set: None,
+                    timestamp_writes: None,
+                });
             if let Some(light_pipeline) = &self.light_manager.pipeline {
                 render_pass.set_pipeline(light_pipeline);
                 for (light, component) in &lights {
-                    if let Some(buffer) = &light.instance_buffer {
-                        render_pass.set_vertex_buffer(1, buffer.slice(..));
-                        if component.visible {
-                            render_pass.draw_light_model(
-                                &light.cube_model,
-                                camera.bind_group(),
-                                light.bind_group(),
-                            );
-                        }
+                    render_pass.set_vertex_buffer(1, light.instance_buffer.buffer().slice(..));
+                    if component.visible {
+                        render_pass.draw_light_model(
+                            &light.cube_model,
+                            &camera.bind_group,
+                            &light.bind_group
+                        );
                     }
                 }
             }
         }
 
         for (model, instance_buffer, instance_count) in prepared_models {
-            let mut render_pass = graphics.continue_pass();
+            let mut render_pass = frame_ctx.encoder
+                .begin_render_pass(&wgpu::RenderPassDescriptor {
+                    label: Some("model render pass"),
+                    color_attachments: &[Some(wgpu::RenderPassColorAttachment {
+                        view: &frame_ctx.view,
+                        depth_slice: None,
+                        resolve_target: None,
+                        ops: wgpu::Operations {
+                            load: wgpu::LoadOp::Load,
+                            store: wgpu::StoreOp::Store,
+                        },
+                    })],
+                    depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment {
+                        view: &graphics.depth_texture.view,
+                        depth_ops: Some(wgpu::Operations {
+                            load: wgpu::LoadOp::Load,
+                            store: wgpu::StoreOp::Store,
+                        }),
+                        stencil_ops: None,
+                    }),
+                    occlusion_query_set: None,
+                    timestamp_writes: None,
+                });
             render_pass.set_pipeline(pipeline);
             render_pass.set_vertex_buffer(1, instance_buffer.slice(..));
             render_pass.draw_model_instanced(
                 &model,
                 0..instance_count,
-                camera.bind_group(),
+                &camera.bind_group,
                 self.light_manager.bind_group(),
             );
         }
+
+        {
+            let show_hitboxes = self
+                .current_scene
+                .as_ref()
+                .and_then(|scene_name| {
+                    let scenes = SCENES.read();
+                    scenes
+                        .iter()
+                        .find(|scene| &scene.scene_name == scene_name)
+                        .map(|scene| scene.settings.show_hitboxes)
+                })
+                .unwrap_or(false);
+
+            if show_hitboxes {
+                if let Some(collider_pipeline) = &self.collider_wireframe_pipeline {
+                    let mut render_pass = frame_ctx.encoder
+                        .begin_render_pass(&wgpu::RenderPassDescriptor {
+                            label: Some("model render pass"),
+                            color_attachments: &[Some(wgpu::RenderPassColorAttachment {
+                                view: &frame_ctx.view,
+                                depth_slice: None,
+                                resolve_target: None,
+                                ops: wgpu::Operations {
+                                    load: wgpu::LoadOp::Load,
+                                    store: wgpu::StoreOp::Store,
+                                },
+                            })],
+                            depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment {
+                                view: &graphics.depth_texture.view,
+                                depth_ops: Some(wgpu::Operations {
+                                    load: wgpu::LoadOp::Load,
+                                    store: wgpu::StoreOp::Store,
+                                }),
+                                stencil_ops: None,
+                            }),
+                            occlusion_query_set: None,
+                            timestamp_writes: None,
+                        });
+                    
+                    render_pass.set_pipeline(&collider_pipeline.pipeline);
+                    render_pass.set_bind_group(0, &camera.bind_group, &[]);
+
+                    let mut instances_by_shape: HashMap<ColliderShapeKey, Vec<ColliderInstanceRaw>> =
+                        HashMap::new();
+
+                    let mut q = self.world.query::<(&EntityTransform, &ColliderGroup)>();
+                    for (entity_transform, group) in q.iter() {
+                        for collider in &group.colliders {
+                            let world_tf = entity_transform.sync();
+
+                            let entity_matrix = DMat4::from_rotation_translation(
+                                world_tf.rotation,
+                                world_tf.position,
+                            )
+                            .as_mat4();
+
+                            let offset_transform = Transform::new()
+                                .with_offset(collider.translation, collider.rotation);
+                            let offset_matrix = offset_transform.matrix().as_mat4();
+
+                            let final_matrix = entity_matrix * offset_matrix;
+
+                            let color = [0.0, 1.0, 0.0, 1.0];
+                            let instance = ColliderInstanceRaw::from_matrix(final_matrix, color);
+
+                            let key = ColliderShapeKey::from(&collider.shape);
+                            instances_by_shape.entry(key).or_default().push(instance);
+
+                            self.collider_wireframe_geometry_cache.entry(key).or_insert_with(|| {
+                                create_wireframe_geometry(
+                                    graphics.clone(),
+                                    &collider.shape,
+                                )
+                            });
+                        }
+                    }
+
+                    if !instances_by_shape.is_empty() {
+                        let total_instances: usize =
+                            instances_by_shape.values().map(|v| v.len()).sum();
+                        let mut all_instances = Vec::with_capacity(total_instances);
+                        let mut draws: Vec<(ColliderShapeKey, usize, usize)> = Vec::new();
+
+                        for (key, instances) in instances_by_shape {
+                            let start = all_instances.len();
+                            all_instances.extend_from_slice(&instances);
+                            let count = instances.len();
+                            draws.push((key, start, count));
+                        }
+
+                        let instance_buffer = self.collider_instance_buffer.get_or_insert_with(|| {
+                            ResizableBuffer::new(
+                                &graphics.device,
+                                all_instances.len().max(10),
+                                wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
+                                "Collider Instance Buffer",
+                            )
+                        });
+                        instance_buffer.write(
+                            &graphics.device,
+                            &graphics.queue,
+                            &all_instances,
+                        );
+
+                        for (key, start, count) in draws {
+                            let Some(geometry) = self.collider_wireframe_geometry_cache.get(&key) else {
+                                continue;
+                            };
+
+                            let start_bytes =
+                                (start * std::mem::size_of::<ColliderInstanceRaw>()) as wgpu::BufferAddress;
+                            let end_bytes =
+                                ((start + count) * std::mem::size_of::<ColliderInstanceRaw>()) as wgpu::BufferAddress;
+
+                            render_pass.set_vertex_buffer(
+                                1,
+                                instance_buffer.buffer().slice(start_bytes..end_bytes),
+                            );
+                            render_pass.set_vertex_buffer(
+                                0,
+                                geometry.vertex_buffer.slice(..),
+                            );
+                            render_pass.set_index_buffer(
+                                geometry.index_buffer.slice(..),
+                                wgpu::IndexFormat::Uint16,
+                            );
+                            render_pass.draw_indexed(
+                                0..geometry.index_count,
+                                0,
+                                0..count as u32,
+                            );
+                        }
+                    }
+                }
+            }
+        }
     }
 
     fn exit(&mut self, _event_loop: &ActiveEventLoop) {}
@@ -412,4 +767,5 @@ impl Scene for Runtime {
     fn run_command(&mut self) -> SceneCommand {
         std::mem::replace(&mut self.scene_command, SceneCommand::None)
     }
-}
\ No newline at end of file
+}
+
diff --git a/crates/redback-runtime/src/utils.rs b/crates/redback-runtime/src/utils.rs
deleted file mode 100644
index e69de29..0000000
--- a/crates/redback-runtime/src/utils.rs
+++ /dev/null
diff --git a/resources/shaders/collider.wgsl b/resources/shaders/collider.wgsl
new file mode 100644
index 0000000..b899ac9
--- /dev/null
+++ b/resources/shaders/collider.wgsl
@@ -0,0 +1,45 @@
+struct CameraUniform {
+    view_pos: vec4<f32>,
+    view_proj: mat4x4<f32>,
+};
+
+@group(0) @binding(0)
+var <uniform> camera: CameraUniform;
+
+struct VertexInput {
+    @location(0) position: vec3<f32>,
+    @location(1) model_0: vec4<f32>,
+    @location(2) model_1: vec4<f32>,
+    @location(3) model_2: vec4<f32>,
+    @location(4) model_3: vec4<f32>,
+    @location(5) color: vec4<f32>,
+}
+
+struct VertexOutput {
+    @builtin(position) clip_position: vec4<f32>,
+    @location(0) color: vec4<f32>,
+}
+
+@vertex
+fn vs_main(input: VertexInput) -> VertexOutput {
+    var output: VertexOutput;
+
+    let model = mat4x4<f32>(
+        input.model_0,
+        input.model_1,
+        input.model_2,
+        input.model_3,
+    );
+
+    let world_position = model * vec4<f32>(input.position, 1.0);
+    output.clip_position = camera.view_proj * world_position;
+
+    output.color = input.color;
+
+    return output;
+}
+
+@fragment
+fn fs_main(input: VertexOutput) -> @location(0) vec4<f32> {
+    return input.color;
+}
\ No newline at end of file
diff --git a/resources/shaders/light.wgsl b/resources/shaders/light.wgsl
new file mode 100644
index 0000000..b976066
--- /dev/null
+++ b/resources/shaders/light.wgsl
@@ -0,0 +1,62 @@
+// Shader for rendering the white cube for lighting (or diff depending on colour)
+
+struct Light {
+    position: vec4<f32>,
+    direction: vec4<f32>, // x, y, z, outer_cutoff_angle
+    color: vec4<f32>, // r, g, b, light_type (0, 1, 2)
+    constant: f32,
+    lin: f32,
+    quadratic: f32,
+    cutoff: f32,
+}
+
+struct CameraUniform {
+    view_pos: vec4<f32>,
+    view_proj: mat4x4<f32>,
+};
+
+@group(0) @binding(0)
+var<uniform> camera: CameraUniform;
+
+@group(1) @binding(0)
+var<uniform> light: Light;
+
+struct InstanceInput {
+    @location(5) model_matrix_0: vec4<f32>,
+    @location(6) model_matrix_1: vec4<f32>,
+    @location(7) model_matrix_2: vec4<f32>,
+    @location(8) model_matrix_3: vec4<f32>,
+}
+
+struct VertexInput {
+    @location(0) position: vec3<f32>,
+};
+
+struct VertexOutput {
+    @builtin(position) clip_position: vec4<f32>,
+    @location(0) color: vec3<f32>,
+};
+
+@vertex
+fn vs_main(
+    model: VertexInput,
+    instance: InstanceInput,
+) -> VertexOutput {
+    let model_matrix = mat4x4<f32>(
+        instance.model_matrix_0,
+        instance.model_matrix_1,
+        instance.model_matrix_2,
+        instance.model_matrix_3,
+    );
+    var out: VertexOutput;
+    out.clip_position = camera.view_proj * model_matrix * vec4<f32>(model.position, 1.0);
+    out.color = light.color.xyz;
+    return out;
+}
+
+// Fragment shader
+
+@fragment
+fn fs_main(in: VertexOutput) -> @location(0) vec4<f32> {
+    return vec4<f32>(in.color, 1.0);
+}
\ No newline at end of file
diff --git a/resources/shaders/shader.wgsl b/resources/shaders/shader.wgsl
new file mode 100644
index 0000000..2ec6c26
--- /dev/null
+++ b/resources/shaders/shader.wgsl
@@ -0,0 +1,245 @@
+// Main shader for standard objects.
+
+const MAX_LIGHTS: u32 = 8;
+
+struct CameraUniform {
+    view_pos: vec4<f32>,
+    view_proj: mat4x4<f32>,
+};
+
+struct Light {
+    position: vec4<f32>,
+    direction: vec4<f32>, // x, y, z, outer_cutoff_angle
+    color: vec4<f32>, // r, g, b, light_type (0, 1, 2)
+    constant: f32,
+    lin: f32,
+    quadratic: f32,
+    cutoff: f32,
+}
+
+struct LightArray {
+    _lights: array<Light, MAX_LIGHTS>,
+    light_count: u32,
+    ambient_strength: f32,
+}
+
+struct MaterialUniform {
+    // for stuff like tinting
+    colour: vec4<f32>,
+
+    // scales incoming UVs before sampling
+    uv_tiling: vec2<f32>,
+    _pad: vec2<f32>,
+}
+
+@group(0) @binding(0)
+var t_diffuse: texture_2d<f32>;
+@group(0) @binding(1)
+var s_diffuse: sampler;
+@group(0) @binding(2)
+var t_normal: texture_2d<f32>;
+@group(0) @binding(3)
+var s_normal: sampler;
+
+@group(3) @binding(0)
+var<uniform> u_material: MaterialUniform;
+
+@group(1) @binding(0)
+var<uniform> camera: CameraUniform;
+
+@group(2) @binding(0)
+var<storage, read> light_array: LightArray;
+
+struct InstanceInput {
+    @location(5) model_matrix_0: vec4<f32>,
+    @location(6) model_matrix_1: vec4<f32>,
+    @location(7) model_matrix_2: vec4<f32>,
+    @location(8) model_matrix_3: vec4<f32>,
+
+    @location(9) normal_matrix_0: vec3<f32>,
+    @location(10) normal_matrix_1: vec3<f32>,
+    @location(11) normal_matrix_2: vec3<f32>,
+};
+
+struct VertexInput {
+    @location(0) position: vec3<f32>,
+    @location(1) tex_coords: vec2<f32>,
+    @location(2) normal: vec3<f32>,
+    @location(3) tangent: vec3<f32>,
+    @location(4) bitangent: vec3<f32>,
+};
+
+struct VertexOutput {
+    @builtin(position) clip_position: vec4<f32>,
+    @location(0) tex_coords: vec2<f32>,
+    @location(1) world_normal: vec3<f32>,
+    @location(2) world_position: vec3<f32>,
+    @location(3) world_tangent: vec3<f32>,
+    @location(4) world_bitangent: vec3<f32>,
+};
+
+@vertex
+fn vs_main(
+    model: VertexInput,
+    instance: InstanceInput,
+) -> VertexOutput {
+    let model_matrix = mat4x4<f32>(
+        instance.model_matrix_0,
+        instance.model_matrix_1,
+        instance.model_matrix_2,
+        instance.model_matrix_3,
+    );
+    let normal_matrix = mat3x3<f32>(
+        instance.normal_matrix_0,
+        instance.normal_matrix_1,
+        instance.normal_matrix_2,
+    );
+
+    let world_normal = normalize(normal_matrix * model.normal);
+    let world_tangent = normalize(normal_matrix * model.tangent);
+    let world_bitangent = normalize(normal_matrix * model.bitangent);
+    let world_position = model_matrix * vec4<f32>(model.position, 1.0);
+
+    var out: VertexOutput;
+    out.clip_position = camera.view_proj * world_position;
+    out.tex_coords = model.tex_coords;
+    out.world_normal = world_normal;
+    out.world_position = world_position.xyz;
+    out.world_tangent = world_tangent;
+    out.world_bitangent = world_bitangent;
+    return out;
+}
+
+fn directional_light(
+    light: Light,
+    world_normal: vec3<f32>,
+    view_dir: vec3<f32>,
+    tex_color: vec3<f32>,
+    world_pos: vec3<f32>
+) -> vec3<f32> {
+    let light_dir = normalize(-light.direction.xyz);
+
+    let ambient = light.color.xyz * light_array.ambient_strength * tex_color;
+
+    let diff = max(dot(world_normal, light_dir), 0.0);
+    let diffuse = light.color.xyz * diff * tex_color;
+
+    let reflect_dir = reflect(-light_dir, world_normal);
+    let spec = pow(max(dot(view_dir, reflect_dir), 0.0), 32.0);
+    let specular = light.color.xyz * spec * tex_color;
+
+    return ambient + diffuse + specular;
+}
+
+fn point_light(
+    light: Light,
+    world_pos: vec3<f32>,
+    world_normal: vec3<f32>,
+    view_dir: vec3<f32>,
+    tex_color: vec3<f32>
+) -> vec3<f32> {
+    let light_dir = normalize(light.position.xyz - world_pos);
+
+    let distance = length(light.position.xyz - world_pos);
+    let attenuation = 1.0 / (light.constant + (light.lin * distance) + (light.quadratic * (distance * distance)));
+
+    let ambient = light.color.xyz * light_array.ambient_strength * tex_color;
+
+    let diff = max(dot(world_normal, light_dir), 0.0);
+    let diffuse = light.color.xyz * diff * tex_color;
+
+    let reflect_dir = reflect(-light_dir, world_normal);
+    let spec = pow(max(dot(view_dir, reflect_dir), 0.0), 32.0);
+    let specular = light.color.xyz * spec * tex_color;
+
+    return (ambient + diffuse + specular) * attenuation;
+}
+
+fn spot_light(
+    light: Light,
+    world_pos: vec3<f32>,
+    world_normal: vec3<f32>,
+    view_dir: vec3<f32>,
+    tex_color: vec3<f32>
+) -> vec3<f32> {
+    let light_dir = normalize(light.position.xyz - world_pos);
+    let theta = dot(light_dir, normalize(-light.direction.xyz));
+    let outer_cutoff = light.direction.w;
+
+    let epsilon = light.cutoff - outer_cutoff;
+    let intensity = clamp((theta - outer_cutoff) / epsilon, 0.0, 1.0);
+
+    let distance = length(light.position.xyz - world_pos);
+    let attenuation = 1.0 / (light.constant + (light.lin * distance) + (light.quadratic * (distance * distance)));
+
+    let ambient = light.color.xyz * light_array.ambient_strength * tex_color;
+
+    let diff = max(dot(world_normal, light_dir), 0.0);
+    let diffuse = light.color.xyz * diff * tex_color * intensity;
+
+    let reflect_dir = reflect(-light_dir, world_normal);
+    let spec = pow(max(dot(view_dir, reflect_dir), 0.0), 32.0);
+    let specular = light.color.xyz * spec * tex_color * intensity;
+
+    return (ambient + diffuse + specular) * attenuation;
+}
+
+fn apply_normal_map(
+    world_normal_in: vec3<f32>,
+    world_tangent_in: vec3<f32>,
+    world_bitangent_in: vec3<f32>,
+    normal_sample_rgb: vec3<f32>,
+) -> vec3<f32> {
+    let normal_ts = normalize(normal_sample_rgb * 2.0 - vec3<f32>(1.0));
+
+    let n = normalize(world_normal_in);
+    var t = normalize(world_tangent_in);
+    t = normalize(t - n * dot(n, t));
+
+    let b_in = normalize(world_bitangent_in);
+    let handedness = select(-1.0, 1.0, dot(cross(n, t), b_in) >= 0.0);
+    let b = cross(n, t) * handedness;
+
+    let tbn = mat3x3<f32>(t, b, n);
+    return normalize(tbn * normal_ts);
+}
+
+@fragment
+fn fs_main(in: VertexOutput) -> @location(0) vec4<f32> {
+    let uv = in.tex_coords * u_material.uv_tiling;
+    var tex_color = textureSample(t_diffuse, s_diffuse, uv);
+    var object_normal = textureSample(t_normal, s_normal, uv);
+
+    let base_colour = tex_color * u_material.colour;
+
+    if (base_colour.a < 0.1) {
+        discard;
+    }
+
+    let view_dir = normalize(camera.view_pos.xyz - in.world_position);
+
+    let world_normal = apply_normal_map(
+        in.world_normal,
+        in.world_tangent,
+        in.world_bitangent,
+        object_normal.xyz,
+    );
+
+    var final_color = vec3<f32>(0.0);
+
+    for (var i: u32 = 0u; i < light_array.light_count; i = i + 1u) {
+        let light = light_array._lights[i];
+
+        let light_type = i32(light.color.w + 0.1);
+
+        if (light_type == 0) {
+            final_color += directional_light(light, world_normal, view_dir, base_colour.xyz, in.world_position);
+        } else if (light_type == 1) {
+            final_color += point_light(light, in.world_position, world_normal, view_dir, base_colour.xyz);
+        } else if (light_type == 2) {
+            final_color += spot_light(light, in.world_position, world_normal, view_dir, base_colour.xyz);
+        }
+    }
+
+    return vec4<f32>(final_color, base_colour.a);
+}
\ No newline at end of file