summaryrefslogtreecommitdiff
path: root/engine
diff options
context:
space:
mode:
Diffstat (limited to 'engine')
-rw-r--r--engine/Cargo.toml11
-rw-r--r--engine/res/default_shader.slang289
-rw-r--r--engine/src/asset.rs168
-rw-r--r--engine/src/data_types/matrix.rs170
-rw-r--r--engine/src/data_types/vector.rs141
-rw-r--r--engine/src/lib.rs5
-rw-r--r--engine/src/material.rs40
-rw-r--r--engine/src/model.rs41
-rw-r--r--engine/src/opengl/glsl.rs616
-rw-r--r--engine/src/opengl/mod.rs1
-rw-r--r--engine/src/renderer.rs299
-rw-r--r--engine/src/renderer/object.rs13
-rw-r--r--engine/src/renderer/opengl.rs769
-rw-r--r--engine/src/renderer/opengl/glsl/fragment.glsl73
-rw-r--r--engine/src/renderer/opengl/glsl/light.glsl133
-rw-r--r--engine/src/renderer/opengl/glsl/vertex.glsl24
-rw-r--r--engine/src/renderer/opengl/glsl/vertex_data.glsl11
-rw-r--r--engine/src/renderer/opengl/graphics_mesh.rs2
-rw-r--r--engine/src/shader.rs818
-rw-r--r--engine/src/shader/cursor.rs160
-rw-r--r--engine/src/shader/default.rs363
-rw-r--r--engine/src/util.rs3
22 files changed, 2558 insertions, 1592 deletions
diff --git a/engine/Cargo.toml b/engine/Cargo.toml
index 4e4600d..9461ee9 100644
--- a/engine/Cargo.toml
+++ b/engine/Cargo.toml
@@ -31,5 +31,16 @@ default-features = false
features = ["png", "jpeg"]
package = "image"
+[dependencies.zerocopy]
+version = "0.8.42"
+default-features = false
+features = ["derive"]
+
+[dependencies.shader-slang]
+git = "https://github.com/HampusMat/slang-rs"
+branch = "with_static_linking"
+default-features = false
+features = ["static"]
+
[build-dependencies]
cfg_aliases = "0.2.1"
diff --git a/engine/res/default_shader.slang b/engine/res/default_shader.slang
new file mode 100644
index 0000000..dd6a6a9
--- /dev/null
+++ b/engine/res/default_shader.slang
@@ -0,0 +1,289 @@
+#define MAX_LIGHT_CNT 64
+
+
+struct Material
+{
+ float3 ambient;
+ float3 diffuse;
+ float3 specular;
+ // Sampler2D ambient_map;
+ // Sampler2D diffuse_map;
+ // Sampler2D specular_map;
+ float shininess;
+};
+
+struct LightPhong
+{
+ float3 diffuse;
+ float3 specular;
+};
+
+struct AttenuationProperties
+{
+ float constant;
+ float linear;
+ float quadratic;
+};
+
+struct PointLight
+{
+ LightPhong phong;
+ float3 position;
+ AttenuationProperties attenuation_props;
+};
+
+struct DirectionalLight
+{
+ LightPhong phong;
+ float3 direction;
+};
+
+struct CalculatedLight
+{
+ float3 diffuse;
+ float3 specular;
+};
+
+struct BlinnPhongLighting
+{
+ float3 view_pos;
+ Material material;
+
+ DirectionalLight directional_lights[MAX_LIGHT_CNT];
+ uint directional_light_cnt;
+
+ PointLight point_lights[MAX_LIGHT_CNT];
+ uint point_light_cnt;
+
+ float4 evaluate(in VertexData vertex_data)
+ {
+ float3 ambient_light =
+ this.calc_ambient_light(vertex_data.texture_coords);
+
+ float3 directional_light_sum = this.calc_dir_light_sum(vertex_data);
+ float3 point_light_sum = this.calc_point_light_sum(vertex_data);
+
+ return float4((ambient_light + directional_light_sum + point_light_sum), 1.0);
+ }
+
+ float3 calc_dir_light_sum(in VertexData vertex_data)
+ {
+ float3 directional_light_sum = float3(0.0, 0.0, 0.0);
+
+ for (uint index = 0; index < this.directional_light_cnt; index++)
+ {
+ CalculatedLight calculated_dir_light;
+
+ this.calc_light(
+ // Negated since we want the light to point from the light direction
+ normalize(-this.directional_lights[index].direction),
+ this.directional_lights[index].phong,
+ vertex_data,
+ calculated_dir_light);
+
+ directional_light_sum +=
+ calculated_dir_light.diffuse + calculated_dir_light.specular;
+ }
+
+ return directional_light_sum;
+ }
+
+ float3 calc_point_light_sum(in VertexData vertex_data)
+ {
+ float3 point_light_sum = float3(0.0, 0.0, 0.0);
+
+ for (uint index = 0; index < this.point_light_cnt; index++)
+ {
+ float3 light_direction =
+ normalize(this.point_lights[index].position - vertex_data.world_space_pos);
+
+ CalculatedLight calculated_point_light;
+
+ this.calc_light(
+ light_direction,
+ this.point_lights[index].phong,
+ vertex_data,
+ calculated_point_light);
+
+ float attenuation =
+ this.calc_attenuation(this.point_lights[index], vertex_data.world_space_pos);
+
+ calculated_point_light.diffuse *= attenuation;
+ calculated_point_light.specular *= attenuation;
+
+ point_light_sum +=
+ calculated_point_light.diffuse + calculated_point_light.specular;
+ }
+
+ return point_light_sum;
+ }
+
+ void calc_light(
+ in float3 light_direction,
+ in LightPhong light_phong,
+ in VertexData vertex_data,
+ out CalculatedLight calculated_light)
+ {
+ float3 norm = normalize(vertex_data.world_space_normal);
+
+ calculated_light.diffuse = this.calc_diffuse_light(
+ light_phong,
+ light_direction,
+ norm,
+ vertex_data.texture_coords);
+
+ calculated_light.specular = this.calc_specular_light(
+ light_phong,
+ light_direction,
+ norm,
+ vertex_data.world_space_pos,
+ vertex_data.texture_coords);
+ }
+
+ float3 calc_ambient_light(in float2 texture_coords)
+ {
+ return ambient_map.Sample(texture_coords).xyz * this.material.ambient;
+ // return this.material.ambient_map.Sample(texture_coords).xyz * this.material.ambient;
+ }
+
+ float3 calc_diffuse_light(
+ in LightPhong light_phong,
+ in float3 light_dir,
+ in float3 norm,
+ in float2 texture_coords)
+ {
+ float diff = max(dot(norm, light_dir), 0.0);
+
+ return light_phong.diffuse * (diff * (diffuse_map.Sample(texture_coords).xyz * this.material.diffuse));
+ // return light_phong.diffuse * (diff * (this.material.diffuse_map.Sample(texture_coords).xyz * this.material.diffuse));
+ }
+
+ float3 calc_specular_light(
+ in LightPhong light_phong,
+ in float3 light_dir,
+ in float3 norm,
+ in float3 frag_pos,
+ in float2 texture_coords)
+ {
+ float3 view_direction = normalize(this.view_pos - frag_pos);
+
+ float3 halfway_direction = normalize(light_dir + view_direction);
+
+ float spec =
+ pow(max(dot(norm, halfway_direction), 0.0), this.material.shininess);
+
+ return light_phong.specular * (spec * (specular_map.Sample(texture_coords).xyz * this.material.specular));
+ // return light_phong.specular * (spec * (this.material.specular_map.Sample(texture_coords).xyz * this.material.specular));
+ }
+
+ float calc_attenuation(in PointLight point_light, in float3 position)
+ {
+ float light_distance = length(point_light.position - position);
+
+ return 1.0 / (point_light.attenuation_props.constant + point_light.attenuation_props.linear * light_distance + point_light.attenuation_props.quadratic * pow(light_distance, 2));
+ }
+};
+
+struct Model3D
+{
+ float4x4 model;
+ float4x4 model_inverted;
+ float4x4 view;
+ float4x4 projection;
+}
+
+// ParameterBlock<BlinnPhongLighting> blinn_phong_lighting;
+
+// ConstantBuffer<BlinnPhongLighting> blinn_phong_lighting;
+
+// ConstantBuffer<Model3D> model_3d;
+
+// ParameterBlock<Model3D> model_3d;
+
+cbuffer Uniforms {
+ Model3D model_3d;
+ BlinnPhongLighting lighting;
+}
+
+Sampler2D ambient_map;
+Sampler2D diffuse_map;
+Sampler2D specular_map;
+
+
+struct VertexData
+{
+ float2 texture_coords;
+ float3 world_space_pos;
+ float3 world_space_normal;
+};
+
+struct VertexStageOutput
+{
+ VertexData vertex_data : VertexData;
+ float4 sv_position : SV_Position;
+};
+
+struct Vertex
+{
+ float3 pos;
+ float2 texture_coords;
+ float3 normal;
+};
+
+struct Fragment
+{
+ float4 color;
+};
+
+[shader("vertex")]
+VertexStageOutput vertex_main(
+ Vertex vertex,
+ // uniform ConstantBuffer<Model3D> model_3d
+)
+{
+ VertexStageOutput stage_output;
+
+ // TODO: Investigate why mul arguments need to be ordered this way.
+ // The mul arguments are reordered in the GLSL output
+
+ // float4x4 proj_view = mul(model_3d.projection, model_3d.view);
+ float4x4 proj_view = mul(model_3d.view, model_3d.projection);
+
+ // float4x4 proj_view_model =
+ // mul(proj_view, model_3d.model);
+
+ float4x4 proj_view_model =
+ mul(model_3d.model, proj_view);
+
+ // stage_output.sv_position = mul(proj_view_model, float4(vertex.pos, 1.0));
+
+ stage_output.sv_position = mul(float4(vertex.pos, 1.0), proj_view_model);
+
+ float4 vertex_pos = float4(vertex.pos, 1.0);
+
+ stage_output.vertex_data.world_space_pos =
+ float3(mul(model_3d.model, vertex_pos).xyz);
+
+ stage_output.vertex_data.texture_coords = vertex.texture_coords;
+
+ stage_output.vertex_data.world_space_normal =
+ mul(float3x3(transpose(model_3d.model_inverted)), vertex.normal);
+
+ return stage_output;
+}
+
+[shader("fragment")]
+Fragment fragment_main(
+ VertexData vertex_data: VertexData,
+ // uniform ConstantBuffer<BlinnPhongLighting> lighting
+) : SV_Target
+{
+ Fragment fragment;
+
+ fragment.color = lighting.evaluate(vertex_data);
+
+ // fragment.color = float4(1.0, 1.0, 1.0, 1.0);
+
+ return fragment;
+}
+
diff --git a/engine/src/asset.rs b/engine/src/asset.rs
index e78b6d8..b089b73 100644
--- a/engine/src/asset.rs
+++ b/engine/src/asset.rs
@@ -15,12 +15,24 @@ use std::sync::mpsc::{
channel as mpsc_channel,
};
-use ecs::Sole;
-use ecs::phase::PRE_UPDATE as PRE_UPDATE_PHASE;
+use ecs::pair::{ChildOf, Pair};
+use ecs::phase::{PRE_UPDATE as PRE_UPDATE_PHASE, Phase};
use ecs::sole::Single;
+use ecs::{Sole, declare_entity};
use crate::work_queue::{Work, WorkQueue};
+declare_entity!(
+ pub HANDLE_ASSETS_PHASE,
+ (
+ Phase,
+ Pair::builder()
+ .relation::<ChildOf>()
+ .target_id(*PRE_UPDATE_PHASE)
+ .build()
+ )
+);
+
/// Asset label.
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Label<'a>
@@ -125,6 +137,7 @@ pub struct Assets
import_work_queue: WorkQueue<ImportWorkUserData>,
import_work_msg_receiver: MpscReceiver<ImportWorkMessage>,
import_work_msg_sender: MpscSender<ImportWorkMessage>,
+ events: Events,
}
impl Assets
@@ -142,6 +155,7 @@ impl Assets
import_work_queue: WorkQueue::new(),
import_work_msg_receiver,
import_work_msg_sender,
+ events: Events::default(),
}
}
@@ -173,13 +187,15 @@ impl Assets
where
'this: 'handle,
{
- let LookupEntry::Occupied(asset_index) =
- *self.asset_lookup.borrow().get(&handle.id.label_hash)?
+ let asset_lookup = self.asset_lookup.borrow();
+
+ let LookupEntry::Occupied(asset_index, _) =
+ asset_lookup.get(&handle.id.label_hash)?
else {
return None;
};
- let stored_asset = self.assets.get(asset_index).expect("Not possible");
+ let stored_asset = self.assets.get(*asset_index).expect("Not possible");
let Some(asset) = stored_asset.strong.downcast_ref::<Asset>() else {
tracing::error!("Wrong asset type");
@@ -189,6 +205,68 @@ impl Assets
Some(asset)
}
+ #[tracing::instrument(skip_all, fields(asset_type=type_name::<Asset>()))]
+ pub fn get_handle_to_loaded<'label, Asset: 'static + Send + Sync>(
+ &self,
+ label: impl Into<Label<'label>>,
+ ) -> Option<Handle<Asset>>
+ {
+ let label = label.into();
+
+ let label_hash = LabelHash::new(&label);
+
+ let asset_lookup = self.asset_lookup.borrow();
+
+ let LookupEntry::Occupied(asset_index, _) = asset_lookup.get(&label_hash)? else {
+ return None;
+ };
+
+ let stored_asset = self.assets.get(*asset_index).expect("Not possible");
+
+ if stored_asset.strong.downcast_ref::<Asset>().is_none() {
+ tracing::error!("Wrong asset type");
+ return None;
+ };
+
+ Some(Handle::new(label_hash))
+ }
+
+ pub fn is_loaded_and_has_type<Asset: 'static + Send + Sync>(
+ &self,
+ handle: &Handle<Asset>,
+ ) -> bool
+ {
+ let asset_lookup = self.asset_lookup.borrow();
+
+ let Some(LookupEntry::Occupied(asset_index, _)) =
+ asset_lookup.get(&handle.id.label_hash)
+ else {
+ return false;
+ };
+
+ let stored_asset = self.assets.get(*asset_index).expect("Not possible");
+
+ stored_asset.strong.downcast_ref::<Asset>().is_some()
+ }
+
+ pub fn get_label<Asset: 'static + Send + Sync>(
+ &self,
+ handle: &Handle<Asset>,
+ ) -> Option<LabelOwned>
+ {
+ let lookup_entry = self
+ .asset_lookup
+ .borrow()
+ .get(&handle.id.label_hash)?
+ .clone();
+
+ let LookupEntry::Occupied(_, label) = lookup_entry else {
+ return None;
+ };
+
+ Some(label)
+ }
+
#[tracing::instrument(skip(self))]
pub fn load<'i, Asset: 'static + Send + Sync>(
&self,
@@ -216,9 +294,9 @@ impl Assets
return Handle::new(label_hash);
};
- match *lookup_entry {
- LookupEntry::Occupied(asset_index) => {
- let stored_asset = self.assets.get(asset_index).expect("Not possible");
+ match lookup_entry {
+ LookupEntry::Occupied(asset_index, _) => {
+ let stored_asset = self.assets.get(*asset_index).expect("Not possible");
if stored_asset.strong.downcast_ref::<Asset>().is_none() {
tracing::error!("Wrong asset type {}", type_name::<Asset>());
@@ -261,9 +339,9 @@ impl Assets
return Handle::new(label_hash);
};
- match *lookup_entry {
- LookupEntry::Occupied(asset_index) => {
- let stored_asset = self.assets.get(asset_index).expect("Not possible");
+ match lookup_entry {
+ LookupEntry::Occupied(asset_index, _) => {
+ let stored_asset = self.assets.get(*asset_index).expect("Not possible");
if stored_asset.strong.downcast_ref::<Asset>().is_none() {
tracing::error!(
@@ -323,7 +401,7 @@ impl Assets
if matches!(
self.asset_lookup.get_mut().get(&label_hash),
- Some(LookupEntry::Occupied(_))
+ Some(LookupEntry::Occupied(_, _))
) {
tracing::error!("Asset already exists");
@@ -338,11 +416,15 @@ impl Assets
self.asset_lookup
.get_mut()
- .insert(label_hash, LookupEntry::Occupied(index));
+ .insert(label_hash, LookupEntry::Occupied(index, label.to_owned()));
if label.name.is_some() {
- let parent_asset_label_hash =
- LabelHash::new(&Label { path: label.path, name: None });
+ let parent_asset_label = Label {
+ path: label.path.as_ref().into(),
+ name: None,
+ };
+
+ let parent_asset_label_hash = LabelHash::new(&parent_asset_label);
if matches!(
self.asset_lookup.get_mut().get(&parent_asset_label_hash),
@@ -360,14 +442,26 @@ impl Assets
self.asset_lookup.get_mut().insert(
parent_asset_label_hash,
- LookupEntry::Occupied(self.assets.len() - 1),
+ LookupEntry::Occupied(
+ self.assets.len() - 1,
+ parent_asset_label.to_owned(),
+ ),
);
}
}
+ self.events
+ .curr_tick_events
+ .push(Event::Stored(Id { label_hash }, label.to_owned()));
+
Handle::new(label_hash)
}
+ pub fn events(&self) -> &Events
+ {
+ &self.events
+ }
+
fn is_pending(asset_lookup: &HashMap<LabelHash, LookupEntry>, label: &Label) -> bool
{
if label.name.is_some() {
@@ -587,6 +681,11 @@ pub struct Handle<Asset: 'static>
impl<Asset: 'static> Handle<Asset>
{
+ pub fn from_id(id: Id) -> Self
+ {
+ Self { id, _pd: PhantomData }
+ }
+
pub fn id(&self) -> Id
{
self.id
@@ -616,6 +715,29 @@ pub struct Id
label_hash: LabelHash,
}
+#[derive(Debug, Default)]
+pub struct Events
+{
+ curr_tick_events: Vec<Event>,
+ last_tick_events: Vec<Event>,
+}
+
+impl Events
+{
+ pub fn last_tick_events(&self) -> impl Iterator<Item = &Event>
+ {
+ self.last_tick_events.iter()
+ }
+}
+
+/// Asset event.
+#[derive(Debug)]
+pub enum Event
+{
+ /// Asset stored.
+ Stored(Id, LabelOwned),
+}
+
#[derive(Debug, thiserror::Error)]
enum ImporterError
{
@@ -718,12 +840,20 @@ impl ecs::extension::Extension for Extension
{
let _ = collector.add_sole(self.assets);
- collector.add_system(*PRE_UPDATE_PHASE, add_received_assets);
+ collector.add_declared_entity(&HANDLE_ASSETS_PHASE);
+
+ collector.add_system(*HANDLE_ASSETS_PHASE, add_received_assets);
}
}
fn add_received_assets(mut assets: Single<Assets>)
{
+ let Events { curr_tick_events, last_tick_events } = &mut assets.events;
+
+ std::mem::swap(last_tick_events, curr_tick_events);
+
+ curr_tick_events.clear();
+
while let Some(import_work_msg) = assets.import_work_msg_receiver.try_recv().ok() {
match import_work_msg {
ImportWorkMessage::Store { do_store, label, asset } => {
@@ -770,10 +900,10 @@ enum ImportWorkMessage
},
}
-#[derive(Debug, Clone, Copy)]
+#[derive(Debug, Clone)]
enum LookupEntry
{
- Occupied(usize),
+ Occupied(usize, LabelOwned),
Pending,
}
diff --git a/engine/src/data_types/matrix.rs b/engine/src/data_types/matrix.rs
index b754b62..39aeea0 100644
--- a/engine/src/data_types/matrix.rs
+++ b/engine/src/data_types/matrix.rs
@@ -1,4 +1,6 @@
-use crate::vector::Vec3;
+use std::ops::Mul;
+
+use crate::vector::{Vec3, Vec4, VecN};
#[derive(Debug, Clone)]
pub struct Matrix<Value, const ROWS: usize, const COLUMNS: usize>
@@ -20,17 +22,24 @@ impl<Value, const ROWS: usize, const COLUMNS: usize> Matrix<Value, ROWS, COLUMNS
}
}
+ pub fn from_columns<ColumnVec>(columns: [ColumnVec; COLUMNS]) -> Self
+ where
+ ColumnVec: VecN<Value, ROWS>,
+ {
+ Self {
+ items: columns.map(|column| column.into_array()),
+ }
+ }
+
/// Sets the value at the specified cell.
pub fn set_cell(&mut self, row: usize, column: usize, value: Value)
{
self.items[column][row] = value;
}
- /// Returns the internal 2D array as a pointer.
- #[must_use]
- pub fn as_ptr(&self) -> *const Value
+ pub fn items(&self) -> &[[Value; ROWS]; COLUMNS]
{
- self.items[0].as_ptr()
+ &self.items
}
}
@@ -119,4 +128,155 @@ impl Matrix<f32, 4, 4>
self.set_cell(3, 3, 1.0);
}
+
+ pub fn inverse(&self) -> Self
+ {
+ let coef_00 =
+ self.items[2][2] * self.items[3][3] - self.items[3][2] * self.items[2][3];
+ let coef_02 =
+ self.items[1][2] * self.items[3][3] - self.items[3][2] * self.items[1][3];
+ let coef_03 =
+ self.items[1][2] * self.items[2][3] - self.items[2][2] * self.items[1][3];
+
+ let coef_04 =
+ self.items[2][1] * self.items[3][3] - self.items[3][1] * self.items[2][3];
+ let coef_06 =
+ self.items[1][1] * self.items[3][3] - self.items[3][1] * self.items[1][3];
+ let coef_07 =
+ self.items[1][1] * self.items[2][3] - self.items[2][1] * self.items[1][3];
+
+ let coef_08 =
+ self.items[2][1] * self.items[3][2] - self.items[3][1] * self.items[2][2];
+ let coef_10 =
+ self.items[1][1] * self.items[3][2] - self.items[3][1] * self.items[1][2];
+ let coef_11 =
+ self.items[1][1] * self.items[2][2] - self.items[2][1] * self.items[1][2];
+
+ let coef_12 =
+ self.items[2][0] * self.items[3][3] - self.items[3][0] * self.items[2][3];
+ let coef_14 =
+ self.items[1][0] * self.items[3][3] - self.items[3][0] * self.items[1][3];
+ let coef_15 =
+ self.items[1][0] * self.items[2][3] - self.items[2][0] * self.items[1][3];
+
+ let coef_16 =
+ self.items[2][0] * self.items[3][2] - self.items[3][0] * self.items[2][2];
+ let coef_18 =
+ self.items[1][0] * self.items[3][2] - self.items[3][0] * self.items[1][2];
+ let coef_19 =
+ self.items[1][0] * self.items[2][2] - self.items[2][0] * self.items[1][2];
+
+ let coef_20 =
+ self.items[2][0] * self.items[3][1] - self.items[3][0] * self.items[2][1];
+ let coef_22 =
+ self.items[1][0] * self.items[3][1] - self.items[3][0] * self.items[1][1];
+ let coef_23 =
+ self.items[1][0] * self.items[2][1] - self.items[2][0] * self.items[1][1];
+
+ let fac_0 = Vec4 {
+ x: coef_00,
+ y: coef_00,
+ z: coef_02,
+ w: coef_03,
+ };
+ let fac_1 = Vec4 {
+ x: coef_04,
+ y: coef_04,
+ z: coef_06,
+ w: coef_07,
+ };
+ let fac_2 = Vec4 {
+ x: coef_08,
+ y: coef_08,
+ z: coef_10,
+ w: coef_11,
+ };
+ let fac_3 = Vec4 {
+ x: coef_12,
+ y: coef_12,
+ z: coef_14,
+ w: coef_15,
+ };
+ let fac_4 = Vec4 {
+ x: coef_16,
+ y: coef_16,
+ z: coef_18,
+ w: coef_19,
+ };
+ let fac_5 = Vec4 {
+ x: coef_20,
+ y: coef_20,
+ z: coef_22,
+ w: coef_23,
+ };
+
+ let vec_0 = Vec4 {
+ x: self.items[1][0],
+ y: self.items[0][0],
+ z: self.items[0][0],
+ w: self.items[0][0],
+ };
+ let vec_1 = Vec4 {
+ x: self.items[1][1],
+ y: self.items[0][1],
+ z: self.items[0][1],
+ w: self.items[0][1],
+ };
+ let vec_2 = Vec4 {
+ x: self.items[1][2],
+ y: self.items[0][2],
+ z: self.items[0][2],
+ w: self.items[0][2],
+ };
+ let vec_3 = Vec4 {
+ x: self.items[1][3],
+ y: self.items[0][3],
+ z: self.items[0][3],
+ w: self.items[0][3],
+ };
+
+ let inv_0 = vec_1 * fac_0 - vec_2 * fac_1 + vec_3 * fac_2;
+ let inv_1 = vec_0 * fac_0 - vec_2 * fac_3 + vec_3 * fac_4;
+ let inv_2 = vec_0 * fac_1 - vec_1 * fac_3 + vec_3 * fac_5;
+ let inv_3 = vec_0 * fac_2 - vec_1 * fac_4 + vec_2 * fac_5;
+
+ let sign_a = Vec4 { x: 1.0, y: -1.0, z: 1.0, w: -1.0 };
+ let sign_b = Vec4 { x: -1.0, y: 1.0, z: -1.0, w: 1.0 };
+
+ let inverse = Self::from_columns([
+ inv_0 * sign_a,
+ inv_1 * sign_b,
+ inv_2 * sign_a,
+ inv_3 * sign_b,
+ ]);
+
+ let row_0 = Vec4 {
+ x: inverse.items[0][0],
+ y: inverse.items[1][0],
+ z: inverse.items[2][0],
+ w: inverse.items[3][0],
+ };
+
+ let dot_0 = Vec4::<f32>::from(self.items[0]) * row_0;
+
+ let dot_1 = (dot_0.x + dot_0.y) + (dot_0.z + dot_0.w);
+
+ let one_over_determinant = 1.0 / dot_1;
+
+ inverse * one_over_determinant
+ }
+}
+
+impl Mul<f32> for Matrix<f32, 4, 4>
+{
+ type Output = Self;
+
+ fn mul(self, scalar: f32) -> Self::Output
+ {
+ Self {
+ items: self
+ .items
+ .map(|column| (Vec4::from(column) * scalar).into_array()),
+ }
+ }
}
diff --git a/engine/src/data_types/vector.rs b/engine/src/data_types/vector.rs
index dc6df30..1a4e49e 100644
--- a/engine/src/data_types/vector.rs
+++ b/engine/src/data_types/vector.rs
@@ -2,6 +2,12 @@ use std::ops::{Add, AddAssign, Div, Mul, Neg, Sub, SubAssign};
use crate::color::Color;
+/// A vector of `Value`s with `N` number of elements.
+pub trait VecN<Value, const N: usize>: sealed::Sealed
+{
+ fn into_array(self) -> [Value; N];
+}
+
#[derive(Debug, Default, Clone, Copy, PartialEq)]
pub struct Vec2<Value>
{
@@ -97,6 +103,16 @@ where
}
}
+impl<Value> VecN<Value, 2> for Vec2<Value>
+{
+ fn into_array(self) -> [Value; 2]
+ {
+ [self.x, self.y]
+ }
+}
+
+impl<Value> sealed::Sealed for Vec2<Value> {}
+
#[derive(Debug, Default, Clone, Copy, PartialEq)]
pub struct Vec3<Value>
{
@@ -365,3 +381,128 @@ impl<Value> From<Color<Value>> for Vec3<Value>
}
}
}
+
+impl<Value> VecN<Value, 3> for Vec3<Value>
+{
+ fn into_array(self) -> [Value; 3]
+ {
+ [self.x, self.y, self.z]
+ }
+}
+
+impl<Value> sealed::Sealed for Vec3<Value> {}
+
+#[derive(Debug, Default, Clone, Copy, PartialEq)]
+pub struct Vec4<Value>
+{
+ pub x: Value,
+ pub y: Value,
+ pub z: Value,
+ pub w: Value,
+}
+
+impl<Value> Mul for Vec4<Value>
+where
+ Value: Mul<Value, Output = Value>,
+{
+ type Output = Self;
+
+ fn mul(self, rhs: Self) -> Self::Output
+ {
+ Self::Output {
+ x: self.x * rhs.x,
+ y: self.y * rhs.y,
+ z: self.z * rhs.z,
+ w: self.w * rhs.w,
+ }
+ }
+}
+
+impl<Value> Add for Vec4<Value>
+where
+ Value: Add<Value, Output = Value>,
+{
+ type Output = Self;
+
+ fn add(self, rhs: Self) -> Self::Output
+ {
+ Self::Output {
+ x: self.x + rhs.x,
+ y: self.y + rhs.y,
+ z: self.z + rhs.z,
+ w: self.w + rhs.w,
+ }
+ }
+}
+
+impl<Value> Sub for Vec4<Value>
+where
+ Value: Sub<Value, Output = Value>,
+{
+ type Output = Self;
+
+ fn sub(self, rhs: Self) -> Self::Output
+ {
+ Self::Output {
+ x: self.x - rhs.x,
+ y: self.y - rhs.y,
+ z: self.z - rhs.z,
+ w: self.w - rhs.w,
+ }
+ }
+}
+
+impl<Value> Mul<Value> for Vec4<Value>
+where
+ Value: Mul<Value, Output = Value> + Clone,
+{
+ type Output = Self;
+
+ fn mul(mut self, rhs: Value) -> Self::Output
+ {
+ self.x = self.x * rhs.clone();
+ self.y = self.y * rhs.clone();
+ self.z = self.z * rhs.clone();
+ self.w = self.w * rhs.clone();
+
+ self
+ }
+}
+
+impl<Value: Clone> From<Value> for Vec4<Value>
+{
+ fn from(value: Value) -> Self
+ {
+ Self {
+ x: value.clone(),
+ y: value.clone(),
+ z: value.clone(),
+ w: value,
+ }
+ }
+}
+
+impl<Value> From<[Value; 4]> for Vec4<Value>
+{
+ fn from(values: [Value; 4]) -> Self
+ {
+ let [x, y, z, w] = values;
+
+ Self { x, y, z, w }
+ }
+}
+
+impl<Value> VecN<Value, 4> for Vec4<Value>
+{
+ fn into_array(self) -> [Value; 4]
+ {
+ [self.x, self.y, self.z, self.w]
+ }
+}
+
+impl<Value> sealed::Sealed for Vec4<Value> {}
+
+mod sealed
+{
+ pub trait Sealed {}
+}
diff --git a/engine/src/lib.rs b/engine/src/lib.rs
index b470cdc..560d288 100644
--- a/engine/src/lib.rs
+++ b/engine/src/lib.rs
@@ -13,8 +13,8 @@ use ecs::{SoleAlreadyExistsError, World};
use crate::asset::{Assets, Extension as AssetExtension};
use crate::delta_time::{DeltaTime, LastUpdate, update as update_delta_time};
+use crate::shader::Extension as ShaderExtension;
-mod opengl;
mod util;
mod work_queue;
@@ -35,6 +35,7 @@ pub mod model;
pub mod projection;
pub mod reflection;
pub mod renderer;
+pub mod shader;
pub mod texture;
pub mod transform;
pub mod windowing;
@@ -76,9 +77,11 @@ impl Engine
crate::model::asset::add_importers(&mut assets);
crate::material::asset::add_importers(&mut assets);
+ crate::shader::add_asset_importers(&mut assets);
crate::image::set_asset_importers(&mut assets);
world.add_extension(AssetExtension { assets });
+ world.add_extension(ShaderExtension);
Self { world }
}
diff --git a/engine/src/material.rs b/engine/src/material.rs
index 2b9a8ca..94ab24e 100644
--- a/engine/src/material.rs
+++ b/engine/src/material.rs
@@ -21,9 +21,9 @@ pub struct Material
impl Material
{
- pub fn builder() -> Builder
+ pub const fn builder() -> Builder
{
- Builder::default()
+ Builder::new()
}
}
@@ -51,7 +51,7 @@ pub struct Builder
impl Builder
{
#[must_use]
- pub fn new() -> Self
+ pub const fn new() -> Self
{
Self {
ambient: Color::WHITE_F32,
@@ -125,7 +125,7 @@ impl Builder
/// # Panics
/// Will panic if no ambient map, diffuse map or specular map is set.
#[must_use]
- pub fn build(self) -> Material
+ pub const fn build(self) -> Material
{
Material {
ambient: self.ambient,
@@ -149,8 +149,8 @@ impl Default for Builder
builder! {
/// Material flags.
-#[builder(name = FlagsBuilder, derives = (Debug, Default, Clone))]
-#[derive(Debug, Default, Clone, Component)]
+#[builder(name = FlagsBuilder, derives = (Debug, Clone))]
+#[derive(Debug, Clone, Component)]
#[non_exhaustive]
pub struct Flags
{
@@ -163,8 +163,32 @@ pub struct Flags
impl Flags
{
#[must_use]
- pub fn builder() -> FlagsBuilder
+ pub const fn builder() -> FlagsBuilder
{
- FlagsBuilder::default()
+ FlagsBuilder::new()
+ }
+}
+
+impl Default for Flags
+{
+ fn default() -> Self
+ {
+ Self::builder().build()
+ }
+}
+
+impl FlagsBuilder
+{
+ pub const fn new() -> Self
+ {
+ Self { use_ambient_color: false }
+ }
+}
+
+impl Default for FlagsBuilder
+{
+ fn default() -> Self
+ {
+ Self::new()
}
}
diff --git a/engine/src/model.rs b/engine/src/model.rs
index 4c88f8f..ebf623f 100644
--- a/engine/src/model.rs
+++ b/engine/src/model.rs
@@ -3,7 +3,7 @@ use std::collections::HashMap;
use ecs::Component;
-use crate::asset::Handle as AssetHandle;
+use crate::asset::{Assets, Handle as AssetHandle};
use crate::material::Material;
use crate::material::asset::Map as MaterialAssetMap;
use crate::mesh::Mesh;
@@ -40,6 +40,38 @@ impl Spec
{
SpecBuilder::default()
}
+
+ pub fn find_first_material<'assets>(
+ &'assets self,
+ assets: &'assets Assets,
+ ) -> MaterialSearchResult<'assets>
+ {
+ let Some(material_name) = self.material_names.first() else {
+ return MaterialSearchResult::NoMaterials;
+ };
+
+ let material_asset = match &self.materials {
+ Materials::Maps(material_asset_map_assets) => material_asset_map_assets
+ .iter()
+ .find_map(|mat_asset_map_asset| {
+ let mat_asset_map = assets.get(mat_asset_map_asset)?;
+
+ mat_asset_map.assets.get(material_name)
+ }),
+ Materials::Direct(material_assets) => material_assets.get(material_name),
+ };
+
+ let Some(material_asset) = material_asset else {
+ return MaterialSearchResult::NotFound;
+ };
+
+ if assets.get(material_asset).is_none() {
+ tracing::trace!("Missing material asset");
+ return MaterialSearchResult::NotFound;
+ }
+
+ MaterialSearchResult::Found(material_asset)
+ }
}
#[derive(Debug, Default, Clone)]
@@ -152,3 +184,10 @@ impl Default for Materials
Self::Maps(Vec::new())
}
}
+
+pub enum MaterialSearchResult<'a>
+{
+ Found(&'a AssetHandle<Material>),
+ NotFound,
+ NoMaterials,
+}
diff --git a/engine/src/opengl/glsl.rs b/engine/src/opengl/glsl.rs
deleted file mode 100644
index 6fd5638..0000000
--- a/engine/src/opengl/glsl.rs
+++ /dev/null
@@ -1,616 +0,0 @@
-use std::borrow::Cow;
-use std::fmt::{Display, Formatter};
-use std::path::{Path, PathBuf};
-use std::string::FromUtf8Error;
-
-const PREINCLUDE_DIRECTIVE: &str = "#preinclude";
-
-pub fn preprocess<'content>(
- shader_content: impl Into<Cow<'content, str>>,
- read_file: &impl Fn(&Path) -> Result<Vec<u8>, std::io::Error>,
-) -> Result<Cow<'content, str>, PreprocessingError>
-{
- do_preprocess(shader_content, SpanPath::Original, read_file)
-}
-
-fn do_preprocess<'content>(
- shader_content: impl Into<Cow<'content, str>>,
- shader_path: SpanPath<'_>,
- read_file: &impl Fn(&Path) -> Result<Vec<u8>, std::io::Error>,
-) -> Result<Cow<'content, str>, PreprocessingError>
-{
- let shader_content = shader_content.into();
-
- let mut preincludes = shader_content
- .match_indices(PREINCLUDE_DIRECTIVE)
- .peekable();
-
- if preincludes.peek().is_none() {
- // Shader content contains no preincludes
- return Ok(shader_content.into());
- };
-
- let mut preprocessed = shader_content.to_string();
-
- let mut curr = shader_content.find(PREINCLUDE_DIRECTIVE);
-
- let mut last_start = 0;
- let mut span_line_offset = 0;
-
- while let Some(preinclude_start) = curr {
- let replacement_job = handle_preinclude(
- &preprocessed,
- &shader_path,
- preinclude_start,
- span_line_offset,
- )?;
-
- let path = replacement_job.path.clone();
-
- let mut included =
- String::from_utf8(read_file(&replacement_job.path).map_err(|err| {
- PreprocessingError::ReadIncludedShaderFailed {
- source: err,
- path: replacement_job.path.clone(),
- }
- })?)
- .map_err(|err| {
- PreprocessingError::IncludedShaderInvalidUtf8 {
- source: err,
- path: path.clone(),
- }
- })?;
-
- if let Some(first_line) = included.lines().next() {
- if first_line.starts_with("#version") {
- included = included
- .chars()
- .skip_while(|character| *character != '\n')
- .collect();
- }
- }
-
- let included_preprocessed = do_preprocess(
- &included,
- SpanPath::Path(replacement_job.path.as_path().into()),
- read_file,
- )?;
-
- let start = replacement_job.start_index;
- let end = replacement_job.end_index;
-
- preprocessed.replace_range(start..end, &included_preprocessed);
-
- curr = preprocessed[last_start + 1..]
- .find(PREINCLUDE_DIRECTIVE)
- .map(|index| index + 1);
-
- last_start = preinclude_start + included_preprocessed.len();
-
- span_line_offset += included_preprocessed.lines().count();
- }
-
- Ok(preprocessed.into())
-}
-
-fn handle_preinclude(
- shader_content: &str,
- shader_path: &SpanPath<'_>,
- preinclude_start_index: usize,
- span_line_offset: usize,
-) -> Result<ReplacementJob, PreprocessingError>
-{
- let expect_token = |token: char, index: usize| {
- let token_found = shader_content.chars().nth(index).ok_or_else(|| {
- PreprocessingError::ExpectedToken {
- expected: token,
- span: Span::new(
- shader_content,
- shader_path.to_owned(),
- index,
- span_line_offset,
- preinclude_start_index,
- ),
- }
- })?;
-
- if token_found != token {
- return Err(PreprocessingError::InvalidToken {
- expected: token,
- found: token_found,
- span: Span::new(
- shader_content,
- shader_path.to_owned(),
- index,
- span_line_offset,
- preinclude_start_index,
- ),
- });
- }
-
- Ok(())
- };
-
- let space_index = preinclude_start_index + PREINCLUDE_DIRECTIVE.len();
- let quote_open_index = space_index + 1;
-
- expect_token(' ', space_index)?;
- expect_token('"', quote_open_index)?;
-
- let buf = shader_content[quote_open_index + 1..]
- .chars()
- .take_while(|character| *character != '"')
- .map(|character| character as u8)
- .collect::<Vec<_>>();
-
- if buf.is_empty() {
- return Err(PreprocessingError::ExpectedToken {
- expected: '"',
- span: Span::new(
- shader_content,
- shader_path.to_owned(),
- shader_content.len() - 1,
- span_line_offset,
- preinclude_start_index,
- ),
- });
- }
-
- let path_len = buf.len();
-
- let path = PathBuf::from(String::from_utf8(buf).map_err(|err| {
- PreprocessingError::PreincludePathInvalidUtf8 {
- source: err,
- span: Span::new(
- shader_content,
- shader_path.to_owned(),
- quote_open_index + 1,
- span_line_offset,
- preinclude_start_index,
- ),
- }
- })?);
-
- Ok(ReplacementJob {
- start_index: preinclude_start_index,
- end_index: quote_open_index + 1 + path_len + 1,
- path,
- })
-}
-
-struct ReplacementJob
-{
- start_index: usize,
- end_index: usize,
- path: PathBuf,
-}
-
-#[derive(Debug, thiserror::Error)]
-pub enum PreprocessingError
-{
- #[error(
- "Invalid token at line {}, column {} of {}. Expected '{}', found '{}'",
- span.line,
- span.column,
- span.path,
- expected,
- found
- )]
- InvalidToken
- {
- expected: char,
- found: char,
- span: Span,
- },
-
- #[error(
- "Expected token '{}' at line {}, column {} of {}. Found eof",
- expected,
- span.line,
- span.column,
- span.path
- )]
- ExpectedToken
- {
- expected: char, span: Span
- },
-
- #[error(
- "Preinclude path at line {}, column {} of {} is invalid UTF-8",
- span.line,
- span.column,
- span.path
- )]
- PreincludePathInvalidUtf8
- {
- #[source]
- source: FromUtf8Error,
- span: Span,
- },
-
- #[error("Failed to read included shader")]
- ReadIncludedShaderFailed
- {
- #[source]
- source: std::io::Error,
- path: PathBuf,
- },
-
- #[error("Included shader is not valid UTF-8")]
- IncludedShaderInvalidUtf8
- {
- #[source]
- source: FromUtf8Error,
- path: PathBuf,
- },
-}
-
-#[derive(Debug, Clone, PartialEq, Eq)]
-#[non_exhaustive]
-pub struct Span
-{
- pub line: usize,
- pub column: usize,
- pub path: SpanPath<'static>,
-}
-
-impl Span
-{
- fn new(
- file_content: &str,
- path: SpanPath<'static>,
- char_index: usize,
- line_offset: usize,
- line_start_index: usize,
- ) -> Self
- {
- let line = find_line_of_index(file_content, char_index) + 1
- - line_offset.saturating_sub(1);
-
- Self {
- line,
- column: char_index - line_start_index + 1,
- path,
- }
- }
-}
-
-#[derive(Debug, Clone, PartialEq, Eq)]
-pub enum SpanPath<'a>
-{
- Original,
- Path(Cow<'a, Path>),
-}
-
-impl<'a> SpanPath<'a>
-{
- fn to_owned(&self) -> SpanPath<'static>
- {
- match self {
- Self::Original => SpanPath::Original,
- Self::Path(path) => SpanPath::Path(Cow::Owned(path.to_path_buf().into())),
- }
- }
-}
-
-impl<'a> Display for SpanPath<'a>
-{
- fn fmt(&self, formatter: &mut Formatter<'_>) -> std::fmt::Result
- {
- match self {
- Self::Original => write!(formatter, "original file"),
- Self::Path(path) => write!(formatter, "file {}", path.display()),
- }
- }
-}
-
-impl<'a, PathLike> PartialEq<PathLike> for SpanPath<'a>
-where
- PathLike: AsRef<Path>,
-{
- fn eq(&self, other: &PathLike) -> bool
- {
- match self {
- Self::Original => false,
- Self::Path(path) => path == other.as_ref(),
- }
- }
-}
-
-fn find_line_of_index(text: &str, index: usize) -> usize
-{
- text.chars()
- .take(index + 1)
- .enumerate()
- .filter(|(_, character)| *character == '\n')
- .count()
-}
-
-#[cfg(test)]
-mod tests
-{
- use std::ffi::OsStr;
- use std::path::Path;
-
- use super::{preprocess, PreprocessingError};
- use crate::opengl::glsl::SpanPath;
-
- #[test]
- fn preprocess_no_directives_is_same()
- {
- assert_eq!(
- preprocess("#version 330 core\n", &|_| { unreachable!() }).unwrap(),
- "#version 330 core\n"
- );
- }
-
- #[test]
- fn preprocess_with_directives_works()
- {
- assert_eq!(
- preprocess(
- concat!(
- "#version 330 core\n",
- "\n",
- "#preinclude \"foo.glsl\"\n",
- "\n",
- "void main() {}",
- ),
- &|_| { Ok(b"out vec4 FragColor;".to_vec()) }
- )
- .unwrap(),
- concat!(
- "#version 330 core\n",
- "\n",
- "out vec4 FragColor;\n",
- "\n",
- "void main() {}",
- )
- );
-
- assert_eq!(
- preprocess(
- concat!(
- "#version 330 core\n",
- "\n",
- "#preinclude \"bar.glsl\"\n",
- "\n",
- "in vec3 in_frag_color;\n",
- "\n",
- "void main() {}",
- ),
- &|_| { Ok(b"out vec4 FragColor;".to_vec()) }
- )
- .unwrap(),
- concat!(
- "#version 330 core\n",
- "\n",
- "out vec4 FragColor;\n",
- "\n",
- "in vec3 in_frag_color;\n",
- "\n",
- "void main() {}",
- )
- );
-
- assert_eq!(
- preprocess(
- concat!(
- "#version 330 core\n",
- "\n",
- "#preinclude \"bar.glsl\"\n",
- "\n",
- "in vec3 in_frag_color;\n",
- "\n",
- "#preinclude \"foo.glsl\"\n",
- "\n",
- "void main() {}",
- ),
- &|path| {
- if path == OsStr::new("bar.glsl") {
- Ok(b"out vec4 FragColor;".to_vec())
- } else {
- Ok(concat!(
- "uniform sampler2D input_texture;\n",
- "in vec2 in_texture_coords;"
- )
- .as_bytes()
- .to_vec())
- }
- },
- )
- .unwrap(),
- concat!(
- "#version 330 core\n",
- "\n",
- "out vec4 FragColor;\n",
- "\n",
- "in vec3 in_frag_color;\n",
- "\n",
- "uniform sampler2D input_texture;\n",
- "in vec2 in_texture_coords;\n",
- "\n",
- "void main() {}",
- )
- );
- }
-
- #[test]
- fn preprocess_invalid_directive_does_not_work()
- {
- let res = preprocess(
- concat!(
- "#version 330 core\n",
- "\n",
- // Missing "
- "#preinclude foo.glsl\"\n",
- "\n",
- "void main() {}",
- ),
- &|_| Ok(b"out vec4 FragColor;".to_vec()),
- );
-
- let Err(PreprocessingError::InvalidToken { expected, found, span }) = res else {
- panic!(
- "Expected result to be Err(Error::InvalidToken {{ ... }}), is {res:?}"
- );
- };
-
- assert_eq!(expected, '"');
- assert_eq!(found, 'f');
- assert_eq!(span.line, 3);
- assert_eq!(span.column, 13);
- assert_eq!(span.path, SpanPath::Original);
- }
-
- #[test]
- fn preprocess_error_has_correct_span()
- {
- let res = preprocess(
- concat!(
- "#version 330 core\n",
- "\n",
- "#preinclude \"bar.glsl\"\n",
- "\n",
- "#preinclude \"foo.glsl\"\n",
- "\n",
- "in vec3 in_frag_color;\n",
- "\n",
- "void main() {}",
- ),
- &|path| {
- if path == OsStr::new("bar.glsl") {
- Ok(concat!(
- "out vec4 FragColor;\n",
- "in vec2 in_texture_coords;\n",
- "in float foo;"
- )
- .as_bytes()
- .to_vec())
- } else if path == OsStr::new("foo.glsl") {
- Ok(concat!(
- "uniform sampler2D input_texture;\n",
- "\n",
- // Missing space before first "
- "#preinclude\"shared_types.glsl\"\n",
- )
- .as_bytes()
- .to_vec())
- } else {
- panic!(concat!(
- "Expected read function to be called with ",
- "either path bar.glsl or foo.glsl"
- ));
- }
- },
- );
-
- let Err(PreprocessingError::InvalidToken { expected, found, span }) = res else {
- panic!(
- "Expected result to be Err(Error::InvalidToken {{ ... }}), is {res:?}"
- );
- };
-
- assert_eq!(expected, ' ');
- assert_eq!(found, '"');
- assert_eq!(span.line, 3);
- assert_eq!(span.column, 12);
- assert_eq!(span.path, SpanPath::Path(Path::new("foo.glsl").into()));
- }
-
- #[test]
- fn preprocess_included_shader_with_include_works()
- {
- assert_eq!(
- preprocess(
- concat!(
- "#version 330 core\n",
- "\n",
- "#preinclude \"bar.glsl\"\n",
- "\n",
- "in vec3 in_frag_color;\n",
- "\n",
- "void main() {}",
- ),
- &|path| {
- if path == OsStr::new("bar.glsl") {
- Ok(concat!(
- "#preinclude \"foo.glsl\"\n",
- "\n",
- "out vec4 FragColor;"
- )
- .as_bytes()
- .to_vec())
- } else {
- Ok(concat!(
- "uniform sampler2D input_texture;\n",
- "in vec2 in_texture_coords;"
- )
- .as_bytes()
- .to_vec())
- }
- }
- )
- .unwrap(),
- concat!(
- "#version 330 core\n",
- "\n",
- "uniform sampler2D input_texture;\n",
- "in vec2 in_texture_coords;\n",
- "\n",
- "out vec4 FragColor;\n",
- "\n",
- "in vec3 in_frag_color;\n",
- "\n",
- "void main() {}",
- )
- );
- }
-
- #[test]
- fn preprocess_included_shader_with_include_error_span_is_correct()
- {
- let res = preprocess(
- concat!(
- "#version 330 core\n",
- "\n",
- "#preinclude \"bar.glsl\"\n",
- "\n",
- "in vec3 in_frag_color;\n",
- "\n",
- "void main() {}",
- ),
- &|path| {
- if path == OsStr::new("bar.glsl") {
- Ok(concat!(
- // ' instead of "
- "#preinclude 'foo.glsl\"\n",
- "\n",
- "out vec4 FragColor;"
- )
- .as_bytes()
- .to_vec())
- } else {
- Ok(concat!(
- "uniform sampler2D input_texture;\n",
- "in vec2 in_texture_coords;"
- )
- .as_bytes()
- .to_vec())
- }
- },
- );
-
- let Err(PreprocessingError::InvalidToken { expected, found, span }) = res else {
- panic!(
- "Expected result to be Err(Error::InvalidToken {{ ... }}), is {res:?}"
- );
- };
-
- assert_eq!(expected, '"');
- assert_eq!(found, '\'');
- assert_eq!(span.line, 1);
- assert_eq!(span.column, 13);
- assert_eq!(span.path, Path::new("bar.glsl"));
- }
-}
diff --git a/engine/src/opengl/mod.rs b/engine/src/opengl/mod.rs
deleted file mode 100644
index 2208ac6..0000000
--- a/engine/src/opengl/mod.rs
+++ /dev/null
@@ -1 +0,0 @@
-pub mod glsl;
diff --git a/engine/src/renderer.rs b/engine/src/renderer.rs
index 2a66a68..6c20102 100644
--- a/engine/src/renderer.rs
+++ b/engine/src/renderer.rs
@@ -1,5 +1,7 @@
use std::any::type_name;
use std::collections::VecDeque;
+use std::path::Path;
+use std::sync::LazyLock;
use std::sync::atomic::{AtomicU64, Ordering};
use bitflags::bitflags;
@@ -10,18 +12,28 @@ use ecs::query::term::Without;
use ecs::sole::Single;
use ecs::{Component, Query, declare_entity};
-use crate::asset::{Assets, Handle as AssetHandle};
+use crate::asset::{Assets, Handle as AssetHandle, Label as AssetLabel};
use crate::builder;
-use crate::camera::{Active as ActiveCamera, Camera};
+use crate::color::Color;
use crate::data_types::dimens::Dimens;
use crate::draw_flags::{DrawFlags, NoDraw, PolygonModeConfig};
-use crate::lighting::{DirectionalLight, GlobalLight, PointLight};
-use crate::material::{Flags as MaterialFlags, Material};
+use crate::image::Image;
use crate::mesh::Mesh;
-use crate::model::{Materials as ModelMaterials, Model, Spec as ModelSpec};
+use crate::model::{MaterialSearchResult, Model};
use crate::renderer::object::{Id as ObjectId, Store as ObjectStore};
-use crate::texture::Texture;
-use crate::transform::{Scale, Transform, WorldPosition};
+use crate::shader::cursor::{
+ BindingLocation as ShaderBindingLocation,
+ BindingValue as ShaderBindingValue,
+ Cursor as ShaderCursor,
+};
+use crate::shader::default::ASSET_LABEL as DEFAULT_SHADER_ASSET_LABEL;
+use crate::shader::{
+ Context as ShaderContext,
+ ModuleSource as ShaderModuleSource,
+ Program as ShaderProgram,
+ Shader,
+};
+use crate::texture::{Properties as TextureProperties, Texture};
use crate::windowing::window::Window;
pub mod object;
@@ -29,8 +41,14 @@ pub mod opengl;
static NEXT_SURFACE_ID: AtomicU64 = AtomicU64::new(0);
+pub static DEFAULT_TEXTURE_ASSET_LABEL: LazyLock<AssetLabel> =
+ LazyLock::new(|| AssetLabel {
+ path: Path::new("").into(),
+ name: Some("default_texture".into()),
+ });
+
declare_entity!(
- pub RENDER_PHASE,
+ pub PRE_RENDER_PHASE,
(
Phase,
Pair::builder()
@@ -40,6 +58,17 @@ declare_entity!(
)
);
+declare_entity!(
+ pub RENDER_PHASE,
+ (
+ Phase,
+ Pair::builder()
+ .relation::<ChildOf>()
+ .target_id(*PRE_RENDER_PHASE)
+ .build()
+ )
+);
+
builder! {
/// Window graphics properties.
#[builder(name=GraphicsPropertiesBuilder, derives=(Debug, Clone))]
@@ -131,23 +160,10 @@ pub enum Command
MakeCurrent(SurfaceId),
ClearBuffers(BufferClearMask),
SwapBuffers(SurfaceId),
- UseShader, // TODO: Add ability to specify shader
- ActivateShader,
- UseCamera(Camera, WorldPosition),
- ApplyTransform
- {
- transform: Transform,
- window_size: Dimens<u32>,
- },
- SetShaderDirectionalLights(Vec<DirectionalLight>),
- SetShaderPointLights(Vec<(PointLight, WorldPosition)>),
+ CreateShaderProgram(ObjectId, ShaderProgram),
+ ActivateShader(ObjectId),
+ SetShaderBinding(ShaderBindingLocation, ShaderBindingValue),
CreateTexture(Texture),
- UseMaterial
- {
- material_asset: Option<AssetHandle<Material>>,
- material_flags: MaterialFlags,
- global_light: GlobalLight,
- },
DrawMesh
{
mesh_asset: AssetHandle<Mesh>,
@@ -202,12 +218,19 @@ pub struct CtxUsedByWindow;
type RenderableEntity<'a> = (
&'a Model,
- Option<&'a MaterialFlags>,
- Option<&'a WorldPosition>,
- Option<&'a Scale>,
Option<&'a DrawFlags>,
+ Option<&'a Shader>,
+ Option<&'a mut PendingShaderBindings>,
);
+pub fn init(mut assets: Single<Assets>)
+{
+ assets.store_with_label(
+ DEFAULT_TEXTURE_ASSET_LABEL.clone(),
+ Image::from_color(Dimens { width: 1, height: 1 }, Color::WHITE_U8),
+ );
+}
+
#[tracing::instrument(skip_all)]
pub fn enqueue_commands(
renderer_ctx_query: Query<(
@@ -216,16 +239,15 @@ pub fn enqueue_commands(
&[Pair<CtxUsedByWindow, Wildcard>],
)>,
renderable_query: Query<RenderableEntity<'_>, (Without<NoDraw>,)>,
- point_light_query: Query<(&PointLight, &WorldPosition)>,
- directional_light_query: Query<(&DirectionalLight,)>,
- camera_query: Query<(&Camera, &WorldPosition, &ActiveCamera)>,
- global_light: Single<GlobalLight>,
assets: Single<Assets>,
+ shader_context: Single<ShaderContext>,
mut actions: Actions,
)
{
- let Some((camera, camera_world_pos, _)) = camera_query.iter().next() else {
- tracing::warn!("No current camera. Nothing will be rendered");
+ let Some(default_shader_asset) = assets
+ .get_handle_to_loaded::<ShaderModuleSource>(DEFAULT_SHADER_ASSET_LABEL.clone())
+ else {
+ tracing::error!("Default shader asset is not loaded");
return;
};
@@ -240,7 +262,7 @@ pub fn enqueue_commands(
continue;
};
- let Some(window) = window_ent.get::<Window>() else {
+ if window_ent.get::<Window>().is_none() {
tracing::debug!(
window_entity_id=%window_ent_id,
"Window entity does not have a {} component",
@@ -270,15 +292,38 @@ pub fn enqueue_commands(
command_queue.push(Command::MakeCurrent(surface_spec.id));
- command_queue
- .push(Command::UseCamera(camera.clone(), camera_world_pos.clone()));
+ let default_texture_asset = assets
+ .get_handle_to_loaded::<Image>(DEFAULT_TEXTURE_ASSET_LABEL.clone())
+ .expect("Not possible");
+
+ if !object_store
+ .contains_with_id(&ObjectId::Asset(default_texture_asset.id()))
+ {
+ command_queue.push(Command::CreateTexture(Texture {
+ asset_handle: default_texture_asset,
+ properties: TextureProperties::default(),
+ }));
+ }
command_queue.push(Command::ClearBuffers(
BufferClearMask::COLOR | BufferClearMask::DEPTH,
));
- for (model, material_flags, world_pos, scale, draw_flags) in &renderable_query
+ for (model, draw_flags, shader, mut pending_shader_bindings) in
+ &renderable_query
{
+ let shader_asset = match &shader {
+ Some(shader) => &shader.asset_handle,
+ None => &default_shader_asset,
+ };
+
+ if pending_shader_bindings.as_ref().map_or_else(
+ || true,
+ |pending_shader_bindings| pending_shader_bindings.bindings.is_empty(),
+ ) {
+ continue;
+ }
+
let Some(model_spec) = assets.get(&model.spec_asset) else {
continue;
};
@@ -289,103 +334,69 @@ pub fn enqueue_commands(
debug_assert!(model_spec.material_names.len() <= 1);
- let model_material_asset =
- match find_first_model_material(model_spec, &assets) {
- MaterialSearchResult::Found(model_material_asset) => {
- Some(model_material_asset.clone())
- }
- MaterialSearchResult::NotFound => {
- continue;
- }
- MaterialSearchResult::NoMaterials => None,
- };
+ let model_material_asset = match model_spec.find_first_material(&assets) {
+ MaterialSearchResult::Found(model_material_asset) => {
+ model_material_asset.clone()
+ // Some(model_material_asset.clone())
+ }
+ MaterialSearchResult::NotFound
+ | MaterialSearchResult::NoMaterials => {
+ // MaterialSearchResult::NotFound => {
+ continue;
+ } // MaterialSearchResult::NoMaterials => None,
+ };
- if let Some(model_material_asset) = &model_material_asset {
- let Some(model_material) = assets.get(model_material_asset) else {
- unreachable!();
+ if !object_store.contains_with_id(&ObjectId::Asset(shader_asset.id())) {
+ let Some(shader_program) =
+ shader_context.get_program(&shader_asset.id())
+ else {
+ tracing::error!(
+ "Shader context doesn't have a program for shader asset {:?}",
+ assets.get_label(&shader_asset)
+ );
+ continue;
};
- if let Some(ambient_map) = &model_material.ambient_map {
- if assets.get(&ambient_map.asset_handle).is_none() {
- continue;
- }
- }
+ command_queue.push(Command::CreateShaderProgram(
+ ObjectId::Asset(shader_asset.id()),
+ shader_program.clone(),
+ ));
+ }
- if let Some(diffuse_map) = &model_material.diffuse_map {
- if assets.get(&diffuse_map.asset_handle).is_none() {
- continue;
- }
- }
+ command_queue
+ .push(Command::ActivateShader(ObjectId::Asset(shader_asset.id())));
- if let Some(specular_map) = &model_material.specular_map {
- if assets.get(&specular_map.asset_handle).is_none() {
- continue;
- }
+ let Some(model_material) = assets.get(&model_material_asset) else {
+ // TODO: Handle this case since it may occur
+ unreachable!();
+ };
+
+ for texture in [
+ &model_material.ambient_map,
+ &model_material.diffuse_map,
+ &model_material.specular_map,
+ ]
+ .into_iter()
+ .flatten()
+ {
+ if !object_store
+ .contains_with_id(&ObjectId::Asset(texture.asset_handle.id()))
+ {
+ command_queue.push(Command::CreateTexture(texture.clone()));
}
}
- command_queue.push(Command::UseShader);
-
- command_queue.push(Command::ApplyTransform {
- transform: Transform {
- position: world_pos
- .as_deref()
- .cloned()
- .unwrap_or_default()
- .position,
- scale: scale.as_deref().cloned().unwrap_or_default().scale,
- },
- window_size: *window.inner_size(),
- });
-
- command_queue.push(Command::SetShaderDirectionalLights(
- directional_light_query
- .iter()
- .map(|(dir_light,)| dir_light.clone())
- .collect::<Vec<_>>(),
- ));
-
- command_queue.push(Command::SetShaderPointLights(
- point_light_query
- .iter()
- .map(|(point_light, point_light_world_pos)| {
- (point_light.clone(), point_light_world_pos.clone())
- })
- .collect::<Vec<_>>(),
- ));
-
- if let Some(model_material_asset) = &model_material_asset {
- let Some(model_material) = assets.get(model_material_asset) else {
- unreachable!();
- };
-
- for texture in [
- &model_material.specular_map,
- &model_material.diffuse_map,
- &model_material.specular_map,
- ]
- .into_iter()
- .flatten()
+ if let Some(pending_shader_bindings) = &mut pending_shader_bindings {
+ for (shader_binding_loc, shader_binding_val) in
+ pending_shader_bindings.bindings.drain(..)
{
- if !object_store
- .contains_with_id(&ObjectId::Asset(texture.asset_handle.id()))
- {
- command_queue.push(Command::CreateTexture(texture.clone()));
- }
+ command_queue.push(Command::SetShaderBinding(
+ shader_binding_loc,
+ shader_binding_val,
+ ));
}
}
- command_queue.push(Command::UseMaterial {
- material_asset: model_material_asset,
- material_flags: material_flags
- .as_deref()
- .cloned()
- .unwrap_or_default(),
- global_light: global_light.clone(),
- });
-
- command_queue.push(Command::ActivateShader);
-
if let Some(draw_flags) = draw_flags.as_deref()
&& draw_flags.polygon_mode_config != PolygonModeConfig::default()
{
@@ -410,39 +421,23 @@ pub fn enqueue_commands(
}
}
-enum MaterialSearchResult<'a>
+#[derive(Default, Clone, Component)]
+pub struct PendingShaderBindings
{
- Found(&'a AssetHandle<Material>),
- NotFound,
- NoMaterials,
+ pub bindings: Vec<(ShaderBindingLocation, ShaderBindingValue)>,
}
-fn find_first_model_material<'assets>(
- model_spec: &'assets ModelSpec,
- assets: &'assets Assets,
-) -> MaterialSearchResult<'assets>
+impl<'a> Extend<(ShaderCursor<'a>, ShaderBindingValue)> for PendingShaderBindings
{
- let Some(material_name) = model_spec.material_names.first() else {
- return MaterialSearchResult::NoMaterials;
- };
-
- let Some(material_asset) = (match &model_spec.materials {
- ModelMaterials::Maps(material_asset_map_assets) => material_asset_map_assets
- .iter()
- .find_map(|mat_asset_map_asset| {
- let mat_asset_map = assets.get(mat_asset_map_asset)?;
-
- mat_asset_map.assets.get(material_name)
- }),
- ModelMaterials::Direct(material_assets) => material_assets.get(material_name),
- }) else {
- return MaterialSearchResult::NotFound;
- };
-
- if assets.get(material_asset).is_none() {
- tracing::trace!("Missing material asset");
- return MaterialSearchResult::NotFound;
+ fn extend<Iter: IntoIterator<Item = (ShaderCursor<'a>, ShaderBindingValue)>>(
+ &mut self,
+ iter: Iter,
+ )
+ {
+ self.bindings.extend(iter.into_iter().map(
+ |(shader_cursor, shader_binding_val)| {
+ (shader_cursor.into_binding_location(), shader_binding_val)
+ },
+ ))
}
-
- MaterialSearchResult::Found(material_asset)
}
diff --git a/engine/src/renderer/object.rs b/engine/src/renderer/object.rs
index d8bb2e3..357bd6a 100644
--- a/engine/src/renderer/object.rs
+++ b/engine/src/renderer/object.rs
@@ -38,6 +38,17 @@ impl Store
Some(obj)
}
+ pub fn get_shader_program_obj(&self, id: &Id) -> Option<&Object>
+ {
+ let obj = self.get_obj(id)?;
+
+ if !matches!(obj.kind(), Kind::ShaderProgram) {
+ return None;
+ }
+
+ Some(obj)
+ }
+
pub fn contains_with_id(&self, id: &Id) -> bool
{
self.objects.contains_key(id)
@@ -108,5 +119,5 @@ impl Object
pub enum Kind
{
Texture,
- Mesh,
+ ShaderProgram,
}
diff --git a/engine/src/renderer/opengl.rs b/engine/src/renderer/opengl.rs
index 4bd67a4..c72a344 100644
--- a/engine/src/renderer/opengl.rs
+++ b/engine/src/renderer/opengl.rs
@@ -1,16 +1,14 @@
//! OpenGL renderer.
use std::any::type_name;
+use std::borrow::Cow;
use std::collections::HashMap;
-use std::ffi::CString;
-use std::io::{Error as IoError, ErrorKind as IoErrorKind};
-use std::path::Path;
use ecs::actions::Actions;
use ecs::entity::obtainer::Obtainer as EntityObtainer;
use ecs::event::component::{Changed, Removed};
use ecs::pair::{ChildOf, Pair, Wildcard};
-use ecs::phase::Phase;
+use ecs::phase::{Phase, START as START_PHASE};
use ecs::query::term::Without;
use ecs::sole::Single;
use ecs::system::observer::Observe;
@@ -44,6 +42,7 @@ use opengl_bindings::shader::{
Kind as ShaderKind,
Program as GlShaderProgram,
Shader as GlShader,
+ // UniformLocation as GlUniformLocation,
};
use opengl_bindings::texture::{
Filtering as GlTextureFiltering,
@@ -59,21 +58,13 @@ use opengl_bindings::vertex_array::{
};
use opengl_bindings::{ContextWithFns, CurrentContextWithFns};
use safer_ffi::layout::ReprC;
+use zerocopy::{Immutable, IntoBytes};
use crate::asset::{Assets, Id as AssetId};
-use crate::camera::Camera;
-use crate::color::Color;
use crate::data_types::dimens::Dimens;
use crate::image::{ColorType as ImageColorType, Image};
-use crate::lighting::{DirectionalLight, GlobalLight, PointLight};
-use crate::material::{Flags as MaterialFlags, Material};
use crate::matrix::Matrix;
use crate::model::Model;
-use crate::opengl::glsl::{
- PreprocessingError as GlslPreprocessingError,
- preprocess as glsl_preprocess,
-};
-use crate::projection::{ClipVolume, Projection};
use crate::renderer::object::{
Id as RendererObjectId,
Kind as RendererObjectKind,
@@ -91,17 +82,23 @@ use crate::renderer::{
CommandQueue as RendererCommandQueue,
CtxUsedByWindow as RendererCtxUsedByWindow,
GraphicsProperties,
+ PRE_RENDER_PHASE,
RENDER_PHASE,
SurfaceId,
SurfaceSpec,
WindowUsingRendererCtx,
};
+use crate::shader::cursor::BindingValue as ShaderBindingValue;
+use crate::shader::{
+ Error as ShaderError,
+ Program as ShaderProgram,
+ Stage as ShaderStage,
+};
use crate::texture::{
Filtering as TextureFiltering,
Properties as TextureProperties,
Wrapping as TextureWrapping,
};
-use crate::transform::WorldPosition;
use crate::vector::{Vec2, Vec3};
use crate::windowing::Context as WindowingContext;
use crate::windowing::window::{
@@ -115,12 +112,6 @@ mod glutin_compat;
mod graphics_mesh;
mod vertex;
-const AMBIENT_MAP_TEXTURE_UNIT: u32 = 0;
-const DIFFUSE_MAP_TEXTURE_UNIT: u32 = 1;
-const SPECULAR_MAP_TEXTURE_UNIT: u32 = 2;
-
-const DEFAULT_TEXTURE_OBJECT_ID: RendererObjectId = RendererObjectId::Other(0xaa);
-
declare_entity!(
pub POST_RENDER_PHASE,
(Phase, Pair::builder().relation::<ChildOf>().target_id(*RENDER_PHASE).build())
@@ -136,9 +127,9 @@ struct WindowGlConfig
struct GraphicsContext
{
gl_context: ContextWithFns,
- shader_program: Option<GlShaderProgram>,
graphics_mesh_store: GraphicsMeshStore,
surfaces: HashMap<SurfaceId, GlutinSurface<GlutinWindowSurface>>,
+ uniform_buffer_objs: HashMap<u32, opengl_bindings::buffer::Buffer<u8>>,
}
#[derive(Debug, Default)]
@@ -155,9 +146,12 @@ impl ecs::extension::Extension for Extension
{
fn collect(self, mut collector: ecs::extension::Collector<'_>)
{
+ collector.add_declared_entity(&PRE_RENDER_PHASE);
collector.add_declared_entity(&RENDER_PHASE);
collector.add_declared_entity(&POST_RENDER_PHASE);
+ collector.add_system(*START_PHASE, super::init);
+
collector.add_system(*RENDER_PHASE, super::enqueue_commands);
collector.add_system(*RENDER_PHASE, handle_commands);
@@ -617,9 +611,9 @@ fn init_window_graphics(
let renderer_ctx_ent_id = actions.spawn((
GraphicsContext {
gl_context,
- shader_program: None,
graphics_mesh_store: GraphicsMeshStore::default(),
surfaces: HashMap::from([(surface_id, surface)]),
+ uniform_buffer_objs: HashMap::new(),
},
RendererObjectStore::default(),
RendererCommandQueue::default(),
@@ -657,17 +651,17 @@ fn handle_commands(
{
let GraphicsContext {
ref gl_context,
- ref mut shader_program,
ref mut graphics_mesh_store,
ref surfaces,
+ ref mut uniform_buffer_objs,
} = *graphics_ctx;
let mut opt_curr_gl_ctx: Option<CurrentContextWithFns> = None;
- let mut opt_curr_camera: Option<(Camera, WorldPosition)> = None;
+ let mut activated_gl_shader_program: Option<GlShaderProgram> = None;
for command in command_queue.drain() {
- let tracing_span = tracing::info_span!("handle_cmd", command = ?command);
+ let tracing_span = tracing::info_span!("handle_cmd");
let _tracing_span_enter = tracing_span.enter();
match command {
@@ -724,89 +718,148 @@ fn handle_commands(
tracing::error!("Failed to swap buffers: {err}");
}
}
- RendererCommand::UseShader => {
+ RendererCommand::CreateShaderProgram(
+ shader_program_obj_id,
+ shader_program,
+ ) => {
let Some(curr_gl_ctx) = &opt_curr_gl_ctx else {
tracing::error!("No GL context is current");
continue;
};
- let _shader_program = shader_program.get_or_insert_with(|| {
- create_default_shader_program(&curr_gl_ctx).unwrap()
- });
- }
- RendererCommand::ActivateShader => {
- let Some(curr_gl_ctx) = &opt_curr_gl_ctx else {
- tracing::error!("No GL context is current");
+ if renderer_object_store.contains_with_id(&shader_program_obj_id) {
+ tracing::error!(
+ object_id=?shader_program_obj_id,
+ "Object store already contains a object with this ID"
+ );
continue;
- };
+ }
- let Some(shader_program) = shader_program else {
- tracing::error!("Shader does not exist");
- continue;
- };
+ let gl_shader_program =
+ match create_shader_program(&curr_gl_ctx, &shader_program) {
+ Ok(gl_shader_program) => gl_shader_program,
+ Err(err) => {
+ tracing::error!("Failed to create shader program: {err}");
+ continue;
+ }
+ };
- shader_program.activate(&curr_gl_ctx);
- }
- RendererCommand::UseCamera(camera, camera_world_pos) => {
- opt_curr_camera = Some((camera, camera_world_pos));
+ renderer_object_store.insert(
+ shader_program_obj_id,
+ RendererObject::from_raw(
+ gl_shader_program.into_raw(),
+ RendererObjectKind::ShaderProgram,
+ ),
+ );
}
- RendererCommand::ApplyTransform { transform, window_size } => {
+ RendererCommand::ActivateShader(shader_program_obj_id) => {
let Some(curr_gl_ctx) = &opt_curr_gl_ctx else {
tracing::error!("No GL context is current");
continue;
};
- let Some(shader_program) = shader_program else {
- tracing::error!("Shader does not exist");
+ let Some(shader_program_obj) = renderer_object_store
+ .get_shader_program_obj(&shader_program_obj_id)
+ else {
+ tracing::error!(
+ "Shader object does not exist or has a wrong kind"
+ );
continue;
};
- let Some((camera, camera_world_pos)) = &opt_curr_camera else {
- tracing::error!("No current camera");
- continue;
- };
+ let gl_shader_program =
+ GlShaderProgram::from_raw(shader_program_obj.as_raw());
- apply_transformation_matrices(
- &curr_gl_ctx,
- Transformation {
- position: transform.position,
- scale: transform.scale,
- },
- shader_program,
- &camera,
- &camera_world_pos,
- &window_size,
- );
+ gl_shader_program.activate(&curr_gl_ctx);
+
+ activated_gl_shader_program = Some(gl_shader_program);
}
- RendererCommand::SetShaderDirectionalLights(directional_lights) => {
+ RendererCommand::SetShaderBinding(binding_location, binding_value) => {
let Some(curr_gl_ctx) = &opt_curr_gl_ctx else {
tracing::error!("No GL context is current");
continue;
};
- let Some(shader_program) = shader_program else {
- tracing::error!("Shader does not exist");
+ if activated_gl_shader_program.is_none() {
+ tracing::error!("No shader program is activated");
continue;
- };
+ }
- set_shader_directional_lights(
- curr_gl_ctx,
- shader_program,
- &directional_lights,
- );
- }
- RendererCommand::SetShaderPointLights(point_lights) => {
- let Some(curr_gl_ctx) = &opt_curr_gl_ctx else {
- tracing::error!("No GL context is current");
- continue;
- };
+ if let ShaderBindingValue::Texture(texture_asset) = &binding_value {
+ let Some(texture_obj) = renderer_object_store.get_texture_obj(
+ &RendererObjectId::Asset(texture_asset.id()),
+ ) else {
+ tracing::error!(
+ "Texture {:?} does not exist in renderer object store",
+ assets.get_label(texture_asset)
+ );
+ continue;
+ };
+
+ let gl_texture = GlTexture::from_raw(texture_obj.as_raw());
+
+ gl_texture.bind_to_texture_unit(
+ curr_gl_ctx,
+ binding_location.binding_index,
+ );
+
+ // gl_shader_program.set_uniform_at_location(
+ // curr_gl_ctx,
+ // GlUniformLocation::from_number(
+ // binding_location.binding_index as i32,
+ // ),
+ // &binding_location.binding_index,
+ // );
- let Some(shader_program) = shader_program else {
- tracing::error!("Shader does not exist");
continue;
- };
+ }
- set_shader_point_lights(curr_gl_ctx, shader_program, &point_lights);
+ let binding_index = binding_location.binding_index;
+
+ let uniform_buffer =
+ uniform_buffer_objs.entry(binding_index).or_insert_with(|| {
+ let uniform_buf =
+ opengl_bindings::buffer::Buffer::<u8>::new(curr_gl_ctx);
+
+ uniform_buf
+ .init(
+ curr_gl_ctx,
+ binding_location.binding_size,
+ opengl_bindings::buffer::Usage::Dynamic,
+ )
+ .unwrap();
+
+ uniform_buf.bind_to_indexed_target(
+ curr_gl_ctx,
+ opengl_bindings::buffer::BindingTarget::UniformBuffer,
+ binding_index as u32,
+ );
+
+ uniform_buf
+ });
+
+ let fvec3_value;
+
+ uniform_buffer
+ .store_at_byte_offset(
+ curr_gl_ctx,
+ binding_location.byte_offset,
+ match binding_value {
+ ShaderBindingValue::Uint(ref value) => value.as_bytes(),
+ ShaderBindingValue::Int(ref value) => value.as_bytes(),
+ ShaderBindingValue::Float(ref value) => value.as_bytes(),
+ ShaderBindingValue::FVec3(value) => {
+ fvec3_value = CF32Vec3::from(value);
+
+ fvec3_value.as_bytes()
+ }
+ ShaderBindingValue::FMat4x4(ref value) => {
+ value.items().as_bytes()
+ }
+ ShaderBindingValue::Texture(_) => unreachable!(),
+ },
+ )
+ .unwrap();
}
RendererCommand::CreateTexture(texture) => {
let Some(curr_gl_ctx) = &opt_curr_gl_ctx else {
@@ -815,6 +868,7 @@ fn handle_commands(
};
let Some(texture_image) = assets.get(&texture.asset_handle) else {
+ tracing::error!("Texture asset is not loaded",);
continue;
};
@@ -828,58 +882,14 @@ fn handle_commands(
tracing::error!("Failed to create texture object: {err}");
}
}
- RendererCommand::UseMaterial {
- material_asset,
- material_flags,
- global_light,
- } => {
- let Some(curr_gl_ctx) = &opt_curr_gl_ctx else {
- tracing::error!("No GL context is current");
- continue;
- };
-
- let Some(shader_program) = shader_program else {
- tracing::error!("Shader does not exist");
- continue;
- };
-
- let Some((_, camera_world_pos)) = &opt_curr_camera else {
- tracing::error!("No current camera");
- continue;
- };
-
- let material = match material_asset.as_ref() {
- Some(material_asset) => {
- let Some(material) = assets.get(&material_asset) else {
- continue;
- };
-
- material
- }
- None => &Material::default(),
- };
-
- set_shader_material(
- curr_gl_ctx,
- material,
- &material_flags,
- &global_light,
- shader_program,
- camera_world_pos,
- );
-
- bind_material_textures(
- curr_gl_ctx,
- material,
- &mut renderer_object_store,
- );
- }
RendererCommand::DrawMesh { mesh_asset } => {
let Some(curr_gl_ctx) = &opt_curr_gl_ctx else {
tracing::error!("No GL context is current");
continue;
};
+ // tracing::warn!("ARGH! Drawing mesh");
+
let graphics_mesh =
match graphics_mesh_store.graphics_meshes.get(&mesh_asset.id()) {
Some(graphics_mesh) => graphics_mesh,
@@ -930,21 +940,6 @@ fn handle_commands(
}
}
-fn create_default_texture(current_context: &CurrentContextWithFns<'_>) -> GlTexture
-{
- match create_gl_texture(
- current_context,
- &Image::from_color(Dimens { width: 1, height: 1 }, Color::WHITE_U8),
- &TextureProperties::default(),
- ) {
- Ok(gl_texture) => gl_texture,
- Err(
- GlTextureGenerateError::SizeWidthValueTooLarge { value: _, max_value: _ }
- | GlTextureGenerateError::SizeHeightValueTooLarge { value: _, max_value: _ },
- ) => unreachable!(),
- }
-}
-
#[tracing::instrument(skip_all)]
fn create_texture_object(
curr_gl_ctx: &CurrentContextWithFns<'_>,
@@ -975,53 +970,6 @@ fn create_texture_object(
Ok(())
}
-fn bind_material_textures(
- current_context: &CurrentContextWithFns<'_>,
- material: &Material,
- renderer_object_store: &mut RendererObjectStore,
-)
-{
- let material_texture_maps = [
- (&material.ambient_map, AMBIENT_MAP_TEXTURE_UNIT),
- (&material.diffuse_map, DIFFUSE_MAP_TEXTURE_UNIT),
- (&material.specular_map, SPECULAR_MAP_TEXTURE_UNIT),
- ];
-
- for (texture, texture_unit) in material_texture_maps {
- let Some(texture) = texture else {
- let default_texture_obj = renderer_object_store
- .entry(DEFAULT_TEXTURE_OBJECT_ID)
- .or_insert_with(|| {
- RendererObject::from_raw(
- create_default_texture(current_context).into_raw(),
- RendererObjectKind::Texture,
- )
- });
-
- let gl_texture = GlTexture::from_raw(default_texture_obj.as_raw());
-
- gl_texture.bind_to_texture_unit(current_context, texture_unit);
-
- continue;
- };
-
- let texture_object_id = RendererObjectId::Asset(texture.asset_handle.id());
-
- let Some(texture_obj) = renderer_object_store.get_texture_obj(&texture_object_id)
- else {
- tracing::error!(
- texture_object_id=?texture_object_id,
- "Texture object does not exist"
- );
- continue;
- };
-
- let gl_texture = GlTexture::from_raw(texture_obj.as_raw());
-
- gl_texture.bind_to_texture_unit(current_context, texture_unit);
- }
-}
-
fn set_viewport(
current_context: &CurrentContextWithFns<'_>,
position: Vec2<u32>,
@@ -1104,21 +1052,60 @@ fn create_gl_texture(
Ok(gl_texture)
}
-const VERTEX_GLSL_SHADER_SRC: &str = include_str!("opengl/glsl/vertex.glsl");
-const FRAGMENT_GLSL_SHADER_SRC: &str = include_str!("opengl/glsl/fragment.glsl");
-
-const VERTEX_DATA_GLSL_SHADER_SRC: &str = include_str!("opengl/glsl/vertex_data.glsl");
-const LIGHT_GLSL_SHADER_SRC: &str = include_str!("opengl/glsl/light.glsl");
-
-fn create_default_shader_program(
+fn create_shader_program(
current_context: &CurrentContextWithFns<'_>,
+ shader_program: &ShaderProgram,
) -> Result<GlShaderProgram, CreateShaderError>
{
+ let shader_program_reflection = shader_program.reflection(0).expect("Not possible");
+
+ let (vs_entry_point_index, vs_entry_point_reflection) = shader_program_reflection
+ .entry_points()
+ .enumerate()
+ .find(|(_, entry_point)| entry_point.stage() == ShaderStage::Vertex)
+ .ok_or_else(|| {
+ CreateShaderError::NoShaderStageEntrypointFound(ShaderStage::Vertex)
+ })?;
+
+ let vertex_shader_entry_point_code = shader_program
+ .get_entry_point_code(vs_entry_point_index.try_into().expect(
+ "Vertex shader entry point index does not fit in 32-bit unsigned int",
+ ))
+ .map_err(|err| CreateShaderError::GetShaderEntryPointCodeFailed {
+ err,
+ stage: ShaderStage::Vertex,
+ entrypoint: vs_entry_point_reflection
+ .name()
+ .map(|name| name.to_string().into())
+ .unwrap_or("(none)".into()),
+ })?;
+
+ let (fs_entry_point_index, fs_entry_point_reflection) = shader_program_reflection
+ .entry_points()
+ .enumerate()
+ .find(|(_, entry_point)| entry_point.stage() == ShaderStage::Fragment)
+ .ok_or_else(|| {
+ CreateShaderError::NoShaderStageEntrypointFound(ShaderStage::Fragment)
+ })?;
+
+ let fragment_shader_entry_point_code = shader_program
+ .get_entry_point_code(fs_entry_point_index.try_into().expect(
+ "Fragment shader entry point index does not fit in 32-bit unsigned int",
+ ))
+ .map_err(|err| CreateShaderError::GetShaderEntryPointCodeFailed {
+ err,
+ stage: ShaderStage::Fragment,
+ entrypoint: fs_entry_point_reflection
+ .name()
+ .map(|name| name.to_string().into())
+ .unwrap_or("(none)".into()),
+ })?;
+
let vertex_shader = GlShader::new(current_context, ShaderKind::Vertex);
vertex_shader.set_source(
current_context,
- &*glsl_preprocess(VERTEX_GLSL_SHADER_SRC, &get_glsl_shader_content)?,
+ &vertex_shader_entry_point_code.as_str().unwrap(),
)?;
vertex_shader.compile(current_context)?;
@@ -1127,7 +1114,7 @@ fn create_default_shader_program(
fragment_shader.set_source(
current_context,
- &*glsl_preprocess(FRAGMENT_GLSL_SHADER_SRC, &get_glsl_shader_content)?,
+ &fragment_shader_entry_point_code.as_str().unwrap(),
)?;
fragment_shader.compile(current_context)?;
@@ -1145,340 +1132,22 @@ fn create_default_shader_program(
#[derive(Debug, thiserror::Error)]
enum CreateShaderError
{
- #[error(transparent)]
- ShaderError(#[from] GlShaderError),
-
- #[error(transparent)]
- PreprocessingError(#[from] GlslPreprocessingError),
-}
-
-fn get_glsl_shader_content(path: &Path) -> Result<Vec<u8>, std::io::Error>
-{
- if path == Path::new("vertex_data.glsl") {
- return Ok(VERTEX_DATA_GLSL_SHADER_SRC.as_bytes().to_vec());
- }
-
- if path == Path::new("light.glsl") {
- return Ok(LIGHT_GLSL_SHADER_SRC.as_bytes().to_vec());
- }
-
- Err(IoError::new(
- IoErrorKind::NotFound,
- format!("Content for shader file {} not found", path.display()),
- ))
-}
-
-fn apply_transformation_matrices(
- current_context: &CurrentContextWithFns<'_>,
- transformation: Transformation,
- gl_shader_program: &mut GlShaderProgram,
- camera: &Camera,
- camera_world_pos: &WorldPosition,
- window_size: &Dimens<u32>,
-)
-{
- gl_shader_program.set_uniform(
- current_context,
- c"model",
- &opengl_bindings::data_types::Matrix {
- items: create_transformation_matrix(transformation).items,
- },
- );
-
- let view_matrix = create_view_matrix(camera, &camera_world_pos.position);
-
- gl_shader_program.set_uniform(
- current_context,
- c"view",
- &opengl_bindings::data_types::Matrix { items: view_matrix.items },
- );
-
- #[allow(clippy::cast_precision_loss)]
- let proj_matrix = match &camera.projection {
- Projection::Perspective(perspective_proj) => perspective_proj.to_matrix_rh(
- window_size.width as f32 / window_size.height as f32,
- ClipVolume::NegOneToOne,
- ),
- Projection::Orthographic(orthographic_proj) => orthographic_proj
- .to_matrix_rh(&camera_world_pos.position, ClipVolume::NegOneToOne),
- };
-
- gl_shader_program.set_uniform(
- current_context,
- c"projection",
- &opengl_bindings::data_types::Matrix { items: proj_matrix.items },
- );
-}
-
-fn set_shader_directional_lights(
- curr_gl_ctx: &CurrentContextWithFns<'_>,
- gl_shader_program: &mut GlShaderProgram,
- directional_lights: &[DirectionalLight],
-)
-{
- debug_assert!(
- directional_lights.len() < 64,
- "Shader cannot handle more than 64 directional lights"
- );
-
- for (dir_light_index, dir_light) in directional_lights.iter().enumerate() {
- let direction: opengl_bindings::data_types::Vec3<_> = dir_light.direction.into();
-
- gl_shader_program.set_uniform(
- curr_gl_ctx,
- &create_light_uniform_name(
- "directional_lights",
- dir_light_index,
- "direction",
- ),
- &direction,
- );
-
- set_light_phong_uniforms(
- curr_gl_ctx,
- gl_shader_program,
- "directional_lights",
- dir_light_index,
- dir_light,
- );
- }
-
- // There probably won't be more than 2147483648 directional lights
- #[allow(clippy::cast_possible_wrap, clippy::cast_possible_truncation)]
- gl_shader_program.set_uniform(
- curr_gl_ctx,
- c"directional_light_cnt",
- &(directional_lights.len() as i32),
- );
-}
-
-fn set_shader_point_lights(
- curr_gl_ctx: &CurrentContextWithFns<'_>,
- gl_shader_program: &mut GlShaderProgram,
- point_lights: &[(PointLight, WorldPosition)],
-)
-{
- debug_assert!(
- point_lights.len() < 64,
- "Shader cannot handle more than 64 point lights"
- );
-
- for (point_light_index, (point_light, point_light_world_pos)) in
- point_lights.iter().enumerate()
- {
- let pos: opengl_bindings::data_types::Vec3<_> =
- (point_light_world_pos.position + point_light.local_position).into();
-
- gl_shader_program.set_uniform(
- curr_gl_ctx,
- &create_light_uniform_name("point_lights", point_light_index, "position"),
- &pos,
- );
-
- set_light_phong_uniforms(
- curr_gl_ctx,
- gl_shader_program,
- "point_lights",
- point_light_index,
- &*point_light,
- );
-
- set_light_attenuation_uniforms(
- curr_gl_ctx,
- gl_shader_program,
- "point_lights",
- point_light_index,
- &*point_light,
- );
- }
-
- // There probably won't be more than 2147483648 point lights
- #[allow(clippy::cast_possible_wrap, clippy::cast_possible_truncation)]
- gl_shader_program.set_uniform(
- curr_gl_ctx,
- c"point_light_cnt",
- &(point_lights.len() as i32),
- );
-}
-
-fn set_shader_material(
- curr_gl_ctx: &CurrentContextWithFns<'_>,
- material: &Material,
- material_flags: &MaterialFlags,
- global_light: &GlobalLight,
- gl_shader_program: &mut GlShaderProgram,
- camera_world_pos: &WorldPosition,
-)
-{
- let ambient: opengl_bindings::data_types::Vec3<_> =
- Vec3::from(if material_flags.use_ambient_color {
- material.ambient.clone()
- } else {
- global_light.ambient.clone()
- })
- .into();
-
- gl_shader_program.set_uniform(curr_gl_ctx, c"material.ambient", &ambient);
-
- let diffuse: opengl_bindings::data_types::Vec3<_> =
- Vec3::from(material.diffuse.clone()).into();
-
- gl_shader_program.set_uniform(curr_gl_ctx, c"material.diffuse", &diffuse);
-
- let specular: opengl_bindings::data_types::Vec3<_> =
- Vec3::from(material.specular.clone()).into();
-
- #[allow(clippy::cast_possible_wrap)]
- gl_shader_program.set_uniform(curr_gl_ctx, c"material.specular", &specular);
-
- #[allow(clippy::cast_possible_wrap)]
- gl_shader_program.set_uniform(
- curr_gl_ctx,
- c"material.ambient_map",
- &(AMBIENT_MAP_TEXTURE_UNIT as i32),
- );
-
- #[allow(clippy::cast_possible_wrap)]
- gl_shader_program.set_uniform(
- curr_gl_ctx,
- c"material.diffuse_map",
- &(DIFFUSE_MAP_TEXTURE_UNIT as i32),
- );
-
- #[allow(clippy::cast_possible_wrap)]
- gl_shader_program.set_uniform(
- curr_gl_ctx,
- c"material.specular_map",
- &(SPECULAR_MAP_TEXTURE_UNIT as i32),
- );
-
- gl_shader_program.set_uniform(
- curr_gl_ctx,
- c"material.shininess",
- &material.shininess,
- );
-
- let view_pos: opengl_bindings::data_types::Vec3<_> = camera_world_pos.position.into();
-
- gl_shader_program.set_uniform(curr_gl_ctx, c"view_pos", &view_pos);
-}
-
-fn set_light_attenuation_uniforms(
- current_context: &CurrentContextWithFns<'_>,
- gl_shader_program: &mut GlShaderProgram,
- light_array: &str,
- light_index: usize,
- light: &PointLight,
-)
-{
- gl_shader_program.set_uniform(
- current_context,
- &create_light_uniform_name(
- light_array,
- light_index,
- "attenuation_props.constant",
- ),
- &light.attenuation_params.constant,
- );
-
- gl_shader_program.set_uniform(
- current_context,
- &create_light_uniform_name(light_array, light_index, "attenuation_props.linear"),
- &light.attenuation_params.linear,
- );
-
- gl_shader_program.set_uniform(
- current_context,
- &create_light_uniform_name(
- light_array,
- light_index,
- "attenuation_props.quadratic",
- ),
- &light.attenuation_params.quadratic,
- );
-}
-
-fn set_light_phong_uniforms(
- current_context: &CurrentContextWithFns<'_>,
- gl_shader_program: &mut GlShaderProgram,
- light_array: &str,
- light_index: usize,
- light: &impl Light,
-)
-{
- gl_shader_program.set_uniform(
- current_context,
- &create_light_uniform_name(light_array, light_index, "phong.diffuse"),
- &opengl_bindings::data_types::Vec3 {
- x: light.diffuse().red,
- y: light.diffuse().green,
- z: light.diffuse().blue,
- },
- );
-
- gl_shader_program.set_uniform(
- current_context,
- &create_light_uniform_name(light_array, light_index, "phong.specular"),
- &opengl_bindings::data_types::Vec3 {
- x: light.specular().red,
- y: light.specular().green,
- z: light.specular().blue,
- },
- );
-}
-
-trait Light
-{
- fn diffuse(&self) -> &Color<f32>;
- fn specular(&self) -> &Color<f32>;
-}
-
-impl Light for PointLight
-{
- fn diffuse(&self) -> &Color<f32>
- {
- &self.diffuse
- }
-
- fn specular(&self) -> &Color<f32>
- {
- &self.specular
- }
-}
-
-impl Light for DirectionalLight
-{
- fn diffuse(&self) -> &Color<f32>
- {
- &self.diffuse
- }
-
- fn specular(&self) -> &Color<f32>
+ #[error(
+ "Failed to get code of shader program entry point {entrypoint} of stage {stage:?}"
+ )]
+ GetShaderEntryPointCodeFailed
{
- &self.specular
- }
-}
-
-fn create_light_uniform_name(
- light_array: &str,
- light_index: usize,
- light_field: &str,
-) -> CString
-{
- unsafe {
- CString::from_vec_with_nul_unchecked(
- format!("{light_array}[{light_index}].{light_field}\0").into(),
- )
- }
-}
-
-fn create_view_matrix(camera: &Camera, camera_pos: &Vec3<f32>) -> Matrix<f32, 4, 4>
-{
- let mut view = Matrix::new();
+ #[source]
+ err: ShaderError,
+ stage: ShaderStage,
+ entrypoint: Cow<'static, str>,
+ },
- view.look_at(&camera_pos, &camera.target, &camera.global_up);
+ #[error("No entrypoint was found for shader stage {0:?}")]
+ NoShaderStageEntrypointFound(ShaderStage),
- view
+ #[error(transparent)]
+ ShaderError(#[from] GlShaderError),
}
#[tracing::instrument(skip_all)]
@@ -1524,23 +1193,6 @@ fn opengl_debug_message_cb(
};
}
-#[derive(Debug)]
-struct Transformation
-{
- position: Vec3<f32>,
- scale: Vec3<f32>,
-}
-
-fn create_transformation_matrix(transformation: Transformation) -> Matrix<f32, 4, 4>
-{
- let mut matrix = Matrix::new_identity();
-
- matrix.translate(&transformation.position);
- matrix.scale(&transformation.scale);
-
- matrix
-}
-
#[inline]
fn texture_wrapping_to_gl(texture_wrapping: TextureWrapping) -> GlTextureWrapping
{
@@ -1569,7 +1221,8 @@ impl<Value: ReprC + Copy> From<Vec2<Value>> for opengl_bindings::data_types::Vec
}
}
-impl<Value: ReprC + Copy> From<Vec3<Value>> for opengl_bindings::data_types::Vec3<Value>
+impl<Value: ReprC + IntoBytes + Copy> From<Vec3<Value>>
+ for opengl_bindings::data_types::Vec3<Value>
{
fn from(vec3: Vec3<Value>) -> Self
{
@@ -1577,6 +1230,15 @@ impl<Value: ReprC + Copy> From<Vec3<Value>> for opengl_bindings::data_types::Vec
}
}
+impl<Value: ReprC + Copy> From<Matrix<Value, 4, 4>>
+ for opengl_bindings::data_types::Matrix<Value, 4, 4>
+{
+ fn from(matrix: Matrix<Value, 4, 4>) -> Self
+ {
+ Self { items: matrix.items }
+ }
+}
+
impl<Value: Copy> From<Dimens<Value>> for opengl_bindings::data_types::Dimens<Value>
{
fn from(dimens: Dimens<Value>) -> Self
@@ -1611,3 +1273,20 @@ impl From<crate::draw_flags::PolygonModeFace> for opengl_bindings::misc::Polygon
}
}
}
+
+#[derive(Debug, IntoBytes, Immutable)]
+#[repr(C)]
+pub struct CF32Vec3
+{
+ x: f32,
+ y: f32,
+ z: f32,
+}
+
+impl From<Vec3<f32>> for CF32Vec3
+{
+ fn from(src: Vec3<f32>) -> Self
+ {
+ Self { x: src.x, y: src.y, z: src.z }
+ }
+}
diff --git a/engine/src/renderer/opengl/glsl/fragment.glsl b/engine/src/renderer/opengl/glsl/fragment.glsl
deleted file mode 100644
index 5bf5ff2..0000000
--- a/engine/src/renderer/opengl/glsl/fragment.glsl
+++ /dev/null
@@ -1,73 +0,0 @@
-#version 330 core
-
-#preinclude "light.glsl"
-#preinclude "vertex_data.glsl"
-
-#define MAX_LIGHT_CNT 64
-
-out vec4 FragColor;
-
-in VertexData vertex_data;
-
-uniform vec3 view_pos;
-uniform sampler2D input_texture;
-uniform Material material;
-
-uniform PointLight point_lights[MAX_LIGHT_CNT];
-uniform int point_light_cnt;
-
-uniform DirectionalLight directional_lights[MAX_LIGHT_CNT];
-uniform int directional_light_cnt;
-
-void main()
-{
- vec3 ambient_light = calc_ambient_light(material, vertex_data.texture_coords);
-
- vec3 directional_light_sum = vec3(0.0, 0.0, 0.0);
-
- for (int dl_index = 0; dl_index < directional_light_cnt; dl_index++) {
- CalculatedLight calculated_dir_light;
-
- calc_light(
- // Negated since we want the light to point from the light direction
- normalize(-directional_lights[dl_index].direction),
- directional_lights[dl_index].phong,
- vertex_data,
- view_pos,
- material,
- calculated_dir_light
- );
-
- directional_light_sum +=
- calculated_dir_light.diffuse + calculated_dir_light.specular;
- }
-
- vec3 point_light_sum = vec3(0.0, 0.0, 0.0);
-
- for (int pl_index = 0; pl_index < point_light_cnt; pl_index++) {
- vec3 light_direction =
- normalize(point_lights[pl_index].position - vertex_data.world_space_pos);
-
- CalculatedLight calculated_point_light;
-
- calc_light(
- light_direction,
- point_lights[pl_index].phong,
- vertex_data,
- view_pos,
- material,
- calculated_point_light
- );
-
- float attenuation =
- calc_attenuation(point_lights[pl_index], vertex_data.world_space_pos);
-
- calculated_point_light.diffuse *= attenuation;
- calculated_point_light.specular *= attenuation;
-
- point_light_sum +=
- calculated_point_light.diffuse + calculated_point_light.specular;
- }
-
- FragColor = vec4((ambient_light + directional_light_sum + point_light_sum), 1.0);
-}
diff --git a/engine/src/renderer/opengl/glsl/light.glsl b/engine/src/renderer/opengl/glsl/light.glsl
deleted file mode 100644
index f12b5fe..0000000
--- a/engine/src/renderer/opengl/glsl/light.glsl
+++ /dev/null
@@ -1,133 +0,0 @@
-#version 330 core
-
-#ifndef LIGHT_GLSL
-#define LIGHT_GLSL
-
-#preinclude "vertex_data.glsl"
-
-struct Material
-{
- vec3 ambient;
- vec3 diffuse;
- vec3 specular;
- sampler2D ambient_map;
- sampler2D diffuse_map;
- sampler2D specular_map;
- float shininess;
-};
-
-struct LightPhong
-{
- vec3 diffuse;
- vec3 specular;
-};
-
-struct AttenuationProperties
-{
- float constant;
- float linear;
- float quadratic;
-};
-
-struct PointLight
-{
- LightPhong phong;
- vec3 position;
- AttenuationProperties attenuation_props;
-};
-
-struct DirectionalLight
-{
- LightPhong phong;
- vec3 direction;
-};
-
-struct CalculatedLight
-{
- vec3 diffuse;
- vec3 specular;
-};
-
-vec3 calc_ambient_light(in Material material, in vec2 texture_coords)
-{
- return vec3(texture(material.ambient_map, texture_coords)) * material.ambient;
-}
-
-vec3 calc_diffuse_light(
- in Material material,
- in LightPhong light_phong,
- in vec3 light_dir,
- in vec3 norm,
- in vec2 texture_coords
-)
-{
- float diff = max(dot(norm, light_dir), 0.0);
-
- return light_phong.diffuse * (
- diff * (vec3(texture(material.diffuse_map, texture_coords)) * material.diffuse)
- );
-}
-
-vec3 calc_specular_light(
- in Material material,
- in LightPhong light_phong,
- in vec3 light_dir,
- in vec3 norm,
- in vec3 view_pos,
- in vec3 frag_pos,
- in vec2 texture_coords
-)
-{
- vec3 view_direction = normalize(view_pos - frag_pos);
-
- vec3 halfway_direction = normalize(light_dir + view_direction);
-
- float spec =
- pow(max(dot(norm, halfway_direction), 0.0), material.shininess);
-
- return light_phong.specular * (
- spec * (vec3(texture(material.specular_map, texture_coords)) * material.specular)
- );
-}
-
-float calc_attenuation(in PointLight point_light, in vec3 position)
-{
- float light_distance = length(point_light.position - position);
-
- return 1.0 / (point_light.attenuation_props.constant
- + point_light.attenuation_props.linear
- * light_distance + point_light.attenuation_props.quadratic
- * pow(light_distance, 2));
-}
-
-void calc_light(
- in vec3 light_direction,
- in LightPhong light_phong,
- in VertexData vertex_data,
- in vec3 view_pos,
- in Material material,
- out CalculatedLight calculated_light
-)
-{
- vec3 norm = normalize(vertex_data.world_space_normal);
-
- calculated_light.diffuse = calc_diffuse_light(
- material,
- light_phong,
- light_direction,
- norm,
- vertex_data.texture_coords
- );
-
- calculated_light.specular = calc_specular_light(
- material,
- light_phong,
- light_direction,
- norm,
- view_pos,
- vertex_data.world_space_pos,
- vertex_data.texture_coords
- );
-}
-
-#endif
diff --git a/engine/src/renderer/opengl/glsl/vertex.glsl b/engine/src/renderer/opengl/glsl/vertex.glsl
deleted file mode 100644
index b57caa6..0000000
--- a/engine/src/renderer/opengl/glsl/vertex.glsl
+++ /dev/null
@@ -1,24 +0,0 @@
-#version 330 core
-
-#preinclude "vertex_data.glsl"
-
-layout (location = 0) in vec3 pos;
-layout (location = 1) in vec2 texture_coords;
-layout (location = 2) in vec3 normal;
-
-out VertexData vertex_data;
-
-uniform mat4 model;
-uniform mat4 view;
-uniform mat4 projection;
-
-void main()
-{
- gl_Position = projection * view * model * vec4(pos, 1.0);
-
- vertex_data.world_space_pos = vec3(model * vec4(pos, 1.0));
- vertex_data.texture_coords = texture_coords;
-
- // TODO: Do this using CPU for performance increase
- vertex_data.world_space_normal = mat3(transpose(inverse(model))) * normal;
-}
diff --git a/engine/src/renderer/opengl/glsl/vertex_data.glsl b/engine/src/renderer/opengl/glsl/vertex_data.glsl
deleted file mode 100644
index 486d445..0000000
--- a/engine/src/renderer/opengl/glsl/vertex_data.glsl
+++ /dev/null
@@ -1,11 +0,0 @@
-#ifndef VERTEX_DATA_GLSL
-#define VERTEX_DATA_GLSL
-
-struct VertexData
-{
- vec2 texture_coords;
- vec3 world_space_pos;
- vec3 world_space_normal;
-};
-
-#endif
diff --git a/engine/src/renderer/opengl/graphics_mesh.rs b/engine/src/renderer/opengl/graphics_mesh.rs
index dc839a8..8e0e3cc 100644
--- a/engine/src/renderer/opengl/graphics_mesh.rs
+++ b/engine/src/renderer/opengl/graphics_mesh.rs
@@ -29,7 +29,7 @@ impl GraphicsMesh
mesh: &Mesh,
) -> Result<Self, Error>
{
- tracing::trace!(
+ tracing::info!(
"Creating vertex array, vertex buffer{}",
if mesh.indices().is_some() {
" and index buffer"
diff --git a/engine/src/shader.rs b/engine/src/shader.rs
new file mode 100644
index 0000000..65872f1
--- /dev/null
+++ b/engine/src/shader.rs
@@ -0,0 +1,818 @@
+use std::any::type_name;
+use std::borrow::Cow;
+use std::collections::HashMap;
+use std::fmt::Debug;
+use std::path::Path;
+use std::str::Utf8Error;
+
+use bitflags::bitflags;
+use ecs::pair::{ChildOf, Pair};
+use ecs::phase::{Phase, START as START_PHASE};
+use ecs::sole::Single;
+use ecs::{Component, Sole, declare_entity};
+use shader_slang::{
+ Blob as SlangBlob,
+ ComponentType as SlangComponentType,
+ DebugInfoLevel as SlangDebugInfoLevel,
+ EntryPoint as SlangEntryPoint,
+ GlobalSession as SlangGlobalSession,
+ Module as SlangModule,
+ ParameterCategory as SlangParameterCategory,
+ Session as SlangSession,
+ TypeKind as SlangTypeKind,
+};
+
+use crate::asset::{
+ Assets,
+ Event as AssetEvent,
+ HANDLE_ASSETS_PHASE,
+ Handle as AssetHandle,
+ Id as AssetId,
+ Submitter as AssetSubmitter,
+};
+use crate::builder;
+use crate::renderer::PRE_RENDER_PHASE;
+use crate::shader::default::{
+ ASSET_LABEL,
+ enqueue_set_shader_bindings as default_shader_enqueue_set_shader_bindings,
+};
+
+pub mod cursor;
+pub mod default;
+
+#[derive(Debug, Clone, Component)]
+pub struct Shader
+{
+ pub asset_handle: AssetHandle<ModuleSource>,
+}
+
+/// Shader module.
+#[derive(Debug)]
+pub struct ModuleSource
+{
+ pub name: Cow<'static, str>,
+ pub file_path: Cow<'static, Path>,
+ pub source: Cow<'static, str>,
+ pub link_entrypoints: EntrypointFlags,
+}
+
+bitflags! {
+ #[derive(Debug, Clone, Copy, Default)]
+ pub struct EntrypointFlags: usize
+ {
+ const FRAGMENT = 1 << 0;
+ const VERTEX = 1 << 1;
+ }
+}
+
+pub struct Module
+{
+ inner: SlangModule,
+}
+
+impl Module
+{
+ pub fn entry_points(&self) -> impl ExactSizeIterator<Item = EntryPoint>
+ {
+ self.inner
+ .entry_points()
+ .map(|entry_point| EntryPoint { inner: entry_point })
+ }
+
+ pub fn get_entry_point(&self, entry_point: &str) -> Option<EntryPoint>
+ {
+ let entry_point = self.inner.find_entry_point_by_name(entry_point)?;
+
+ Some(EntryPoint { inner: entry_point })
+ }
+}
+
+pub struct EntryPoint
+{
+ inner: SlangEntryPoint,
+}
+
+impl EntryPoint
+{
+ pub fn function_name(&self) -> Option<&str>
+ {
+ self.inner.function_reflection().name()
+ }
+}
+
+pub struct EntryPointReflection<'a>
+{
+ inner: &'a shader_slang::reflection::EntryPoint,
+}
+
+impl<'a> EntryPointReflection<'a>
+{
+ pub fn name(&self) -> Option<&str>
+ {
+ self.inner.name()
+ }
+
+ pub fn name_override(&self) -> Option<&str>
+ {
+ self.inner.name_override()
+ }
+
+ pub fn stage(&self) -> Stage
+ {
+ Stage::from_slang_stage(self.inner.stage())
+ }
+
+ pub fn parameters(&self) -> impl ExactSizeIterator<Item = VariableLayout<'a>>
+ {
+ self.inner
+ .parameters()
+ .map(|param| VariableLayout { inner: param })
+ }
+
+ pub fn var_layout(&self) -> Option<VariableLayout<'a>>
+ {
+ Some(VariableLayout { inner: self.inner.var_layout()? })
+ }
+}
+
+#[derive(Clone)]
+pub struct Program
+{
+ inner: SlangComponentType,
+}
+
+impl Program
+{
+ pub fn link(&self) -> Result<Program, Error>
+ {
+ let linked_program = self.inner.link()?;
+
+ Ok(Program { inner: linked_program })
+ }
+
+ pub fn get_entry_point_code(&self, entry_point_index: u32) -> Result<Blob, Error>
+ {
+ let blob = self.inner.entry_point_code(entry_point_index.into(), 0)?;
+
+ Ok(Blob { inner: blob })
+ }
+
+ pub fn reflection(&self, target: u32) -> Result<ProgramReflection<'_>, Error>
+ {
+ let reflection = self.inner.layout(target as i64)?;
+
+ Ok(ProgramReflection { inner: reflection })
+ }
+}
+
+impl Debug for Program
+{
+ fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result
+ {
+ formatter
+ .debug_struct(type_name::<Self>())
+ .finish_non_exhaustive()
+ }
+}
+
+pub struct ProgramReflection<'a>
+{
+ inner: &'a shader_slang::reflection::Shader,
+}
+
+impl<'a> ProgramReflection<'a>
+{
+ pub fn get_entry_point_by_index(&self, index: u32)
+ -> Option<EntryPointReflection<'a>>
+ {
+ Some(EntryPointReflection {
+ inner: self.inner.entry_point_by_index(index)?,
+ })
+ }
+
+ pub fn get_entry_point_by_name(&self, name: &str)
+ -> Option<EntryPointReflection<'a>>
+ {
+ Some(EntryPointReflection {
+ inner: self.inner.find_entry_point_by_name(name)?,
+ })
+ }
+
+ pub fn entry_points(&self)
+ -> impl ExactSizeIterator<Item = EntryPointReflection<'a>>
+ {
+ self.inner
+ .entry_points()
+ .map(|entry_point| EntryPointReflection { inner: entry_point })
+ }
+
+ pub fn global_params_type_layout(&self) -> Option<TypeLayout<'a>>
+ {
+ Some(TypeLayout {
+ inner: self.inner.global_params_type_layout()?,
+ })
+ }
+
+ pub fn global_params_var_layout(&self) -> Option<VariableLayout<'a>>
+ {
+ Some(VariableLayout {
+ inner: self.inner.global_params_var_layout()?,
+ })
+ }
+
+ pub fn get_type(&self, name: &str) -> Option<TypeReflection<'a>>
+ {
+ Some(TypeReflection {
+ inner: self.inner.find_type_by_name(name)?,
+ })
+ }
+
+ pub fn get_type_layout(&self, ty: &TypeReflection<'a>) -> Option<TypeLayout<'a>>
+ {
+ Some(TypeLayout {
+ inner: self
+ .inner
+ .type_layout(&ty.inner, shader_slang::LayoutRules::Default)?,
+ })
+ }
+}
+
+#[derive(Clone, Copy)]
+pub struct VariableLayout<'a>
+{
+ inner: &'a shader_slang::reflection::VariableLayout,
+}
+
+impl<'a> VariableLayout<'a>
+{
+ pub fn name(&self) -> Option<&str>
+ {
+ self.inner.name()
+ }
+
+ pub fn semantic_name(&self) -> Option<&str>
+ {
+ self.inner.semantic_name()
+ }
+
+ pub fn binding_index(&self) -> u32
+ {
+ self.inner
+ .offset(shader_slang::ParameterCategory::DescriptorTableSlot) as u32
+
+ // self.inner.binding_index()
+ }
+
+ pub fn binding_space(&self) -> u32
+ {
+ self.inner.binding_space()
+ }
+
+ pub fn semantic_index(&self) -> usize
+ {
+ self.inner.semantic_index()
+ }
+
+ pub fn offset(&self) -> usize
+ {
+ self.inner.offset(shader_slang::ParameterCategory::Uniform)
+ }
+
+ pub fn ty(&self) -> Option<TypeReflection<'a>>
+ {
+ self.inner.ty().map(|ty| TypeReflection { inner: ty })
+ }
+
+ pub fn type_layout(&self) -> Option<TypeLayout<'a>>
+ {
+ Some(TypeLayout { inner: self.inner.type_layout()? })
+ }
+}
+
+#[derive(Clone, Copy)]
+pub struct TypeLayout<'a>
+{
+ inner: &'a shader_slang::reflection::TypeLayout,
+}
+
+impl<'a> TypeLayout<'a>
+{
+ pub fn get_field_by_name(&self, name: &str) -> Option<VariableLayout<'a>>
+ {
+ let index = self.inner.find_field_index_by_name(name);
+
+ if index < 0 {
+ return None;
+ }
+
+ let index = u32::try_from(index.cast_unsigned()).expect("Should not happend");
+
+ let field = self.inner.field_by_index(index)?;
+
+ Some(VariableLayout { inner: field })
+ }
+
+ pub fn binding_range_descriptor_set_index(&self, index: i64) -> i64
+ {
+ self.inner.binding_range_descriptor_set_index(index)
+ }
+
+ pub fn get_field_binding_range_offset_by_name(&self, name: &str) -> Option<u64>
+ {
+ let field_index = self.inner.find_field_index_by_name(name);
+
+ if field_index < 0 {
+ return None;
+ }
+
+ let field_binding_range_offset =
+ self.inner.field_binding_range_offset(field_index);
+
+ if field_binding_range_offset < 0 {
+ return None;
+ }
+
+ Some(field_binding_range_offset.cast_unsigned())
+ }
+
+ pub fn ty(&self) -> Option<TypeReflection<'a>>
+ {
+ self.inner.ty().map(|ty| TypeReflection { inner: ty })
+ }
+
+ pub fn fields(&self) -> impl ExactSizeIterator<Item = VariableLayout<'a>>
+ {
+ self.inner
+ .fields()
+ .map(|field| VariableLayout { inner: field })
+ }
+
+ pub fn field_cnt(&self) -> u32
+ {
+ self.inner.field_count()
+ }
+
+ pub fn element_type_layout(&self) -> Option<TypeLayout<'a>>
+ {
+ self.inner
+ .element_type_layout()
+ .map(|type_layout| TypeLayout { inner: type_layout })
+ }
+
+ pub fn element_var_layout(&self) -> Option<VariableLayout<'a>>
+ {
+ self.inner
+ .element_var_layout()
+ .map(|var_layout| VariableLayout { inner: var_layout })
+ }
+
+ pub fn container_var_layout(&self) -> Option<VariableLayout<'a>>
+ {
+ self.inner
+ .container_var_layout()
+ .map(|var_layout| VariableLayout { inner: var_layout })
+ }
+
+ pub fn uniform_size(&self) -> Option<usize>
+ {
+ // tracing::debug!(
+ // "uniform_size: {:?} categories: {:?}",
+ // self.inner.name(),
+ // self.inner.categories().collect::<Vec<_>>(),
+ // );
+
+ if !self
+ .inner
+ .categories()
+ .any(|category| category == SlangParameterCategory::Uniform)
+ {
+ return None;
+ }
+
+ // let category = self.inner.categories().next().unwrap();
+
+ // println!(
+ // "AARGH size Category: {category:?} Category count: {}",
+ // self.inner.category_count()
+ // );
+
+ // Some(self.inner.size(category))
+
+ Some(self.inner.size(SlangParameterCategory::Uniform))
+ }
+
+ pub fn stride(&self) -> usize
+ {
+ self.inner.stride(self.inner.categories().next().unwrap())
+ }
+}
+
+pub struct TypeReflection<'a>
+{
+ inner: &'a shader_slang::reflection::Type,
+}
+
+impl TypeReflection<'_>
+{
+ pub fn kind(&self) -> TypeKind
+ {
+ TypeKind::from_slang_type_kind(self.inner.kind())
+ }
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
+#[non_exhaustive]
+pub enum TypeKind
+{
+ None,
+ Struct,
+ Enum,
+ Array,
+ Matrix,
+ Vector,
+ Scalar,
+ ConstantBuffer,
+ Resource,
+ SamplerState,
+ TextureBuffer,
+ ShaderStorageBuffer,
+ ParameterBlock,
+ GenericTypeParameter,
+ Interface,
+ OutputStream,
+ MeshOutput,
+ Specialized,
+ Feedback,
+ Pointer,
+ DynamicResource,
+ Count,
+}
+
+impl TypeKind
+{
+ fn from_slang_type_kind(type_kind: SlangTypeKind) -> Self
+ {
+ match type_kind {
+ SlangTypeKind::None => Self::None,
+ SlangTypeKind::Struct => Self::Struct,
+ SlangTypeKind::Enum => Self::Enum,
+ SlangTypeKind::Array => Self::Array,
+ SlangTypeKind::Matrix => Self::Matrix,
+ SlangTypeKind::Vector => Self::Vector,
+ SlangTypeKind::Scalar => Self::Scalar,
+ SlangTypeKind::ConstantBuffer => Self::ConstantBuffer,
+ SlangTypeKind::Resource => Self::Resource,
+ SlangTypeKind::SamplerState => Self::SamplerState,
+ SlangTypeKind::TextureBuffer => Self::TextureBuffer,
+ SlangTypeKind::ShaderStorageBuffer => Self::ShaderStorageBuffer,
+ SlangTypeKind::ParameterBlock => Self::ParameterBlock,
+ SlangTypeKind::GenericTypeParameter => Self::GenericTypeParameter,
+ SlangTypeKind::Interface => Self::Interface,
+ SlangTypeKind::OutputStream => Self::OutputStream,
+ SlangTypeKind::MeshOutput => Self::MeshOutput,
+ SlangTypeKind::Specialized => Self::Specialized,
+ SlangTypeKind::Feedback => Self::Feedback,
+ SlangTypeKind::Pointer => Self::Pointer,
+ SlangTypeKind::DynamicResource => Self::DynamicResource,
+ SlangTypeKind::Count => Self::Count,
+ }
+ }
+}
+
+pub struct Blob
+{
+ inner: SlangBlob,
+}
+
+impl Blob
+{
+ pub fn as_bytes(&self) -> &[u8]
+ {
+ self.inner.as_slice()
+ }
+
+ pub fn as_str(&self) -> Result<&str, Utf8Error>
+ {
+ self.inner.as_str()
+ }
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
+#[non_exhaustive]
+pub enum Stage
+{
+ None,
+ Vertex,
+ Hull,
+ Domain,
+ Geometry,
+ Fragment,
+ Compute,
+ RayGeneration,
+ Intersection,
+ AnyHit,
+ ClosestHit,
+ Miss,
+ Callable,
+ Mesh,
+ Amplification,
+ Dispatch,
+ Count,
+}
+
+impl Stage
+{
+ fn from_slang_stage(stage: shader_slang::Stage) -> Self
+ {
+ match stage {
+ shader_slang::Stage::None => Self::None,
+ shader_slang::Stage::Vertex => Self::Vertex,
+ shader_slang::Stage::Hull => Self::Hull,
+ shader_slang::Stage::Domain => Self::Domain,
+ shader_slang::Stage::Geometry => Self::Geometry,
+ shader_slang::Stage::Fragment => Self::Fragment,
+ shader_slang::Stage::Compute => Self::Compute,
+ shader_slang::Stage::RayGeneration => Self::RayGeneration,
+ shader_slang::Stage::Intersection => Self::Intersection,
+ shader_slang::Stage::AnyHit => Self::AnyHit,
+ shader_slang::Stage::ClosestHit => Self::ClosestHit,
+ shader_slang::Stage::Miss => Self::Miss,
+ shader_slang::Stage::Callable => Self::Callable,
+ shader_slang::Stage::Mesh => Self::Mesh,
+ shader_slang::Stage::Amplification => Self::Amplification,
+ shader_slang::Stage::Dispatch => Self::Dispatch,
+ shader_slang::Stage::Count => Self::Count,
+ }
+ }
+}
+
+builder! {
+#[builder(name = SettingsBuilder, derives=(Debug))]
+#[derive(Debug)]
+#[non_exhaustive]
+pub struct Settings
+{
+ link_entrypoints: EntrypointFlags,
+}
+}
+
+#[derive(Sole)]
+pub struct Context
+{
+ _global_session: SlangGlobalSession,
+ session: SlangSession,
+ modules: HashMap<AssetId, Module>,
+ programs: HashMap<AssetId, Program>,
+}
+
+impl Context
+{
+ pub fn get_module(&self, asset_id: &AssetId) -> Option<&Module>
+ {
+ self.modules.get(asset_id)
+ }
+
+ pub fn get_program(&self, asset_id: &AssetId) -> Option<&Program>
+ {
+ self.programs.get(asset_id)
+ }
+
+ pub fn compose_into_program(
+ &self,
+ modules: &[&Module],
+ entry_points: &[&EntryPoint],
+ ) -> Result<Program, Error>
+ {
+ let components =
+ modules
+ .iter()
+ .map(|module| SlangComponentType::from(module.inner.clone()))
+ .chain(entry_points.iter().map(|entry_point| {
+ SlangComponentType::from(entry_point.inner.clone())
+ }))
+ .collect::<Vec<_>>();
+
+ let program = self.session.create_composite_component_type(&components)?;
+
+ Ok(Program { inner: program })
+ }
+}
+
+#[derive(Debug, thiserror::Error)]
+#[error(transparent)]
+pub struct Error(#[from] shader_slang::Error);
+
+pub(crate) fn add_asset_importers(assets: &mut Assets)
+{
+ assets.set_importer::<_, _>(["slang"], import_slang_asset);
+}
+
+fn import_slang_asset(
+ asset_submitter: &mut AssetSubmitter<'_>,
+ file_path: &Path,
+ settings: Option<&'_ Settings>,
+) -> Result<(), ImportError>
+{
+ let file_name = file_path
+ .file_name()
+ .ok_or(ImportError::NoPathFileName)?
+ .to_str()
+ .ok_or(ImportError::PathFileNameNotUtf8)?;
+
+ let file_path_canonicalized = file_path
+ .canonicalize()
+ .map_err(ImportError::CanonicalizePathFailed)?;
+
+ asset_submitter.submit_store(ModuleSource {
+ name: file_name.to_owned().into(),
+ file_path: file_path_canonicalized.into(),
+ source: std::fs::read_to_string(file_path)
+ .map_err(ImportError::ReadFileFailed)?
+ .into(),
+ link_entrypoints: settings
+ .map(|settings| settings.link_entrypoints)
+ .unwrap_or_default(),
+ });
+
+ Ok(())
+}
+
+#[derive(Debug, thiserror::Error)]
+enum ImportError
+{
+ #[error("Failed to read file")]
+ ReadFileFailed(#[source] std::io::Error),
+
+ #[error("Asset path does not have a file name")]
+ NoPathFileName,
+
+ #[error("Asset path file name is not valid UTF8")]
+ PathFileNameNotUtf8,
+
+ #[error("Failed to canonicalize asset path")]
+ CanonicalizePathFailed(#[source] std::io::Error),
+}
+
+declare_entity!(
+ IMPORT_SHADERS_PHASE,
+ (
+ Phase,
+ Pair::builder()
+ .relation::<ChildOf>()
+ .target_id(*HANDLE_ASSETS_PHASE)
+ .build()
+ )
+);
+
+pub(crate) struct Extension;
+
+impl ecs::extension::Extension for Extension
+{
+ fn collect(self, mut collector: ecs::extension::Collector<'_>)
+ {
+ let Some(global_session) = SlangGlobalSession::new() else {
+ tracing::error!("Unable to create global shader-slang session");
+ return;
+ };
+
+ let session_options = shader_slang::CompilerOptions::default()
+ .optimization(shader_slang::OptimizationLevel::None)
+ .matrix_layout_column(true)
+ .debug_information(SlangDebugInfoLevel::Maximal)
+ .no_mangle(true);
+
+ let target_desc = shader_slang::TargetDesc::default()
+ .format(shader_slang::CompileTarget::Glsl)
+ // .format(shader_slang::CompileTarget::Spirv)
+ .profile(global_session.find_profile("glsl_330"));
+ // .profile(global_session.find_profile("spirv_1_5"));
+
+ let targets = [target_desc];
+
+ let session_desc = shader_slang::SessionDesc::default()
+ .targets(&targets)
+ .search_paths(&[""])
+ .options(&session_options);
+
+ let Some(session) = global_session.create_session(&session_desc) else {
+ tracing::error!("Failed to create shader-slang session");
+ return;
+ };
+
+ collector
+ .add_sole(Context {
+ _global_session: global_session,
+ session,
+ modules: HashMap::new(),
+ programs: HashMap::new(),
+ })
+ .ok();
+
+ collector.add_declared_entity(&IMPORT_SHADERS_PHASE);
+
+ collector.add_system(*START_PHASE, initialize);
+ collector.add_system(*IMPORT_SHADERS_PHASE, load_modules);
+
+ collector.add_system(
+ *PRE_RENDER_PHASE,
+ default_shader_enqueue_set_shader_bindings,
+ );
+ }
+}
+
+fn initialize(mut assets: Single<Assets>)
+{
+ assets.store_with_label(
+ ASSET_LABEL.clone(),
+ ModuleSource {
+ name: "default_shader.slang".into(),
+ file_path: Path::new("@engine/default_shader").into(),
+ source: include_str!("../res/default_shader.slang").into(),
+ link_entrypoints: EntrypointFlags::VERTEX | EntrypointFlags::FRAGMENT,
+ },
+ );
+}
+
+#[tracing::instrument(skip_all)]
+fn load_modules(mut context: Single<Context>, assets: Single<Assets>)
+{
+ for AssetEvent::Stored(asset_id, asset_label) in assets.events().last_tick_events() {
+ let asset_handle = AssetHandle::<ModuleSource>::from_id(*asset_id);
+
+ if !assets.is_loaded_and_has_type(&asset_handle) {
+ continue;
+ }
+
+ let Some(module_source) = assets.get(&asset_handle) else {
+ unreachable!();
+ };
+
+ tracing::debug!(asset_label=?asset_label, "Loading shader module");
+
+ let module = match load_module(&context.session, module_source) {
+ Ok(module) => module,
+ Err(err) => {
+ tracing::error!("Failed to load shader module: {err}");
+ continue;
+ }
+ };
+
+ if !module_source.link_entrypoints.is_empty() {
+ assert!(context.programs.get(asset_id).is_none());
+
+ let Some(vertex_shader_entry_point) = module.get_entry_point("vertex_main")
+ else {
+ tracing::error!(
+ "Shader module does not contain a vertex shader entry point"
+ );
+ continue;
+ };
+
+ let Some(fragment_shader_entry_point) =
+ module.get_entry_point("fragment_main")
+ else {
+ tracing::error!(
+ "Shader module don't contain a fragment_main entry point"
+ );
+ continue;
+ };
+
+ let shader_program = match context.compose_into_program(
+ &[&module],
+ &[&vertex_shader_entry_point, &fragment_shader_entry_point],
+ ) {
+ Ok(shader_program) => shader_program,
+ Err(err) => {
+ tracing::error!("Failed to compose shader into program: {err}");
+ continue;
+ }
+ };
+
+ let linked_shader_program = match shader_program.link() {
+ Ok(linked_shader_program) => linked_shader_program,
+ Err(err) => {
+ tracing::error!("Failed to link shader: {err}");
+ continue;
+ }
+ };
+
+ context.programs.insert(*asset_id, linked_shader_program);
+ }
+
+ context.modules.insert(*asset_id, module);
+ }
+}
+
+fn load_module(
+ session: &SlangSession,
+ module_source: &ModuleSource,
+) -> Result<Module, Error>
+{
+ let module = session.load_module_from_source_string(
+ &module_source.name,
+ &module_source.file_path.to_string_lossy(),
+ &module_source.source,
+ )?;
+
+ Ok(Module { inner: module })
+}
diff --git a/engine/src/shader/cursor.rs b/engine/src/shader/cursor.rs
new file mode 100644
index 0000000..b5ba4e0
--- /dev/null
+++ b/engine/src/shader/cursor.rs
@@ -0,0 +1,160 @@
+use crate::asset::Handle as AssetHandle;
+use crate::image::Image;
+use crate::matrix::Matrix;
+use crate::shader::{TypeKind, TypeLayout, VariableLayout};
+use crate::vector::Vec3;
+
+/// Shader cursor
+#[derive(Clone)]
+pub struct Cursor<'a>
+{
+ type_layout: TypeLayout<'a>,
+ binding_location: BindingLocation,
+}
+
+impl<'a> Cursor<'a>
+{
+ pub fn new(var_layout: VariableLayout<'a>) -> Self
+ {
+ let binding_location = BindingLocation {
+ binding_index: var_layout.binding_index(),
+ binding_size: 0,
+ byte_offset: var_layout.offset(),
+ };
+
+ Self {
+ type_layout: var_layout.type_layout().unwrap(),
+ binding_location,
+ }
+ }
+
+ pub fn field(&self, name: &str) -> Self
+ {
+ let Some(field_var_layout) = self.type_layout.get_field_by_name(name) else {
+ panic!("Field '{name}' does not exist");
+ };
+
+ let field_type_kind = field_var_layout.ty().unwrap().kind();
+
+ let field_var_layout = match field_type_kind {
+ TypeKind::ConstantBuffer => field_var_layout
+ .type_layout()
+ .expect("Constant buffer field has no type layout")
+ .element_var_layout()
+ .expect("Constant buffer field type layout has no element var layout"),
+ TypeKind::Array
+ | TypeKind::Matrix
+ | TypeKind::Scalar
+ | TypeKind::Vector
+ | TypeKind::Struct
+ | TypeKind::Resource => field_var_layout,
+ type_kind => unimplemented!("Type kind {type_kind:?} is not yet supported"),
+ };
+
+ Self {
+ type_layout: field_var_layout.type_layout().unwrap(),
+ binding_location: BindingLocation {
+ binding_index: self.binding_location.binding_index
+ + field_var_layout.binding_index(),
+ binding_size: if field_type_kind == TypeKind::ConstantBuffer {
+ field_var_layout
+ .type_layout()
+ .unwrap()
+ .uniform_size()
+ .unwrap()
+ } else {
+ self.binding_location.binding_size
+ },
+ byte_offset: self.binding_location.byte_offset
+ + field_var_layout.offset(),
+ },
+ }
+ }
+
+ pub fn element(mut self, index: usize) -> Self
+ {
+ let element_type_layout = self.type_layout.element_type_layout().unwrap();
+
+ self.binding_location.byte_offset += index * element_type_layout.stride();
+
+ self.type_layout = element_type_layout;
+
+ self
+ }
+
+ pub fn with_field(self, name: &str, func: impl FnOnce(Self) -> Self) -> Self
+ {
+ let _ = func(self.field(name));
+
+ self
+ }
+
+ pub fn binding_location(&self) -> &BindingLocation
+ {
+ &self.binding_location
+ }
+
+ pub fn into_binding_location(self) -> BindingLocation
+ {
+ self.binding_location
+ }
+}
+
+#[derive(Debug, Clone)]
+pub struct BindingLocation
+{
+ pub binding_index: u32,
+ pub binding_size: usize,
+ pub byte_offset: usize,
+}
+
+#[derive(Debug, Clone)]
+pub enum BindingValue
+{
+ Uint(u32),
+ Int(i32),
+ Float(f32),
+ FVec3(Vec3<f32>),
+ FMat4x4(Matrix<f32, 4, 4>),
+ Texture(AssetHandle<Image>),
+}
+
+impl From<u32> for BindingValue
+{
+ fn from(value: u32) -> Self
+ {
+ BindingValue::Uint(value)
+ }
+}
+
+impl From<i32> for BindingValue
+{
+ fn from(value: i32) -> Self
+ {
+ BindingValue::Int(value)
+ }
+}
+
+impl From<f32> for BindingValue
+{
+ fn from(value: f32) -> Self
+ {
+ BindingValue::Float(value)
+ }
+}
+
+impl From<Vec3<f32>> for BindingValue
+{
+ fn from(vec: Vec3<f32>) -> Self
+ {
+ BindingValue::FVec3(vec)
+ }
+}
+
+impl From<Matrix<f32, 4, 4>> for BindingValue
+{
+ fn from(matrix: Matrix<f32, 4, 4>) -> Self
+ {
+ BindingValue::FMat4x4(matrix)
+ }
+}
diff --git a/engine/src/shader/default.rs b/engine/src/shader/default.rs
new file mode 100644
index 0000000..28bbdc9
--- /dev/null
+++ b/engine/src/shader/default.rs
@@ -0,0 +1,363 @@
+use std::path::Path;
+use std::sync::LazyLock;
+
+use ecs::Query;
+use ecs::actions::Actions;
+use ecs::query::term::Without;
+use ecs::sole::Single;
+
+use crate::asset::{Assets, Label as AssetLabel};
+use crate::camera::{Active as ActiveCamera, Camera};
+use crate::data_types::dimens::Dimens;
+use crate::draw_flags::NoDraw;
+use crate::lighting::{DirectionalLight, GlobalLight, PointLight};
+use crate::material::{Flags as MaterialFlags, Material};
+use crate::matrix::Matrix;
+use crate::model::{MaterialSearchResult, Model};
+use crate::projection::{ClipVolume as ProjectionClipVolume, Projection};
+use crate::renderer::{DEFAULT_TEXTURE_ASSET_LABEL, PendingShaderBindings};
+use crate::shader::cursor::{BindingValue as ShaderBindingValue, Cursor as ShaderCursor};
+use crate::shader::{
+ Context as ShaderContext,
+ ModuleSource as ShaderModuleSource,
+ Shader,
+};
+use crate::transform::{Scale, Transform, WorldPosition};
+use crate::vector::Vec3;
+use crate::windowing::window::Window;
+
+pub static ASSET_LABEL: LazyLock<AssetLabel> = LazyLock::new(|| AssetLabel {
+ path: Path::new("").into(),
+ name: Some("default_shader".into()),
+});
+
+pub fn enqueue_set_shader_bindings(
+ renderable_query: Query<RenderableEntity<'_>, (Without<NoDraw>,)>,
+ camera_query: Query<(&Camera, &WorldPosition, &ActiveCamera)>,
+ window_query: Query<(&Window,)>,
+ point_light_query: Query<(&PointLight, &WorldPosition)>,
+ directional_light_query: Query<(&DirectionalLight,)>,
+ assets: Single<Assets>,
+ shader_context: Single<ShaderContext>,
+ global_light: Single<GlobalLight>,
+ mut actions: Actions,
+)
+{
+ let Some((camera, camera_world_pos, _)) = camera_query.iter().next() else {
+ tracing::warn!("No current camera");
+ return;
+ };
+
+ let Some((window,)) = window_query.iter().next() else {
+ // tracing::warn!("No window");
+ return;
+ };
+
+ let default_shader_asset = assets
+ .get_handle_to_loaded::<ShaderModuleSource>(ASSET_LABEL.clone())
+ .unwrap();
+
+ for (
+ entity_id,
+ (model, material_flags, world_pos, scale, shader, mut pending_shader_bindings),
+ ) in renderable_query.iter_with_euids()
+ {
+ let shader_asset = match &shader {
+ Some(shader) => &shader.asset_handle,
+ None => &default_shader_asset,
+ };
+
+ if shader_asset.id() != default_shader_asset.id() {
+ continue;
+ }
+
+ let Some(shader_program) = shader_context.get_program(&shader_asset.id()) else {
+ continue;
+ };
+
+ let Some(model_spec) = assets.get(&model.spec_asset) else {
+ continue;
+ };
+
+ let has_pending_shader_bindings_comp = pending_shader_bindings.is_some();
+
+ let shader_bindings = match pending_shader_bindings.as_deref_mut() {
+ Some(pending_shader_bindings) => pending_shader_bindings,
+ None => &mut PendingShaderBindings::default(),
+ };
+
+ let shader_cursor = ShaderCursor::new(
+ shader_program
+ .reflection(0)
+ .unwrap()
+ .global_params_var_layout()
+ .unwrap(),
+ );
+
+ let model_matrix = create_model_matrix(Transform {
+ position: world_pos.as_deref().cloned().unwrap_or_default().position,
+ scale: scale.as_deref().cloned().unwrap_or_default().scale,
+ });
+
+ let inverted_model_matrix = model_matrix.inverse();
+
+ let model_material = match model_spec.find_first_material(&assets) {
+ MaterialSearchResult::Found(model_material_asset) => {
+ let Some(model_material) = assets.get(&model_material_asset) else {
+ continue;
+ };
+
+ model_material
+ }
+ MaterialSearchResult::NotFound => {
+ continue;
+ }
+ MaterialSearchResult::NoMaterials => &const { Material::builder().build() },
+ };
+
+ if [
+ &model_material.ambient_map,
+ &model_material.diffuse_map,
+ &model_material.specular_map,
+ ]
+ .into_iter()
+ .flatten()
+ .any(|texture| !assets.is_loaded_and_has_type(&texture.asset_handle))
+ {
+ continue;
+ }
+
+ let material_flags = material_flags
+ .as_deref()
+ .unwrap_or(&const { MaterialFlags::builder().build() });
+
+ let model_3d_shader_cursor = shader_cursor.field("Uniforms").field("model_3d");
+ let lighting_shader_cursor = shader_cursor.field("Uniforms").field("lighting");
+ let material_shader_cursor = lighting_shader_cursor.field("material");
+
+ shader_bindings.extend([
+ (model_3d_shader_cursor.field("model"), model_matrix.into()),
+ (
+ model_3d_shader_cursor.field("model_inverted"),
+ inverted_model_matrix.into(),
+ ),
+ (
+ model_3d_shader_cursor.field("view"),
+ create_view_matrix(&camera, &camera_world_pos.position).into(),
+ ),
+ (
+ model_3d_shader_cursor.field("projection"),
+ create_projection_matrix(
+ &camera,
+ &camera_world_pos.position,
+ window.inner_size(),
+ )
+ .into(),
+ ),
+ (
+ lighting_shader_cursor.field("view_pos"),
+ camera_world_pos.position.into(),
+ ),
+ (
+ material_shader_cursor.field("ambient"),
+ Vec3::from(
+ if material_flags.use_ambient_color {
+ &model_material.ambient
+ } else {
+ &global_light.ambient
+ }
+ .clone(),
+ )
+ .into(),
+ ),
+ (
+ material_shader_cursor.field("diffuse"),
+ Vec3::from(model_material.diffuse.clone()).into(),
+ ),
+ (
+ material_shader_cursor.field("specular"),
+ Vec3::from(model_material.specular.clone()).into(),
+ ),
+ (
+ material_shader_cursor.field("shininess"),
+ model_material.shininess.into(),
+ ),
+ (
+ lighting_shader_cursor.field("directional_light_cnt"),
+ u32::try_from(directional_light_query.iter().count())
+ .expect(
+ "Directional light count does not fit in 32-bit unsigned integer",
+ )
+ .into(),
+ ),
+ (
+ lighting_shader_cursor.field("point_light_cnt"),
+ u32::try_from(point_light_query.iter().count())
+ .expect("Point light count does not fit in 32-bit unsigned integer")
+ .into(),
+ ),
+ ]);
+
+ shader_bindings.extend(point_light_query.iter().enumerate().flat_map(
+ |(point_light_index, (point_light, point_light_world_pos))| {
+ let point_light_shader_cursor = lighting_shader_cursor
+ .field("point_lights")
+ .element(point_light_index);
+
+ let phong_shader_cursor = point_light_shader_cursor.field("phong");
+
+ let attenuation_props_shader_cursor =
+ point_light_shader_cursor.field("attenuation_props");
+
+ [
+ (
+ phong_shader_cursor.field("diffuse"),
+ Vec3::from(point_light.diffuse.clone()).into(),
+ ),
+ (
+ phong_shader_cursor.field("specular"),
+ Vec3::from(point_light.specular.clone()).into(),
+ ),
+ (
+ point_light_shader_cursor.field("position"),
+ (point_light_world_pos.position + point_light.local_position)
+ .into(),
+ ),
+ (
+ attenuation_props_shader_cursor.field("constant"),
+ point_light.attenuation_params.constant.into(),
+ ),
+ (
+ attenuation_props_shader_cursor.field("linear"),
+ point_light.attenuation_params.linear.into(),
+ ),
+ (
+ attenuation_props_shader_cursor.field("quadratic"),
+ point_light.attenuation_params.quadratic.into(),
+ ),
+ ]
+ },
+ ));
+
+ shader_bindings.extend(directional_light_query.iter().enumerate().flat_map(
+ |(directional_light_index, (directional_light,))| {
+ let directional_light_shader_cursor = lighting_shader_cursor
+ .field("directional_lights")
+ .element(directional_light_index);
+
+ let phong_shader_cursor = directional_light_shader_cursor.field("phong");
+
+ [
+ (
+ phong_shader_cursor.field("diffuse"),
+ Vec3::from(directional_light.diffuse.clone()).into(),
+ ),
+ (
+ phong_shader_cursor.field("specular"),
+ Vec3::from(directional_light.specular.clone()).into(),
+ ),
+ (
+ directional_light_shader_cursor.field("direction"),
+ directional_light.direction.into(),
+ ),
+ ]
+ },
+ ));
+
+ shader_bindings.bindings.push((
+ shader_cursor.field("ambient_map").into_binding_location(),
+ ShaderBindingValue::Texture(
+ model_material
+ .ambient_map
+ .as_ref()
+ .map(|ambient_map| ambient_map.asset_handle.clone())
+ .unwrap_or_else(|| {
+ assets
+ .get_handle_to_loaded(DEFAULT_TEXTURE_ASSET_LABEL.clone())
+ .expect("Not possible")
+ }),
+ ),
+ ));
+
+ shader_bindings.bindings.push((
+ shader_cursor.field("diffuse_map").into_binding_location(),
+ ShaderBindingValue::Texture(
+ model_material
+ .diffuse_map
+ .as_ref()
+ .map(|diffuse_map| diffuse_map.asset_handle.clone())
+ .unwrap_or_else(|| {
+ assets
+ .get_handle_to_loaded(DEFAULT_TEXTURE_ASSET_LABEL.clone())
+ .expect("Not possible")
+ }),
+ ),
+ ));
+
+ shader_bindings.bindings.push((
+ shader_cursor.field("specular_map").into_binding_location(),
+ ShaderBindingValue::Texture(
+ model_material
+ .specular_map
+ .as_ref()
+ .map(|specular_map| specular_map.asset_handle.clone())
+ .unwrap_or_else(|| {
+ assets
+ .get_handle_to_loaded(DEFAULT_TEXTURE_ASSET_LABEL.clone())
+ .expect("Not possible")
+ }),
+ ),
+ ));
+
+ if !has_pending_shader_bindings_comp {
+ actions.add_components(entity_id, (shader_bindings.clone(),));
+ }
+ }
+}
+
+fn create_model_matrix(transform: Transform) -> Matrix<f32, 4, 4>
+{
+ let mut matrix = Matrix::new_identity();
+
+ matrix.translate(&transform.position);
+ matrix.scale(&transform.scale);
+
+ matrix
+}
+
+fn create_view_matrix(camera: &Camera, camera_world_pos: &Vec3<f32>)
+-> Matrix<f32, 4, 4>
+{
+ let mut view = Matrix::new();
+
+ // tracing::debug!("Camera target: {:?}", camera.target);
+
+ view.look_at(camera_world_pos, &camera.target, &camera.global_up);
+
+ view
+}
+
+fn create_projection_matrix(
+ camera: &Camera,
+ camera_world_pos: &Vec3<f32>,
+ window_size: &Dimens<u32>,
+) -> Matrix<f32, 4, 4>
+{
+ match &camera.projection {
+ Projection::Perspective(perspective_proj) => perspective_proj.to_matrix_rh(
+ window_size.width as f32 / window_size.height as f32,
+ ProjectionClipVolume::NegOneToOne,
+ ),
+ Projection::Orthographic(orthographic_proj) => orthographic_proj
+ .to_matrix_rh(camera_world_pos, ProjectionClipVolume::NegOneToOne),
+ }
+}
+
+type RenderableEntity<'a> = (
+ &'a Model,
+ Option<&'a MaterialFlags>,
+ Option<&'a WorldPosition>,
+ Option<&'a Scale>,
+ Option<&'a Shader>,
+ Option<&'a mut PendingShaderBindings>,
+);
diff --git a/engine/src/util.rs b/engine/src/util.rs
index 9174734..0c81c71 100644
--- a/engine/src/util.rs
+++ b/engine/src/util.rs
@@ -158,7 +158,8 @@ macro_rules! builder {
)*
#[must_use]
- $visibility fn build(self) -> $name {
+ $visibility const fn build(self) -> $name
+ {
$name {
$(
$field: self.$field,