Compare commits

..

3 Commits

40 changed files with 3002 additions and 591 deletions

1
.gitignore vendored
View File

@ -1 +1,2 @@
/target
assets/models/bugatti/*

33
Cargo.lock generated
View File

@ -49,7 +49,7 @@ dependencies = [
"ndk-context",
"ndk-sys",
"num_enum",
"thiserror",
"thiserror 1.0.69",
]
[[package]]
@ -132,7 +132,7 @@ dependencies = [
"polling",
"rustix 0.38.44",
"slab",
"thiserror",
"thiserror 1.0.69",
]
[[package]]
@ -490,7 +490,7 @@ dependencies = [
"combine",
"jni-sys",
"log",
"thiserror",
"thiserror 1.0.69",
"walkdir",
"windows-sys 0.45.0",
]
@ -599,7 +599,7 @@ dependencies = [
"ndk-sys",
"num_enum",
"raw-window-handle",
"thiserror",
"thiserror 1.0.69",
]
[[package]]
@ -991,6 +991,7 @@ dependencies = [
"glutin",
"glutin-winit",
"raw-window-handle",
"thiserror 2.0.17",
"winit",
]
@ -1163,7 +1164,7 @@ dependencies = [
"log",
"memmap2",
"rustix 0.38.44",
"thiserror",
"thiserror 1.0.69",
"wayland-backend",
"wayland-client",
"wayland-csd-frame",
@ -1206,7 +1207,16 @@ version = "1.0.69"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52"
dependencies = [
"thiserror-impl",
"thiserror-impl 1.0.69",
]
[[package]]
name = "thiserror"
version = "2.0.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8"
dependencies = [
"thiserror-impl 2.0.17",
]
[[package]]
@ -1220,6 +1230,17 @@ dependencies = [
"syn",
]
[[package]]
name = "thiserror-impl"
version = "2.0.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "tiny-skia"
version = "0.11.4"

View File

@ -9,6 +9,7 @@ gl = "0.14.0"
glutin = "0.32.3"
glutin-winit = "0.5.0"
raw-window-handle = "0.6.2"
thiserror = "2.0.17"
winit = "0.30.12"
[build-dependencies]

View File

@ -0,0 +1,9 @@
Hi, thanks for downloadiing this model.It was created by Bob Kimani (Kimz Auto).
if you like the model please feel free to donate to my PayPal account at
bobiankimani@gmail.com
Or simply buy the model for a price of your choice here
https://sellfy.com/p/FrEB/
I would reaaly appreciate it even if its just one dollar.Thanks;)
Please include this credit when you use the file.
Regards Bob :)

View File

@ -0,0 +1,400 @@
# Blender MTL File: 'bugatti.blend'
# Material Count: 40
newmtl BLUE.002
Ns 96.078431
Ka 1.000000 1.000000 1.000000
Kd 0.000000 0.000000 0.800000
Ks 0.500000 0.500000 0.500000
Ke 0.000000 0.000000 0.000000
Ni 1.000000
d 1.000000
illum 2
newmtl ENGINE
Ns 96.078431
Ka 1.000000 1.000000 1.000000
Kd 0.640000 0.640000 0.640000
Ks 0.500000 0.500000 0.500000
Ke 0.000000 0.000000 0.000000
Ni 1.000000
d 1.000000
illum 2
newmtl GRILL.004
Ns 96.078431
Ka 1.000000 1.000000 1.000000
Kd 0.026482 0.026482 0.026482
Ks 0.500000 0.500000 0.500000
Ke 0.000000 0.000000 0.000000
Ni 1.000000
d 1.000000
illum 2
newmtl Mirrors
Ns 96.078431
Ka 1.000000 1.000000 1.000000
Kd 0.640000 0.640000 0.640000
Ks 0.500000 0.500000 0.500000
Ke 0.000000 0.000000 0.000000
Ni 1.000000
d 1.000000
illum 2
newmtl NAVY
Ns 96.078431
Ka 1.000000 1.000000 1.000000
Kd 0.000000 0.017778 0.047407
Ks 0.500000 0.500000 0.500000
Ke 0.000000 0.000000 0.000000
Ni 1.000000
d 1.000000
illum 2
newmtl None
Ns 0
Ka 0.000000 0.000000 0.000000
Kd 0.8 0.8 0.8
Ks 0.8 0.8 0.8
d 1
illum 2
newmtl Nut.004
Ns 96.078431
Ka 1.000000 1.000000 1.000000
Kd 0.640000 0.640000 0.640000
Ks 0.500000 0.500000 0.500000
Ke 0.000000 0.000000 0.000000
Ni 1.000000
d 1.000000
illum 2
newmtl Studio_Lights.004
Ns 96.078431
Ka 1.000000 1.000000 1.000000
Kd 0.640000 0.640000 0.640000
Ks 0.500000 0.500000 0.500000
Ke 0.000000 0.000000 0.000000
Ni 1.000000
d 1.000000
illum 2
newmtl Studio_Lights.005
Ns 96.078431
Ka 1.000000 1.000000 1.000000
Kd 0.640000 0.640000 0.640000
Ks 0.500000 0.500000 0.500000
Ke 0.000000 0.000000 0.000000
Ni 1.000000
d 1.000000
illum 2
newmtl Trim.004
Ns 96.078431
Ka 1.000000 1.000000 1.000000
Kd 0.640000 0.640000 0.640000
Ks 0.500000 0.500000 0.500000
Ke 0.000000 0.000000 0.000000
Ni 1.000000
d 1.000000
illum 2
newmtl WHEEL_CURB.004
Ns 96.078431
Ka 1.000000 1.000000 1.000000
Kd 0.640000 0.640000 0.640000
Ks 0.500000 0.500000 0.500000
Ke 0.000000 0.000000 0.000000
Ni 1.000000
d 1.000000
illum 2
newmtl WHITE.002
Ns 96.078431
Ka 1.000000 1.000000 1.000000
Kd 0.640000 0.640000 0.640000
Ks 0.500000 0.500000 0.500000
Ke 0.000000 0.000000 0.000000
Ni 1.000000
d 1.000000
illum 2
newmtl aluminiumm
Ns 96.078431
Ka 1.000000 1.000000 1.000000
Kd 0.640000 0.640000 0.640000
Ks 0.500000 0.500000 0.500000
Ke 0.000000 0.000000 0.000000
Ni 1.000000
d 1.000000
illum 2
newmtl back_drop.004
Ns 96.078431
Ka 1.000000 1.000000 1.000000
Kd 0.640000 0.640000 0.640000
Ks 0.500000 0.500000 0.500000
Ke 0.000000 0.000000 0.000000
Ni 1.000000
d 1.000000
illum 2
newmtl black
Ns 96.078431
Ka 1.000000 1.000000 1.000000
Kd 0.102592 0.102592 0.102592
Ks 0.500000 0.500000 0.500000
Ke 0.000000 0.000000 0.000000
Ni 1.000000
d 1.000000
illum 2
newmtl black_cable
Ns 96.078431
Ka 1.000000 1.000000 1.000000
Kd 0.640000 0.640000 0.640000
Ks 0.500000 0.500000 0.500000
Ke 0.000000 0.000000 0.000000
Ni 1.000000
d 1.000000
illum 2
newmtl black_fuel_tank.004
Ns 96.078431
Ka 1.000000 1.000000 1.000000
Kd 0.640000 0.640000 0.640000
Ks 0.500000 0.500000 0.500000
Ke 0.000000 0.000000 0.000000
Ni 1.000000
d 1.000000
illum 2
newmtl black_side_vent
Ns 96.078431
Ka 1.000000 1.000000 1.000000
Kd 0.001651 0.001651 0.001651
Ks 0.500000 0.500000 0.500000
Ke 0.000000 0.000000 0.000000
Ni 1.000000
d 1.000000
illum 2
newmtl black_strip.004
Ns 96.078431
Ka 1.000000 1.000000 1.000000
Kd 0.009774 0.009774 0.009774
Ks 0.500000 0.500000 0.500000
Ke 0.000000 0.000000 0.000000
Ni 1.000000
d 1.000000
illum 2
newmtl black_vent.004
Ns 96.078431
Ka 1.000000 1.000000 1.000000
Kd 0.640000 0.640000 0.640000
Ks 0.500000 0.500000 0.500000
Ke 0.000000 0.000000 0.000000
Ni 1.000000
d 1.000000
illum 2
newmtl brake_disk.004
Ns 96.078431
Ka 1.000000 1.000000 1.000000
Kd 0.640000 0.640000 0.640000
Ks 0.500000 0.500000 0.500000
Ke 0.000000 0.000000 0.000000
Ni 1.000000
d 1.000000
illum 2
newmtl breaks_
Ns 96.078431
Ka 1.000000 1.000000 1.000000
Kd 0.640000 0.640000 0.640000
Ks 0.500000 0.500000 0.500000
Ke 0.000000 0.000000 0.000000
Ni 1.000000
d 1.000000
illum 2
newmtl cALLIPERS.004
Ns 96.078431
Ka 1.000000 1.000000 1.000000
Kd 0.014633 0.586055 0.640002
Ks 0.500000 0.500000 0.500000
Ke 0.000000 0.000000 0.000000
Ni 1.000000
d 1.000000
illum 2
newmtl exhaust
Ns 96.078431
Ka 1.000000 1.000000 1.000000
Kd 0.204648 0.451652 0.640001
Ks 0.500000 0.500000 0.500000
Ke 0.000000 0.000000 0.000000
Ni 1.000000
d 1.000000
illum 2
newmtl front_window.004
Ns 96.078431
Ka 1.000000 1.000000 1.000000
Kd 0.640000 0.640000 0.640000
Ks 0.500000 0.500000 0.500000
Ke 0.000000 0.000000 0.000000
Ni 1.000000
d 1.000000
illum 2
newmtl glass_front.004
Ns 96.078431
Ka 1.000000 1.000000 1.000000
Kd 0.054000 0.640003 0.568643
Ks 0.500000 0.500000 0.500000
Ke 0.000000 0.000000 0.000000
Ni 1.000000
d 1.000000
illum 2
newmtl glossy
Ns 96.078431
Ka 1.000000 1.000000 1.000000
Kd 0.000000 0.568960 0.640002
Ks 0.500000 0.500000 0.500000
Ke 0.000000 0.000000 0.000000
Ni 1.000000
d 1.000000
illum 2
newmtl headlight_glass.004
Ns 96.078431
Ka 1.000000 1.000000 1.000000
Kd 0.640000 0.640000 0.640000
Ks 0.500000 0.500000 0.500000
Ke 0.000000 0.000000 0.000000
Ni 1.000000
d 1.000000
illum 2
newmtl headlight_square.004
Ns 96.078431
Ka 1.000000 1.000000 1.000000
Kd 0.640000 0.640000 0.640000
Ks 0.500000 0.500000 0.500000
Ke 0.000000 0.000000 0.000000
Ni 1.000000
d 1.000000
illum 2
newmtl red.002
Ns 96.078431
Ka 1.000000 1.000000 1.000000
Kd 0.640000 0.640000 0.640000
Ks 0.500000 0.500000 0.500000
Ke 0.000000 0.000000 0.000000
Ni 1.000000
d 1.000000
illum 2
newmtl red_glass
Ns 96.078431
Ka 1.000000 1.000000 1.000000
Kd 0.640002 0.047774 0.026908
Ks 0.500000 0.500000 0.500000
Ke 0.000000 0.000000 0.000000
Ni 1.000000
d 1.000000
illum 2
newmtl reflective_coat.004
Ns 96.078431
Ka 1.000000 1.000000 1.000000
Kd 0.026482 0.026482 0.026482
Ks 0.500000 0.500000 0.500000
Ke 0.000000 0.000000 0.000000
Ni 1.000000
d 1.000000
illum 2
newmtl rim_blue.004
Ns 96.078431
Ka 1.000000 1.000000 1.000000
Kd 0.026033 0.152250 0.640001
Ks 0.500000 0.500000 0.500000
Ke 0.000000 0.000000 0.000000
Ni 1.000000
d 1.000000
illum 2
newmtl rim_silver.004
Ns 96.078431
Ka 1.000000 1.000000 1.000000
Kd 0.640000 0.640000 0.640000
Ks 0.500000 0.500000 0.500000
Ke 0.000000 0.000000 0.000000
Ni 1.000000
d 1.000000
illum 2
newmtl silver_door_strip.004
Ns 96.078431
Ka 1.000000 1.000000 1.000000
Kd 0.640000 0.601480 0.265530
Ks 0.500000 0.500000 0.500000
Ke 0.000000 0.000000 0.000000
Ni 1.000000
d 1.000000
illum 2
newmtl silver_trim
Ns 96.078431
Ka 1.000000 1.000000 1.000000
Kd 0.043975 0.043975 0.043975
Ks 0.500000 0.500000 0.500000
Ke 0.000000 0.000000 0.000000
Ni 1.000000
d 1.000000
illum 2
newmtl sun
Ns 96.078431
Ka 1.000000 1.000000 1.000000
Kd 0.640000 0.640000 0.640000
Ks 0.500000 0.500000 0.500000
Ke 0.000000 0.000000 0.000000
Ni 1.000000
d 1.000000
illum 2
newmtl tyre.004
Ns 96.078431
Ka 1.000000 1.000000 1.000000
Kd 0.011736 0.011736 0.011736
Ks 0.500000 0.500000 0.500000
Ke 0.000000 0.000000 0.000000
Ni 1.000000
d 1.000000
illum 2
newmtl wheel_joint.004
Ns 96.078431
Ka 1.000000 1.000000 1.000000
Kd 0.640000 0.640000 0.640000
Ks 0.500000 0.500000 0.500000
Ke 0.000000 0.000000 0.000000
Ni 1.000000
d 1.000000
illum 2
newmtl white_holders
Ns 96.078431
Ka 1.000000 1.000000 1.000000
Kd 0.640000 0.640000 0.640000
Ks 0.500000 0.500000 0.500000
Ke 0.000000 0.000000 0.000000
Ni 1.000000
d 1.000000
illum 2

View File

@ -1,63 +1,73 @@
use std::num::NonZero;
use glutin::config::{ ConfigTemplateBuilder, GetGlConfig };
use glutin::context::PossiblyCurrentContext;
use glutin::display::GetGlDisplay;
use glutin::prelude::{GlDisplay, NotCurrentGlContext, PossiblyCurrentGlContext};
use glutin::surface::{GlSurface, Surface, SwapInterval, WindowSurface};
use glutin_winit::{DisplayBuilder, GlWindow};
use winit::application::ApplicationHandler;
use winit::event::KeyEvent;
use winit::keyboard::{Key, NamedKey};
use winit::window::{ Window, WindowAttributes };
use winit::keyboard::{KeyCode, PhysicalKey};
use winit::window::WindowAttributes;
use winit::{event, window};
use crate::app::gl::{gl_config_picker, gl_create_context};
use crate::app::renderer::Renderer;
use crate::models::ModelData;
use crate::app::scene::Scene;
use crate::backend::{DrawCommand, RenderBackend};
use crate::geometry::Mesh;
use crate::handles::{MeshHandle, ShaderHandle};
use crate::models::{self, ModelData};
enum GlDisplayCreationState {
Builder(Box<DisplayBuilder>),
Init
pub enum UploadData<B: RenderBackend> {
ModelData(ModelData),
Shader(B::ShaderSource),
}
struct AppState {
gl_surface: Surface<WindowSurface>,
window: Window,
pub struct App<B: RenderBackend> {
pub backend: B,
pub scene: Scene,
pub meshes: Vec<Mesh>,
pub mesh_handles: Vec<MeshHandle>,
pub model_queue: Vec<ModelData>,
pub shader_queue: Vec<B::ShaderSource>,
pub shaders: Vec<ShaderHandle>,
}
pub struct App {
gl_display: GlDisplayCreationState,
gl_context: Option<PossiblyCurrentContext>,
template: ConfigTemplateBuilder,
renderer: Option<Renderer>,
current_scene: Option<String>,
scene_data: ModelData,
state: Option<AppState>,
}
impl App {
pub fn new(scene_data: ModelData) -> Self {
let template = ConfigTemplateBuilder::new()
.with_alpha_size(8)
.with_transparency(cfg!(target_os = "macos"));
let display_builder = DisplayBuilder::new().with_window_attributes(Some(window_attributes()));
impl<B: RenderBackend> App<B> {
pub fn new() -> Self {
Self {
template,
gl_display: GlDisplayCreationState::Builder(Box::new(display_builder)),
gl_context: None,
renderer: None,
state: None,
scene_data,
current_scene: None,
backend: B::new(),
scene: Default::default(),
meshes: Vec::new(),
mesh_handles: Vec::new(),
shaders: Vec::new(),
model_queue: Vec::new(),
shader_queue: Vec::new(),
}
}
pub fn set_current_scene(&mut self, current_scene: &str) {
self.current_scene = Some(current_scene.to_string());
pub fn add_shader(&mut self, shader_src: B::ShaderSource) {
self.shader_queue.push(shader_src);
}
pub fn add_model(&mut self, model_data: ModelData) {
self.model_queue.push(model_data);
}
fn upload_data_queue(&mut self) {
for mesh in &self.meshes {
let handle = self.backend.create_mesh(mesh).expect("Should be able to create mesh");
self.mesh_handles.push(handle);
}
let mut model: models::Model = Default::default();
for shader_src in &self.shader_queue {
let shader = self.backend.create_shader(&shader_src).expect("Should be able to create shaders");
self.shaders.push(shader);
}
for model_data in &mut self.model_queue {
for mesh in &mut model_data.meshes {
let mat = model_data.materials.iter_mut().find(|mat| mat.name == *mesh.material_name.as_ref()
.expect("Mesh should have associated Material!")
).expect("Model should have Material requested by mesh");
mat.shader = *self.shaders.get(0).expect("Should have a shader");
let new_mesh = self.backend.create_mesh(mesh).unwrap();
let mat_handle = self.scene.add_material(mat);
model.parts.push((new_mesh, mat_handle))
}
model.set_transform(model_data.transform);
}
self.scene.models.push(model);
}
}
@ -67,74 +77,14 @@ pub fn window_attributes() -> WindowAttributes {
.with_title("Test Window")
}
impl ApplicationHandler for App {
impl<B: RenderBackend> ApplicationHandler for App<B> {
fn resumed(&mut self, event_loop: &winit::event_loop::ActiveEventLoop) {
let (window, gl_config) = match &self.gl_display {
GlDisplayCreationState::Builder(builder) => {
let (window, gl_config) = match builder.clone().build(
event_loop,
self.template.clone(),
gl_config_picker
) {
Ok((window, gl_config)) => (window.unwrap(), gl_config),
Err(e) => {
panic!("Encountered Error {:#?}", e);
}
};
self.gl_display = GlDisplayCreationState::Init;
self.gl_context = Some(gl_create_context(&window, &gl_config).treat_as_possibly_current());
(window, gl_config)
},
GlDisplayCreationState::Init => {
let gl_config = self.gl_context.as_ref().unwrap().config();
match glutin_winit::finalize_window(event_loop, window_attributes(), &gl_config) {
Ok(window) => (window, gl_config),
Err(e) => {
panic!("Todo: Handle error for {:#?}", e);
}
}
},
};
let attrs = window
.build_surface_attributes(Default::default())
.expect("Failed to build surface attributes");
let gl_surface = unsafe {
gl_config.display().create_window_surface(&gl_config, &attrs).unwrap()
};
let gl_context = self.gl_context.as_ref().unwrap();
gl_context.make_current(&gl_surface).unwrap();
if self.renderer.is_none() {
let mut renderer = Renderer::new(&gl_context.display());
renderer.upload_scene_data(&self.scene_data);
println!("Built Renderer");
self.renderer.get_or_insert_with(|| renderer);
}
if let Err(res) = gl_surface.set_swap_interval(
gl_context,
SwapInterval::Wait(NonZero::new(1).unwrap())
) {
eprintln!("Error setting vsync: {:?}", res)
}
assert!(self.state.replace(AppState { gl_surface, window }).is_none());
self.backend.resume(event_loop);
self.upload_data_queue();
}
fn suspended(&mut self, _event_loop: &winit::event_loop::ActiveEventLoop) {
println!("Android window removed");
self.state = None;
self.gl_context = Some(
self.gl_context.take().unwrap().make_not_current().unwrap().treat_as_possibly_current()
);
}
fn window_event(
@ -145,52 +95,53 @@ impl ApplicationHandler for App {
) {
match event {
event::WindowEvent::Resized(size) if size.width != 0 && size.height != 0 => {
if let Some(AppState { gl_surface, window: _ }) = &self.state {
let gl_context = self.gl_context.as_ref().unwrap();
gl_surface.resize(
gl_context,
NonZero::new(size.width).unwrap(),
NonZero::new(size.height).unwrap()
);
let renderer = self.renderer.as_ref().unwrap();
renderer.resize(size.width as i32, size.height as i32);
}
},
event::WindowEvent::CloseRequested
| event::WindowEvent::KeyboardInput { event: KeyEvent { logical_key: Key::Named(NamedKey::Escape), .. }, .. } => {
event::WindowEvent::CloseRequested => {
event_loop.exit();
}
event::WindowEvent::KeyboardInput { event, .. } => {
println!("Hit key: {:#?}", &event.physical_key);
match event.physical_key {
PhysicalKey::Code(KeyCode::KeyW) => {
self.scene.camera.position.z -= 0.1;
}
PhysicalKey::Code(KeyCode::KeyS) => {
self.scene.camera.position.z += 0.1;
}
PhysicalKey::Code(KeyCode::Escape) => {
event_loop.exit();
}
_ => {
}
}
}
_ => ()
}
}
fn exiting(&mut self, _event_loop: &winit::event_loop::ActiveEventLoop) {
let _gl_display = self.gl_context.take().unwrap().display();
self.state = None;
#[cfg(egl_backend)]
#[allow(irrefutable_let_patterns)]
if let glutin::display::Display::Egl(display) = _gl_display {
unsafe {
display.terminate();
}
}
// let _gl_display = self.gl_context.take().unwrap().display();
//
// #[cfg(egl_backend)]
// #[allow(irrefutable_let_patterns)]
// if let glutin::display::Display::Egl(display) = _gl_display {
// unsafe {
// display.terminate();
// }
// }
}
fn about_to_wait(&mut self, _event_loop: &winit::event_loop::ActiveEventLoop) {
if let Some(AppState { gl_surface, window }) = self.state.as_ref() {
let gl_context = self.gl_context.as_ref().unwrap();
let renderer = self.renderer.as_ref().unwrap();
if let Some(current_scene) = &self.current_scene {
renderer.render(current_scene);
}
window.request_redraw();
gl_surface.swap_buffers(gl_context).unwrap();
self.backend.begin_frame();
for mesh in &self.mesh_handles {
let cmd = DrawCommand {
mesh: mesh.clone(),
shader: ShaderHandle(0),
textures: &[],
uniforms: &[]
};
self.backend.draw(&cmd);
}
self.backend.end_frame();
}
}

35
src/app/camera.rs Normal file
View File

@ -0,0 +1,35 @@
use crate::math::{Mat4, Vec3};
pub struct Camera {
pub position: Vec3,
pub target: Vec3,
pub up: Vec3,
pub fov: f32,
pub aspect_ratio: f32,
pub near: f32,
pub far: f32,
}
impl Default for Camera {
fn default() -> Self {
Self {
position: Vec3::new(0.0, 2.0, 5.0),
target: Vec3::new(0.0, 0.0, 0.0),
up: Vec3::new(0.0, 1.0, 0.0),
fov: 45.0_f32.to_radians(),
aspect_ratio: 16.0 / 9.0,
near: 0.1,
far: 100.0,
}
}
}
impl Camera {
pub fn view_matrix(&self) -> Mat4 {
Mat4::look_at(self.position, self.target, self.up)
}
pub fn projection_matrix(&self) -> Mat4 {
Mat4::perspective(self.fov, self.aspect_ratio, self.near, self.far)
}
}

15
src/app/light.rs Normal file
View File

@ -0,0 +1,15 @@
use crate::math::Vec3;
pub struct Light {
pub position: Vec3,
pub color: Vec3,
}
impl Default for Light {
fn default() -> Self {
Self {
position: Vec3::new(3.0, 5.0, 3.0),
color: Vec3::new(1.0, 1.0, 1.0),
}
}
}

View File

@ -1,208 +0,0 @@
use glutin::prelude::GlDisplay;
use crate::app::gl;
use crate::models::{self, Model, ModelData, RenderObject, Scene, ShaderSource};
use std::collections::HashMap;
use std::ops::Deref;
use std::ffi::{ CStr, CString };
fn get_gl_string(gl: &gl::Gl, variant: gl::types::GLenum) -> Option<&'static CStr>{
unsafe {
let s = gl.GetString(variant);
(!s.is_null()).then(|| CStr::from_ptr(s.cast()))
}
}
pub struct Renderer {
gl: gl::Gl,
shaders: HashMap<String, gl::types::GLuint>,
scenes: HashMap<String, Scene>
}
impl Renderer {
pub fn new<D: GlDisplay>(gl_display: &D) -> Self {
let gl = gl::Gl::load_with(|symbol| {
let symbol = CString::new(symbol).unwrap();
gl_display.get_proc_address(symbol.as_c_str()).cast()
});
if let Some(renderer) = get_gl_string(&gl, gl::RENDERER) {
println!("Running on {}", renderer.to_string_lossy());
}
if let Some(version) = get_gl_string(&gl, gl::VERSION) {
println!("OpenGL version {}", version.to_string_lossy());
}
if let Some(shaders_version) = get_gl_string(&gl, gl::SHADING_LANGUAGE_VERSION) {
println!("Shaders version on {}", shaders_version.to_string_lossy());
}
Self {
gl,
shaders: HashMap::new(),
scenes: HashMap::new()
}
}
pub fn upload_scene_data(&mut self, scene_data: &ModelData) {
let objects = scene_data.objects_data
.iter()
.map(|(mesh, shader_src)| {
let shader = self.create_shader(shader_src);
let render_object = self.upload_mesh(mesh);
(render_object, shader)
})
.collect::<Vec<(RenderObject, u32)>>();
println!("Uploading Scene {}", scene_data.name);
self.scenes.insert(scene_data.name.clone(), Model { objects });
}
pub fn upload_mesh(&self, mesh: &models::Mesh) -> RenderObject {
unsafe {
let gl = &self.gl;
let mut vao = std::mem::zeroed();
gl.GenVertexArrays(1, &mut vao);
gl.BindVertexArray(vao);
let mut vbo = std::mem::zeroed();
gl.GenBuffers(1, &mut vbo);
gl.BindBuffer(gl::ARRAY_BUFFER, vbo);
let vertex_data = mesh.vertices
.iter()
.map(|v| {
[v.x, v.y, v.z, 1.0, 0.1, 0.1]
})
.collect::<Vec<[f32; 6]>>();
gl.BufferData(
gl::ARRAY_BUFFER,
(vertex_data.len() * std::mem::size_of::<[f32; 6]>()) as gl::types::GLsizeiptr,
vertex_data.as_ptr() as *const _,
gl::STATIC_DRAW,
);
let pos_attrib = 0;
let color_attrib = 1;
gl.VertexAttribPointer(
pos_attrib as gl::types::GLuint,
3,
gl::FLOAT,
0,
6 * std::mem::size_of::<f32>() as gl::types::GLsizei,
std::ptr::null()
);
gl.VertexAttribPointer(
color_attrib as gl::types::GLuint,
3,
gl::FLOAT,
0,
6 * std::mem::size_of::<f32>() as gl::types::GLsizei,
(3 * std::mem::size_of::<f32>()) as *const _,
);
gl.EnableVertexAttribArray(pos_attrib as gl::types::GLuint);
gl.EnableVertexAttribArray(color_attrib as gl::types::GLuint);
RenderObject {
vao,
vbo,
_ebo: None,
vertex_count: mesh.vertices.len() as i32,
_index_count: None
}
}
}
fn compile_shader(gl: &gl::Gl, shader: gl::types::GLenum, source: &[u8]) -> gl::types::GLuint {
unsafe {
let shader = gl.CreateShader(shader);
gl.ShaderSource(shader, 1, [source.as_ptr().cast()].as_ptr(), std::ptr::null());
gl.CompileShader(shader);
shader
}
}
pub fn create_shader(&self, shader_src: &ShaderSource) -> gl::types::GLuint {
unsafe {
let vertex_shader = Renderer::compile_shader(&self.gl, gl::VERTEX_SHADER, shader_src.vertex_src);
let fragment_shader = Renderer::compile_shader(&self.gl, gl::FRAGMENT_SHADER, shader_src.fragment_src);
let program = self.gl.CreateProgram();
self.gl.AttachShader(program, vertex_shader);
self.gl.AttachShader(program, fragment_shader);
self.gl.LinkProgram(program);
self.gl.DeleteShader(vertex_shader);
self.gl.DeleteShader(fragment_shader);
program
}
}
pub fn add_shader(&mut self, name: &str, shader: gl::types::GLuint) {
if let None = self.shaders.get(name) {
self.shaders.insert(name.to_string(), shader);
}
}
pub fn get_shader(&self, name: &str) -> Option<&gl::types::GLuint> {
self.shaders.get(name)
}
pub fn remove_shader(&mut self, name: &str) {
self.shaders.remove(name);
}
pub fn resize(&self, width: i32, height: i32) {
unsafe {
self.gl.Viewport(0, 0, width, height);
}
}
pub fn draw_object(&self, obj: &RenderObject, shader: gl::types::GLuint) {
unsafe {
self.UseProgram(shader);
self.BindBuffer(gl::ARRAY_BUFFER, obj.vbo);
self.BindVertexArray(obj.vao);
self.DrawArrays(gl::TRIANGLES, 0, obj.vertex_count);
}
}
pub fn render(&self, current_scene: &String) {
unsafe {
self.ClearColor(1.0, 1.0, 1.0, 0.9);
self.Clear(gl::COLOR_BUFFER_BIT);
}
if let Some(scene) = self.scenes.get(current_scene) {
for model in scene.models {
self.draw_object(obj, *shader);
}
} else {
eprintln!("Unknown Scene Requested: {}", current_scene);
}
}
}
impl Deref for Renderer {
type Target = gl::Gl;
fn deref(&self) -> &Self::Target {
&self.gl
}
}
impl Drop for Renderer {
fn drop(&mut self) {
unsafe {
self.shaders.values().for_each(|shader| self.gl.DeleteProgram(*shader));
}
}
}

74
src/app/scene.rs Normal file
View File

@ -0,0 +1,74 @@
use std::collections::HashMap;
use crate::{app::{camera::Camera, light::Light}, assets, backend::{Uniform, backend}, handles, math::Mat4, models::Model};
#[derive(Default)]
pub struct Scene {
pub light: Light,
pub camera: Camera,
pub models: Vec<Model>,
pub materials: HashMap<handles::MaterialHandle, assets::mtl::Material>,
next_mat_id: u32,
}
impl Scene {
pub fn new() -> Self {
Self {
light: Light::default(),
camera: Camera::default(),
materials: HashMap::new(),
next_mat_id: 0,
models: vec![]
}
}
pub fn add_material(&mut self, material: &assets::mtl::Material) -> handles::MaterialHandle {
let mat_handle = handles::MaterialHandle(self.next_mat_id);
self.materials.insert(mat_handle, material.clone());
self.next_mat_id += 1;
mat_handle
}
pub fn render(&mut self, backend: &mut impl backend::RenderBackend) {
backend.begin_frame();
let view = self.camera.view_matrix();
let projection = self.camera.projection_matrix();
let scene_uniforms = [
Uniform::Mat4(c"u_view".into(), view),
Uniform::Mat4(c"u_projection".into(), projection),
Uniform::Vec3(c"u_view_pos".into(), self.camera.position),
Uniform::Vec3(c"u_light_pos".into(), self.light.position),
Uniform::Vec3(c"u_light_color".into(), self.light.color),
];
for model in &mut self.models {
let current_transform = model.transform;
let transform = current_transform
* Mat4::rotation_y(0.05);
model.set_transform(transform);
let model_uniform = Uniform::Mat4(c"u_model".into(), model.transform);
for (mesh, mtl) in &model.parts {
let mtl = &self.materials[&mtl];
let mut all_uniforms = vec![model_uniform.clone()];
all_uniforms.extend(scene_uniforms.iter().cloned());
all_uniforms.extend(mtl.uniforms().iter().cloned());
let draw_cmd = backend::DrawCommand {
mesh: *mesh,
shader: mtl.shader,
uniforms: &all_uniforms,
textures: &vec![],
};
backend.draw(&draw_cmd);
}
}
backend.end_frame();
}
}

33
src/assets/error.rs Normal file
View File

@ -0,0 +1,33 @@
use thiserror::Error;
#[derive(Debug, Error)]
pub enum ParseError {
#[error("\n expected {expected} values, got {got}")]
TooFewValues { expected: usize, got: usize },
#[error("\n expected {expected} values, got {got}")]
TooManyValues { expected: usize, got: usize },
#[error("\n invalid float: {0}")]
InvalidFloat(#[from] std::num::ParseFloatError),
#[error("\n invalid value: {0}")]
InvalidValue(#[from] Box<dyn std::error::Error + Send + Sync>),
#[error("\n unknown prefix: {0}")]
UnknownPrefix(String),
}
#[derive(Debug, Error)]
pub enum AssetError {
#[error("\n Parse in {file} at line {line}: {source}")]
Parse {
file: String,
line: usize,
#[source]
source: ParseError
},
#[error("I/O: {0}")]
IO(#[from] std::io::Error),
}

255
src/assets/mtl.rs Normal file
View File

@ -0,0 +1,255 @@
use core::ffi;
use crate::{assets::{error::AssetError, parser::parse_args}, backend::Uniform, handles::ShaderHandle, math::{Mat4, Vec3, Vec4, Vec2}};
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct TextureId(pub u32);
#[derive(Clone, Debug)]
pub struct Material {
pub name: String,
pub shader: ShaderHandle,
ambient: Vec3,
diffuse: Vec3,
specular: Vec3,
emissive: Vec3,
_shininess: f32,
opacity: f32,
pub diffuse_texture: Option<TextureId>,
pub normal_texture: Option<TextureId>,
pub specular_texture: Option<TextureId>,
uniforms: Vec<Uniform>,
textures: Vec<TextureId>,
}
impl Default for Material {
fn default() -> Self {
let mut mat = Self {
name: String::new(),
shader: ShaderHandle::default(),
ambient: Vec3::new(0.1, 0.1, 0.1),
diffuse: Vec3::new(0.8, 0.8, 0.8),
specular: Vec3::new(0.5, 0.5, 0.5),
emissive: Vec3::new(0.0, 0.0, 0.0),
_shininess: 32.0,
opacity: 1.0,
diffuse_texture: None,
normal_texture: None,
specular_texture: None,
uniforms: Vec::new(),
textures: Vec::new(),
};
// Set default uniforms
mat.set_uniform_v3(c"u_ambient", mat.ambient);
mat.set_uniform_v3(c"u_diffuse", mat.diffuse);
mat.set_uniform_v3(c"u_specular", mat.specular);
mat.set_uniform_f(c"u_shininess", mat._shininess);
mat
}
}
impl Material {
pub fn new() -> Self {
Self::default()
}
pub fn set_ambient(&mut self, v: Vec3) {
self.ambient = v;
self.set_uniform_v3(c"u_ambient", v);
}
pub fn set_diffuse(&mut self, v: Vec3) {
self.diffuse = v;
self.set_uniform_v3(c"u_diffuse", v);
}
pub fn set_specular(&mut self, v: Vec3) {
self.specular = v;
self.set_uniform_v3(c"u_specular", v);
}
pub fn set_emissive(&mut self, v: Vec3) {
self.emissive = v;
self.set_uniform_v3(c"u_emissive", v);
}
pub fn set_shininess(&mut self, v: f32) {
self._shininess = v;
self.set_uniform_f(c"u_shininess", v);
}
pub fn set_opacity(&mut self, v: f32) {
self.opacity = v;
self.set_uniform_f(c"u_opacity", v);
}
pub fn set_uniform_f(&mut self, name: &ffi::CStr, val: f32) {
if let Some(Uniform::Float(_, v)) = self.uniforms
.iter_mut()
.find(|u| matches!(u, Uniform::Float(n, _) if n == name))
{
*v = val;
} else {
self.uniforms.push(Uniform::Float(name.into(), val));
}
}
pub fn set_uniform_v2(&mut self, name: &ffi::CStr, val: Vec2) {
if let Some(Uniform::Vec2(_, v)) = self.uniforms
.iter_mut()
.find(|u| matches!(u, Uniform::Vec2(n, _) if n == name))
{
*v = val;
} else {
self.uniforms.push(Uniform::Vec2(name.into(), val));
}
}
pub fn set_uniform_v3(&mut self, name: &ffi::CStr, val: Vec3) {
if let Some(Uniform::Vec3(_, v)) = self.uniforms
.iter_mut()
.find(|u| matches!(u, Uniform::Vec3(n, _) if n == name))
{
*v = val;
} else {
self.uniforms.push(Uniform::Vec3(name.into(), val));
}
}
pub fn set_uniform_v4(&mut self, name: &ffi::CStr, val: Vec4) {
if let Some(Uniform::Vec4(_, v)) = self.uniforms
.iter_mut()
.find(|u| matches!(u, Uniform::Vec4(n, _) if n == name))
{
*v = val;
} else {
self.uniforms.push(Uniform::Vec4(name.into(), val));
}
}
pub fn set_uniform_m4(&mut self, name: &ffi::CStr, val: Mat4) {
if let Some(Uniform::Mat4(_, v)) = self.uniforms
.iter_mut()
.find(|u| matches!(u, Uniform::Mat4(n, _) if n == name))
{
*v = val;
} else {
self.uniforms.push(Uniform::Mat4(name.into(), val));
}
}
pub fn uniforms(&self) -> &[Uniform] {
&self.uniforms
}
pub fn textures(&self) -> &[TextureId] {
&self.textures
}
}
const NEW_MATERIAL_DELIMITER: &str = "newmtl ";
pub fn load_materials(path: &str) -> Result<Vec<Material>, AssetError> {
let content = std::fs::read_to_string(path)?;
let mut materials = vec![];
let mut current_material: Option<Material> = None;
for (i, line) in content.lines().enumerate() {
let line = line.trim();
if line.is_empty() || line.starts_with('#') {
continue;
}
if line.starts_with(NEW_MATERIAL_DELIMITER) {
if let Some(mat) = current_material.take() {
materials.push(mat);
}
let mat_name = line.strip_prefix(NEW_MATERIAL_DELIMITER).unwrap().trim();
let mut material = Material::new();
material.name = mat_name.to_string();
current_material = Some(material);
} else if let Some(ref mut material) = current_material {
let delim = " ";
let i = i + 1;
if let Some((key, values)) = line.split_once(' ') {
match key.trim() {
"Ka" => {
match parse_args::<3, f32>(values, delim) {
Ok([x, y, z]) => material.set_ambient(Vec3::new(x, y, z)),
Err(e) => return Err(AssetError::Parse {
file: path.to_string(),
line: i,
source: e,
})
}
},
"Kd" => {
match parse_args::<3, f32>(values, delim) {
Ok([x, y, z]) => material.set_diffuse(Vec3::new(x, y, z)),
Err(e) => return Err(AssetError::Parse {
file: path.to_string(),
line: i,
source: e,
})
}
},
"Ks" => {
match parse_args::<3, f32>(values, delim) {
Ok([x, y, z]) => material.set_specular(Vec3::new(x, y, z)),
Err(e) => return Err(AssetError::Parse {
file: path.to_string(),
line: i,
source: e,
})
}
},
"Ke" => {
match parse_args::<3, f32>(values, delim) {
Ok([x, y, z]) => material.set_emissive(Vec3::new(x, y, z)),
Err(e) => return Err(AssetError::Parse {
file: path.to_string(),
line: i,
source: e,
})
}
}
"Ns" => {
match parse_args::<1, f32>(values, delim) {
Ok(v) => material.set_shininess(v[0]),
Err(e) => return Err(AssetError::Parse {
file: path.to_string(),
line: i,
source: e,
})
}
},
"d" => {
match parse_args::<1, f32>(values, delim) {
Ok(v) => material.set_opacity(v[0]),
Err(e) => return Err(AssetError::Parse {
file: path.to_string(),
line: i,
source: e,
})
}
},
_ => {}
}
}
}
}
if let Some(mat) = current_material {
materials.push(mat);
}
Ok(materials)
}

105
src/assets/obj.rs Normal file
View File

@ -0,0 +1,105 @@
use crate::{assets::parser::parse_args, math::{ Vec2, Vec3 }};
use super::types;
use super::error::AssetError;
pub fn load_obj(path: &str) -> Result<types::ObjData, AssetError> {
let mut obj_data = types::ObjData::new();
let content = std::fs::read_to_string(path)?;
let mut current_name = String::new();
let mut current_mash = types::ObjMesh {
name: String::new(),
material_name: None,
faces: vec![]
};
let delim = " ";
for (i, line) in content.lines().enumerate() {
let i = i + 1;
let line = line.trim();
if let Some((prefix, values)) = line.split_once(' ') {
match prefix.trim() {
"v" => {
match parse_args::<3, f32>(values, delim) {
Ok([x, y, z]) => obj_data.vertices.push(Vec3::new(x, y, z)),
Err(e) => return Err(AssetError::Parse {
file: path.to_string(),
line: i,
source: e,
})
}
},
"vt" => {
match parse_args::<2, f32>(values, delim) {
Ok([x, y]) => obj_data.tex_coords.push(Vec2::new(x, y)),
Err(e) => return Err(AssetError::Parse {
file: path.to_string(),
line: i,
source: e,
})
}
},
"vn" => {
match parse_args::<3, f32>(values, delim) {
Ok([x, y, z]) => obj_data.normals.push(Vec3::new(x, y, z)),
Err(e) => return Err(AssetError::Parse {
file: path.to_string(),
line: i,
source: e,
})
}
},
"o" | "g" => {
current_name = values.to_string();
current_mash.name = current_name.clone();
}
"usemtl" => {
if !current_mash.faces.is_empty() {
obj_data.meshes.push(current_mash);
}
let material_name = Some(values.to_string());
current_mash = types::ObjMesh {
name: current_name.clone(),
material_name,
faces: vec![]
};
},
"mtllib" => {
obj_data.material_lib = Some(values.to_string());
}
"f" => {
let mut current_face = types::ObjFace { indices: vec![] };
let mut line_split = values.split_whitespace();
while let Some(val) = line_split.next() {
let face_index: [u32; 3] = parse_args::<3, u32>(val, "/")
.map_err(|e| AssetError::Parse {
file: path.to_string(),
line: i,
source: e
})?;
// OBJ indices are 1-based, convert to 0-based
let vertex_index = types::VertexIndex {
position: face_index[0] - 1,
tex_coord: if face_index[1] != 0 { Some(face_index[1] - 1) } else { None },
normal: if face_index[2] != 0 { Some(face_index[2] - 1) } else { None },
};
current_face.indices.push(vertex_index);
}
current_mash.faces.push(current_face);
}
_ => {}
}
}
}
if !current_mash.faces.is_empty() {
obj_data.meshes.push(current_mash);
}
Ok(obj_data)
}

62
src/assets/parser.rs Normal file
View File

@ -0,0 +1,62 @@
use std::str::FromStr;
use crate::assets::error::ParseError;
pub fn parse_args<const N: usize, T>(
values: &str,
delim: &str
) -> Result<[T; N], ParseError>
where
T: FromStr + Copy + Default,
T::Err: std::error::Error + 'static + Send + Sync
{
let mut result = [T::default(); N];
let mut i = 0;
let values = values.split(delim);
for v in values {
if i < N {
let parsed = if !v.is_empty() {
v.parse::<T>().map_err(|e| {ParseError::InvalidValue(Box::new(e))})?
} else {
Default::default()
};
result[i] = parsed;
}
i += 1;
}
if i > N {
Err(ParseError::TooManyValues{ expected: N, got: i })
} else if i < N {
Err(ParseError::TooFewValues{ expected: N, got: i })
} else {
Ok(result)
}
}
pub fn parse_all_args<T>(
values: &str,
delim: &str
) -> Result<Vec<T>, ParseError>
where
T: FromStr + Copy + Default,
T::Err: std::error::Error + 'static + Send + Sync
{
let mut result: Vec<T> = Vec::new();
let mut i = 0;
let values = values.split(delim);
for v in values {
let parsed = if !v.is_empty() {
v.parse::<T>().map_err(|e| {ParseError::InvalidValue(Box::new(e))})?
} else {
Default::default()
};
result[i] = parsed;
i += 1;
}
Ok(result)
}

41
src/assets/types.rs Normal file
View File

@ -0,0 +1,41 @@
use crate::math::{ Vec2, Vec3 };
#[derive(Debug)]
pub struct ObjData {
pub normals: Vec<Vec3>,
pub vertices: Vec<Vec3>,
pub tex_coords: Vec<Vec2>,
pub meshes: Vec<ObjMesh>,
pub material_lib: Option<String>
}
#[derive(Debug)]
pub struct ObjMesh {
pub name: String,
pub material_name: Option<String>,
pub faces: Vec<ObjFace>,
}
#[derive(Debug)]
pub struct ObjFace {
pub indices: Vec<VertexIndex>,
}
impl ObjData {
pub fn new() -> Self {
Self {
normals: vec![],
vertices: vec![],
tex_coords: vec![],
meshes: vec![],
material_lib: None
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct VertexIndex {
pub position: u32,
pub tex_coord: Option<u32>,
pub normal: Option<u32>
}

49
src/backend/backend.rs Normal file
View File

@ -0,0 +1,49 @@
use crate::{geometry, handles::{self, MeshHandle}, math};
use std::ffi::CString;
pub struct DrawCommand<'a> {
pub mesh: handles::MeshHandle,
pub shader: handles::ShaderHandle,
pub uniforms: &'a [Uniform],
pub textures: &'a [(u32, u32)],
}
#[derive(Clone, Debug)]
pub enum Uniform {
Float(CString, f32),
Vec2(CString, math::Vec2),
Vec3(CString, math::Vec3),
Vec4(CString, math::Vec4),
Mat4(CString, math::Mat4),
Int(CString, i32)
}
pub trait RenderBackend {
type ShaderSource: Default;
fn new() -> Self;
fn resume(&mut self, event_loop: &winit::event_loop::ActiveEventLoop);
fn resize(&mut self, width: u32, height: u32);
fn suspend(&mut self);
fn begin_frame(&mut self);
fn draw(&self, cmd: &DrawCommand);
fn end_frame(&self);
fn create_shader(&mut self, desc: &Self::ShaderSource) -> Result<handles::ShaderHandle, String>;
fn create_mesh(&mut self, mesh: &geometry::Mesh) -> Result<MeshHandle, String>;
// fn create_buffer(&mut self, data: &[u8], usage: BufferUsage) -> BufferHandle;
// fn create_texture(&mut self, desc: TextureDescriptor) -> TextureHandle;
fn set_target_fps(&mut self, fps: u32);
}
// pub struct FrameContext<'a> {
// backend: &'a mut dyn RenderBackend,
// }
//
// impl FrameContext<'_> {
// pub fn draw(&mut self, mesh: &Mesh, material: MaterialHandle, transform: Mat4);
// }

8
src/backend/gpu/mesh.rs Normal file
View File

@ -0,0 +1,8 @@
#[derive(Debug)]
pub struct GpuMesh {
pub vao: gl::types::GLuint,
pub vbo: gl::types::GLuint,
pub ebo: Option<gl::types::GLuint>,
pub vertex_count: i32,
pub index_count: Option<i32>,
}

View File

@ -3,6 +3,7 @@
include!(concat!(env!("OUT_DIR"), "/gl_bindings.rs"));
pub use Gles2 as Gl;
pub use crate::math;
use glutin::config::{Config, GlConfig};
use glutin::context::{ContextApi, ContextAttributesBuilder, NotCurrentContext, Version};
@ -50,4 +51,3 @@ pub fn gl_create_context(window: &Window, gl_config: &Config) -> NotCurrentConte
})
}
}

View File

@ -0,0 +1,227 @@
use std::collections::HashMap;
use std::num::NonZero;
use std::thread;
use std::time::{Duration, Instant};
use glutin::config::GetGlConfig;
use glutin::display::GetGlDisplay;
use glutin::prelude::{GlDisplay, NotCurrentGlContext, PossiblyCurrentGlContext};
use glutin::surface::{GlSurface, SwapInterval};
use glutin_winit::GlWindow;
use winit::window::Window;
use crate::backend::gpu;
use crate::{backend, geometry, handles, shaders};
use crate::app::app::window_attributes;
use crate::backend::opengl::{self, GlRenderer};
#[derive(Debug)]
enum GlDisplayCreationState {
Builder(glutin_winit::DisplayBuilder),
Init
}
#[derive(Debug)]
struct AppState {
gl_surface: glutin::surface::Surface<glutin::surface::WindowSurface>,
window: Window,
}
pub struct GlBackend {
gl_display: GlDisplayCreationState,
gl_context: Option<glutin::context::PossiblyCurrentContext>,
gl: Option<GlRenderer>,
template: glutin::config::ConfigTemplateBuilder,
state: Option<AppState>,
meshes: HashMap<handles::MeshHandle, gpu::GpuMesh>,
shaders: HashMap<handles::ShaderHandle, shaders::Shader>,
// tmp
next_mesh_id: u32,
next_shader_id: u32,
frame_time: Duration,
start: Instant,
}
impl backend::RenderBackend for GlBackend {
type ShaderSource = opengl::GlShaderSource;
fn new() -> Self where Self: Sized {
let template = glutin::config::ConfigTemplateBuilder::new()
.with_alpha_size(8)
.with_multisampling(4)
.with_transparency(cfg!(target_os = "macos"));
let display_builder = glutin_winit::DisplayBuilder::new().with_window_attributes(Some(window_attributes()));
Self {
gl_display: GlDisplayCreationState::Builder(display_builder),
gl_context: None,
state: None,
gl: None,
meshes: HashMap::new(),
shaders: HashMap::new(),
next_mesh_id: 0,
next_shader_id: 0,
frame_time: Duration::from_secs_f32(1.0 / 60.0),
start: Instant::now(),
template,
}
}
fn resume(&mut self, event_loop: &winit::event_loop::ActiveEventLoop) {
let (window, gl_config) = match &self.gl_display {
GlDisplayCreationState::Builder(builder) => {
let (window, gl_config) = match builder.clone().build(
event_loop,
self.template.clone(),
opengl::gl_config_picker
) {
Ok((window, gl_config)) => (window.unwrap(), gl_config),
Err(e) => {
eprintln!("Error: {:#?}", e);
return;
}
};
self.gl_display = GlDisplayCreationState::Init;
self.gl_context = Some(opengl::gl_create_context(&window, &gl_config).treat_as_possibly_current());
(window, gl_config)
},
GlDisplayCreationState::Init => {
let gl_config = self.gl_context.as_ref().unwrap().config();
match glutin_winit::finalize_window(event_loop, window_attributes(), &gl_config) {
Ok(window) => (window, gl_config),
Err(e) => {
eprintln!("Error: {:#?}", e);
return;
}
}
},
};
let attrs = window
.build_surface_attributes(Default::default())
.expect("Failed to build surface attributes");
let gl_surface = unsafe {
gl_config.display().create_window_surface(&gl_config, &attrs).unwrap()
};
let gl_context = self.gl_context.as_ref().unwrap();
gl_context.make_current(&gl_surface).unwrap();
if self.gl.is_none() {
let renderer = opengl::GlRenderer::new(
&gl_context.display()
);
self.gl = Some(renderer);
}
if let Err(res) = gl_surface.set_swap_interval(
gl_context,
SwapInterval::Wait(NonZero::new(1).unwrap())
) {
eprintln!("Error setting vsync: {:?}", res)
}
window.request_redraw();
assert!(self.state.replace(AppState { gl_surface, window }).is_none());
}
fn suspend(&mut self) {
self.state = None;
self.gl_context = Some(
self.gl_context.take().unwrap().make_not_current().unwrap().treat_as_possibly_current()
);
}
fn resize(&mut self, width: u32, height: u32) {
if let Some(AppState { gl_surface, window: _ }) = &self.state && self.gl.is_some() {
let gl_context = self.gl_context.as_ref().unwrap();
gl_surface.resize(
gl_context,
NonZero::new(width).unwrap(),
NonZero::new(height).unwrap()
);
unsafe {
self.gl
.as_ref()
.unwrap()
.Viewport(0, 0, width as i32, height as i32);
}
}
}
fn create_shader(&mut self, desc: &Self::ShaderSource) -> Result<handles::ShaderHandle, String> {
if let Some(gl) = &self.gl {
let shader = gl.create_shader(&desc);
let shader_handle = handles::ShaderHandle(self.next_shader_id);
self.shaders.insert(shader_handle, shader);
self.next_shader_id += 1;
Ok(shader_handle)
} else {
todo!("implement error type for GlBackend")
}
}
fn create_mesh(&mut self, mesh: &geometry::Mesh) -> Result<handles::MeshHandle, String> {
if let Some(gl) = &self.gl {
let gpu_mesh = gl.upload_mesh(mesh);
let mesh_handle = handles::MeshHandle(self.next_mesh_id);
self.meshes.insert(mesh_handle, gpu_mesh);
self.next_mesh_id += 1;
Ok(mesh_handle)
} else {
todo!("implement error type for GlBackend")
}
}
fn begin_frame(&mut self) {
if let Some(gl) = &self.gl {
gl.clear(0.1, 0.1, 0.1, 1.0);
self.start = Instant::now();
}
}
fn draw(&self, cmd: &backend::DrawCommand) {
let mesh = self.meshes.get(&cmd.mesh);
let shader = self.shaders.get(&cmd.shader);
if let (Some(mesh), Some(shader), Some(gl)) = (mesh, shader, &self.gl) {
gl.use_shader(*shader);
for u in cmd.uniforms {
gl.apply_uniform(*shader, &u);
}
gl.draw_mesh(mesh);
} else {
println!("{:#?}", mesh);
println!("{:#?}", shader);
println!("{:#?}", self.state);
println!("{:#?}", self.gl.is_some());
}
}
fn end_frame(&self) {
if let (Some(state), Some(gl_context)) = (&self.state, &self.gl_context) {
state.window.request_redraw();
state.gl_surface.swap_buffers(gl_context).unwrap();
let elapsed = self.start.elapsed();
if elapsed < self.frame_time {
thread::sleep(self.frame_time - elapsed);
}
}
}
fn set_target_fps(&mut self, fps: u32) {
self.frame_time = Duration::from_secs_f32(1.0 / (fps as f32));
}
}

View File

@ -0,0 +1,244 @@
use gl::types::GLfloat;
use glutin::prelude::GlDisplay;
use crate::backend::{opengl, Uniform};
use crate::math::{ Mat4, Vec3 };
use crate::{geometry, backend::gpu::GpuMesh};
use crate::shaders::Shader;
use std::ops::Deref;
use std::ffi::{ CStr, CString, c_void };
use super::gl::Gl;
#[derive(Debug)]
pub struct RenderState {
pub projection: Mat4,
pub view: Mat4,
pub model: Mat4,
pub camera_pos: Vec3,
pub light_pos: Vec3,
pub light_color: Vec3,
}
impl RenderState {
pub fn new(aspect_ratio: f32) -> Self {
let camera_pos = Vec3::new(0.0, 2.0, 5.0);
Self {
projection: Mat4::perspective(45.0_f32.to_radians(), aspect_ratio, 0.1, 100.0),
view: Mat4::look_at(
camera_pos,
Vec3::new(0.0, 0.0, 0.0),
Vec3::new(0.0, 1.0, 0.0),
),
model: Mat4::identity(),
camera_pos,
light_pos: Vec3::new(3.0, 5.0, 3.0),
light_color: Vec3::new(1.0, 1.0, 1.0),
}
}
}
fn get_gl_string(gl: &opengl::Gl, variant: gl::types::GLenum) -> Option<&'static CStr>{
unsafe {
let s = gl.GetString(variant);
(!s.is_null()).then(|| CStr::from_ptr(s.cast()))
}
}
pub struct GlRenderer {
gl: Gl,
}
impl GlRenderer {
pub fn new<D: GlDisplay>(gl_display: &D) -> Self {
let gl = opengl::Gl::load_with(|symbol| {
let symbol = CString::new(symbol).unwrap();
gl_display.get_proc_address(symbol.as_c_str()).cast()
});
if let Some(renderer) = get_gl_string(&gl, gl::RENDERER) {
println!("Running on {}", renderer.to_string_lossy());
}
if let Some(version) = get_gl_string(&gl, gl::VERSION) {
println!("OpenGL version {}", version.to_string_lossy());
}
if let Some(shaders_version) = get_gl_string(&gl, gl::SHADING_LANGUAGE_VERSION) {
println!("Shaders version on {}", shaders_version.to_string_lossy());
}
unsafe {
gl.Enable(gl::DEPTH_TEST);
gl.DepthFunc(gl::LESS);
gl.Enable(gl::MULTISAMPLE);
}
Self {
gl
}
}
pub fn upload_mesh(&self, mesh: &geometry::Mesh) -> GpuMesh {
unsafe {
let gl = &self.gl;
let mut vao = std::mem::zeroed();
gl.GenVertexArrays(1, &mut vao);
gl.BindVertexArray(vao);
let mut vbo = std::mem::zeroed();
gl.GenBuffers(1, &mut vbo);
gl.BindBuffer(gl::ARRAY_BUFFER, vbo);
for attribute in &mesh.format.attributes {
let location = attribute.semantic.location();
gl.VertexAttribPointer(
attribute.semantic.location(),
attribute.comp_count as i32,
attribute.data_type.to_gpu(),
attribute.normalized as u8,
mesh.format.stride as i32,
attribute.offset as *const c_void,
);
gl.EnableVertexAttribArray(location);
}
gl.BufferData(
gl::ARRAY_BUFFER,
mesh.vertex_data.len() as gl::types::GLsizeiptr,
mesh.vertex_data.as_ptr() as *const _,
gl::STATIC_DRAW,
);
let (ebo, index_count) = if !mesh.indices.is_empty() {
let mut ebo = std::mem::zeroed();
gl.GenBuffers(1, &mut ebo);
gl.BindBuffer(gl::ELEMENT_ARRAY_BUFFER, ebo);
gl.BufferData(
gl::ELEMENT_ARRAY_BUFFER,
(mesh.indices.len() * std::mem::size_of::<u32>()) as gl::types::GLsizeiptr,
mesh.indices.as_ptr() as *const _,
gl::STATIC_DRAW,
);
(Some(ebo), Some(mesh.indices.len() as i32))
} else {
(None, None)
};
gl.BindVertexArray(0);
GpuMesh {
vao,
vbo,
ebo,
vertex_count: mesh.vertex_count as i32,
index_count,
}
}
}
fn compile_shader(gl: &opengl::Gl, shader: gl::types::GLenum, source: &[u8]) -> gl::types::GLuint {
unsafe {
let shader = gl.CreateShader(shader);
gl.ShaderSource(shader, 1, [source.as_ptr().cast()].as_ptr(), std::ptr::null());
gl.CompileShader(shader);
shader
}
}
pub fn create_shader(&self, shader_src: &opengl::GlShaderSource) -> Shader {
unsafe {
let vertex_shader = Self::compile_shader(
&self.gl,
gl::VERTEX_SHADER,
shader_src.vertex_src.as_bytes()
);
let fragment_shader = Self::compile_shader(
&self.gl,
gl::FRAGMENT_SHADER,
shader_src.fragment_src.as_bytes()
);
let program = self.gl.CreateProgram();
self.gl.AttachShader(program, vertex_shader);
self.gl.AttachShader(program, fragment_shader);
self.gl.LinkProgram(program);
self.gl.DeleteShader(vertex_shader);
self.gl.DeleteShader(fragment_shader);
Shader(program)
}
}
pub fn apply_uniform(&self, shader: Shader, uniform: &Uniform) {
unsafe {
match uniform {
Uniform::Float(name, value) => {
let location = self.GetUniformLocation(*shader, name.as_ptr());
self.Uniform1f(location, *value);
}
Uniform::Int(name, value) => {
let location = self.GetUniformLocation(*shader, name.as_ptr());
self.Uniform1i(location, *value);
}
Uniform::Vec2(name, value) => {
let location = self.GetUniformLocation(*shader, name.as_ptr());
self.Uniform2f(location, value[0], value[1]);
}
Uniform::Vec3(name, value) => {
let location = self.GetUniformLocation(*shader, name.as_ptr());
self.Uniform3f(location, value[0], value[1], value[2]);
}
Uniform::Vec4(name, value) => {
let location = self.GetUniformLocation(*shader, name.as_ptr());
self.Uniform4f(location, value[0], value[1], value[2], value[3]);
}
Uniform::Mat4(name, value) => {
let location = self.GetUniformLocation(*shader, name.as_ptr());
self.UniformMatrix4fv(location, 1, gl::FALSE, value.as_ptr());
}
}
}
}
pub fn clear(&self, red: GLfloat, green: GLfloat, blue: GLfloat, alpha: GLfloat) {
unsafe {
self.ClearColor(red, green, blue, alpha);
self.Clear(gl::COLOR_BUFFER_BIT | gl::DEPTH_BUFFER_BIT);
}
}
pub fn use_shader(&self, shader: Shader) {
unsafe { self.UseProgram(*shader) };
}
pub fn resize(&self, width: i32, height: i32) {
unsafe {
self.gl.Viewport(0, 0, width, height);
}
}
pub fn draw_mesh(&self, mesh: &GpuMesh) {
unsafe {
self.BindVertexArray(mesh.vao);
if let (Some(ebo), Some(count)) = (mesh.ebo, mesh.index_count) {
self.BindBuffer(gl::ELEMENT_ARRAY_BUFFER, ebo);
self.DrawElements(gl::TRIANGLES, count, gl::UNSIGNED_INT, std::ptr::null());
} else {
self.DrawArrays(gl::TRIANGLES, 0, mesh.vertex_count);
}
}
}
}
impl Deref for GlRenderer {
type Target = opengl::Gl;
fn deref(&self) -> &Self::Target {
&self.gl
}
}

View File

@ -0,0 +1,5 @@
#[derive(Default)]
pub struct GlShaderSource {
pub vertex_src: String,
pub fragment_src: String,
}

96
src/geometry/mesh.rs Normal file
View File

@ -0,0 +1,96 @@
use std::collections::HashMap;
use crate::{assets::types::{ObjData, ObjMesh, VertexIndex}, geometry::VertexFormat, math::{Vec2, Vec4}};
use super::Vertex;
#[derive(Debug, Default)]
pub struct Mesh {
pub name: String,
pub format: VertexFormat,
pub vertex_data: Vec<u8>,
pub vertex_count: usize,
pub vertices: Vec<Vertex>,
pub indices: Vec<u32>,
pub material_name: Option<String>,
}
#[derive(Default)]
pub struct MeshBuilder {
mesh: Mesh,
current_vertex: Vec<u8>,
}
impl MeshBuilder {
pub fn build(self) -> Mesh {
self.mesh
}
pub fn begin_vertex(mut self) -> Self {
self.current_vertex.clear();
self
}
pub fn end_vertex(mut self) -> Self {
if self.current_vertex.len() > 0 {
self.mesh.vertex_data.extend_from_slice(&self.current_vertex);
self.mesh.vertex_count += 1;
}
self
}
pub fn position2(mut self, pos: Vec2) -> Self {
self.current_vertex.extend_from_slice(&pos.to_bytes());
self
}
pub fn tex_coord(mut self, pos: Vec2) -> Self {
self.current_vertex.extend_from_slice(&pos.to_bytes());
self
}
pub fn color(mut self, color: Vec4) -> Self {
self.current_vertex.extend_from_slice(&color.to_bytes());
self
}
}
impl Mesh {
pub fn builder(name: impl AsRef<str>, format: VertexFormat) -> MeshBuilder {
let mesh: Self = Self { name: name.as_ref().to_string(), format, ..Default::default() };
MeshBuilder {
mesh,
current_vertex: vec![],
}
}
pub fn new(obj: &ObjData, obj_mash: &ObjMesh) -> Self {
let mut vertices: Vec<Vertex> = Vec::new();
let mut indices: Vec<u32> = Vec::new();
let mut vertex_map: HashMap<VertexIndex, u32> = HashMap::new();
for face in &obj_mash.faces {
for i in 1..(face.indices.len() - 1) {
for &idx in &[face.indices[0], face.indices[i], face.indices[i +1]] {
let index = *vertex_map.entry(idx).or_insert_with(|| {
let v = Vertex {
position: obj.vertices[idx.position as usize],
normal: idx.normal.map(|n| obj.normals[n as usize]).unwrap_or_default(),
tex_coord: idx.tex_coord.map(|t| obj.tex_coords[t as usize]).unwrap_or_default(),
};
vertices.push(v);
(vertices.len() - 1) as u32
});
indices.push(index);
}
}
}
Mesh {
vertices,
format: Default::default(),
vertex_data: vec![],
vertex_count: 0,
indices,
name: obj_mash.name.clone(),
material_name: obj_mash.material_name.clone(),
}
}
}

100
src/geometry/vertex.rs Normal file
View File

@ -0,0 +1,100 @@
use std::fmt::Debug;
use crate::math::{ Vec2, Vec3 };
#[derive(Debug)]
pub enum VertexSemantic {
Position,
Normal,
TexCoord0,
TexCoord1,
Color,
}
impl VertexSemantic {
pub fn location(&self) -> u32 {
match self {
VertexSemantic::Position => 0,
VertexSemantic::Normal => 1,
VertexSemantic::Color => 2,
VertexSemantic::TexCoord0 => 3,
VertexSemantic::TexCoord1 => 4,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum AttributeType {
Float,
Int,
UnsignedInt,
Byte,
UnsignedByte,
}
impl AttributeType {
pub fn to_gpu(&self) -> gl::types::GLenum {
match self {
AttributeType::Float => gl::FLOAT,
AttributeType::Int => gl::INT,
AttributeType::UnsignedInt => gl::UNSIGNED_INT,
AttributeType::Byte => gl::BYTE,
AttributeType::UnsignedByte => gl::UNSIGNED_BYTE,
}
}
}
#[derive(Debug)]
pub struct VertexAttribute {
pub semantic: VertexSemantic,
pub comp_count: usize,
pub data_type: AttributeType,
pub normalized: bool,
pub offset: usize,
}
#[derive(Debug)]
pub struct VertexFormat {
pub attributes: Vec<VertexAttribute>,
pub stride: usize,
}
impl VertexFormat {
pub fn ui_2d() -> Self {
Self {
attributes: vec![
VertexAttribute {
semantic: VertexSemantic::Position,
comp_count: 2,
data_type: AttributeType::Float,
normalized: false,
offset: 0,
},
VertexAttribute {
semantic: VertexSemantic::Color,
comp_count: 4,
data_type: AttributeType::Float,
normalized: false,
offset: 2 * std::mem::size_of::<f32>()
}
],
stride: (2 + 4) * std::mem::size_of::<f32>()
}
}
}
impl Default for VertexFormat {
fn default() -> Self {
Self {
attributes: vec![],
stride: 0
}
}
}
#[derive(Debug)]
pub struct Vertex {
pub position: Vec3,
pub normal: Vec3,
pub tex_coord: Vec2,
}

17
src/handles.rs Normal file
View File

@ -0,0 +1,17 @@
use std::ops::Deref;
#[derive(Debug, Default, Copy, Clone, Hash, PartialEq, Eq)]
pub struct MeshHandle(pub u32);
#[derive(Debug, Default, Copy, Clone, Hash, PartialEq, Eq)]
pub struct MaterialHandle(pub u32);
#[derive(Default, Debug, Copy, Clone, Hash, PartialEq, Eq)]
pub struct ShaderHandle(pub u32);
impl Deref for ShaderHandle {
type Target = u32;
fn deref(&self) -> &Self::Target {
&self.0
}
}

View File

@ -1,27 +1,79 @@
pub mod models {
pub mod mesh;
pub use mesh::*;
pub mod model;
pub use model::*;
}
pub mod assets {
pub mod mtl;
pub mod obj;
pub mod types;
pub mod parser;
pub mod error;
}
pub mod geometry {
pub mod vertex;
pub use vertex::*;
pub mod model;
pub use model::*;
pub mod mesh;
pub use mesh::*;
}
pub mod scene;
pub use scene::*;
pub mod math {
pub use vector::*;
pub mod vector {
pub mod vec2;
pub mod vec3;
pub mod vec4;
pub mod material;
pub use material::*;
pub use vec2::*;
pub use vec3::*;
pub use vec4::*;
}
pub use matrix::*;
pub mod matrix {
pub mod mat4;
pub use mat4::*;
}
}
pub mod app {
pub mod renderer;
pub mod app;
pub mod gl;
pub mod light;
pub mod camera;
pub mod scene;
}
pub mod shaders {
pub mod shaders;
pub use shaders::*;
pub mod types;
pub use types::*;
pub mod constants;
pub use constants::*;
pub mod inputs;
}
pub mod backend {
pub mod opengl {
pub mod gl_renderer;
pub use gl_renderer::*;
pub mod gl_backend;
pub use gl_backend::*;
pub mod gl_shader;
pub use gl_shader::*;
pub mod gl;
pub use gl::*;
}
pub mod gpu {
pub mod mesh;
pub use mesh::*;
}
pub mod backend;
pub use backend::*;
}
pub mod handles;

View File

@ -1,31 +1,45 @@
use proj::{app::app::App, models::{Mesh, ModelData, ShaderSource}, shaders::{FRAGMENT_SHADER_SOURCE, VERTEX_SHADER_SOURCE}};
use proj::{
app::app::App, assets, backend::opengl, geometry::{AttributeType, Mesh, MeshBuilder, VertexFormat, VertexSemantic}, math::{Mat4, Vec2, Vec4}, models::{self, Model}, shaders::{FRAGMENT_SHADER_2D, FRAGMENT_SHADER_SOURCE, VERTEX_SHADER_2D, VERTEX_SHADER_SOURCE, inputs::ShaderInputs}
};
use winit::event_loop::EventLoop;
const TRIANGLE_OBJ_PATH: &str = "assets/models/small_house/model.obj";
const HOUSE_OBJ_PATH: &str = "assets/models/small_house/model.obj";
const HOUSE_MAT_PATH: &str = "assets/models/small_house/materials.mtl";
const BUGATTI_OBJ_PATH: &str = "assets/models/bugatti/bugatti.obj";
const BUGATTI_MTL_PATH: &str = "assets/models/bugatti/bugatti.mtl";
fn main() {
dotenvy::dotenv().ok();
let event_loop = EventLoop::new()
.expect("Should be able to have Event Loop");
let mut mesh = Mesh::new();
if let Err(e) = mesh.load_obj(TRIANGLE_OBJ_PATH) {
panic!("{}", e);
}
let shader_src = ShaderSource {
vertex_src: VERTEX_SHADER_SOURCE,
fragment_src: FRAGMENT_SHADER_SOURCE
let shader_src = opengl::GlShaderSource {
vertex_src: VERTEX_SHADER_2D.to_string(),
fragment_src: FRAGMENT_SHADER_2D.to_string(),
};
let scene_data = ModelData {
name: "home".to_string(),
objects_data: vec![(mesh, shader_src)]
};
let mut app = App::new(scene_data);
let mut app = App::<opengl::GlBackend>::new();
app.set_current_scene("home");
let mesh = Mesh::builder("triangle", VertexFormat::ui_2d())
.begin_vertex()
.position2(Vec2::new(0.0, 0.5))
.color(Vec4::new(1.0, 0.0, 0.0, 1.0))
.end_vertex()
.begin_vertex()
.position2(Vec2::new(-0.5, -0.5))
.color(Vec4::new(1.0, 1.0, 0.0, 1.0))
.end_vertex()
.begin_vertex()
.position2(Vec2::new(0.5, -0.5))
.color(Vec4::new(0.0, 0.0, 1.0, 1.0))
.end_vertex()
.build();
app.meshes.push(mesh);
app.add_shader(shader_src);
let event_res = event_loop.run_app(&mut app);

127
src/math/matrix/mat4.rs Normal file
View File

@ -0,0 +1,127 @@
use crate::math::{ Vec3 };
#[repr(C)]
#[derive(Clone, Copy, Debug)]
pub struct Mat4 {
pub data: [f32; 16],
}
impl Mat4 {
pub fn identity() -> Self {
Self {
data: [
1.0, 0.0, 0.0, 0.0,
0.0, 1.0, 0.0, 0.0,
0.0, 0.0, 1.0, 0.0,
0.0, 0.0, 0.0, 1.0,
],
}
}
pub fn translation(x: f32, y: f32, z: f32) -> Self {
Self {
data: [
1.0, 0.0, 0.0, 0.0,
0.0, 1.0, 0.0, 0.0,
0.0, 0.0, 1.0, 0.0,
x, y, z, 1.0,
],
}
}
pub fn scale(x: f32, y: f32, z: f32) -> Self {
Self {
data: [
x, 0.0, 0.0, 0.0,
0.0, y, 0.0, 0.0,
0.0, 0.0, z, 0.0,
0.0, 0.0, 0.0, 1.0,
],
}
}
pub fn rotation_x(angle: f32) -> Self {
let c = angle.cos();
let s = angle.sin();
Self {
data: [
1.0, 0.0, 0.0, 0.0,
0.0, c, s, 0.0,
0.0, -s, c, 0.0,
0.0, 0.0, 0.0, 1.0,
],
}
}
pub fn rotation_y(angle: f32) -> Self {
let c = angle.cos();
let s = angle.sin();
Self {
data: [
c, 0.0, -s, 0.0,
0.0, 1.0, 0.0, 0.0,
s, 0.0, c, 0.0,
0.0, 0.0, 0.0, 1.0,
],
}
}
pub fn rotation_z(angle: f32) -> Self {
let c = angle.cos();
let s = angle.sin();
Self {
data: [
c, s, 0.0, 0.0,
-s, c, 0.0, 0.0,
0.0, 0.0, 1.0, 0.0,
0.0, 0.0, 0.0, 1.0,
],
}
}
pub fn perspective(fov: f32, aspect: f32, near: f32, far: f32) -> Self {
let f = 1.0 / (fov / 2.0).tan();
let nf = 1.0 / (near - far);
Self {
data: [
f / aspect, 0.0, 0.0, 0.0,
0.0, f, 0.0, 0.0,
0.0, 0.0, (far + near) * nf, -1.0,
0.0, 0.0, 2.0 * far * near * nf, 0.0,
],
}
}
pub fn look_at(eye: Vec3, target: Vec3, up: Vec3) -> Self {
let f = (target - eye).normalize();
let r = f.cross(&up).normalize();
let u = r.cross(&f);
Self {
data: [
r.x, u.x, -f.x, 0.0,
r.y, u.y, -f.y, 0.0,
r.z, u.z, -f.z, 0.0,
-r.dot(&eye), -u.dot(&eye), f.dot(&eye), 1.0,
],
}
}
pub fn as_ptr(&self) -> *const f32 {
self.data.as_ptr()
}
}
impl std::ops::Mul for Mat4 {
type Output = Self;
fn mul(self, rhs: Self) -> Self {
let mut result = [0.0f32; 16];
for row in 0..4 {
for col in 0..4 {
for k in 0..4 {
result[col * 4 + row] += self.data[k * 4 + row] * rhs.data[col * 4 + k];
}
}
}
Self { data: result }
}
}

181
src/math/vector/vec2.rs Normal file
View File

@ -0,0 +1,181 @@
use std::ops::{Add, Sub, Mul, Div, Neg, Index, IndexMut};
#[derive(Clone, Copy, Debug, Default, PartialEq)]
#[repr(C)]
pub struct Vec2 {
pub x: f32,
pub y: f32,
}
impl Vec2 {
pub const fn new(x: f32, y: f32) -> Self {
Self { x, y }
}
pub const fn zero() -> Self {
Self { x: 0.0, y: 0.0 }
}
pub const fn one() -> Self {
Self { x: 1.0, y: 1.0 }
}
pub fn length_squared(&self) -> f32 {
self.x * self.x + self.y * self.y
}
pub fn length(&self) -> f32 {
self.length_squared().sqrt()
}
pub fn normalize(&self) -> Self {
let len = self.length();
if len == 0.0 {
return *self;
}
Self {
x: self.x / len,
y: self.y / len,
}
}
pub fn dot(&self, other: &Self) -> f32 {
self.x * other.x + self.y * other.y
}
pub fn as_ptr(&self) -> *const f32 {
&self.x as *const f32
}
pub fn as_mut_ptr(&mut self) -> *mut f32 {
&mut self.x as *mut f32
}
pub fn to_bytes(&self) -> [u8; 8] {
let mut bytes = [0u8; 8];
bytes[0..4].copy_from_slice(&self.x.to_ne_bytes());
bytes[4..8].copy_from_slice(&self.y.to_ne_bytes());
bytes
}
}
impl Add for Vec2 {
type Output = Self;
fn add(self, rhs: Self) -> Self::Output {
Self {
x: self.x + rhs.x,
y: self.y + rhs.y,
}
}
}
impl Add<f32> for Vec2 {
type Output = Self;
fn add(self, rhs: f32) -> Self::Output {
Self {
x: self.x + rhs,
y: self.y + rhs,
}
}
}
impl Sub for Vec2 {
type Output = Self;
fn sub(self, rhs: Self) -> Self::Output {
Self {
x: self.x - rhs.x,
y: self.y - rhs.y,
}
}
}
impl Sub<f32> for Vec2 {
type Output = Self;
fn sub(self, rhs: f32) -> Self::Output {
Self {
x: self.x - rhs,
y: self.y - rhs,
}
}
}
impl Mul for Vec2 {
type Output = Self;
fn mul(self, rhs: Self) -> Self::Output {
Self {
x: self.x * rhs.x,
y: self.y * rhs.y,
}
}
}
impl Mul<f32> for Vec2 {
type Output = Self;
fn mul(self, rhs: f32) -> Self::Output {
Self {
x: self.x * rhs,
y: self.y * rhs,
}
}
}
impl Div for Vec2 {
type Output = Self;
fn div(self, rhs: Self) -> Self::Output {
Self {
x: self.x / rhs.x,
y: self.y / rhs.y,
}
}
}
impl Div<f32> for Vec2 {
type Output = Self;
fn div(self, rhs: f32) -> Self::Output {
Self {
x: self.x / rhs,
y: self.y / rhs,
}
}
}
impl Neg for Vec2 {
type Output = Self;
fn neg(self) -> Self::Output {
Self {
x: -self.x,
y: -self.y,
}
}
}
impl Index<usize> for Vec2 {
type Output = f32;
fn index(&self, index: usize) -> &Self::Output {
match index {
0 => &self.x,
1 => &self.y,
_ => panic!("Vec2 index out of bounds: {}", index),
}
}
}
impl IndexMut<usize> for Vec2 {
fn index_mut(&mut self, index: usize) -> &mut Self::Output {
match index {
0 => &mut self.x,
1 => &mut self.y,
_ => panic!("Vec2 index out of bounds: {}", index),
}
}
}

203
src/math/vector/vec3.rs Normal file
View File

@ -0,0 +1,203 @@
use std::ops::{Add, Sub, Mul, Div, Neg, Index, IndexMut};
#[derive(Clone, Copy, Debug, Default, PartialEq)]
#[repr(C)]
pub struct Vec3 {
pub x: f32,
pub y: f32,
pub z: f32,
}
impl Vec3 {
pub const fn new(x: f32, y: f32, z: f32) -> Self {
Self { x, y, z }
}
pub const fn zero() -> Self {
Self { x: 0.0, y: 0.0, z: 0.0 }
}
pub const fn one() -> Self {
Self { x: 1.0, y: 1.0, z: 1.0 }
}
pub fn length_squared(&self) -> f32 {
self.x * self.x + self.y * self.y + self.z * self.z
}
pub fn length(&self) -> f32 {
self.length_squared().sqrt()
}
pub fn normalize(&self) -> Self {
let len = self.length();
if len == 0.0 {
return *self;
}
Self {
x: self.x / len,
y: self.y / len,
z: self.z / len,
}
}
pub fn dot(&self, other: &Self) -> f32 {
self.x * other.x + self.y * other.y + self.z * other.z
}
pub fn cross(&self, other: &Self) -> Self {
Self {
x: self.y * other.z - self.z * other.y,
y: self.z * other.x - self.x * other.z,
z: self.x * other.y - self.y * other.x,
}
}
pub fn as_ptr(&self) -> *const f32 {
&self.x as *const f32
}
pub fn as_mut_ptr(&mut self) -> *mut f32 {
&mut self.x as *mut f32
}
pub fn to_bytes(&self) -> [u8; 12] {
let mut bytes = [0u8; 12];
bytes[0..4].copy_from_slice(&self.x.to_ne_bytes());
bytes[4..8].copy_from_slice(&self.y.to_ne_bytes());
bytes[8..12].copy_from_slice(&self.z.to_ne_bytes());
bytes
}
}
impl Add for Vec3 {
type Output = Self;
fn add(self, rhs: Self) -> Self::Output {
Self {
x: self.x + rhs.x,
y: self.y + rhs.y,
z: self.z + rhs.z,
}
}
}
impl Add<f32> for Vec3 {
type Output = Self;
fn add(self, rhs: f32) -> Self::Output {
Self {
x: self.x + rhs,
y: self.y + rhs,
z: self.z + rhs,
}
}
}
impl Sub for Vec3 {
type Output = Self;
fn sub(self, rhs: Self) -> Self::Output {
Self {
x: self.x - rhs.x,
y: self.y - rhs.y,
z: self.z - rhs.z,
}
}
}
impl Sub<f32> for Vec3 {
type Output = Self;
fn sub(self, rhs: f32) -> Self::Output {
Self {
x: self.x - rhs,
y: self.y - rhs,
z: self.z - rhs,
}
}
}
impl Mul for Vec3 {
type Output = Self;
fn mul(self, rhs: Self) -> Self::Output {
Self {
x: self.x * rhs.x,
y: self.y * rhs.y,
z: self.z * rhs.z,
}
}
}
impl Mul<f32> for Vec3 {
type Output = Self;
fn mul(self, rhs: f32) -> Self::Output {
Self {
x: self.x * rhs,
y: self.y * rhs,
z: self.z * rhs,
}
}
}
impl Div for Vec3 {
type Output = Self;
fn div(self, rhs: Self) -> Self::Output {
Self {
x: self.x / rhs.x,
y: self.y / rhs.y,
z: self.z / rhs.z,
}
}
}
impl Div<f32> for Vec3 {
type Output = Self;
fn div(self, rhs: f32) -> Self::Output {
Self {
x: self.x / rhs,
y: self.y / rhs,
z: self.z / rhs,
}
}
}
impl Neg for Vec3 {
type Output = Self;
fn neg(self) -> Self::Output {
Self {
x: -self.x,
y: -self.y,
z: -self.z,
}
}
}
impl Index<usize> for Vec3 {
type Output = f32;
fn index(&self, index: usize) -> &Self::Output {
match index {
0 => &self.x,
1 => &self.y,
2 => &self.z,
_ => panic!("Vec3 index out of bounds: {}", index),
}
}
}
impl IndexMut<usize> for Vec3 {
fn index_mut(&mut self, index: usize) -> &mut Self::Output {
match index {
0 => &mut self.x,
1 => &mut self.y,
2 => &mut self.z,
_ => panic!("Vec3 index out of bounds: {}", index),
}
}
}

209
src/math/vector/vec4.rs Normal file
View File

@ -0,0 +1,209 @@
use std::ops::{Add, Sub, Mul, Div, Neg, Index, IndexMut};
#[derive(Clone, Copy, Debug, Default, PartialEq)]
#[repr(C)]
pub struct Vec4 {
pub x: f32,
pub y: f32,
pub z: f32,
pub w: f32,
}
impl Vec4 {
pub const fn new(x: f32, y: f32, z: f32, w: f32) -> Self {
Self { x, y, z, w }
}
pub const fn zero() -> Self {
Self { x: 0.0, y: 0.0, z: 0.0, w: 0.0 }
}
pub const fn one() -> Self {
Self { x: 1.0, y: 1.0, z: 1.0, w: 1.0 }
}
pub fn length_squared(&self) -> f32 {
self.x * self.x + self.y * self.y + self.z * self.z + self.w * self.w
}
pub fn length(&self) -> f32 {
self.length_squared().sqrt()
}
pub fn normalize(&self) -> Self {
let len = self.length();
if len == 0.0 {
return *self;
}
Self {
x: self.x / len,
y: self.y / len,
z: self.z / len,
w: self.w / len,
}
}
pub fn dot(&self, other: &Self) -> f32 {
self.x * other.x + self.y * other.y + self.z * other.z + self.w * other.w
}
pub fn as_ptr(&self) -> *const f32 {
&self.x as *const f32
}
pub fn as_mut_ptr(&mut self) -> *mut f32 {
&mut self.x as *mut f32
}
pub fn to_bytes(&self) -> [u8; 16] {
let mut bytes = [0u8; 16];
bytes[0..4].copy_from_slice(&self.x.to_ne_bytes());
bytes[4..8].copy_from_slice(&self.y.to_ne_bytes());
bytes[8..12].copy_from_slice(&self.z.to_ne_bytes());
bytes[12..16].copy_from_slice(&self.w.to_ne_bytes());
bytes
}
}
impl Add for Vec4 {
type Output = Self;
fn add(self, rhs: Self) -> Self::Output {
Self {
x: self.x + rhs.x,
y: self.y + rhs.y,
z: self.z + rhs.z,
w: self.w + rhs.w,
}
}
}
impl Add<f32> for Vec4 {
type Output = Self;
fn add(self, rhs: f32) -> Self::Output {
Self {
x: self.x + rhs,
y: self.y + rhs,
z: self.z + rhs,
w: self.w + rhs,
}
}
}
impl Sub for Vec4 {
type Output = Self;
fn sub(self, rhs: Self) -> Self::Output {
Self {
x: self.x - rhs.x,
y: self.y - rhs.y,
z: self.z - rhs.z,
w: self.w - rhs.w,
}
}
}
impl Sub<f32> for Vec4 {
type Output = Self;
fn sub(self, rhs: f32) -> Self::Output {
Self {
x: self.x - rhs,
y: self.y - rhs,
z: self.z - rhs,
w: self.w - rhs,
}
}
}
impl Mul for Vec4 {
type Output = Self;
fn mul(self, rhs: Self) -> Self::Output {
Self {
x: self.x * rhs.x,
y: self.y * rhs.y,
z: self.z * rhs.z,
w: self.w * rhs.w,
}
}
}
impl Mul<f32> for Vec4 {
type Output = Self;
fn mul(self, rhs: f32) -> Self::Output {
Self {
x: self.x * rhs,
y: self.y * rhs,
z: self.z * rhs,
w: self.w * rhs,
}
}
}
impl Div for Vec4 {
type Output = Self;
fn div(self, rhs: Self) -> Self::Output {
Self {
x: self.x / rhs.x,
y: self.y / rhs.y,
z: self.z / rhs.z,
w: self.w / rhs.w,
}
}
}
impl Div<f32> for Vec4 {
type Output = Self;
fn div(self, rhs: f32) -> Self::Output {
Self {
x: self.x / rhs,
y: self.y / rhs,
z: self.z / rhs,
w: self.w / rhs,
}
}
}
impl Neg for Vec4 {
type Output = Self;
fn neg(self) -> Self::Output {
Self {
x: -self.x,
y: -self.y,
z: -self.z,
w: -self.w,
}
}
}
impl Index<usize> for Vec4 {
type Output = f32;
fn index(&self, index: usize) -> &Self::Output {
match index {
0 => &self.x,
1 => &self.y,
2 => &self.z,
3 => &self.w,
_ => panic!("Vec4 index out of bounds: {}", index),
}
}
}
impl IndexMut<usize> for Vec4 {
fn index_mut(&mut self, index: usize) -> &mut Self::Output {
match index {
0 => &mut self.x,
1 => &mut self.y,
2 => &mut self.z,
3 => &mut self.w,
_ => panic!("Vec4 index out of bounds: {}", index),
}
}
}

68
src/math/vector/vector.rs Normal file
View File

@ -0,0 +1,68 @@
use std::ops::Sub;
#[derive(Clone, Copy, Debug, Default)]
#[repr(C)]
pub struct Vec2 {
pub x: f32,
pub y: f32,
}
#[derive(Clone, Copy, Debug, Default)]
#[repr(C)]
pub struct Vec3 {
pub x: f32,
pub y: f32,
pub z: f32,
}
impl Sub for Vec3 {
type Output = Self;
fn sub(self, rhs: Self) -> Self::Output {
Self {
x: self.x - rhs.x,
y: self.y - rhs.y,
z: self.z - rhs.z,
}
}
}
impl Vec3 {
pub fn length(&self) -> f32 {
(self.x * self.x + self.y * self.y + self.z * self.z).sqrt()
}
pub fn normalize(&mut self) -> Self {
let len = self.length();
if len == 0.0 {
return *self;
}
Self {
x: self.x / len,
y: self.y / len,
z: self.z / len,
}
}
pub fn dot(&self, other: Vec3) -> f32 {
self.x * other.x + self.y * other.y + self.z * other.z
}
pub fn cross(&self, other: Self) -> Self {
Self {
x: self.y * other.z - self.z * other.y,
y: self.z * other.x - self.x * other.z,
z: self.x * other.y - self.y * other.x,
}
}
}
#[derive(Clone, Copy, Debug, Default)]
#[repr(C)]
pub struct Vec4 {
pub x: f32,
pub y: f32,
pub z: f32,
pub w: f32,
}

View File

@ -1,18 +0,0 @@
#[derive(Debug)]
pub struct Material {
pub ambient: [f32; 3],
pub diffuse: [f32; 3],
pub specular: [f32; 3],
pub shader_name: String,
}
impl Material {
pub fn new() -> Self {
Self {
ambient: Default::default(),
diffuse: Default::default(),
specular: Default::default(),
shader_name: String::new(),
}
}
}

View File

@ -1,124 +0,0 @@
use std::collections::HashMap;
use crate::models::Material;
use super::Vertex;
#[derive(Debug)]
pub struct Mesh {
pub vertices: Vec<Vertex>,
pub material_name: String,
}
const NEW_MATERIAL_DELIMITER: &str = "newmtl ";
fn parse_rgb(values: &str) -> Result<[f32; 3], Box<dyn std::error::Error>> {
let mut rgb = [0.0; 3];
let mut i = 0;
let values = values.split_whitespace();
for v in values {
let parsed = v.parse::<f32>()?;
rgb[i] = parsed;
i += 1;
if i > 2 {
todo!("Implement custom error handling: too many values");
}
};
if i != 3 {
todo!("Implement custom error handling: too few values");
}
Ok(rgb)
}
impl Mesh {
pub fn new() -> Self {
Self {
vertices: vec![],
material_name: String::new(),
}
}
pub fn load_obj(&mut self, path: &str) -> Result<(), Box<dyn std::error::Error>> {
let content = std::fs::read_to_string(path)?;
for line in content.lines() {
if line.starts_with("v ") {
let coords: Result<Vec<f32>, _> = line
.split_whitespace()
.skip(1)
.take(3)
.map(|s| s.parse::<f32>())
.collect();
match coords {
Ok(v) if v.len() == 3 => {
self.vertices.push(Vertex { x: v[0], y: v[1], z: v[2] })
},
_ => eprintln!("Warning: Invalid vertex line: {}", line)
}
}
}
println!("{:#?}", self.vertices);
Ok(())
}
pub fn load_material(&mut self, path: &str) -> Result<(), Box<dyn std::error::Error>> {
let content = std::fs::read_to_string(path)?;
let mut lines = content.lines().peekable();
while let Some(line) = lines.next() {
let line = line.trim();
if line.is_empty() {
continue ;
}
if line.starts_with(NEW_MATERIAL_DELIMITER) {
let mat_name = line.strip_prefix(NEW_MATERIAL_DELIMITER).unwrap();
while let Some(&next_line) = lines.peek() {
let next = next_line.trim();
if next.starts_with(NEW_MATERIAL_DELIMITER) || next.is_empty() {
lines.next();
let mut material = Material::new();
if let Some((key, values)) = next.split_once(' ') {
match key {
"Ka" => {
match parse_rgb(values) {
Ok(v) => material.ka = v,
Err(e) => eprintln!("Error parsing Ka values: {}", e.to_string())
}
},
"Kd" => {
match parse_rgb(values) {
Ok(v) => material.kd = v,
Err(e) => eprintln!("Error parsing Kd values: {}", e.to_string())
}
}
"Ks" => {
match parse_rgb(values) {
Ok(v) => material.ks = v,
Err(e) => eprintln!("Error parsing Ks values: {}", e.to_string())
}
}
_ => { eprintln!("Unknown key: {key}") }
}
}
self.materials.insert(mat_name.to_string(), material);
}
}
}
}
println!("{:#?}", self.vertices);
Ok(())
}
}

View File

@ -1,23 +1,61 @@
use crate::models::Mesh;
pub struct ShaderSource {
pub vertex_src: &'static [u8],
pub fragment_src: &'static [u8],
}
pub struct Model {
pub objects: Vec<RenderObject>
}
pub struct RenderObject {
pub vao: gl::types::GLuint,
pub vbo: gl::types::GLuint,
pub _ebo: Option<gl::types::GLuint>,
pub vertex_count: i32,
pub _index_count: Option<i32>,
}
use crate::geometry::Mesh;
use crate::assets::mtl::Material;
use crate::handles::{ MeshHandle, MaterialHandle };
use crate::assets::types::ObjData;
use crate::math::Mat4;
pub struct ModelData {
pub name: String,
pub meshes: Vec<Mesh>
pub meshes: Vec<Mesh>,
pub materials: Vec<Material>,
pub transform: Mat4,
}
impl From<&ObjData> for ModelData {
fn from(obj: &ObjData) -> Self {
let meshes = obj.meshes.iter()
.map(|obj_mesh| Mesh::new(obj, obj_mesh))
.collect();
ModelData {
name: String::new(),
meshes,
materials: Vec::new(),
transform: Mat4::identity(),
}
}
}
#[derive(Debug)]
pub struct Model {
pub name: String,
pub parts: Vec<(MeshHandle, MaterialHandle)>,
pub transform: Mat4,
}
impl Default for Model {
fn default() -> Self {
Self {
name: String::new(),
parts: Vec::new(),
transform: Mat4::identity(),
}
}
}
impl Model {
/// Set position (translation)
pub fn set_position(&mut self, x: f32, y: f32, z: f32) {
self.transform = Mat4::translation(x, y, z);
}
/// Set scale
pub fn set_scale(&mut self, x: f32, y: f32, z: f32) {
self.transform = Mat4::scale(x, y, z);
}
/// Set full transform (for combining translation * rotation * scale)
pub fn set_transform(&mut self, transform: Mat4) {
self.transform = transform;
}
}

View File

@ -1,8 +0,0 @@
use std::collections::HashMap;
use crate::models::{Material, Model};
pub struct Scene {
pub models: Vec<Model>,
pub materials: HashMap<String, Material>,
}

View File

@ -1,7 +0,0 @@
#[derive(Debug)]
pub struct Vertex {
pub x: f32,
pub y: f32,
pub z: f32,
}

87
src/shaders/constants.rs Normal file
View File

@ -0,0 +1,87 @@
pub const VERTEX_SHADER_SOURCE: &str = r#"
#version 330 core
layout(location = 0) in vec3 a_position;
layout(location = 1) in vec3 a_normal;
layout(location = 2) in vec2 a_texcoord;
uniform mat4 u_model;
uniform mat4 u_view;
uniform mat4 u_projection;
out vec3 v_position;
out vec3 v_normal;
out vec2 v_texcoord;
void main() {
vec4 world_pos = u_model * vec4(a_position, 1.0);
v_position = world_pos.xyz;
v_normal = mat3(transpose(inverse(u_model))) * a_normal;
v_texcoord = a_texcoord;
gl_Position = u_projection * u_view * world_pos;
}"#;
pub const FRAGMENT_SHADER_SOURCE: &str = r#"
#version 330 core
in vec3 v_position;
in vec3 v_normal;
in vec2 v_texcoord;
uniform vec3 u_light_pos;
uniform vec3 u_light_color;
uniform vec3 u_view_pos;
uniform vec3 u_ambient;
uniform vec3 u_diffuse;
uniform vec3 u_specular;
uniform float u_shininess;
out vec4 frag_color;
void main() {
vec3 normal = normalize(v_normal);
vec3 light_dir = normalize(u_light_pos - v_position);
vec3 view_dir = normalize(u_view_pos - v_position);
vec3 halfway = normalize(light_dir + view_dir);
// Ambient
vec3 ambient = u_ambient * 0.1;
// Diffuse
float diff = max(dot(normal, light_dir), 0.0);
vec3 diffuse = diff * u_diffuse * u_light_color;
// Specular (Blinn-Phong)
float spec = pow(max(dot(normal, halfway), 0.0), u_shininess);
vec3 specular = spec * u_specular * u_light_color;
vec3 result = ambient + diffuse + specular;
frag_color = vec4(result, 1.0);
}"#;
pub const VERTEX_SHADER_2D: &str = r#"
#version 330 core
layout (location = 0) in vec2 a_position;
layout (location = 2) in vec4 a_color;
out vec4 v_color;
void main() {
gl_Position = vec4(a_position, 0.0, 1.0);
v_color = a_color;
}
"#;
pub const FRAGMENT_SHADER_2D: &str = r#"
#version 330 core
in vec4 v_color;
out vec4 frag_color;
void main() {
frag_color = v_color;
}
"#;

55
src/shaders/inputs.rs Normal file
View File

@ -0,0 +1,55 @@
use crate::geometry;
pub enum InputRequirement {
Required,
Optional
}
pub struct ShaderInput {
pub semantic: geometry::VertexSemantic,
pub component_count: usize,
pub data_type: geometry::AttributeType,
pub requirement: InputRequirement
}
pub struct ShaderInputs {
pub inputs: Vec<ShaderInput>,
}
impl ShaderInputs {
pub fn new() -> Self {
Self {
inputs: vec![]
}
}
pub fn required(
mut self,
semantic: geometry::VertexSemantic,
component_count: usize,
data_type: geometry::AttributeType
) -> Self {
self.inputs.push(ShaderInput {
semantic,
data_type,
component_count,
requirement: InputRequirement::Required,
});
self
}
pub fn optional(
mut self,
semantic: geometry::VertexSemantic,
component_count: usize,
data_type: geometry::AttributeType
) -> Self {
self.inputs.push(ShaderInput {
semantic,
data_type,
component_count,
requirement: InputRequirement::Optional,
});
self
}
}

View File

@ -1,26 +0,0 @@
pub const VERTEX_SHADER_SOURCE: &[u8] = b"
#version 100
precision mediump float;
attribute vec3 position;
attribute vec3 color;
varying vec3 v_color;
void main() {
gl_Position = vec4(position, 1.0);
v_color = color;
}
\0";
pub const FRAGMENT_SHADER_SOURCE: &[u8] = b"
#version 100
precision mediump float;
varying vec3 v_color;
void main() {
gl_FragColor = vec4(v_color, 1.0);
}
\0";

19
src/shaders/types.rs Normal file
View File

@ -0,0 +1,19 @@
use std::ops::Deref;
pub struct ShaderSource {}
#[derive(Clone, Copy, Debug)]
pub struct Shader(pub u32);
impl From<Shader> for u32 {
fn from(value: Shader) -> Self {
value.0
}
}
impl Deref for Shader {
type Target = u32;
fn deref(&self) -> &Self::Target {
&self.0
}
}