65bc7fc
· Thribhu K
feature: added support for lighting as a storage resource.
fix: dealt with some weird "../../../" corruption or bug.
wip: redback-runtime runner (euca-runner) bin
Unverified
diff --git a/crates/dropbear-engine/src/buffer.rs b/crates/dropbear-engine/src/buffer.rs
index 6ad4874..27552d0 100644
--- a/crates/dropbear-engine/src/buffer.rs
+++ b/crates/dropbear-engine/src/buffer.rs
@@ -2,8 +2,6 @@
use std::marker::PhantomData;
-use wgpu::BufferUsages;
-
#[repr(C)]
#[derive(Copy, Clone, Debug, Default, bytemuck::Pod, bytemuck::Zeroable)]
pub struct Vertex {
@@ -54,7 +52,7 @@ impl<T: bytemuck::Pod> UniformBuffer<T> {
let buffer = device.create_buffer(&wgpu::BufferDescriptor {
label: Some(label),
size,
- usage: BufferUsages::UNIFORM | BufferUsages::COPY_DST,
+ usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
mapped_at_creation: false,
});
@@ -78,6 +76,7 @@ impl<T: bytemuck::Pod> UniformBuffer<T> {
}
}
+/// A wrapper to a [wgpu::Buffer] that stores
#[derive(Debug, Clone)]
pub struct StorageBuffer<T> {
buffer: wgpu::Buffer,
@@ -95,7 +94,7 @@ impl<T: bytemuck::Pod> StorageBuffer<T> {
let buffer = device.create_buffer(&wgpu::BufferDescriptor {
label: Some(label),
size,
- usage: BufferUsages::STORAGE | BufferUsages::COPY_DST,
+ usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST,
mapped_at_creation: false,
});
@@ -143,13 +142,6 @@ 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/graphics.rs b/crates/dropbear-engine/src/graphics.rs
index 8cb81c3..b93c290 100644
--- a/crates/dropbear-engine/src/graphics.rs
+++ b/crates/dropbear-engine/src/graphics.rs
@@ -1,10 +1,7 @@
-use crate::shader::Shader;
-use crate::texture::Texture;
use crate::{BindGroupLayouts, texture};
use crate::{
State,
egui_renderer::EguiRenderer,
- model::{self, Vertex},
};
use dropbear_future_queue::FutureQueue;
use egui::{Context, TextureId};
@@ -34,6 +31,7 @@ pub struct SharedGraphicsContext {
pub egui_renderer: Arc<Mutex<EguiRenderer>>,
pub texture_id: Arc<TextureId>,
pub future_queue: Arc<FutureQueue>,
+ pub supports_storage: bool,
}
impl SharedGraphicsContext {
@@ -74,71 +72,9 @@ impl SharedGraphicsContext {
texture_id: state.texture_id.clone(),
surface: state.surface.clone(),
surface_format: state.surface_format,
+ supports_storage: state.supports_storage,
}
}
-
- pub fn create_render_pipline(
- &self,
- shader: &Shader,
- bind_group_layouts: Vec<&BindGroupLayout>,
- label: Option<&str>,
- ) -> RenderPipeline {
- let render_pipeline_layout =
- self.device
- .create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
- label: Some(label.unwrap_or("Render Pipeline Descriptor")),
- bind_group_layouts: bind_group_layouts.as_slice(),
- push_constant_ranges: &[],
- });
-
- let render_pipeline =
- self.device
- .create_render_pipeline(&wgpu::RenderPipelineDescriptor {
- label: Some(label.unwrap_or("Render Pipeline")),
- layout: Some(&render_pipeline_layout),
- vertex: wgpu::VertexState {
- module: &shader.module,
- entry_point: Some("vs_main"),
- buffers: &[model::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: Texture::TEXTURE_FORMAT,
- 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::Back),
- 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: CompareFunction::Greater,
- stencil: StencilState::default(),
- bias: DepthBiasState::default(),
- }),
- multisample: wgpu::MultisampleState {
- count: 1,
- mask: !0,
- alpha_to_coverage_enabled: false,
- },
- multiview: None,
- cache: None,
- });
- log::debug!("Created new render pipeline");
- render_pipeline
- }
}
#[derive(Default, Clone)]
@@ -176,6 +112,19 @@ impl Instance {
}
}
+/// Maps to
+/// ```wgsl
+/// 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>,
+/// };
+/// ```
#[repr(C)]
#[derive(Copy, Clone, Default, bytemuck::Pod, bytemuck::Zeroable)]
pub struct InstanceRaw {
@@ -189,38 +138,46 @@ impl InstanceRaw {
array_stride: size_of::<InstanceRaw>() as BufferAddress,
step_mode: wgpu::VertexStepMode::Instance,
attributes: &[
- // model
+ // model_matrix_0
wgpu::VertexAttribute {
offset: 0,
shader_location: 5,
format: wgpu::VertexFormat::Float32x4,
},
+ // model_matrix_1
wgpu::VertexAttribute {
offset: size_of::<[f32; 4]>() as wgpu::BufferAddress,
shader_location: 6,
format: wgpu::VertexFormat::Float32x4,
},
+ // model_matrix_2
wgpu::VertexAttribute {
offset: size_of::<[f32; 8]>() as wgpu::BufferAddress,
shader_location: 7,
format: wgpu::VertexFormat::Float32x4,
},
+ // model_matrix_3
wgpu::VertexAttribute {
offset: size_of::<[f32; 12]>() as wgpu::BufferAddress,
shader_location: 8,
format: wgpu::VertexFormat::Float32x4,
},
- // normal
+
+ // normal_matrix_0
wgpu::VertexAttribute {
offset: size_of::<[f32; 16]>() as wgpu::BufferAddress,
shader_location: 9,
format: wgpu::VertexFormat::Float32x3,
},
+
+ // normal_matrix_1
wgpu::VertexAttribute {
offset: size_of::<[f32; 19]>() as wgpu::BufferAddress,
shader_location: 10,
format: wgpu::VertexFormat::Float32x3,
},
+
+ // normal_matrix_2
wgpu::VertexAttribute {
offset: size_of::<[f32; 22]>() as wgpu::BufferAddress,
shader_location: 11,
diff --git a/crates/dropbear-engine/src/lib.rs b/crates/dropbear-engine/src/lib.rs
index bc3694f..6e85de7 100644
--- a/crates/dropbear-engine/src/lib.rs
+++ b/crates/dropbear-engine/src/lib.rs
@@ -16,6 +16,7 @@ pub mod scene;
pub mod shader;
pub mod utils;
pub mod texture;
+pub mod pipelines;
pub static WGPU_BACKEND: OnceLock<String> = OnceLock::new();
pub const PHYSICS_STEP_RATE: u32 = 120;
@@ -39,7 +40,7 @@ use std::sync::OnceLock;
use std::{fs, sync::Arc, time::{Duration, Instant}};
use std::collections::HashMap;
use std::rc::Rc;
-use wgpu::{BindGroupLayout, Device, ExperimentalFeatures, Instance, Queue, Surface, SurfaceConfiguration, SurfaceError, TextureFormat};
+use wgpu::{BindGroupLayout, BindGroupLayoutEntry, BindingType, BufferBindingType, Device, ExperimentalFeatures, Instance, Queue, ShaderStages, Surface, SurfaceConfiguration, SurfaceError, TextureFormat};
use winit::event::{DeviceEvent, DeviceId};
use winit::{
application::ApplicationHandler,
@@ -51,7 +52,7 @@ use winit::{
use crate::camera::Camera;
use crate::graphics::{FrameGraphicsContext, SharedGraphicsContext};
-use crate::lighting::{Light, LightManager};
+use crate::lighting::{Light};
use crate::texture::Texture;
use crate::{egui_renderer::EguiRenderer};
@@ -63,6 +64,7 @@ use winit::window::{WindowAttributes, WindowId};
use crate::scene::Scene;
pub struct BindGroupLayouts {
+ pub shader_globals_bind_group_layout: BindGroupLayout,
pub texture_bind_layout: BindGroupLayout,
pub material_tint_bind_layout: BindGroupLayout,
pub camera_bind_group_layout: BindGroupLayout,
@@ -76,6 +78,7 @@ pub struct State {
// keep top for drop order
pub window: Arc<Window>,
pub instance: Arc<Instance>,
+ pub supports_storage: bool,
pub surface: Arc<Surface<'static>>,
pub surface_format: TextureFormat,
@@ -96,7 +99,7 @@ pub struct State {
}
impl State {
- /// As defined in `shader.wgsl` as
+ /// As defined in `shaders.wgsl` as
/// ```
/// @group(3) @binding(0)
/// var<uniform> u_material: MaterialUniform;
@@ -146,6 +149,14 @@ impl State {
})
.await?;
+ let supports_storage_resources = adapter
+ .get_downlevel_capabilities()
+ .flags
+ .contains(wgpu::DownlevelFlags::VERTEX_STORAGE)
+ && device.limits().max_storage_buffers_per_shader_stage > 0;
+
+ log::debug!("graphics device {} support storage resources", if !supports_storage_resources { "does not" } else { "does" });
+
if WGPU_BACKEND.get().is_none() {
let info = adapter.get_info();
let os_info = os_info::get();
@@ -218,12 +229,6 @@ Hardware:
let viewport_texture =
Texture::viewport(&config, &device, Some("viewport texture"));
- let 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(&Self::MATERIAL_BIND_GROUP_LAYOUT);
-
let mut egui_renderer = Arc::new(Mutex::new(EguiRenderer::new(
&device,
config.format,
@@ -238,11 +243,80 @@ Hardware:
.renderer()
.register_native_texture(&device, &viewport_texture.view, wgpu::FilterMode::Linear);
+ // shaders/shader.wgsl - @group(1)
let camera_bind_group_layout = device.create_bind_group_layout(&Camera::CAMERA_BIND_GROUP_LAYOUT);
+
+ // shader/light.wgsl - @group(1)
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);
+
+ // shaders/light.wgsl - @group(1)
let light_cube_bind_group_layout = device
- .create_bind_group_layout(&LightManager::LIGHT_CUBE_BIND_GROUP_LAYOUT);
+ .create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
+ entries: &[wgpu::BindGroupLayoutEntry {
+ // @binding(0)
+ 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("Per-Light Layout"),
+ });
+
+ // shaders/shader.wgsl - @group(0)
+ let texture_bind_group_layout =
+ device.create_bind_group_layout(&texture::TEXTURE_BIND_GROUP_LAYOUT);
+
+ // shaders/shader.wgsl - @group(2)
+ let light_array_bind_group_layout = device.create_bind_group_layout(
+ &wgpu::BindGroupLayoutDescriptor {
+ entries: &[
+ // @binding(0)
+ wgpu::BindGroupLayoutEntry {
+ binding: 0,
+ visibility: wgpu::ShaderStages::VERTEX | wgpu::ShaderStages::FRAGMENT,
+ ty: wgpu::BindingType::Buffer {
+ ty: if supports_storage_resources {
+ // s_light_array
+ wgpu::BufferBindingType::Storage { read_only: true }
+ } else {
+ // u_light_array
+ wgpu::BufferBindingType::Uniform
+ },
+ has_dynamic_offset: false,
+ min_binding_size: None,
+ },
+ count: None,
+ },
+ ],
+ label: Some("Light Array Layout"),
+ }
+ );
+
+ // shaders/shader.wgsl - @group(3)
+ let material_tint_bind_group_layout =
+ device.create_bind_group_layout(&Self::MATERIAL_BIND_GROUP_LAYOUT);
+
+ // shaders/shader.wgsl - @group(4)
+ let shader_globals_bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
+ label: Some("shader.wgsl globals bind group layout"),
+ entries: &[
+ BindGroupLayoutEntry {
+ // @binding(0)
+ binding: 0,
+ visibility: ShaderStages::VERTEX | ShaderStages::FRAGMENT,
+ ty: BindingType::Buffer {
+ ty: BufferBindingType::Uniform,
+ has_dynamic_offset: false,
+ min_binding_size: None,
+ },
+ count: None,
+ }
+ ],
+ });
let result = Self {
surface: Arc::new(surface),
@@ -261,6 +335,7 @@ Hardware:
physics_accumulator: Duration::ZERO,
scene_manager: scene::Manager::new(),
layouts: Arc::new(BindGroupLayouts {
+ shader_globals_bind_group_layout,
texture_bind_layout: texture_bind_group_layout,
material_tint_bind_layout: material_tint_bind_group_layout,
camera_bind_group_layout,
@@ -268,6 +343,7 @@ Hardware:
light_array_bind_group_layout,
light_cube_bind_group_layout,
}),
+ supports_storage: supports_storage_resources,
};
Ok(result)
diff --git a/crates/dropbear-engine/src/lighting.rs b/crates/dropbear-engine/src/lighting.rs
index faa7d07..33c7115 100644
--- a/crates/dropbear-engine/src/lighting.rs
+++ b/crates/dropbear-engine/src/lighting.rs
@@ -1,20 +1,19 @@
use crate::attenuation::{Attenuation, RANGE_50};
-use crate::buffer::{ResizableBuffer, StorageBuffer, UniformBuffer};
+use crate::buffer::{ResizableBuffer, UniformBuffer};
use crate::graphics::SharedGraphicsContext;
-use crate::shader::Shader;
-use crate::texture::Texture;
+use crate::pipelines::light_cube::InstanceInput;
use crate::{
- entity::{EntityTransform, Transform},
- model::{self, Model, Vertex},
+ entity::Transform,
+ model::Model,
};
use dropbear_macro::SerializableComponent;
use dropbear_traits::SerializableComponent;
-use glam::{DMat4, DQuat, DVec3};
+use glam::{DMat4, DVec3};
use std::fmt::{Display, Formatter};
use std::sync::Arc;
-use wgpu::{BindGroup, Buffer, BufferAddress, CompareFunction, DepthBiasState, RenderPipeline, StencilState, VertexBufferLayout};
+use wgpu::{BindGroup};
-pub const MAX_LIGHTS: usize = 8;
+pub const MAX_LIGHTS: usize = 10;
#[repr(C)]
#[derive(Debug, Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
@@ -245,12 +244,13 @@ pub struct Light {
pub label: String,
pub buffer: UniformBuffer<LightUniform>,
pub bind_group: BindGroup,
- pub instance_buffer: ResizableBuffer<InstanceRaw>,
+ pub instance_buffer: ResizableBuffer<InstanceInput>,
}
impl Light {
pub const LIGHT_BIND_GROUP_LAYOUT: wgpu::BindGroupLayoutDescriptor<'_> =
wgpu::BindGroupLayoutDescriptor {
+ // @binding(0)
entries: &[wgpu::BindGroupLayoutEntry {
binding: 0,
visibility: wgpu::ShaderStages::VERTEX.union(wgpu::ShaderStages::FRAGMENT),
@@ -313,11 +313,7 @@ impl Light {
label,
});
- let instance = Instance::new(
- transform.position,
- transform.rotation,
- DVec3::new(0.25, 0.25, 0.25),
- );
+ let instance: InstanceInput = DMat4::from_scale_rotation_translation(transform.scale, transform.rotation, transform.position).into();
let mut instance_buffer = ResizableBuffer::new(
&graphics.device,
@@ -326,7 +322,7 @@ impl Light {
"Light Instance Buffer"
);
- instance_buffer.write(&graphics.device, &graphics.queue, &[instance.to_raw()]);
+ instance_buffer.write(&graphics.device, &graphics.queue, &[instance]);
log::debug!("Created new light [{}]", label_str);
@@ -370,292 +366,4 @@ impl Light {
pub fn label(&self) -> &str {
&self.label
}
-}
-
-#[derive(Clone)]
-pub struct LightManager {
- pub pipeline: Option<RenderPipeline>,
- light_array_buffer: Option<StorageBuffer<LightArrayUniform>>,
- light_array_bind_group: Option<BindGroup>,
-}
-
-impl Default for LightManager {
- fn default() -> Self {
- Self::new()
- }
-}
-
-impl LightManager {
- pub const LIGHT_ARRAY_BIND_GROUP_LAYOUT: wgpu::BindGroupLayoutDescriptor<'_> =
- wgpu::BindGroupLayoutDescriptor {
- entries: &[
- // light data
- wgpu::BindGroupLayoutEntry {
- binding: 0,
- visibility: wgpu::ShaderStages::VERTEX.union(wgpu::ShaderStages::FRAGMENT),
- ty: wgpu::BindingType::Buffer {
- ty: wgpu::BufferBindingType::Storage { read_only: true },
- has_dynamic_offset: false,
- min_binding_size: None,
- },
- count: None,
- },
- ],
- 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,
- },
- 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 buffer = StorageBuffer::new(&graphics.device, "Light Array");
-
- let bind_group = graphics
- .device
- .create_bind_group(&wgpu::BindGroupDescriptor {
- layout: &graphics.layouts.light_array_bind_group_layout,
- entries: &[
- wgpu::BindGroupEntry {
- binding: 0,
- resource: buffer.buffer().as_entire_binding(),
- },
- ],
- label: Some("Light Array Bind Group"),
- });
-
- self.light_array_buffer = Some(buffer);
- self.light_array_bind_group = Some(bind_group);
-
- log::debug!("Created light array resources");
- }
-
- pub fn update(&mut self, graphics: Arc<SharedGraphicsContext>, world: &hecs::World) {
- let mut light_array = LightArrayUniform::default();
- let mut light_index = 0;
-
- for (light_component, s_trans, e_trans, light) in world
- .query::<(&LightComponent, Option<&Transform>, Option<&EntityTransform>, &mut Light)>()
- .iter()
- {
- let instance = if let Some(transform) = e_trans {
- let sync_transform = transform.sync();
- Instance::from_matrix(sync_transform.matrix())
- } else if let Some(transform) = s_trans {
- Instance::from_matrix(transform.matrix())
- } else {
- panic!("Unable to locate either a \"Transform\" or an \"EntityTransform\" component for the light {}", light.label);
- };
-
- light.instance_buffer.write(&graphics.device, &graphics.queue, &[instance.to_raw()]);
-
- if light_component.enabled && light_index < MAX_LIGHTS {
- let uniform = *light.uniform();
-
- light.buffer.write(&graphics.queue, &uniform);
-
- light_array.lights[light_index] = uniform;
- light_index += 1;
- }
- }
-
- light_array.light_count = light_index as u32;
- 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 bind_group(&self) -> &BindGroup {
- self.light_array_bind_group.as_ref().unwrap()
- }
-
- pub fn create_render_pipeline(
- &mut self,
- graphics: Arc<SharedGraphicsContext>,
- shader_contents: &str,
- label: Option<&str>,
- ) {
- use crate::shader::Shader;
-
- let shader = Shader::new(graphics.clone(), shader_contents, label);
-
- // the light cube rendering
- let pipeline = Self::create_render_pipeline_for_lighting(
- graphics,
- &shader,
- label,
- );
-
- self.pipeline = Some(pipeline);
- log::debug!("Created ECS light render pipeline");
- }
-
- // light cube rendering
- fn create_render_pipeline_for_lighting(
- graphics: Arc<SharedGraphicsContext>,
- shader: &Shader,
- label: Option<&str>,
- ) -> RenderPipeline {
- let render_pipeline_layout =
- graphics
- .device
- .create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
- label: Some(label.unwrap_or("Light Render Pipeline Descriptor")),
- bind_group_layouts: &[&graphics.layouts.camera_bind_group_layout, &graphics.layouts.light_cube_bind_group_layout],
- push_constant_ranges: &[],
- });
-
- graphics
- .device
- .create_render_pipeline(&wgpu::RenderPipelineDescriptor {
- label: Some("Render Pipeline"),
- layout: Some(&render_pipeline_layout),
- vertex: wgpu::VertexState {
- module: &shader.module,
- entry_point: Some("vs_main"),
- buffers: &[model::ModelVertex::desc(), InstanceRaw::desc()],
- compilation_options: Default::default(),
- },
- fragment: Some(wgpu::FragmentState {
- module: &shader.module,
- entry_point: Some("fs_main"),
- targets: &[Some(wgpu::ColorTargetState {
- format: Texture::TEXTURE_FORMAT,
- blend: Some(wgpu::BlendState {
- alpha: wgpu::BlendComponent::REPLACE,
- color: wgpu::BlendComponent::REPLACE,
- }),
- write_mask: wgpu::ColorWrites::ALL,
- })],
- compilation_options: Default::default(),
- }),
- primitive: wgpu::PrimitiveState {
- topology: wgpu::PrimitiveTopology::TriangleList,
- strip_index_format: None,
- front_face: wgpu::FrontFace::Ccw,
- cull_mode: Some(wgpu::Face::Back),
- // Setting this to anything other than Fill requires Features::NON_FILL_POLYGON_MODE
- polygon_mode: wgpu::PolygonMode::Fill,
- // Requires Features::DEPTH_CLIP_CONTROL
- unclipped_depth: false,
- // Requires Features::CONSERVATIVE_RASTERIZATION
- conservative: false,
- },
- depth_stencil: Some(wgpu::DepthStencilState {
- format: crate::Texture::DEPTH_FORMAT,
- depth_write_enabled: true,
- depth_compare: CompareFunction::Greater,
- stencil: StencilState::default(),
- bias: DepthBiasState::default(),
- }),
- multisample: wgpu::MultisampleState {
- count: 1,
- mask: !0,
- alpha_to_coverage_enabled: false,
- },
- multiview: None,
- cache: None,
- })
- }
-}
-
-#[derive(Default)]
-pub struct Instance {
- pub position: DVec3,
- pub rotation: DQuat,
- pub scale: DVec3,
-
- buffer: Option<Buffer>,
-}
-
-impl Instance {
- pub fn new(position: DVec3, rotation: DQuat, scale: DVec3) -> Self {
- Self {
- position,
- rotation,
- scale,
- buffer: None,
- }
- }
-
- pub fn to_raw(&self) -> InstanceRaw {
- let model_matrix =
- DMat4::from_scale_rotation_translation(self.scale, self.rotation, self.position);
- InstanceRaw {
- model: model_matrix.as_mat4().to_cols_array_2d(),
- }
- }
-
- 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,
- }
- }
-}
-
-#[repr(C)]
-#[derive(Copy, Clone, Default, bytemuck::Pod, bytemuck::Zeroable)]
-pub struct InstanceRaw {
- model: [[f32; 4]; 4],
-}
-
-impl InstanceRaw {
- fn desc() -> VertexBufferLayout<'static> {
- VertexBufferLayout {
- array_stride: size_of::<InstanceRaw>() as BufferAddress,
- step_mode: wgpu::VertexStepMode::Instance,
- attributes: &[
- // model
- wgpu::VertexAttribute {
- offset: 0,
- shader_location: 5,
- format: wgpu::VertexFormat::Float32x4,
- },
- wgpu::VertexAttribute {
- offset: size_of::<[f32; 4]>() as wgpu::BufferAddress,
- shader_location: 6,
- format: wgpu::VertexFormat::Float32x4,
- },
- wgpu::VertexAttribute {
- offset: size_of::<[f32; 8]>() as wgpu::BufferAddress,
- shader_location: 7,
- format: wgpu::VertexFormat::Float32x4,
- },
- wgpu::VertexAttribute {
- offset: size_of::<[f32; 12]>() as wgpu::BufferAddress,
- shader_location: 8,
- format: wgpu::VertexFormat::Float32x4,
- },
- ],
- }
- }
-}
+}
\ No newline at end of file
diff --git a/crates/dropbear-engine/src/model.rs b/crates/dropbear-engine/src/model.rs
index 6966208..56d03f5 100644
--- a/crates/dropbear-engine/src/model.rs
+++ b/crates/dropbear-engine/src/model.rs
@@ -537,9 +537,20 @@ impl Model {
.map(|(material_name, diffuse_bytes, normal_bytes, tint)| {
let material_start = Instant::now();
- let processed_diffuse = diffuse_bytes.as_ref().map(|bytes| {
+ let processed_diffuse = diffuse_bytes.as_ref().and_then(|bytes| {
let load_start = Instant::now();
- let image = image::load_from_memory(bytes).unwrap();
+ let image = match image::load_from_memory(bytes) {
+ Ok(image) => image,
+ Err(err) => {
+ log::warn!(
+ "Failed to decode diffuse texture for material '{}': {} ({} bytes)",
+ material_name,
+ err,
+ bytes.len()
+ );
+ return None;
+ }
+ };
log::trace!("Loading diffuse image to memory: {:?}", load_start.elapsed());
let rgba_start = Instant::now();
@@ -550,12 +561,23 @@ impl Model {
);
let dimensions = image.dimensions();
- (rgba.into_raw(), dimensions)
+ Some((rgba.into_raw(), dimensions))
});
- let processed_normal = normal_bytes.as_ref().map(|bytes| {
+ let processed_normal = normal_bytes.as_ref().and_then(|bytes| {
let load_start = Instant::now();
- let image = image::load_from_memory(bytes).unwrap();
+ let image = match image::load_from_memory(bytes) {
+ Ok(image) => image,
+ Err(err) => {
+ log::warn!(
+ "Failed to decode normal texture for material '{}': {} ({} bytes)",
+ material_name,
+ err,
+ bytes.len()
+ );
+ return None;
+ }
+ };
log::trace!("Loading normal image to memory: {:?}", load_start.elapsed());
let rgba_start = Instant::now();
@@ -566,7 +588,7 @@ impl Model {
);
let dimensions = image.dimensions();
- (rgba.into_raw(), dimensions)
+ Some((rgba.into_raw(), dimensions))
});
log::trace!(
@@ -1091,6 +1113,16 @@ pub trait Vertex {
fn desc() -> VertexBufferLayout<'static>;
}
+/// Maps to
+/// ```wgsl
+/// 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>,
+/// };
+/// ```
#[repr(C)]
#[derive(Copy, Clone, Debug, bytemuck::Pod, bytemuck::Zeroable, serde::Serialize, serde::Deserialize)]
pub struct ModelVertex {
diff --git a/crates/dropbear-engine/src/pipelines/globals.rs b/crates/dropbear-engine/src/pipelines/globals.rs
new file mode 100644
index 0000000..6d63b61
--- /dev/null
+++ b/crates/dropbear-engine/src/pipelines/globals.rs
@@ -0,0 +1,72 @@
+use std::sync::Arc;
+
+use crate::buffer::UniformBuffer;
+use crate::graphics::SharedGraphicsContext;
+
+/// Mirrors `Globals` in `pipelines/shaders/shader.wgsl` (`@group(4) @binding(0)`).
+///
+/// Note: the `_padding` ensures the uniform is 16 bytes, which matches WGSL
+/// uniform layout expectations.
+#[repr(C)]
+#[derive(Debug, Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
+pub struct Globals {
+ pub num_lights: u32,
+ pub ambient_strength: f32,
+ pub _padding: [u32; 2],
+}
+
+impl Default for Globals {
+ fn default() -> Self {
+ Self {
+ num_lights: 0,
+ ambient_strength: 0.1,
+ _padding: [0; 2],
+ }
+ }
+}
+
+/// Owns the globals uniform buffer and its bind group.
+#[derive(Debug, Clone)]
+pub struct GlobalsUniform {
+ pub data: Globals,
+ pub buffer: UniformBuffer<Globals>,
+ pub bind_group: wgpu::BindGroup,
+}
+
+impl GlobalsUniform {
+ pub fn new(graphics: Arc<SharedGraphicsContext>, label: Option<&str>) -> Self {
+ let label = label.unwrap_or("shader globals");
+
+ let buffer = UniformBuffer::new(&graphics.device, label);
+
+ let bind_group = graphics.device.create_bind_group(&wgpu::BindGroupDescriptor {
+ layout: &graphics.layouts.shader_globals_bind_group_layout,
+ entries: &[wgpu::BindGroupEntry {
+ binding: 0,
+ resource: buffer.buffer().as_entire_binding(),
+ }],
+ label: Some(label),
+ });
+
+ let data = Globals::default();
+ buffer.write(&graphics.queue, &data);
+
+ Self {
+ data,
+ buffer,
+ bind_group,
+ }
+ }
+
+ pub fn write(&mut self, queue: &wgpu::Queue) {
+ self.buffer.write(queue, &self.data);
+ }
+
+ pub fn set_num_lights(&mut self, num_lights: u32) {
+ self.data.num_lights = num_lights;
+ }
+
+ pub fn set_ambient_strength(&mut self, ambient_strength: f32) {
+ self.data.ambient_strength = ambient_strength;
+ }
+}
diff --git a/crates/dropbear-engine/src/pipelines/light_cube.rs b/crates/dropbear-engine/src/pipelines/light_cube.rs
new file mode 100644
index 0000000..8caccc2
--- /dev/null
+++ b/crates/dropbear-engine/src/pipelines/light_cube.rs
@@ -0,0 +1,292 @@
+use std::sync::Arc;
+use std::mem::size_of;
+use glam::DMat4;
+use wgpu::{BufferAddress, VertexAttribute, VertexFormat};
+use crate::buffer::{StorageBuffer, UniformBuffer};
+use crate::entity::{EntityTransform, Transform};
+use crate::graphics::SharedGraphicsContext;
+use crate::lighting::{Light, LightArrayUniform, LightComponent, MAX_LIGHTS};
+use crate::model::Vertex;
+use crate::pipelines::DropbearShaderPipeline;
+use crate::shader::Shader;
+use crate::texture::Texture;
+
+pub struct LightCubePipeline {
+ shader: Shader,
+ pipeline_layout: wgpu::PipelineLayout,
+ pipeline: wgpu::RenderPipeline,
+ storage_buffer: Option<StorageBuffer<LightArrayUniform>>,
+ uniform_buffer: Option<UniformBuffer<LightArrayUniform>>,
+ /// Bind group, defined in `shaders/shader.wgsl` as @group(2).
+ ///
+ /// This can either be a storage buffer or a uniform buffer depending on if the backend
+ /// supports storage resources.
+ light_bind_group: wgpu::BindGroup,
+}
+
+impl DropbearShaderPipeline for LightCubePipeline {
+ fn new(graphics: Arc<SharedGraphicsContext>) -> Self {
+ let shader = Shader::new(
+ graphics.clone(),
+ include_str!("shaders/light.wgsl"),
+ Some("light cube shader"),
+ );
+
+ let pipeline_layout = graphics.device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
+ label: Some("light cube pipeline layout"),
+ bind_group_layouts: &[
+ &graphics.layouts.camera_bind_group_layout,
+ &graphics.layouts.light_cube_bind_group_layout,
+ ],
+ push_constant_ranges: &[],
+ });
+
+ let pipeline = graphics.device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
+ label: Some("light cube pipeline"),
+ layout: Some(&pipeline_layout),
+ vertex: wgpu::VertexState {
+ module: &shader,
+ entry_point: Some("vs_main"),
+ compilation_options: Default::default(),
+ buffers: &[
+ // model
+ VertexInput::desc(),
+ // instance
+ InstanceInput::desc(),
+ ],
+ },
+ fragment: Some(wgpu::FragmentState {
+ module: &shader.module,
+ entry_point: Some("fs_main"),
+ targets: &[Some(wgpu::ColorTargetState {
+ format: Texture::TEXTURE_FORMAT,
+ blend: Some(wgpu::BlendState {
+ alpha: wgpu::BlendComponent::REPLACE,
+ color: wgpu::BlendComponent::REPLACE,
+ }),
+ write_mask: wgpu::ColorWrites::ALL,
+ })],
+ compilation_options: Default::default(),
+ }),
+ primitive: wgpu::PrimitiveState {
+ topology: wgpu::PrimitiveTopology::TriangleList,
+ strip_index_format: None,
+ front_face: wgpu::FrontFace::Ccw,
+ cull_mode: Some(wgpu::Face::Back),
+ polygon_mode: wgpu::PolygonMode::Fill,
+ unclipped_depth: false,
+ conservative: false,
+ },
+ depth_stencil: Some(wgpu::DepthStencilState {
+ format: crate::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,
+ });
+
+ let mut storage_buffer = None;
+ let mut uniform_buffer = None;
+
+ if graphics.supports_storage {
+ storage_buffer = Some(StorageBuffer::new(
+ &graphics.device,
+ "light cube pipeline storage buffer",
+ ));
+ } else {
+ uniform_buffer = Some(UniformBuffer::new(
+ &graphics.device,
+ "light cube pipeline uniform buffer",
+ ));
+ }
+
+ let light_buffer: &wgpu::Buffer = if let Some(buf) = &storage_buffer {
+ buf.buffer()
+ } else if let Some(buf) = &uniform_buffer {
+ buf.buffer()
+ } else {
+ panic!("Either a storage buffer or a uniform buffer should have been created");
+ };
+
+ let light_bind_group = graphics.device.create_bind_group(&wgpu::BindGroupDescriptor {
+ layout: &graphics.layouts.light_array_bind_group_layout,
+ entries: &[wgpu::BindGroupEntry {
+ binding: 0,
+ resource: light_buffer.as_entire_binding(),
+ }],
+ label: Some("light array bind group"),
+ });
+
+ Self {
+ shader,
+ pipeline_layout,
+ pipeline,
+ storage_buffer,
+ uniform_buffer,
+ light_bind_group,
+ }
+ }
+
+ fn shader(&self) -> &Shader {
+ &self.shader
+ }
+
+ fn pipeline_layout(&self) -> &wgpu::PipelineLayout {
+ &self.pipeline_layout
+ }
+
+ fn pipeline(&self) -> &wgpu::RenderPipeline {
+ &self.pipeline
+ }
+}
+
+impl LightCubePipeline {
+ pub fn bind_group(&self) -> &wgpu::BindGroup {
+ &self.light_bind_group
+ }
+
+ pub fn update(&mut self, graphics: Arc<SharedGraphicsContext>, world: &hecs::World) {
+ debug_assert!(
+ self.storage_buffer.is_some() ^ self.uniform_buffer.is_some(),
+ "Expected exactly one of storage_buffer/uniform_buffer to exist"
+ );
+
+ let mut light_array = LightArrayUniform::default();
+
+ let mut light_index: usize = 0;
+
+ for (light_component, s_trans, e_trans, light) in world
+ .query::<(&LightComponent, Option<&Transform>, Option<&EntityTransform>, &mut Light)>()
+ .iter()
+ {
+ let instance: InstanceInput = if let Some(transform) = e_trans {
+ let sync_transform = transform.sync();
+ sync_transform.matrix().into()
+ } else if let Some(transform) = s_trans {
+ transform.matrix().into()
+ } else {
+ panic!("Unable to locate either a \"Transform\" or an \"EntityTransform\" component for the light {}", light.label);
+ };
+
+ light.instance_buffer.write(&graphics.device, &graphics.queue, &[instance]);
+
+ if light_component.enabled && light_index < MAX_LIGHTS {
+ let uniform = *light.uniform();
+
+ light.buffer.write(&graphics.queue, &uniform);
+
+ light_array.lights[light_index] = uniform;
+ light_index += 1;
+ }
+ }
+
+ light_array.light_count = light_index as u32;
+
+ if let Some(buf) = &self.storage_buffer {
+ buf.write(&graphics.queue, &light_array);
+ } else if let Some(buf) = &self.uniform_buffer {
+ buf.write(&graphics.queue, &light_array);
+ } else {
+ panic!("Either a storage buffer or a uniform buffer should have been created");
+ }
+ }
+
+ pub fn buffer(&self) -> &wgpu::Buffer {
+ if let Some(s) = &self.storage_buffer {
+ s.buffer()
+ } else if let Some(u) = &self.uniform_buffer {
+ u.buffer()
+ } else {
+ panic!("Either a storage buffer or a uniform buffer should have been created");
+ }
+ }
+}
+
+#[repr(C)]
+#[derive(Debug, Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
+pub struct VertexInput {
+ position: [f32; 3],
+}
+
+impl Vertex for VertexInput {
+ fn desc() -> wgpu::VertexBufferLayout<'static> {
+ wgpu::VertexBufferLayout {
+ array_stride: size_of::<VertexInput>() as BufferAddress,
+ step_mode: wgpu::VertexStepMode::Vertex,
+ attributes: &[
+ // position
+ VertexAttribute {
+ format: VertexFormat::Float32x3,
+ offset: 0,
+ shader_location: 0,
+ }
+ ],
+ }
+ }
+}
+
+/// As mapped in `shaders/light.wgsl` as
+/// ```wgsl
+/// 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>,
+/// }
+/// ```
+#[repr(C)]
+#[derive(Debug, Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
+pub struct InstanceInput {
+ pub model_matrix: [[f32; 4]; 4],
+}
+
+impl Vertex for InstanceInput {
+ fn desc() -> wgpu::VertexBufferLayout<'static> {
+ wgpu::VertexBufferLayout {
+ array_stride: size_of::<InstanceInput>() as BufferAddress,
+ step_mode: wgpu::VertexStepMode::Instance,
+ attributes: &[
+ // model_matrix_0
+ wgpu::VertexAttribute {
+ offset: 0,
+ shader_location: 5,
+ format: wgpu::VertexFormat::Float32x4,
+ },
+ // model_matrix_1
+ wgpu::VertexAttribute {
+ offset: size_of::<[f32; 4]>() as wgpu::BufferAddress,
+ shader_location: 6,
+ format: wgpu::VertexFormat::Float32x4,
+ },
+ // model_matrix_2
+ wgpu::VertexAttribute {
+ offset: size_of::<[f32; 8]>() as wgpu::BufferAddress,
+ shader_location: 7,
+ format: wgpu::VertexFormat::Float32x4,
+ },
+ // model_matrix_3
+ wgpu::VertexAttribute {
+ offset: size_of::<[f32; 12]>() as wgpu::BufferAddress,
+ shader_location: 8,
+ format: wgpu::VertexFormat::Float32x4,
+ },
+ ],
+ }
+ }
+}
+
+impl Into<InstanceInput> for DMat4 {
+ fn into(self) -> InstanceInput {
+ InstanceInput {
+ model_matrix: self.as_mat4().to_cols_array_2d(),
+ }
+ }
+}
\ No newline at end of file
diff --git a/crates/dropbear-engine/src/pipelines/mod.rs b/crates/dropbear-engine/src/pipelines/mod.rs
new file mode 100644
index 0000000..f151c40
--- /dev/null
+++ b/crates/dropbear-engine/src/pipelines/mod.rs
@@ -0,0 +1,23 @@
+use std::sync::Arc;
+use crate::graphics::SharedGraphicsContext;
+use crate::shader::Shader;
+
+pub mod shader;
+pub mod light_cube;
+pub mod globals;
+
+pub use globals::{Globals, GlobalsUniform};
+
+/// A helper in defining a pipelines required information, as well as getters.
+///
+/// This contains the bare minimum for any pipeline.
+pub trait DropbearShaderPipeline {
+ /// Creates a new instance of a pipeline.
+ fn new(graphics: Arc<SharedGraphicsContext>) -> Self;
+ /// Fetches the shader property
+ fn shader(&self) -> &Shader;
+ /// Fetches the pipeline layout
+ fn pipeline_layout(&self) -> &wgpu::PipelineLayout;
+ /// Fetches the pipeline
+ fn pipeline(&self) -> &wgpu::RenderPipeline;
+}
\ No newline at end of file
diff --git a/crates/dropbear-engine/src/pipelines/shader.rs b/crates/dropbear-engine/src/pipelines/shader.rs
new file mode 100644
index 0000000..eaa8a1d
--- /dev/null
+++ b/crates/dropbear-engine/src/pipelines/shader.rs
@@ -0,0 +1,111 @@
+use std::sync::Arc;
+use wgpu::{CompareFunction, DepthBiasState, StencilState};
+use crate::graphics::{InstanceRaw, SharedGraphicsContext};
+use crate::model;
+use crate::model::Vertex;
+use crate::pipelines::DropbearShaderPipeline;
+use crate::shader::Shader;
+use crate::texture::Texture;
+
+/// As defined in `shaders/shader.wgsl`
+pub struct MainRenderPipeline {
+ shader: Shader,
+ pipeline_layout: wgpu::PipelineLayout,
+ pipeline: wgpu::RenderPipeline,
+}
+
+impl DropbearShaderPipeline for MainRenderPipeline {
+ fn new(graphics: Arc<SharedGraphicsContext>) -> Self {
+ let shader = Shader::new(
+ graphics.clone(),
+ include_str!("shaders/shader.wgsl"),
+ Some("viewport shaders"),
+ );
+
+ let bind_group_layouts = vec![
+ &graphics.layouts.texture_bind_layout, // @group(0)
+ &graphics.layouts.camera_bind_group_layout, // @group(1)
+ &graphics.layouts.light_array_bind_group_layout, // @group(2)
+ &graphics.layouts.material_tint_bind_layout, // @group(3)
+ &graphics.layouts.shader_globals_bind_group_layout, // @group(4)
+ ];
+
+ let pipeline_layout =
+ graphics.device
+ .create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
+ label: Some("main render pipeline layout"),
+ bind_group_layouts: bind_group_layouts.as_slice(),
+ push_constant_ranges: &[],
+ });
+
+ let pipeline =
+ graphics.device
+ .create_render_pipeline(&wgpu::RenderPipelineDescriptor {
+ label: Some("main render pipeline"),
+ layout: Some(&pipeline_layout),
+ vertex: wgpu::VertexState {
+ module: &shader.module,
+ entry_point: Some("vs_main"),
+ buffers: &[model::ModelVertex::desc(), InstanceRaw::desc()],
+ compilation_options: wgpu::PipelineCompilationOptions::default(),
+ },
+ fragment: Some(wgpu::FragmentState {
+ module: &shader.module,
+ entry_point: if graphics.supports_storage {
+ Some("s_fs_main")
+ } else {
+ Some("u_fs_main")
+ },
+ targets: &[Some(wgpu::ColorTargetState {
+ format: Texture::TEXTURE_FORMAT,
+ 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::Back),
+ 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: CompareFunction::Greater,
+ stencil: StencilState::default(),
+ bias: DepthBiasState::default(),
+ }),
+ multisample: wgpu::MultisampleState {
+ count: 1,
+ mask: !0,
+ alpha_to_coverage_enabled: false,
+ },
+ multiview: None,
+ cache: None,
+ });
+
+ log::debug!("Created main render pipeline");
+
+ Self {
+ shader,
+ pipeline_layout,
+ pipeline,
+ }
+ }
+
+ fn shader(&self) -> &Shader {
+ &self.shader
+ }
+
+ fn pipeline_layout(&self) -> &wgpu::PipelineLayout {
+ &self.pipeline_layout
+ }
+
+ fn pipeline(&self) -> &wgpu::RenderPipeline {
+ &self.pipeline
+ }
+}
diff --git a/crates/dropbear-engine/src/pipelines/shaders/light.wgsl b/crates/dropbear-engine/src/pipelines/shaders/light.wgsl
new file mode 100644
index 0000000..d31ebc8
--- /dev/null
+++ b/crates/dropbear-engine/src/pipelines/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 shaders
+
+@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-engine/src/pipelines/shaders/shader.wgsl b/crates/dropbear-engine/src/pipelines/shaders/shader.wgsl
new file mode 100644
index 0000000..dcf0012
--- /dev/null
+++ b/crates/dropbear-engine/src/pipelines/shaders/shader.wgsl
@@ -0,0 +1,290 @@
+// Main shaders for standard objects.
+
+struct Globals {
+ num_lights: u32,
+ ambient_strength: f32,
+}
+
+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 MaterialUniform {
+ // for stuff like tinting
+ colour: vec4<f32>,
+
+ // scales incoming UVs before sampling
+ uv_tiling: vec2<f32>,
+}
+
+const c_max_lights: u32 = 10u;
+
+@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(1) @binding(0)
+var<uniform> u_camera: CameraUniform;
+
+@group(2) @binding(0)
+var<storage, read> s_light_array: array<Light>;
+@group(2) @binding(0)
+var<uniform> u_light_array: array<Light, 10>; // when storage is not available
+
+@group(3) @binding(0)
+var<uniform> u_material: MaterialUniform;
+
+@group(4) @binding(0)
+var<uniform> u_globals: Globals;
+
+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 = u_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 * u_globals.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 * u_globals.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 * u_globals.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);
+}
+
+// when storage is supported
+@fragment
+fn s_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(u_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 = 0u; i < min(u_globals.num_lights, c_max_lights); i += 1u) {
+ let light = s_light_array[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);
+}
+
+// when storage is NOT supported
+@fragment
+fn u_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(u_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 = 0u; i < min(u_globals.num_lights, c_max_lights); i += 1u) {
+ let light = u_light_array[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-engine/src/shader.rs b/crates/dropbear-engine/src/shader.rs
index 17636a1..772572f 100644
--- a/crates/dropbear-engine/src/shader.rs
+++ b/crates/dropbear-engine/src/shader.rs
@@ -1,14 +1,26 @@
-//! Deals with shaders, including WESL shaders
+//! Deals with shaders, primarily around the [Shader] struct.
use std::ops::Deref;
use crate::graphics::SharedGraphicsContext;
use std::sync::Arc;
use wgpu::ShaderModule;
-/// A nice little struct that stored basic information about a WGPU shader.
+/// A nice little struct that stored basic information about a WGPU shaders.
pub struct Shader {
+ /// The label of the shader.
+ ///
+ /// If it is not set in [Shader::new], the default is "shader".
pub label: String,
+
+ /// The compiled content of the WGSL shader.
+ ///
+ /// When [Shader] is dereferenced (such as that in `&shader`), it will automatically reference
+ /// this module.
pub module: ShaderModule,
+
+ /// The content of the shader as a readable string content, in the case you need to look
+ /// at the original source.
+ pub content: String,
}
impl Deref for Shader {
@@ -33,7 +45,7 @@ impl Shader {
source: wgpu::ShaderSource::Wgsl(shader_file_contents.into()),
});
- log::debug!("Created new shader under the label: {:?}", label);
+ log::debug!("Created new shaders under the label: {:?}", label);
Self {
label: match label {
@@ -41,6 +53,7 @@ impl Shader {
None => "shader".into(),
},
module,
+ content: shader_file_contents.to_string(),
}
}
}
diff --git a/crates/dropbear-engine/src/texture.rs b/crates/dropbear-engine/src/texture.rs
index fdb663e..0bb37b7 100644
--- a/crates/dropbear-engine/src/texture.rs
+++ b/crates/dropbear-engine/src/texture.rs
@@ -1,12 +1,12 @@
use std::{fs, path::PathBuf, sync::Arc};
-use image::{EncodableLayout, GenericImageView};
+use image::GenericImageView;
use serde::{Deserialize, Serialize};
use crate::graphics::SharedGraphicsContext;
-/// As defined in `shader.wgsl` as
+/// As defined in `shaders.wgsl` as
/// ```
/// @group(0) @binding(0)
/// var t_diffuse: texture_2d<f32>;
@@ -193,10 +193,39 @@ impl Texture {
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 (diffuse_rgba, dimensions) = match image::load_from_memory(bytes) {
+ Ok(image) => {
+ let rgba = image.to_rgba8().into_raw();
+ let dims = dimensions.unwrap_or_else(|| image.dimensions());
+ (rgba, dims)
+ }
+ Err(err) => {
+ if let Some(dims) = dimensions {
+ let expected_len = (dims.0 as usize)
+ .saturating_mul(dims.1 as usize)
+ .saturating_mul(4);
+ if bytes.len() == expected_len {
+ (bytes.to_vec(), dims)
+ } else {
+ log::error!(
+ "Texture decode failed ({:?}); expected {} bytes for raw RGBA ({}x{}), got {}. Falling back.",
+ err,
+ expected_len,
+ dims.0,
+ dims.1,
+ bytes.len()
+ );
+ (vec![255, 0, 255, 255], (1, 1))
+ }
+ } else {
+ log::error!(
+ "Texture decode failed ({:?}) and no dimensions were provided; falling back to 1x1 magenta.",
+ err
+ );
+ (vec![255, 0, 255, 255], (1, 1))
+ }
+ }
+ };
let size = wgpu::Extent3d {
width: dimensions.0,
height: dimensions.1,
@@ -219,7 +248,6 @@ impl Texture {
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);
@@ -239,34 +267,34 @@ impl Texture {
},
size,
);
- }
+ } else {
+ 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[src_start..src_start + src_stride]);
+ }
- 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,
+ );
}
- 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 {
diff --git a/crates/euca-runner/Cargo.toml b/crates/euca-runner/Cargo.toml
new file mode 100644
index 0000000..5cbba10
--- /dev/null
+++ b/crates/euca-runner/Cargo.toml
@@ -0,0 +1,31 @@
+[package]
+name = "euca-runner"
+version.workspace = true
+edition.workspace = true
+license.workspace = true
+repository.workspace = true
+readme = "README.md"
+
+[dependencies]
+eucalyptus-core = { path = "../eucalyptus-core" }
+redback-runtime = { path = "../redback-runtime", default-features = false }
+dropbear-engine = { path = "../dropbear-engine" }
+
+tokio.workspace = true
+anyhow.workspace = true
+postcard.workspace = true
+log.workspace = true
+parking_lot.workspace = true
+app_dirs2.workspace = true
+chrono.workspace = true
+env_logger.workspace = true
+colored.workspace = true
+ron.workspace = true
+hecs.workspace = true
+bytemuck.workspace = true
+log-once.workspace = true
+winit.workspace = true
+serde.workspace = true
+crossbeam-channel.workspace = true
+gilrs.workspace = true
+futures.workspace = true
diff --git a/crates/euca-runner/README.md b/crates/euca-runner/README.md
new file mode 100644
index 0000000..695914d
--- /dev/null
+++ b/crates/euca-runner/README.md
@@ -0,0 +1,5 @@
+# euca-runner
+
+Runs any app made with the eucalyptus editor.
+
+Bascially redback-runtime without the debug feature.
\ No newline at end of file
diff --git a/crates/euca-runner/src/main.rs b/crates/euca-runner/src/main.rs
new file mode 100644
index 0000000..b3bc66c
--- /dev/null
+++ b/crates/euca-runner/src/main.rs
@@ -0,0 +1,121 @@
+use app_dirs2::AppInfo;
+use dropbear_engine::future::FutureQueue;
+use eucalyptus_core::runtime::RuntimeProjectConfig;
+use parking_lot::RwLock;
+use redback_runtime::PlayMode;
+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() {
+ env_logger::init();
+
+ 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 = postcard::from_bytes(&scene_config).unwrap();
+
+ log::debug!("Converted scene config file to RuntimeProjectConfig");
+
+ let runtime_scene = Rc::new(RwLock::new(PlayMode::new(None).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)]
+pub struct ConfigFile {
+ pub jvm_args: Option<String>,
+ pub max_fps: u32,
+ pub target_resolution: WindowModes
+}
+
+#[derive(Debug, Clone, Deserialize, Serialize)]
+pub enum WindowModes {
+ Windowed(u32, u32),
+ Maximised,
+ Fullscreen,
+}
\ No newline at end of file
diff --git a/crates/eucalyptus-core/src/config.rs b/crates/eucalyptus-core/src/config.rs
index e743cae..631d5c1 100644
--- a/crates/eucalyptus-core/src/config.rs
+++ b/crates/eucalyptus-core/src/config.rs
@@ -101,9 +101,10 @@ impl ProjectConfig {
/// # Parameters
/// * path - The root config **file** for the project
pub fn read_from(path: impl AsRef<Path>) -> anyhow::Result<Self> {
- let ron_str = fs::read_to_string(path.as_ref())?;
+ let path = path.as_ref().to_path_buf();
+ let ron_str = fs::read_to_string(&path)?;
let mut config: ProjectConfig = ron::de::from_str(ron_str.as_str())?;
- config.project_path = path.as_ref().parent().unwrap().to_path_buf();
+ config.project_path = path.parent().unwrap().to_path_buf();
log::info!("Loaded project!");
log::debug!("Loaded config info");
log::debug!("Updating with new content");
@@ -129,7 +130,7 @@ impl ProjectConfig {
if io_err.kind() == std::io::ErrorKind::NotFound {
log::warn!("resources.eucc not found, creating default.");
let default = ResourceConfig {
- path: project_root.join("/resources"),
+ path: project_root.join("resources"),
nodes: vec![],
};
log::debug!("Writing to {}", default.path.display());
@@ -156,7 +157,7 @@ impl ProjectConfig {
if io_err.kind() == std::io::ErrorKind::NotFound {
log::warn!("source.eucc not found, creating default.");
let default = SourceConfig {
- path: project_root.join("../../../src"),
+ path: project_root.join("src"),
nodes: vec![],
};
default.write_to(&project_root)?;
@@ -343,14 +344,14 @@ impl ResourceConfig {
/// # Parameters
/// - path: The root **folder** of the project
pub fn write_to(&self, path: impl AsRef<Path>) -> anyhow::Result<()> {
- let resource_dir = path.as_ref().join("../../../resources");
+ let resource_dir = path.as_ref().join("resources");
let updated_config = ResourceConfig {
path: resource_dir.clone(),
nodes: collect_nodes(&resource_dir, path.as_ref(), vec!["thumbnails"].as_slice()),
};
let ron_str = ron::ser::to_string_pretty(&updated_config, PrettyConfig::default())
.map_err(|e| anyhow::anyhow!("RON serialization error: {}", e))?;
- let config_path = path.as_ref().join("../../../resources").join("resources.eucc");
+ let config_path = path.as_ref().join("resources").join("resources.eucc");
fs::create_dir_all(config_path.parent().unwrap())?;
fs::write(&config_path, ron_str).map_err(|e| anyhow::anyhow!(e.to_string()))?;
Ok(())
@@ -370,7 +371,7 @@ impl ResourceConfig {
/// # Parameters
/// - path: The location to the **resources.eucc** file
pub fn read_from(path: impl AsRef<Path>) -> anyhow::Result<Self> {
- let config_path = path.as_ref().join("../../../resources").join("resources.eucc");
+ let config_path = path.as_ref().join("resources").join("resources.eucc");
let ron_str = fs::read_to_string(&config_path)?;
let config: ResourceConfig = ron::de::from_str(&ron_str)
.map_err(|e| anyhow::anyhow!("RON deserialization error: {}", e))?;
@@ -396,7 +397,7 @@ impl SourceConfig {
/// # Parameters
/// - path: The root **folder** of the project
pub fn write_to(&self, path: impl AsRef<Path>) -> anyhow::Result<()> {
- let resource_dir = path.as_ref().join("../../../src");
+ let resource_dir = path.as_ref().join("src");
let updated_config = SourceConfig {
path: resource_dir.clone(),
nodes: collect_nodes(&resource_dir, path.as_ref(), vec!["scripts"].as_slice()),
@@ -404,7 +405,7 @@ impl SourceConfig {
let ron_str = ron::ser::to_string_pretty(&updated_config, PrettyConfig::default())
.map_err(|e| anyhow::anyhow!("RON serialisation error: {}", e))?;
- let config_path = path.as_ref().join("../../../src").join("source.eucc");
+ let config_path = path.as_ref().join("src").join("source.eucc");
fs::create_dir_all(config_path.parent().unwrap())?;
fs::write(&config_path, ron_str).map_err(|e| anyhow::anyhow!(e.to_string()))?;
Ok(())
@@ -413,7 +414,7 @@ impl SourceConfig {
/// # Parameters
/// - path: The location to the **source.eucc** file
pub fn read_from(path: impl AsRef<Path>) -> anyhow::Result<Self> {
- let config_path = path.as_ref().join("../../../src").join("source.eucc");
+ let config_path = path.as_ref().join("src").join("source.eucc");
let ron_str = fs::read_to_string(&config_path)?;
let config: SourceConfig = ron::de::from_str(&ron_str)
.map_err(|e| anyhow::anyhow!("RON deserialization error: {}", e))?;
@@ -459,7 +460,7 @@ fn collect_nodes(
ResourceType::Model
} else if parent_folder.contains("texture") {
ResourceType::Texture
- } else if parent_folder.contains("shader") {
+ } else if parent_folder.contains("shaders") {
ResourceType::Shader
} else if entry_path
.extension()
diff --git a/crates/eucalyptus-core/src/physics/collider/shader.rs b/crates/eucalyptus-core/src/physics/collider/shader.rs
index 50d6368..e1067df 100644
--- a/crates/eucalyptus-core/src/physics/collider/shader.rs
+++ b/crates/eucalyptus-core/src/physics/collider/shader.rs
@@ -3,6 +3,7 @@
use std::mem::size_of;
use std::sync::Arc;
+use dropbear_engine::pipelines::DropbearShaderPipeline;
use dropbear_engine::{entity::Transform, texture::Texture};
use dropbear_engine::graphics::SharedGraphicsContext;
use dropbear_engine::shader::Shader;
@@ -12,17 +13,17 @@ use glam::Mat4;
use crate::physics::collider::{ColliderShape, WireframeGeometry};
pub struct ColliderWireframePipeline {
+ pub shader: Shader,
+ pub pipeline_layout: PipelineLayout,
pub pipeline: RenderPipeline,
}
-impl ColliderWireframePipeline {
- pub fn new(
- graphics: Arc<SharedGraphicsContext>,
- ) -> Self {
+impl DropbearShaderPipeline for ColliderWireframePipeline {
+ fn new(graphics: Arc<SharedGraphicsContext>) -> Self {
let shader = Shader::new(
graphics.clone(),
- include_str!("../../../../../resources/shaders/collider.wgsl"),
- Some("collider wireframe shader"),
+ include_str!("shaders/collider.wgsl"),
+ Some("collider wireframe shaders"),
);
let pipeline_layout = graphics.device.create_pipeline_layout(&PipelineLayoutDescriptor {
@@ -90,7 +91,23 @@ impl ColliderWireframePipeline {
cache: None,
});
- Self { pipeline }
+ Self {
+ shader,
+ pipeline_layout,
+ pipeline,
+ }
+ }
+
+ fn shader(&self) -> &Shader {
+ &self.shader
+ }
+
+ fn pipeline_layout(&self) -> &PipelineLayout {
+ &self.pipeline_layout
+ }
+
+ fn pipeline(&self) -> &RenderPipeline {
+ &self.pipeline
}
}
diff --git a/crates/eucalyptus-core/src/physics/collider/shaders/collider.wgsl b/crates/eucalyptus-core/src/physics/collider/shaders/collider.wgsl
new file mode 100644
index 0000000..b899ac9
--- /dev/null
+++ b/crates/eucalyptus-core/src/physics/collider/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/crates/eucalyptus-core/src/scripting.rs b/crates/eucalyptus-core/src/scripting.rs
index d4a8ef7..4926101 100644
--- a/crates/eucalyptus-core/src/scripting.rs
+++ b/crates/eucalyptus-core/src/scripting.rs
@@ -675,14 +675,14 @@ impl Drop for ScriptManager {
fn get_gradle_command(project_root: impl AsRef<Path>) -> String {
let project_root = project_root.as_ref().to_owned();
if cfg!(target_os = "windows") {
- let gradlew = project_root.join("../../../gradlew.bat");
+ let gradlew = project_root.join("gradlew.bat");
if gradlew.exists() {
gradlew.to_string_lossy().to_string()
} else {
"gradle.bat".to_string()
}
} else {
- let gradlew = project_root.join("../../../gradlew");
+ let gradlew = project_root.join("gradlew");
if gradlew.exists() {
"./gradlew".to_string()
} else {
@@ -707,14 +707,14 @@ pub async fn build_jvm(
let _ = status_sender.send(BuildStatus::Started);
let _ = status_sender.send(BuildStatus::Building(format!("Building manifest at {}", project_root.join("build/magna-carta/jvmMain/RunnableRegistry.kt").display())));
- if let Err(e) = magna_carta::parse(project_root.join("../../../src"), Target::Jvm, project_root.join("build/magna-carta/jvmMain")) {
+ if let Err(e) = magna_carta::parse(project_root.join("src"), Target::Jvm, project_root.join("build/magna-carta/jvmMain")) {
let _ = status_sender.send(BuildStatus::Failed(format!("Failed to build manifest: {}", e)));
return Err(e);
}
let _ = status_sender.send(BuildStatus::Building(String::from("Successfully built manifest!")));
if !(project_root.join("build.gradle").exists()
- || project_root.join("../../../build.gradle.kts").exists())
+ || project_root.join("build.gradle.kts").exists())
{
let err = format!("No Gradle build script found in: {:?}", project_root);
let _ = status_sender.send(BuildStatus::Failed(err.clone()));
@@ -766,7 +766,7 @@ pub async fn build_jvm(
return Err(anyhow::anyhow!(err_msg));
}
- let libs_dir = project_root.join("../../../build").join("libs");
+ let libs_dir = project_root.join("build").join("libs");
if !libs_dir.exists() {
let err = "Build succeeded but 'build/libs' directory is missing".to_string();
let _ = status_sender.send(BuildStatus::Failed(err.clone()));
@@ -870,7 +870,7 @@ pub async fn build_native(
}
let _ = status_sender.send(BuildStatus::Building(format!("Building manifest at {}", project_root.join("build/magna-carta/jvmMain/RunnableRegistry.kt").display())));
- if let Err(e) = magna_carta::parse(project_root.join("../../../src"), Target::Jvm, project_root.join("build/magna-carta/jvmMain")) {
+ if let Err(e) = magna_carta::parse(project_root.join("src"), Target::Jvm, project_root.join("build/magna-carta/jvmMain")) {
let _ = status_sender.send(BuildStatus::Failed(format!("Failed to build manifest: {}", e)));
return Err(e);
}
diff --git a/crates/eucalyptus-core/src/states.rs b/crates/eucalyptus-core/src/states.rs
index 7dbd854..a0e0ad7 100644
--- a/crates/eucalyptus-core/src/states.rs
+++ b/crates/eucalyptus-core/src/states.rs
@@ -16,7 +16,6 @@ use once_cell::sync::Lazy;
use parking_lot::RwLock;
use serde::{Deserialize, Serialize};
use std::borrow::Borrow;
-use std::collections::HashMap;
use std::fmt;
use std::fmt::{Display, Formatter};
use std::ops::{Deref, DerefMut};
@@ -138,7 +137,7 @@ impl Display for ResourceType {
ResourceType::Unknown => "unknown",
ResourceType::Model => "model",
ResourceType::Texture => "texture",
- ResourceType::Shader => "shader",
+ ResourceType::Shader => "shaders",
ResourceType::Thumbnail => "thumbnail",
ResourceType::Script => "script",
ResourceType::Config => "eucalyptus project config",
@@ -280,14 +279,6 @@ pub enum WorldLoadingStatus {
Completed,
}
-#[derive(Clone, Serialize, Deserialize)]
-pub struct RuntimeData {
- pub project_config: ProjectConfig,
- pub source_config: SourceConfig,
- pub scene_data: Vec<SceneConfig>,
- pub scripts: HashMap<String, String>,
-}
-
#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Hash, Clone, SerializableComponent)]
pub struct Label(String);
diff --git a/crates/eucalyptus-core/src/utils.rs b/crates/eucalyptus-core/src/utils.rs
index a471eaa..8a5e054 100644
--- a/crates/eucalyptus-core/src/utils.rs
+++ b/crates/eucalyptus-core/src/utils.rs
@@ -274,7 +274,7 @@ impl ResolveReference for ResourceReference {
};
if !project_path.as_os_str().is_empty() {
- let root = project_path.join("../../../resources");
+ let root = project_path.join("resources");
return resolve_resource_from_root(relative, &root);
}
}
@@ -294,7 +294,7 @@ fn runtime_resources_dir() -> anyhow::Result<PathBuf> {
let dir = current_exe
.parent()
.ok_or_else(|| anyhow::anyhow!("Unable to locate parent folder of runtime executable"))?;
- let resources_dir = dir.join("../../../resources");
+ let resources_dir = dir.join("resources");
if !resources_dir.exists() {
anyhow::bail!(
"Runtime resources directory is missing at '{}'. Ensure the packaged build includes a 'resources' folder next to the executable (current exe: {}).",
diff --git a/crates/eucalyptus-editor/src/build.rs b/crates/eucalyptus-editor/src/build.rs
index 4520cf8..e86eb37 100644
--- a/crates/eucalyptus-editor/src/build.rs
+++ b/crates/eucalyptus-editor/src/build.rs
@@ -82,8 +82,8 @@ pub fn build(project_config: PathBuf) -> anyhow::Result<PathBuf> {
log::debug!("Exported scene config to {:?}", eupak_path);
// copy resources
- let resources_src = project_root.join("../../../resources");
- let resources_dst = build_dir.join("../../../resources");
+ let resources_src = project_root.join("resources");
+ let resources_dst = build_dir.join("resources");
if resources_src.exists() {
copy_dir_recursive(&resources_src, &resources_dst)?;
log::debug!("Copied resources to {:?}", resources_dst);
@@ -212,8 +212,8 @@ pub async fn package(
}
tokio_fs::copy(&data_src, package_dir.join("data.eupak")).await?;
- let resources_src = build_dir.join("../../../resources");
- let resources_dst = package_dir.join("../../../resources");
+ let resources_src = build_dir.join("resources");
+ let resources_dst = package_dir.join("resources");
if resources_src.exists() && !resources_dst.exists() {
let src = resources_src.clone();
let dst = resources_dst.clone();
@@ -231,7 +231,7 @@ pub async fn package(
let magna_output_dir = project_root.join("build/magna-carta/nativeLibMain");
tokio_fs::create_dir_all(&magna_output_dir).await?;
- magna_carta::parse(project_root.join("../../../src"), Target::Native, &magna_output_dir)?;
+ magna_carta::parse(project_root.join("src"), Target::Native, &magna_output_dir)?;
// let magna_status = Command::new("magna-carta")
// .arg("--input")
@@ -600,7 +600,7 @@ async fn run_gradle_build(project_root: &Path) -> anyhow::Result<()> {
#[cfg(not(windows))]
{
- let script = project_root.join("../../../gradlew");
+ let script = project_root.join("gradlew");
if !script.exists() {
bail!("Gradle wrapper not found at {}", script.display());
}
diff --git a/crates/eucalyptus-editor/src/editor/dock.rs b/crates/eucalyptus-editor/src/editor/dock.rs
index 6590b8d..04f0bb3 100644
--- a/crates/eucalyptus-editor/src/editor/dock.rs
+++ b/crates/eucalyptus-editor/src/editor/dock.rs
@@ -1149,7 +1149,7 @@ impl<'a> EditorTabViewer<'a> {
) {
let label = "euca://resources";
builder.node(Self::dir_node_labeled(label, "resources"));
- let resources_root = project_root.join("../../../../resources");
+ let resources_root = project_root.join("resources");
if resources_root.exists() {
Self::walk_resource_directory(cfg, builder, &resources_root, &resources_root);
} else {
@@ -1219,7 +1219,7 @@ impl<'a> EditorTabViewer<'a> {
) {
let label = "euca://scripts";
builder.node(Self::dir_node_labeled(label, "scripts"));
- let scripts_root = project_root.join("../../../../src");
+ let scripts_root = project_root.join("src");
if !scripts_root.exists() {
Self::walk_resource_directory(cfg, builder, &scripts_root, &scripts_root);
builder.close_dir();
diff --git a/crates/eucalyptus-editor/src/editor/mod.rs b/crates/eucalyptus-editor/src/editor/mod.rs
index b4c171d..ed8cb05 100644
--- a/crates/eucalyptus-editor/src/editor/mod.rs
+++ b/crates/eucalyptus-editor/src/editor/mod.rs
@@ -15,9 +15,9 @@ use crossbeam_channel::{unbounded, Receiver, Sender};
use dropbear_engine::buffer::ResizableBuffer;
use dropbear_engine::entity::EntityTransform;
use dropbear_engine::graphics::InstanceRaw;
-use dropbear_engine::shader::Shader;
+use dropbear_engine::pipelines::light_cube::LightCubePipeline;
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 dropbear_engine::{camera::Camera, entity::{MeshRenderer, Transform}, future::FutureHandle, graphics::{SharedGraphicsContext}, 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};
@@ -53,10 +53,13 @@ use std::rc::Rc;
use log::debug;
use tokio::sync::oneshot;
use transform_gizmo_egui::{EnumSet, Gizmo, GizmoMode, GizmoOrientation};
-use wgpu::{Color, Extent3d, RenderPipeline};
+use wgpu::{Color, Extent3d};
use winit::window::{CursorGrabMode, WindowAttributes};
use winit::{keyboard::KeyCode, window::Window};
use winit::dpi::PhysicalSize;
+use dropbear_engine::pipelines::DropbearShaderPipeline;
+use dropbear_engine::pipelines::shader::MainRenderPipeline;
+use dropbear_engine::pipelines::GlobalsUniform;
use eucalyptus_core::physics::collider::{ColliderShapeKey, WireframeGeometry};
use eucalyptus_core::physics::collider::shader::ColliderInstanceRaw;
use eucalyptus_core::physics::collider::shader::ColliderWireframePipeline;
@@ -71,14 +74,17 @@ pub struct Editor {
pub dock_state: DockState<EditorTab>,
pub texture_id: Option<egui::TextureId>,
pub size: Extent3d,
- pub render_pipeline: Option<RenderPipeline>,
pub instance_buffer_cache: HashMap<ModelId, ResizableBuffer<InstanceRaw>>,
- 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,
+ // rendering
+ pub light_cube_pipeline: Option<LightCubePipeline>,
+ pub main_render_pipeline: Option<MainRenderPipeline>,
+ pub shader_globals: Option<GlobalsUniform>,
+ pub collider_wireframe_pipeline: Option<ColliderWireframePipeline>,
+
pub active_camera: Arc<Mutex<Option<Entity>>>,
pub is_viewport_focused: bool,
@@ -186,7 +192,8 @@ impl Editor {
dock_state,
texture_id: None,
size: Extent3d::default(),
- render_pipeline: None,
+ main_render_pipeline: None,
+ shader_globals: None,
color: Color::default(),
is_viewport_focused: false,
// is_cursor_locked: false,
@@ -208,7 +215,7 @@ impl Editor {
gizmo_orientation: GizmoOrientation::Global,
play_mode_backup: None,
input_state: Box::new(InputState::new()),
- light_manager: LightManager::new(),
+ light_cube_pipeline: None,
active_camera: Arc::new(Mutex::new(None)),
progress_tx: None,
is_world_loaded: IsWorldLoadedYet::new(),
@@ -616,9 +623,10 @@ impl Editor {
self.previously_selected_entity = None;
self.active_camera.lock().take();
- self.render_pipeline = None;
+ self.main_render_pipeline = None;
+ self.shader_globals = None;
self.texture_id = None;
- self.light_manager = LightManager::new();
+ self.light_cube_pipeline = None;
{
let mut cache = MODEL_CACHE.lock();
@@ -1042,13 +1050,22 @@ impl Editor {
let editor_ptr = self as *mut Editor;
+ let Some(view) = self.texture_id else {
+ egui::CentralPanel::default().show(ctx, |ui| {
+ ui.centered_and_justified(|ui| {
+ ui.label("Viewport is still initialising...");
+ });
+ });
+ return;
+ };
+
egui::CentralPanel::default().show(ctx, |ui| {
DockArea::new(&mut self.dock_state)
.style(Style::from_egui(ui.style().as_ref()))
.show_inside(
ui,
&mut EditorTabViewer {
- view: self.texture_id.unwrap(),
+ view,
gizmo: &mut self.gizmo,
tex_size: self.size,
world: &mut self.world,
@@ -1302,38 +1319,12 @@ impl Editor {
///
/// **Note**: To be ran AFTER [`Editor::load_project_config`]
pub fn load_wgpu_nerdy_stuff<'a>(&mut self, graphics: std::sync::Arc<dropbear_engine::graphics::SharedGraphicsContext>) {
- let shader = Shader::new(
- graphics.clone(),
- include_str!("../../../../resources/shaders/shader.wgsl"),
- Some("viewport shader"),
- );
-
- self.light_manager
- .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);
+ self.main_render_pipeline = Some(MainRenderPipeline::new(graphics.clone()));
+ self.light_cube_pipeline = Some(LightCubePipeline::new(graphics.clone()));
+ self.shader_globals = Some(GlobalsUniform::new(graphics.clone(), Some("editor shader globals")));
+ self.collider_wireframe_pipeline = Some(ColliderWireframePipeline::new(graphics.clone()));
self.texture_id = Some((*graphics.texture_id).clone());
-
self.window = Some(graphics.window.clone());
self.is_world_loaded.mark_rendering_loaded();
}
diff --git a/crates/eucalyptus-editor/src/editor/scene.rs b/crates/eucalyptus-editor/src/editor/scene.rs
index ffdd8e9..9c6ac22 100644
--- a/crates/eucalyptus-editor/src/editor/scene.rs
+++ b/crates/eucalyptus-editor/src/editor/scene.rs
@@ -11,7 +11,7 @@ use dropbear_engine::graphics::{FrameGraphicsContext, InstanceRaw};
use dropbear_engine::model::MODEL_CACHE;
use dropbear_engine::{
entity::{EntityTransform, MeshRenderer, Transform},
- lighting::{Light, LightComponent},
+ lighting::{Light, LightComponent, MAX_LIGHTS},
model::{DrawLight, DrawModel},
scene::{Scene, SceneCommand},
};
@@ -333,13 +333,12 @@ impl Scene for Editor {
}
}
- {
- let sim_world = self.world.as_ref();
-
- self.light_manager
- .update(graphics.clone(), sim_world);
+ if let Some(l) = &mut self.light_cube_pipeline {
+ l.update(graphics.clone(), &self.world);
+ }
- self.nerd_stats.write().record_stats(dt, sim_world.len() as u32);
+ {
+ self.nerd_stats.write().record_stats(dt, self.world.len() as u32);
}
self.input_state.window = self.window.clone();
@@ -348,6 +347,10 @@ impl Scene for Editor {
}
fn render<'a>(&mut self, graphics: Arc<SharedGraphicsContext>, frame_ctx: FrameGraphicsContext<'a>) {
+ self.size = graphics.viewport_texture.size;
+ self.texture_id = Some(*graphics.texture_id.clone());
+ self.window = Some(graphics.window.clone());
+
self.show_ui(&graphics.get_egui_context());
let clear_color = Color {
r: 100.0 / 255.0,
@@ -356,9 +359,6 @@ impl Scene for Editor {
a: 1.0,
};
self.color = clear_color;
- self.size = graphics.viewport_texture.size;
- self.texture_id = Some(*graphics.texture_id.clone());
- self.window = Some(graphics.window.clone());
eucalyptus_core::logging::render(&graphics.get_egui_context());
let cam = {
@@ -379,7 +379,7 @@ impl Scene for Editor {
log_once::debug_once!("Camera ready");
log_once::debug_once!("Camera currently being viewed: {}", camera.label);
- let Some(pipeline) = &self.render_pipeline else {
+ let Some(pipeline) = &self.main_render_pipeline else {
log_once::warn_once!("Render pipeline not ready");
return;
};
@@ -394,6 +394,16 @@ impl Scene for Editor {
lights
};
+ if let Some(globals) = &mut self.shader_globals {
+ let enabled_count = lights
+ .iter()
+ .filter(|(_, comp)| comp.enabled)
+ .take(MAX_LIGHTS)
+ .count() as u32;
+ globals.set_num_lights(enabled_count);
+ globals.write(&graphics.queue);
+ }
+
let renderers = {
let mut renderers = Vec::new();
let mut query = self.world.query::<&MeshRenderer>();
@@ -455,8 +465,6 @@ impl Scene for Editor {
}
}
- self.light_manager.update(graphics.clone(), &self.world);
-
{
let mut render_pass = frame_ctx.encoder
.begin_render_pass(&wgpu::RenderPassDescriptor {
@@ -481,8 +489,8 @@ impl Scene for Editor {
occlusion_query_set: None,
timestamp_writes: None,
});
- if let Some(light_pipeline) = &self.light_manager.pipeline {
- render_pass.set_pipeline(light_pipeline);
+ if let Some(light_pipeline) = &self.light_cube_pipeline {
+ render_pass.set_pipeline(light_pipeline.pipeline());
for (light, component) in &lights {
render_pass.set_vertex_buffer(1, light.instance_buffer.buffer().slice(..));
if component.visible {
@@ -495,39 +503,47 @@ impl Scene for Editor {
}
}
}
-
- 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,
+ if let Some(lcp) = &self.light_cube_pipeline {
+ for (model, instance_buffer, instance_count) in prepared_models {
+ let globals_bind_group = &self
+ .shader_globals
+ .as_ref()
+ .expect("Shader globals not initialised")
+ .bind_group;
+
+ 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,
}),
- 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(),
- );
+ occlusion_query_set: None,
+ timestamp_writes: None,
+ });
+ render_pass.set_pipeline(pipeline.pipeline());
+ render_pass.set_vertex_buffer(1, instance_buffer.slice(..));
+ render_pass.set_bind_group(4, globals_bind_group, &[]);
+ render_pass.draw_model_instanced(
+ &model,
+ 0..instance_count,
+ &camera.bind_group,
+ lcp.bind_group(),
+ );
+ }
}
{
diff --git a/crates/eucalyptus-editor/src/menu.rs b/crates/eucalyptus-editor/src/menu.rs
index e886b5a..caf6fec 100644
--- a/crates/eucalyptus-editor/src/menu.rs
+++ b/crates/eucalyptus-editor/src/menu.rs
@@ -6,6 +6,7 @@ use eucalyptus_core::config::ProjectConfig;
use eucalyptus_core::states::PROJECT;
use git2::Repository;
use log::{self, debug};
+use log_once::debug_once;
use rfd::FileDialog;
use std::sync::Arc;
use std::{fs, path::PathBuf};
@@ -143,7 +144,7 @@ impl MainMenu {
fs::write(&dest_script_path, content)?;
- let build_gradle_path = project_root.join("../../../build.gradle.kts");
+ let build_gradle_path = project_root.join("build.gradle.kts");
let gradle_content = fs::read_to_string(&build_gradle_path)?;
let updated_gradle_content = gradle_content
@@ -510,26 +511,26 @@ impl Mouse for MainMenu {
impl Controller for MainMenu {
fn button_down(&mut self, button: gilrs::Button, id: gilrs::GamepadId) {
- debug!("Controller button {:?} pressed! [{}]", button, id);
+ debug_once!("Controller button {:?} pressed! [{}]", button, id);
}
fn button_up(&mut self, button: gilrs::Button, id: gilrs::GamepadId) {
- debug!("Controller button {:?} released! [{}]", button, id);
+ debug_once!("Controller button {:?} released! [{}]", button, id);
}
fn left_stick_changed(&mut self, x: f32, y: f32, id: gilrs::GamepadId) {
- debug!("Left stick changed: x = {} | y = {} | id = {}", x, y, id);
+ debug_once!("Left stick changed: x = {} | y = {} | id = {}", x, y, id);
}
fn right_stick_changed(&mut self, x: f32, y: f32, id: gilrs::GamepadId) {
- debug!("Right stick changed: x = {} | y = {} | id = {}", x, y, id);
+ debug_once!("Right stick changed: x = {} | y = {} | id = {}", x, y, id);
}
fn on_connect(&mut self, id: gilrs::GamepadId) {
- debug!("Controller connected [{}]", id);
+ debug_once!("Controller connected [{}]", id);
}
fn on_disconnect(&mut self, id: gilrs::GamepadId) {
- debug!("Controller disconnected [{}]", id);
+ debug_once!("Controller disconnected [{}]", id);
}
}
diff --git a/crates/eucalyptus-editor/src/signal.rs b/crates/eucalyptus-editor/src/signal.rs
index 8556ba7..1302e58 100644
--- a/crates/eucalyptus-editor/src/signal.rs
+++ b/crates/eucalyptus-editor/src/signal.rs
@@ -26,7 +26,7 @@ fn resolve_editor_path(uri: &str) -> PathBuf {
let relative = relative_path_from_euca(uri)
.unwrap_or_else(|_| uri.trim_start_matches(EUCA_SCHEME));
let project_path = PROJECT.read().project_path.clone();
- project_path.join("../../../resources").join(relative)
+ project_path.join("resources").join(relative)
} else {
PathBuf::from(uri)
}
@@ -198,7 +198,7 @@ impl SignalController for Editor {
let cfg = PROJECT.read();
cfg.project_path.clone()
};
- let libs_dir = project_root.join("../../../build").join("libs");
+ let libs_dir = project_root.join("build").join("libs");
if !libs_dir.exists() {
let err =
"Build succeeded but 'build/libs' directory is missing".to_string();
diff --git a/crates/eucalyptus-editor/src/utils.rs b/crates/eucalyptus-editor/src/utils.rs
index d93493c..974229c 100644
--- a/crates/eucalyptus-editor/src/utils.rs
+++ b/crates/eucalyptus-editor/src/utils.rs
@@ -113,7 +113,7 @@ pub fn start_project_creation(
("git", 0.1, "Creating a git folder..."),
("src", 0.2, "Creating src folder..."),
("resources/models", 0.4, "Creating models folder..."),
- ("resources/shaders", 0.6, "Creating shader folder..."),
+ ("resources/shaders", 0.6, "Creating shaders folder..."),
("resources/textures", 0.8, "Creating textures folder..."),
("src2", 0.9, "Creating project config file..."),
];
diff --git a/crates/redback-runtime/src/lib.rs b/crates/redback-runtime/src/lib.rs
index d904a9f..816d008 100644
--- a/crates/redback-runtime/src/lib.rs
+++ b/crates/redback-runtime/src/lib.rs
@@ -3,17 +3,18 @@
use std::sync::Arc;
use crossbeam_channel::{unbounded, Receiver};
use dropbear_engine::buffer::ResizableBuffer;
+use dropbear_engine::pipelines::DropbearShaderPipeline;
+use dropbear_engine::pipelines::GlobalsUniform;
+use dropbear_engine::pipelines::light_cube::LightCubePipeline;
+use dropbear_engine::pipelines::shader::MainRenderPipeline;
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::future::FutureHandle;
use dropbear_engine::graphics::SharedGraphicsContext;
-use dropbear_engine::lighting::LightManager;
use dropbear_engine::scene::SceneCommand;
-use dropbear_engine::shader::Shader;
use eucalyptus_core::input::InputState;
use eucalyptus_core::scripting::{ScriptManager, ScriptTarget};
use eucalyptus_core::states::{WorldLoadingStatus, SCENES, Script, PROJECT};
@@ -42,7 +43,7 @@ fn find_jvm_library_path() -> PathBuf {
} else {
proj.project_path.clone()
}
- .join("../../../../build/libs");
+ .join("build/libs");
let mut latest_jar: Option<(PathBuf, std::time::SystemTime)> = None;
@@ -80,8 +81,10 @@ pub struct PlayMode {
active_camera: Option<Entity>,
// rendering
- render_pipeline: Option<RenderPipeline>,
- light_manager: LightManager,
+ light_cube_pipeline: Option<LightCubePipeline>,
+ main_pipeline: Option<MainRenderPipeline>,
+ shader_globals: Option<GlobalsUniform>,
+ collider_wireframe_pipeline: Option<ColliderWireframePipeline>,
initial_scene: Option<String>,
current_scene: Option<String>,
@@ -103,7 +106,6 @@ pub struct PlayMode {
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>>,
}
@@ -135,8 +137,9 @@ impl PlayMode {
pending_world: None,
pending_camera: None,
active_camera: None,
- render_pipeline: None,
- light_manager: Default::default(),
+ main_pipeline: None,
+ light_cube_pipeline: None,
+ shader_globals: None,
scripts_ready: false,
has_initial_resize_done: false,
physics_pipeline: Default::default(),
@@ -157,35 +160,10 @@ impl PlayMode {
}
pub fn load_wgpu_nerdy_stuff<'a>(&mut self, graphics: Arc<SharedGraphicsContext>) {
- let shader = Shader::new(
- graphics.clone(),
- include_str!("../../../resources/shaders/shader.wgsl"),
- Some("viewport shader"),
- );
-
- self.light_manager
- .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);
+ self.light_cube_pipeline = Some(LightCubePipeline::new(graphics.clone()));
+ self.main_pipeline = Some(MainRenderPipeline::new(graphics.clone()));
+ self.shader_globals = Some(GlobalsUniform::new(graphics.clone(), Some("runtime shader globals")));
+ self.collider_wireframe_pipeline = Some(ColliderWireframePipeline::new(graphics.clone()));
}
fn reload_scripts_for_current_world(&mut self) {
@@ -305,7 +283,8 @@ impl PlayMode {
self.physics_state = Box::new(PhysicsState::new());
self.physics_receiver = None;
self.active_camera = None;
- self.render_pipeline = None;
+ self.main_pipeline = None;
+ self.light_cube_pipeline = None;
self.current_scene = None;
self.world_loading_progress = None;
self.world_receiver = None;
diff --git a/crates/redback-runtime/src/scene.rs b/crates/redback-runtime/src/scene.rs
index 6cdb995..4299975 100644
--- a/crates/redback-runtime/src/scene.rs
+++ b/crates/redback-runtime/src/scene.rs
@@ -1,6 +1,7 @@
use std::sync::Arc;
use std::collections::HashMap;
+use dropbear_engine::pipelines::DropbearShaderPipeline;
use eucalyptus_core::egui::{CentralPanel, MenuBar, TopBottomPanel};
use eucalyptus_core::physics::collider::ColliderGroup;
use eucalyptus_core::physics::collider::ColliderShapeKey;
@@ -15,6 +16,7 @@ use dropbear_engine::buffer::ResizableBuffer;
use dropbear_engine::entity::{EntityTransform, MeshRenderer, Transform};
use dropbear_engine::graphics::{InstanceRaw, SharedGraphicsContext, FrameGraphicsContext};
use dropbear_engine::lighting::{Light, LightComponent};
+use dropbear_engine::lighting::MAX_LIGHTS;
use dropbear_engine::model::{DrawLight, DrawModel, ModelId, MODEL_CACHE};
use dropbear_engine::scene::{Scene, SceneCommand};
use eucalyptus_core::camera::CameraComponent;
@@ -362,9 +364,11 @@ impl Scene for PlayMode {
}
}
- self.light_manager
- .update(graphics.clone(), &self.world);
-
+ if let Some(l) = &mut self.light_cube_pipeline {
+ l.update(graphics.clone(), &self.world);
+ }
+
+ #[cfg(feature = "debug")]
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| {
@@ -466,7 +470,7 @@ impl Scene for PlayMode {
log_once::debug_once!("Camera ready");
log_once::debug_once!("Camera currently being viewed: {}", camera.label);
- let Some(pipeline) = &self.render_pipeline else {
+ let Some(pipeline) = &self.main_pipeline else {
log_once::warn_once!("Render pipeline not ready");
return;
};
@@ -488,6 +492,16 @@ impl Scene for PlayMode {
lights
};
+ if let Some(globals) = &mut self.shader_globals {
+ let enabled_count = lights
+ .iter()
+ .filter(|(_, comp)| comp.enabled)
+ .take(MAX_LIGHTS)
+ .count() as u32;
+ globals.set_num_lights(enabled_count);
+ globals.write(&graphics.queue);
+ }
+
let renderers = {
let mut renderers = Vec::new();
let mut query = self.world.query::<&MeshRenderer>();
@@ -549,8 +563,6 @@ impl Scene for PlayMode {
}
}
- self.light_manager.update(graphics.clone(), &self.world);
-
{
let mut render_pass = frame_ctx.encoder
.begin_render_pass(&wgpu::RenderPassDescriptor {
@@ -575,8 +587,8 @@ impl Scene for PlayMode {
occlusion_query_set: None,
timestamp_writes: None,
});
- if let Some(light_pipeline) = &self.light_manager.pipeline {
- render_pass.set_pipeline(light_pipeline);
+ if let Some(light_pipeline) = &self.light_cube_pipeline {
+ render_pass.set_pipeline(light_pipeline.pipeline());
for (light, component) in &lights {
render_pass.set_vertex_buffer(1, light.instance_buffer.buffer().slice(..));
if component.visible {
@@ -591,6 +603,18 @@ impl Scene for PlayMode {
}
for (model, instance_buffer, instance_count) in prepared_models {
+ let light_bind_group = self
+ .light_cube_pipeline
+ .as_ref()
+ .expect("Light cube pipeline not initialised")
+ .bind_group();
+
+ let globals_bind_group = &self
+ .shader_globals
+ .as_ref()
+ .expect("Shader globals not initialised")
+ .bind_group;
+
let mut render_pass = frame_ctx.encoder
.begin_render_pass(&wgpu::RenderPassDescriptor {
label: Some("model render pass"),
@@ -614,13 +638,14 @@ impl Scene for PlayMode {
occlusion_query_set: None,
timestamp_writes: None,
});
- render_pass.set_pipeline(pipeline);
+ render_pass.set_pipeline(pipeline.pipeline());
render_pass.set_vertex_buffer(1, instance_buffer.slice(..));
+ render_pass.set_bind_group(4, globals_bind_group, &[]);
render_pass.draw_model_instanced(
&model,
0..instance_count,
&camera.bind_group,
- self.light_manager.bind_group(),
+ light_bind_group,
);
}
diff --git a/resources/shaders/collider.wgsl b/resources/shaders/collider.wgsl
deleted file mode 100644
index b899ac9..0000000
--- a/resources/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/resources/shaders/light.wgsl b/resources/shaders/light.wgsl
deleted file mode 100644
index b976066..0000000
--- a/resources/shaders/light.wgsl
+++ /dev/null
@@ -1,62 +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,
-}
-
-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
deleted file mode 100644
index 2ec6c26..0000000
--- a/resources/shaders/shader.wgsl
+++ /dev/null
@@ -1,245 +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,
-}
-
-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