commit
d9bc0e36f5
@ -0,0 +1 @@ |
||||
/target |
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,12 @@ |
||||
[package] |
||||
name = "blocks" |
||||
version = "0.1.0" |
||||
edition = "2018" |
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html |
||||
|
||||
[dependencies] |
||||
cgmath = "0.18" |
||||
gl = "0.14" |
||||
glutin = "0.27" |
||||
image = "0.23" |
@ -0,0 +1,8 @@ |
||||
#version 430 |
||||
layout(location = 0) out vec4 out_color; |
||||
|
||||
// uniform vec3 color; |
||||
|
||||
void main() { |
||||
out_color = vec4(1.0, 0.0, 0.5, 1.0); |
||||
} |
@ -0,0 +1,10 @@ |
||||
#version 430 |
||||
layout(location = 0) in vec2 position; |
||||
|
||||
uniform mat4 model; |
||||
uniform mat4 view; |
||||
uniform mat4 projection; |
||||
|
||||
void main() { |
||||
gl_Position = projection * view * model * vec4(position, 0.0, 1.0); |
||||
} |
@ -0,0 +1,99 @@ |
||||
use std::ops::Rem; |
||||
|
||||
use cgmath::{perspective, point3, vec3, Angle, Deg, InnerSpace, Matrix4, Point3, Vector3}; |
||||
|
||||
#[derive(Clone, Copy, Debug)] |
||||
pub struct Camera { |
||||
pub position: Point3<f32>, |
||||
pub yaw: Deg<f32>, |
||||
pub pitch: Deg<f32>, |
||||
} |
||||
|
||||
static WORLD_UP: Vector3<f32> = vec3(0.0, 1.0, 0.0); |
||||
|
||||
impl Camera { |
||||
pub fn new() -> Self { |
||||
Self { |
||||
position: point3(0.0, 0.0, 0.0), |
||||
yaw: Deg(0.0), |
||||
pitch: Deg(0.0), |
||||
} |
||||
} |
||||
|
||||
pub fn move_right(&mut self, amount: f32) { |
||||
let right_dir = self.right_dir(); |
||||
self.position += amount * right_dir; |
||||
} |
||||
|
||||
pub fn move_left(&mut self, amount: f32) { |
||||
self.move_right(-amount); |
||||
} |
||||
|
||||
pub fn move_forward(&mut self, amount: f32) { |
||||
let forward_dir = self.horizontal_front_dir(); |
||||
self.position += amount * forward_dir; |
||||
} |
||||
|
||||
pub fn move_backward(&mut self, amount: f32) { |
||||
self.move_forward(-amount); |
||||
} |
||||
|
||||
pub fn move_up(&mut self, amount: f32) { |
||||
self.position += amount * WORLD_UP; |
||||
} |
||||
|
||||
pub fn move_down(&mut self, amount: f32) { |
||||
self.move_up(-amount); |
||||
} |
||||
|
||||
pub fn rotate_yaw<D: Into<Deg<f32>>>(&mut self, amount: D) { |
||||
self.yaw -= amount.into(); |
||||
self.yaw = Deg(self.yaw.0.rem(360.0)); |
||||
} |
||||
|
||||
pub fn rotate_pitch<D: Into<Deg<f32>>>(&mut self, amount: D) { |
||||
self.pitch += amount.into(); |
||||
self.pitch = Deg(self.pitch.0.clamp(-89.9, 89.9)); |
||||
} |
||||
|
||||
pub fn view_matrix(&self) -> Matrix4<f32> { |
||||
let front_dir = self.front_dir(); |
||||
let right_dir = self.right_dir(); |
||||
let up_dir = front_dir.cross(right_dir); |
||||
Matrix4::look_to_rh(self.position, front_dir, WORLD_UP) |
||||
} |
||||
|
||||
pub fn projection_matrix(&self) -> Matrix4<f32> { |
||||
perspective(Deg(45.0), 16.0 / 9.0, 0.0001, 1000.0) |
||||
} |
||||
|
||||
fn horizontal_front_dir(&self) -> Vector3<f32> { |
||||
vec3(self.yaw.cos(), 0.0, -self.yaw.sin()).normalize() |
||||
} |
||||
|
||||
fn front_dir(&self) -> Vector3<f32> { |
||||
println!( |
||||
"{:?}", |
||||
vec3( |
||||
self.yaw.cos() * self.pitch.cos(), |
||||
self.pitch.sin(), |
||||
-self.yaw.sin() * self.pitch.cos(), |
||||
) |
||||
); |
||||
vec3( |
||||
self.yaw.cos() * self.pitch.cos(), |
||||
self.pitch.sin(), |
||||
-self.yaw.sin() * self.pitch.cos(), |
||||
) |
||||
.normalize() |
||||
} |
||||
|
||||
fn right_dir(&self) -> Vector3<f32> { |
||||
vec3( |
||||
(self.yaw - Deg(90.0)).cos(), |
||||
0.0, |
||||
-(self.yaw - Deg(90.0)).sin(), |
||||
) |
||||
.normalize() |
||||
} |
||||
} |
@ -0,0 +1,138 @@ |
||||
use std::collections::{HashMap, HashSet}; |
||||
|
||||
use glutin::event::{ |
||||
ElementState, KeyboardInput, MouseButton, MouseScrollDelta, VirtualKeyCode, WindowEvent, |
||||
}; |
||||
|
||||
pub struct KeyboardState { |
||||
state: HashSet<VirtualKeyCode>, |
||||
momentary_state: HashMap<VirtualKeyCode, ElementState>, |
||||
} |
||||
|
||||
impl KeyboardState { |
||||
pub fn new() -> Self { |
||||
Self { |
||||
state: HashSet::new(), |
||||
momentary_state: HashMap::new(), |
||||
} |
||||
} |
||||
|
||||
pub fn is_pressed(&self, key: VirtualKeyCode) -> bool { |
||||
self.state.get(&key).is_some() |
||||
} |
||||
|
||||
pub fn was_pressed(&self, key: VirtualKeyCode) -> bool { |
||||
self.momentary_state.get(&key) == Some(&ElementState::Pressed) |
||||
} |
||||
|
||||
pub fn was_released(&self, key: VirtualKeyCode) -> bool { |
||||
self.momentary_state.get(&key) == Some(&ElementState::Released) |
||||
} |
||||
|
||||
pub fn process_event(&mut self, event: &WindowEvent) { |
||||
match *event { |
||||
WindowEvent::KeyboardInput { |
||||
input: |
||||
KeyboardInput { |
||||
state, |
||||
virtual_keycode: Some(virtual_keycode), |
||||
.. |
||||
}, |
||||
.. |
||||
} => { |
||||
self.momentary_state.insert(virtual_keycode, state); |
||||
match state { |
||||
ElementState::Pressed => { |
||||
self.state.insert(virtual_keycode); |
||||
} |
||||
ElementState::Released => { |
||||
self.state.remove(&virtual_keycode); |
||||
} |
||||
} |
||||
} |
||||
_ => (), |
||||
} |
||||
} |
||||
|
||||
pub fn clear_momentary_state(&mut self) { |
||||
self.momentary_state.clear(); |
||||
} |
||||
} |
||||
|
||||
#[derive(Clone, Copy, Debug, Default)] |
||||
pub struct Point { |
||||
pub x: f64, |
||||
pub y: f64, |
||||
} |
||||
|
||||
impl Point { |
||||
pub fn new(x: f64, y: f64) -> Self { |
||||
Self { x, y } |
||||
} |
||||
} |
||||
|
||||
pub struct MouseState { |
||||
button_state: HashSet<MouseButton>, |
||||
momentary_button_state: HashMap<MouseButton, ElementState>, |
||||
pub position: Point, |
||||
pub mouse_delta: Point, |
||||
pub scroll_delta: f32, |
||||
} |
||||
|
||||
impl MouseState { |
||||
pub fn new() -> Self { |
||||
Self { |
||||
button_state: HashSet::new(), |
||||
momentary_button_state: HashMap::new(), |
||||
position: Default::default(), |
||||
mouse_delta: Default::default(), |
||||
scroll_delta: 0.0, |
||||
} |
||||
} |
||||
|
||||
pub fn is_pressed(&self, button: MouseButton) -> bool { |
||||
self.button_state.get(&button).is_some() |
||||
} |
||||
|
||||
pub fn was_pressed(&self, button: MouseButton) -> bool { |
||||
self.momentary_button_state.get(&button) == Some(&ElementState::Pressed) |
||||
} |
||||
|
||||
pub fn was_released(&self, button: MouseButton) -> bool { |
||||
self.momentary_button_state.get(&button) == Some(&ElementState::Released) |
||||
} |
||||
|
||||
pub fn process_event(&mut self, event: &WindowEvent) { |
||||
match *event { |
||||
WindowEvent::CursorMoved { position, .. } => { |
||||
self.mouse_delta.x += position.x - self.position.x; |
||||
self.mouse_delta.y += position.y - self.position.y; |
||||
self.position = Point::new(position.x, position.y); |
||||
} |
||||
WindowEvent::MouseInput { button, state, .. } => { |
||||
self.momentary_button_state.insert(button, state); |
||||
match state { |
||||
ElementState::Pressed => { |
||||
self.button_state.insert(button); |
||||
} |
||||
ElementState::Released => { |
||||
self.button_state.remove(&button); |
||||
} |
||||
} |
||||
} |
||||
WindowEvent::MouseWheel { delta, .. } => match delta { |
||||
MouseScrollDelta::LineDelta(_, y) => { |
||||
self.scroll_delta += y; |
||||
} |
||||
_ => (), |
||||
}, |
||||
_ => (), |
||||
} |
||||
} |
||||
|
||||
pub fn clear_momentary_state(&mut self) { |
||||
self.momentary_button_state.clear(); |
||||
self.mouse_delta = Default::default(); |
||||
self.scroll_delta = 0.0; |
||||
} |
||||
} |
@ -0,0 +1,159 @@ |
||||
mod camera; |
||||
mod input; |
||||
mod renderer; |
||||
|
||||
use std::{ffi::CString, time::Instant}; |
||||
|
||||
use cgmath::{Deg, Matrix4}; |
||||
use gl::types::*; |
||||
use glutin::dpi::PhysicalSize; |
||||
use input::{KeyboardState, MouseState}; |
||||
use renderer::{shader::Shader, vertex_buffer::VertexBuffer}; |
||||
|
||||
use crate::{ |
||||
camera::Camera, |
||||
renderer::{debug::DebugCallback, vertex_array::VertexArray, VertexBufferElement}, |
||||
}; |
||||
|
||||
// Vertex data
|
||||
static QUAD_DATA: [GLfloat; 24] = [ |
||||
0.5, 0.5, 1.0, 1.0, // top right
|
||||
-0.5, 0.5, 0.0, 1.0, // top left
|
||||
-0.5, -0.5, 0.0, 0.0, // bottom left
|
||||
-0.5, -0.5, 0.0, 0.0, // bottom left
|
||||
0.5, -0.5, 1.0, 0.0, // bottom right
|
||||
0.5, 0.5, 1.0, 1.0, // top right
|
||||
]; |
||||
|
||||
fn main() { |
||||
let event_loop = glutin::event_loop::EventLoop::new(); |
||||
let window = |
||||
glutin::window::WindowBuilder::new().with_inner_size(PhysicalSize::new(1920.0, 1080.0)); |
||||
let gl_window = glutin::ContextBuilder::new() |
||||
.build_windowed(window, &event_loop) |
||||
.unwrap(); |
||||
|
||||
// It is essential to make the context current before calling `gl::load_with`.
|
||||
let gl_window = unsafe { gl_window.make_current() }.unwrap(); |
||||
|
||||
// Load the OpenGL function pointers
|
||||
gl::load_with(|symbol| gl_window.get_proc_address(symbol)); |
||||
|
||||
let chunk_shader = Shader::from_file("chunk.vert", "chunk.frag"); |
||||
|
||||
unsafe { |
||||
gl::Enable(gl::BLEND); |
||||
gl::BlendFunc(gl::SRC_ALPHA, gl::ONE_MINUS_SRC_ALPHA); |
||||
} |
||||
|
||||
let _debug_callback = unsafe { |
||||
DebugCallback::new(|message| { |
||||
println!("{:?}", message); |
||||
}) |
||||
}; |
||||
|
||||
let quad_va = unsafe { |
||||
let vb = VertexBuffer::new( |
||||
&QUAD_DATA, |
||||
vec![ |
||||
VertexBufferElement::floats(2), |
||||
VertexBufferElement::floats(2), |
||||
], |
||||
); |
||||
VertexArray::new(&[vb]) |
||||
}; |
||||
|
||||
let mut camera = Camera::new(); |
||||
camera.position.x = 10.0; |
||||
camera.position.y = 10.0; |
||||
|
||||
let mut keyboard_state = KeyboardState::new(); |
||||
let mut mouse_state = MouseState::new(); |
||||
|
||||
let mut show_fps = false; |
||||
|
||||
let mut last_update_time = Instant::now(); |
||||
event_loop.run(move |event, _, control_flow| { |
||||
use glutin::event::{Event, VirtualKeyCode, WindowEvent}; |
||||
use glutin::event_loop::ControlFlow; |
||||
*control_flow = ControlFlow::Poll; |
||||
|
||||
match event { |
||||
Event::LoopDestroyed => return, |
||||
Event::WindowEvent { event, .. } => { |
||||
keyboard_state.process_event(&event); |
||||
mouse_state.process_event(&event); |
||||
match event { |
||||
WindowEvent::CloseRequested => *control_flow = ControlFlow::Exit, |
||||
WindowEvent::Resized(size) => unsafe { |
||||
gl::Viewport(0, 0, size.width as i32, size.height as i32); |
||||
}, |
||||
_ => (), |
||||
} |
||||
} |
||||
_ => (), |
||||
} |
||||
|
||||
let now = Instant::now(); |
||||
let ms_since_last_update = (now - last_update_time).as_nanos() as f64 / 1_000_000.0; |
||||
if ms_since_last_update > 16.666 { |
||||
last_update_time = now; |
||||
println!("{:?}", camera); |
||||
|
||||
if keyboard_state.was_pressed(VirtualKeyCode::F) { |
||||
show_fps = !show_fps; |
||||
} |
||||
|
||||
let move_speed = 0.1; |
||||
|
||||
if keyboard_state.is_pressed(VirtualKeyCode::W) { |
||||
camera.move_forward(move_speed); |
||||
} else if keyboard_state.is_pressed(VirtualKeyCode::S) { |
||||
camera.move_backward(move_speed); |
||||
} |
||||
|
||||
if keyboard_state.is_pressed(VirtualKeyCode::D) { |
||||
camera.move_right(move_speed); |
||||
} else if keyboard_state.is_pressed(VirtualKeyCode::A) { |
||||
camera.move_left(move_speed); |
||||
} |
||||
|
||||
if keyboard_state.is_pressed(VirtualKeyCode::Space) { |
||||
camera.move_up(move_speed); |
||||
} else if keyboard_state.is_pressed(VirtualKeyCode::LShift) { |
||||
camera.move_down(move_speed); |
||||
} |
||||
|
||||
let mouse_sens = 0.4; |
||||
camera.rotate_yaw(Deg(mouse_sens * mouse_state.mouse_delta.x as f32)); |
||||
camera.rotate_pitch(-Deg(mouse_sens * mouse_state.mouse_delta.y as f32)); |
||||
|
||||
let start = Instant::now(); |
||||
unsafe { |
||||
gl::ClearColor(0.3, 0.3, 0.6, 1.0); |
||||
gl::Clear(gl::COLOR_BUFFER_BIT); |
||||
|
||||
let model_matrix = Matrix4::from_scale(10.0); |
||||
|
||||
chunk_shader.enable(); |
||||
chunk_shader.set_mat4(&CString::new("model").unwrap(), model_matrix); |
||||
chunk_shader.set_mat4(&CString::new("view").unwrap(), camera.view_matrix()); |
||||
chunk_shader.set_mat4( |
||||
&CString::new("projection").unwrap(), |
||||
camera.projection_matrix(), |
||||
); |
||||
quad_va.bind(); |
||||
gl::DrawArrays(gl::TRIANGLES, 0, 6); |
||||
} |
||||
|
||||
let dur = Instant::now() - start; |
||||
let ms = dur.as_nanos() as f64 / 1_000_000.0; |
||||
if show_fps { |
||||
println!("Render time: {}ms", ms); |
||||
} |
||||
gl_window.swap_buffers().unwrap(); |
||||
keyboard_state.clear_momentary_state(); |
||||
mouse_state.clear_momentary_state(); |
||||
} |
||||
}); |
||||
} |
@ -0,0 +1,134 @@ |
||||
use std::{ |
||||
ffi::{c_void, CStr}, |
||||
panic, ptr, |
||||
}; |
||||
|
||||
use gl::types::{GLchar, GLenum, GLsizei}; |
||||
|
||||
#[derive(Clone, Debug)] |
||||
pub struct DebugMessage { |
||||
id: GLenum, |
||||
source: DebugSource, |
||||
type_: DebugType, |
||||
severity: DebugSeverity, |
||||
message: String, |
||||
} |
||||
|
||||
#[derive(Clone, Copy, Debug)] |
||||
pub enum DebugSource { |
||||
Api, |
||||
WindowSystem, |
||||
ShaderCompiler, |
||||
ThirdParty, |
||||
Application, |
||||
Other, |
||||
} |
||||
|
||||
#[derive(Clone, Copy, Debug)] |
||||
pub enum DebugType { |
||||
Error, |
||||
DeprecatedBehavior, |
||||
UndefinedBehavior, |
||||
Portability, |
||||
Performance, |
||||
Marker, |
||||
Other, |
||||
PopGroup, |
||||
PushGroup, |
||||
} |
||||
|
||||
#[derive(Clone, Copy, Debug)] |
||||
pub enum DebugSeverity { |
||||
Notification, |
||||
Low, |
||||
Medium, |
||||
High, |
||||
} |
||||
|
||||
pub struct DebugCallback { |
||||
_user_callback: Box<Box<dyn Fn(DebugMessage)>>, |
||||
} |
||||
|
||||
impl DebugCallback { |
||||
pub unsafe fn new<F>(user_callback: F) -> Self |
||||
where |
||||
F: Fn(DebugMessage) + 'static + Send + panic::RefUnwindSafe, |
||||
{ |
||||
let user_callback = Box::new(Box::new(user_callback) as Box<_>); |
||||
|
||||
gl::Enable(gl::DEBUG_OUTPUT); |
||||
gl::DebugMessageCallback( |
||||
Some(debug_callback), |
||||
&*user_callback as &Box<_> as *const Box<_> as *const c_void, |
||||
); |
||||
|
||||
Self { |
||||
_user_callback: user_callback, |
||||
} |
||||
} |
||||
} |
||||
|
||||
impl Drop for DebugCallback { |
||||
fn drop(&mut self) { |
||||
unsafe { |
||||
gl::DebugMessageCallback(None, ptr::null()); |
||||
gl::Disable(gl::DEBUG_OUTPUT); |
||||
} |
||||
} |
||||
} |
||||
|
||||
extern "system" fn debug_callback( |
||||
source: GLenum, |
||||
type_: GLenum, |
||||
id: GLenum, |
||||
severity: GLenum, |
||||
_length: GLsizei, |
||||
message: *const GLchar, |
||||
user_param: *mut c_void, |
||||
) { |
||||
let user_callback = user_param as *mut Box<dyn Fn()> as *const _; |
||||
let user_callback: &Box<dyn Fn(DebugMessage)> = unsafe { &*user_callback }; |
||||
|
||||
let msg_src = match source { |
||||
gl::DEBUG_SOURCE_API => DebugSource::Api, |
||||
gl::DEBUG_SOURCE_WINDOW_SYSTEM => DebugSource::WindowSystem, |
||||
gl::DEBUG_SOURCE_SHADER_COMPILER => DebugSource::ShaderCompiler, |
||||
gl::DEBUG_SOURCE_THIRD_PARTY => DebugSource::ThirdParty, |
||||
gl::DEBUG_SOURCE_APPLICATION => DebugSource::Application, |
||||
gl::DEBUG_SOURCE_OTHER => DebugSource::Other, |
||||
_ => unreachable!(), |
||||
}; |
||||
|
||||
let msg_type = match type_ { |
||||
gl::DEBUG_TYPE_ERROR => DebugType::Error, |
||||
gl::DEBUG_TYPE_DEPRECATED_BEHAVIOR => DebugType::DeprecatedBehavior, |
||||
gl::DEBUG_TYPE_UNDEFINED_BEHAVIOR => DebugType::UndefinedBehavior, |
||||
gl::DEBUG_TYPE_PORTABILITY => DebugType::Portability, |
||||
gl::DEBUG_TYPE_PERFORMANCE => DebugType::Performance, |
||||
gl::DEBUG_TYPE_MARKER => DebugType::Marker, |
||||
gl::DEBUG_TYPE_OTHER => DebugType::Other, |
||||
gl::DEBUG_TYPE_POP_GROUP => DebugType::PopGroup, |
||||
gl::DEBUG_TYPE_PUSH_GROUP => DebugType::PushGroup, |
||||
_ => unreachable!(), |
||||
}; |
||||
|
||||
let msg_severity = match severity { |
||||
gl::DEBUG_SEVERITY_NOTIFICATION => DebugSeverity::Notification, |
||||
gl::DEBUG_SEVERITY_LOW => DebugSeverity::Low, |
||||
gl::DEBUG_SEVERITY_MEDIUM => DebugSeverity::Medium, |
||||
gl::DEBUG_SEVERITY_HIGH => DebugSeverity::High, |
||||
_ => unreachable!(), |
||||
}; |
||||
|
||||
let msg = unsafe { String::from_utf8(CStr::from_ptr(message).to_bytes().to_vec()).unwrap() }; |
||||
|
||||
let message = DebugMessage { |
||||
id, |
||||
source: msg_src, |
||||
type_: msg_type, |
||||
severity: msg_severity, |
||||
message: msg, |
||||
}; |
||||
|
||||
user_callback(message); |
||||
} |
@ -0,0 +1,51 @@ |
||||
pub mod debug; |
||||
pub mod shader; |
||||
pub mod texture; |
||||
pub mod vertex_array; |
||||
pub mod vertex_buffer; |
||||
|
||||
use std::mem; |
||||
|
||||
use gl::types::GLfloat; |
||||
|
||||
#[derive(Debug)] |
||||
pub enum GlType { |
||||
Float, |
||||
} |
||||
|
||||
impl GlType { |
||||
pub fn to_raw_enum(&self) -> u32 { |
||||
match *self { |
||||
GlType::Float => gl::FLOAT, |
||||
} |
||||
} |
||||
|
||||
pub fn mem_size(&self) -> u32 { |
||||
match *self { |
||||
GlType::Float => mem::size_of::<GLfloat>() as u32, |
||||
} |
||||
} |
||||
} |
||||
|
||||
#[derive(Debug)] |
||||
pub struct VertexBufferElement { |
||||
size: u32, |
||||
type_: GlType, |
||||
} |
||||
|
||||
impl VertexBufferElement { |
||||
pub fn floats(size: u32) -> Self { |
||||
Self { |
||||
size, |
||||
type_: GlType::Float, |
||||
} |
||||
} |
||||
|
||||
pub fn to_raw_enum(&self) -> u32 { |
||||
self.type_.to_raw_enum() |
||||
} |
||||
|
||||
pub fn mem_size(&self) -> u32 { |
||||
self.size * self.type_.mem_size() |
||||
} |
||||
} |
@ -0,0 +1,141 @@ |
||||
use cgmath::{Matrix, Matrix2, Matrix4, Vector3, Vector4}; |
||||
use gl::types::*; |
||||
use std::{ |
||||
ffi::{CStr, CString}, |
||||
fs, ptr, str, |
||||
}; |
||||
|
||||
pub struct Shader { |
||||
id: GLuint, |
||||
} |
||||
|
||||
impl Shader { |
||||
pub fn from_file(vs_path: &str, fs_path: &str) -> Self { |
||||
let vs_source = fs::read_to_string(format!("assets/shaders/{}", vs_path)) |
||||
.expect(&format!("Could not read vertex shader {}", vs_path)); |
||||
let fs_source = fs::read_to_string(format!("assets/shaders/{}", fs_path)) |
||||
.expect(&format!("Could not read fragment shader {}", fs_path)); |
||||
|
||||
Self::from_source(&vs_source, &fs_source) |
||||
} |
||||
|
||||
pub fn from_source(vs_source: &str, fs_source: &str) -> Self { |
||||
let vs_id = Self::compile_shader(vs_source, gl::VERTEX_SHADER); |
||||
let fs_id = Self::compile_shader(fs_source, gl::FRAGMENT_SHADER); |
||||
let program_id = Self::link_program(vs_id, fs_id); |
||||
unsafe { |
||||
gl::DeleteShader(vs_id); |
||||
gl::DeleteShader(fs_id); |
||||
} |
||||
|
||||
Self { id: program_id } |
||||
} |
||||
|
||||
pub fn enable(&self) { |
||||
unsafe { |
||||
gl::UseProgram(self.id); |
||||
} |
||||
} |
||||
|
||||
pub fn set_mat2(&self, name: &CStr, matrix: Matrix2<f32>) { |
||||
unsafe { |
||||
let location = gl::GetUniformLocation(self.id, name.as_ptr()); |
||||
gl::UniformMatrix2fv(location, 1, gl::FALSE, matrix.as_ptr()); |
||||
} |
||||
} |
||||
|
||||
pub fn set_mat4(&self, name: &CStr, matrix: Matrix4<f32>) { |
||||
unsafe { |
||||
let location = gl::GetUniformLocation(self.id, name.as_ptr()); |
||||
gl::UniformMatrix4fv(location, 1, gl::FALSE, matrix.as_ptr()); |
||||
} |
||||
} |
||||
|
||||
pub fn set_vec3(&self, name: &CStr, vec: Vector3<f32>) { |
||||
unsafe { |
||||
let location = gl::GetUniformLocation(self.id, name.as_ptr()); |
||||
gl::Uniform3f(location, vec.x, vec.y, vec.z); |
||||
} |
||||
} |
||||
|
||||
pub fn set_vec4(&self, name: &CStr, vec: Vector4<f32>) { |
||||
unsafe { |
||||
let location = gl::GetUniformLocation(self.id, name.as_ptr()); |
||||
gl::Uniform4f(location, vec.x, vec.y, vec.z, vec.w); |
||||
} |
||||
} |
||||
|
||||
pub fn set_int(&self, name: &CStr, int: i32) { |
||||
unsafe { |
||||
let location = gl::GetUniformLocation(self.id, name.as_ptr()); |
||||
gl::Uniform1i(location, int); |
||||
} |
||||
} |
||||
|
||||
fn compile_shader(src: &str, ty: GLenum) -> GLuint { |
||||
let shader; |
||||
unsafe { |
||||
shader = gl::CreateShader(ty); |
||||
|
||||
let c_str = CString::new(src.as_bytes()).unwrap(); |
||||
gl::ShaderSource(shader, 1, &c_str.as_ptr(), ptr::null()); |
||||
gl::CompileShader(shader); |
||||
|
||||
let mut status = gl::FALSE as GLint; |
||||
gl::GetShaderiv(shader, gl::COMPILE_STATUS, &mut status); |
||||
|
||||
if status != (gl::TRUE as GLint) { |
||||
let mut len = 0; |
||||
gl::GetShaderiv(shader, gl::INFO_LOG_LENGTH, &mut len); |
||||
let mut buf = Vec::with_capacity(len as usize); |
||||
buf.set_len((len as usize) - 1); // subtract 1 to skip the trailing null character
|
||||
gl::GetShaderInfoLog( |
||||
shader, |
||||
len, |
||||
ptr::null_mut(), |
||||
buf.as_mut_ptr() as *mut GLchar, |
||||
); |
||||
panic!( |
||||
"{}", |
||||
str::from_utf8(&buf) |
||||
.ok() |
||||
.expect("ShaderInfoLog not valid utf8") |
||||
); |
||||
} |
||||
} |
||||
shader |
||||
} |
||||
|
||||
fn link_program(vs: GLuint, fs: GLuint) -> GLuint { |
||||
unsafe { |
||||
let program = gl::CreateProgram(); |
||||
gl::AttachShader(program, vs); |
||||
gl::AttachShader(program, fs); |
||||
gl::LinkProgram(program); |
||||
// Get the link status
|
||||
let mut status = gl::FALSE as GLint; |
||||
gl::GetProgramiv(program, gl::LINK_STATUS, &mut status); |
||||
|
||||
// Fail on error
|
||||
if status != (gl::TRUE as GLint) { |
||||
let mut len: GLint = 0; |
||||
gl::GetProgramiv(program, gl::INFO_LOG_LENGTH, &mut len); |
||||
let mut buf = Vec::with_capacity(len as usize); |
||||
buf.set_len((len as usize) - 1); // subtract 1 to skip the trailing null character
|
||||
gl::GetProgramInfoLog( |
||||
program, |
||||
len, |
||||
ptr::null_mut(), |
||||
buf.as_mut_ptr() as *mut GLchar, |
||||
); |
||||
panic!( |
||||
"{}", |
||||
str::from_utf8(&buf) |
||||
.ok() |
||||
.expect("ProgramInfoLog not valid utf8") |
||||
); |
||||
} |
||||
program |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,59 @@ |
||||
use std::{ffi::c_void, path::Path}; |
||||
|
||||
use image::{DynamicImage, GenericImageView}; |
||||
|
||||
#[derive(Debug)] |
||||
pub struct Texture { |
||||
pub id: u32, |
||||
pub width: u32, |
||||
pub height: u32, |
||||
} |
||||
|
||||
impl Texture { |
||||
pub unsafe fn new(image: DynamicImage, flip_vertical: bool) -> Self { |
||||
let image = if flip_vertical { image.flipv() } else { image }; |
||||
|
||||
let width = image.width(); |
||||
let height = image.width(); |
||||
|
||||
let (data, gl_format) = match image { |
||||
DynamicImage::ImageRgb8(image) => (image.into_raw(), gl::RGB), |
||||
DynamicImage::ImageRgba8(image) => (image.into_raw(), gl::RGBA), |
||||
_ => unimplemented!(), |
||||
}; |
||||
|
||||
let mut id = 0; |
||||
gl::CreateTextures(gl::TEXTURE_2D, 1, &mut id); |
||||
gl::TextureParameteri(id, gl::TEXTURE_MIN_FILTER, gl::NEAREST as i32); |
||||
gl::TextureParameteri(id, gl::TEXTURE_MAG_FILTER, gl::NEAREST as i32); |
||||
gl::TextureParameteri(id, gl::TEXTURE_WRAP_S, gl::CLAMP_TO_EDGE as i32); |
||||
gl::TextureParameteri(id, gl::TEXTURE_WRAP_T, gl::CLAMP_TO_EDGE as i32); |
||||
|
||||
gl::TextureStorage2D(id, 1, gl::RGBA8, width as i32, height as i32); |
||||
gl::TextureSubImage2D( |
||||
id, |
||||
0, |
||||
0, |
||||
0, |
||||
width as i32, |
||||
height as i32, |
||||
gl_format, |
||||
gl::UNSIGNED_BYTE, |
||||
data.as_ptr() as *const c_void, |
||||
); |
||||
|
||||
Self { id, width, height } |
||||
} |
||||
|
||||
pub unsafe fn from_path<P>(path: P, flip_vertical: bool) -> Self |
||||
where |
||||
P: AsRef<Path>, |
||||
{ |
||||
let image = image::open(path).expect("Couldn't open image"); |
||||
Self::new(image, flip_vertical) |
||||
} |
||||
|
||||
pub unsafe fn bind_to_unit(&self, unit: u32) { |
||||
gl::BindTextureUnit(unit, self.id); |
||||
} |
||||
} |
@ -0,0 +1,48 @@ |
||||
use super::vertex_buffer::VertexBuffer; |
||||
|
||||
#[derive(Debug)] |
||||
pub struct VertexArray { |
||||
pub id: u32, |
||||
pub elements: u32, |
||||
} |
||||
|
||||
impl VertexArray { |
||||
pub unsafe fn new(buffers: &[VertexBuffer]) -> Self { |
||||
let mut id = 0; |
||||
gl::CreateVertexArrays(1, &mut id); |
||||
|
||||
let mut attrib_index = 0; |
||||
for (binding_index, buffer) in buffers.iter().enumerate() { |
||||
let stride = buffer |
||||
.layout |
||||
.iter() |
||||
.fold(0, |prev, element| prev + element.mem_size()); |
||||
gl::VertexArrayVertexBuffer(id, binding_index as u32, buffer.id, 0, stride as i32); |
||||
|
||||
let mut offset = 0; |
||||
for element in buffer.layout.iter() { |
||||
gl::EnableVertexArrayAttrib(id, attrib_index as u32); |
||||
gl::VertexArrayAttribFormat( |
||||
id, |
||||
attrib_index as u32, |
||||
element.size as i32, |
||||
element.to_raw_enum(), |
||||
gl::FALSE, |
||||
offset, |
||||
); |
||||
gl::VertexArrayAttribBinding(id, attrib_index as u32, binding_index as u32); |
||||
|
||||
offset += element.mem_size(); |
||||
attrib_index += 1; |
||||
} |
||||
} |
||||
|
||||
let elements = buffers.iter().min_by_key(|b| b.elements).unwrap().elements; |
||||
|
||||
Self { id, elements } |
||||
} |
||||
|
||||
pub unsafe fn bind(&self) { |
||||
gl::BindVertexArray(self.id); |
||||
} |
||||
} |
@ -0,0 +1,34 @@ |
||||
use std::mem; |
||||
|
||||
use gl::types::{GLfloat, GLsizeiptr, GLuint}; |
||||
|
||||
use super::VertexBufferElement; |
||||
|
||||
#[derive(Debug)] |
||||
pub struct VertexBuffer { |
||||
pub id: GLuint, |
||||
pub elements: u32, |
||||
pub layout: Vec<VertexBufferElement>, |
||||
} |
||||
|
||||
impl VertexBuffer { |
||||
pub unsafe fn new(data: &[GLfloat], layout: Vec<VertexBufferElement>) -> Self { |
||||
let mut id = 0; |
||||
gl::CreateBuffers(1, &mut id); |
||||
|
||||
gl::NamedBufferStorage( |
||||
id, |
||||
(data.len() * mem::size_of::<GLfloat>()) as GLsizeiptr, |
||||
mem::transmute(&data[0]), |
||||
gl::DYNAMIC_STORAGE_BIT, |
||||
); |
||||
|
||||
let elements = |
||||
data.len() as u32 / layout.iter().fold(0, |prev, element| prev + element.size); |
||||
Self { |
||||
id, |
||||
elements, |
||||
layout, |
||||
} |
||||
} |
||||
} |
Loading…
Reference in new issue