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